@capgo/camera-preview 7.4.0-alpha.1 → 7.4.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -102,6 +102,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
102
102
|
private GridOverlayView gridOverlayView;
|
|
103
103
|
private FrameLayout previewContainer;
|
|
104
104
|
private View focusIndicatorView;
|
|
105
|
+
private long focusIndicatorAnimationId = 0; // Incrementing token to invalidate previous animations
|
|
105
106
|
private CameraSelector currentCameraSelector;
|
|
106
107
|
private String currentDeviceId;
|
|
107
108
|
private int currentFlashMode = ImageCapture.FLASH_MODE_OFF;
|
|
@@ -1040,12 +1041,40 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1040
1041
|
saveImageToGallery(bytes);
|
|
1041
1042
|
}
|
|
1042
1043
|
|
|
1043
|
-
String
|
|
1044
|
+
String resultValue;
|
|
1045
|
+
boolean returnFileUri =
|
|
1046
|
+
sessionConfig != null && sessionConfig.isStoreToFile();
|
|
1047
|
+
if (returnFileUri) {
|
|
1048
|
+
// Persist processed image to a file and return its URI to avoid heavy base64 bridging
|
|
1049
|
+
try {
|
|
1050
|
+
String fileName =
|
|
1051
|
+
"cpcp_" +
|
|
1052
|
+
new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(
|
|
1053
|
+
new java.util.Date()
|
|
1054
|
+
) +
|
|
1055
|
+
".jpg";
|
|
1056
|
+
File outDir = context.getCacheDir();
|
|
1057
|
+
File outFile = new File(outDir, fileName);
|
|
1058
|
+
FileOutputStream outFos = new FileOutputStream(outFile);
|
|
1059
|
+
outFos.write(bytes);
|
|
1060
|
+
outFos.close();
|
|
1061
|
+
|
|
1062
|
+
// Return a file path; apps can convert via Capacitor.convertFileSrc on JS side
|
|
1063
|
+
resultValue = outFile.getAbsolutePath();
|
|
1064
|
+
} catch (IOException ioEx) {
|
|
1065
|
+
Log.e(TAG, "capturePhoto: Failed to write image file", ioEx);
|
|
1066
|
+
// Fallback to base64 if file write fails
|
|
1067
|
+
resultValue = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
// Backward-compatible behavior
|
|
1071
|
+
resultValue = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
1072
|
+
}
|
|
1044
1073
|
|
|
1045
1074
|
tempFile.delete();
|
|
1046
1075
|
|
|
1047
1076
|
if (listener != null) {
|
|
1048
|
-
listener.onPictureTaken(
|
|
1077
|
+
listener.onPictureTaken(resultValue, exifData);
|
|
1049
1078
|
}
|
|
1050
1079
|
} catch (Exception e) {
|
|
1051
1080
|
Log.e(TAG, "capturePhoto: Error processing image", e);
|
|
@@ -1748,8 +1777,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1748
1777
|
return;
|
|
1749
1778
|
}
|
|
1750
1779
|
|
|
1751
|
-
// Remove any existing focus indicator
|
|
1780
|
+
// Remove any existing focus indicator and cancel its animation
|
|
1752
1781
|
if (focusIndicatorView != null) {
|
|
1782
|
+
try {
|
|
1783
|
+
focusIndicatorView.clearAnimation();
|
|
1784
|
+
} catch (Exception ignore) {}
|
|
1753
1785
|
previewContainer.removeView(focusIndicatorView);
|
|
1754
1786
|
focusIndicatorView = null;
|
|
1755
1787
|
}
|
|
@@ -1783,6 +1815,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1783
1815
|
container.setBackground(drawable);
|
|
1784
1816
|
|
|
1785
1817
|
focusIndicatorView = container;
|
|
1818
|
+
// Bump animation token; everything after this must validate against this token
|
|
1819
|
+
final long thisAnimationId = ++focusIndicatorAnimationId;
|
|
1820
|
+
final View thisIndicatorView = focusIndicatorView;
|
|
1786
1821
|
|
|
1787
1822
|
// Set initial state for smooth animation
|
|
1788
1823
|
focusIndicatorView.setAlpha(1f); // Start visible
|
|
@@ -1835,7 +1870,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1835
1870
|
new Runnable() {
|
|
1836
1871
|
@Override
|
|
1837
1872
|
public void run() {
|
|
1838
|
-
|
|
1873
|
+
// Ensure this runnable belongs to the latest indicator
|
|
1874
|
+
if (
|
|
1875
|
+
focusIndicatorView != null &&
|
|
1876
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1877
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1878
|
+
) {
|
|
1839
1879
|
// Smooth fade to semi-transparent
|
|
1840
1880
|
AlphaAnimation fadeToTransparent = new AlphaAnimation(1f, 0.4f);
|
|
1841
1881
|
fadeToTransparent.setDuration(400);
|
|
@@ -1857,7 +1897,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1857
1897
|
"showFocusIndicator: Fade to transparent ended, starting final fade out"
|
|
1858
1898
|
);
|
|
1859
1899
|
// Final smooth fade out and scale down
|
|
1860
|
-
if (
|
|
1900
|
+
if (
|
|
1901
|
+
focusIndicatorView != null &&
|
|
1902
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1903
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1904
|
+
) {
|
|
1861
1905
|
AnimationSet finalAnimation = new AnimationSet(false);
|
|
1862
1906
|
|
|
1863
1907
|
AlphaAnimation finalFadeOut = new AlphaAnimation(0.4f, 0f);
|
|
@@ -1905,8 +1949,13 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1905
1949
|
// Remove the focus indicator
|
|
1906
1950
|
if (
|
|
1907
1951
|
focusIndicatorView != null &&
|
|
1908
|
-
previewContainer != null
|
|
1952
|
+
previewContainer != null &&
|
|
1953
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1954
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1909
1955
|
) {
|
|
1956
|
+
try {
|
|
1957
|
+
focusIndicatorView.clearAnimation();
|
|
1958
|
+
} catch (Exception ignore) {}
|
|
1910
1959
|
previewContainer.removeView(focusIndicatorView);
|
|
1911
1960
|
focusIndicatorView = null;
|
|
1912
1961
|
}
|
|
@@ -1917,7 +1966,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1917
1966
|
}
|
|
1918
1967
|
);
|
|
1919
1968
|
|
|
1920
|
-
|
|
1969
|
+
if (
|
|
1970
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1971
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1972
|
+
) {
|
|
1973
|
+
focusIndicatorView.startAnimation(finalAnimation);
|
|
1974
|
+
}
|
|
1921
1975
|
}
|
|
1922
1976
|
}
|
|
1923
1977
|
|
|
@@ -1926,7 +1980,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1926
1980
|
}
|
|
1927
1981
|
);
|
|
1928
1982
|
|
|
1929
|
-
|
|
1983
|
+
if (
|
|
1984
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1985
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1986
|
+
) {
|
|
1987
|
+
focusIndicatorView.startAnimation(fadeToTransparent);
|
|
1988
|
+
}
|
|
1930
1989
|
}
|
|
1931
1990
|
}
|
|
1932
1991
|
},
|
|
@@ -25,7 +25,7 @@ class CameraController: NSObject {
|
|
|
25
25
|
var focusIndicatorView: UIView?
|
|
26
26
|
|
|
27
27
|
var flashMode = AVCaptureDevice.FlashMode.off
|
|
28
|
-
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
28
|
+
var photoCaptureCompletionBlock: ((UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void)?
|
|
29
29
|
|
|
30
30
|
var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
31
31
|
|
|
@@ -555,11 +555,11 @@ extension CameraController {
|
|
|
555
555
|
self.updateVideoOrientation()
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
func captureImage(width: Int?, height: Int?, aspectRatio: String?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Error?) -> Void) {
|
|
558
|
+
func captureImage(width: Int?, height: Int?, aspectRatio: String?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
|
|
559
559
|
print("[CameraPreview] captureImage called - width: \(width ?? -1), height: \(height ?? -1), aspectRatio: \(aspectRatio ?? "nil")")
|
|
560
560
|
|
|
561
561
|
guard let photoOutput = self.photoOutput else {
|
|
562
|
-
completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
562
|
+
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
563
563
|
return
|
|
564
564
|
}
|
|
565
565
|
|
|
@@ -585,14 +585,14 @@ extension CameraController {
|
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
-
self.photoCaptureCompletionBlock = { (image, error) in
|
|
588
|
+
self.photoCaptureCompletionBlock = { (image, photoData, metadata, error) in
|
|
589
589
|
if let error = error {
|
|
590
|
-
completion(nil, error)
|
|
590
|
+
completion(nil, nil, nil, error)
|
|
591
591
|
return
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
guard let image = image else {
|
|
595
|
-
completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
|
|
595
|
+
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
|
|
596
596
|
return
|
|
597
597
|
}
|
|
598
598
|
|
|
@@ -646,7 +646,7 @@ extension CameraController {
|
|
|
646
646
|
}
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
-
completion(finalImage, nil)
|
|
649
|
+
completion(finalImage, photoData, metadata, nil)
|
|
650
650
|
}
|
|
651
651
|
|
|
652
652
|
photoOutput.capturePhoto(with: settings, delegate: self)
|
|
@@ -1460,22 +1460,23 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
1460
1460
|
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
1461
1461
|
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
|
|
1462
1462
|
if let error = error {
|
|
1463
|
-
self.photoCaptureCompletionBlock?(nil, error)
|
|
1463
|
+
self.photoCaptureCompletionBlock?(nil, nil, nil, error)
|
|
1464
1464
|
return
|
|
1465
1465
|
}
|
|
1466
1466
|
|
|
1467
1467
|
// Get the photo data using the modern API
|
|
1468
1468
|
guard let imageData = photo.fileDataRepresentation() else {
|
|
1469
|
-
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
1469
|
+
self.photoCaptureCompletionBlock?(nil, nil, nil, CameraControllerError.unknown)
|
|
1470
1470
|
return
|
|
1471
1471
|
}
|
|
1472
1472
|
|
|
1473
1473
|
guard let image = UIImage(data: imageData) else {
|
|
1474
|
-
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
1474
|
+
self.photoCaptureCompletionBlock?(nil, nil, nil, CameraControllerError.unknown)
|
|
1475
1475
|
return
|
|
1476
1476
|
}
|
|
1477
1477
|
|
|
1478
|
-
|
|
1478
|
+
// Pass through original file data and metadata so callers can preserve EXIF
|
|
1479
|
+
self.photoCaptureCompletionBlock?(image.fixedOrientation(), imageData, photo.metadata, nil)
|
|
1479
1480
|
}
|
|
1480
1481
|
}
|
|
1481
1482
|
|
|
@@ -705,7 +705,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
705
705
|
print("[CameraPreview] Current location: \(self.currentLocation?.description ?? "nil")")
|
|
706
706
|
print("[CameraPreview] Preview dimensions: \(self.previewView.frame.width)x\(self.previewView.frame.height)")
|
|
707
707
|
|
|
708
|
-
self.cameraController.captureImage(width: width, height: height, aspectRatio: captureAspectRatio, quality: quality, gpsLocation: self.currentLocation) { (image, error) in
|
|
708
|
+
self.cameraController.captureImage(width: width, height: height, aspectRatio: captureAspectRatio, quality: quality, gpsLocation: self.currentLocation) { (image, originalPhotoData, originalMetadata, error) in
|
|
709
709
|
print("[CameraPreview] captureImage callback received")
|
|
710
710
|
DispatchQueue.main.async {
|
|
711
711
|
print("[CameraPreview] Processing capture on main thread")
|
|
@@ -719,7 +719,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
719
719
|
let imageDataWithExif = self.createImageDataWithExif(
|
|
720
720
|
from: image,
|
|
721
721
|
quality: Int(quality),
|
|
722
|
-
location: withExifLocation ? self.currentLocation : nil
|
|
722
|
+
location: withExifLocation ? self.currentLocation : nil,
|
|
723
|
+
originalPhotoData: originalPhotoData
|
|
723
724
|
)
|
|
724
725
|
else {
|
|
725
726
|
print("[CameraPreview] Failed to create image data with EXIF")
|
|
@@ -792,24 +793,29 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
792
793
|
return exifData
|
|
793
794
|
}
|
|
794
795
|
|
|
795
|
-
private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?) -> Data? {
|
|
796
|
-
guard let
|
|
796
|
+
private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?, originalPhotoData: Data?) -> Data? {
|
|
797
|
+
guard let jpegDataAtQuality = image.jpegData(compressionQuality: CGFloat(Double(quality) / 100.0)) else {
|
|
797
798
|
return nil
|
|
798
799
|
}
|
|
799
800
|
|
|
800
|
-
|
|
801
|
+
// Prefer metadata from the original AVCapturePhoto file data to preserve lens/EXIF
|
|
802
|
+
let sourceDataForMetadata = (originalPhotoData ?? jpegDataAtQuality) as CFData
|
|
803
|
+
guard let imageSource = CGImageSourceCreateWithData(sourceDataForMetadata, nil),
|
|
801
804
|
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
|
|
802
805
|
let cgImage = image.cgImage else {
|
|
803
|
-
return
|
|
806
|
+
return jpegDataAtQuality
|
|
804
807
|
}
|
|
805
808
|
|
|
806
809
|
let mutableData = NSMutableData()
|
|
807
810
|
guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
|
|
808
|
-
return
|
|
811
|
+
return jpegDataAtQuality
|
|
809
812
|
}
|
|
810
813
|
|
|
811
814
|
var finalProperties = imageProperties
|
|
812
815
|
|
|
816
|
+
// Ensure orientation reflects the pixel data (we pass an orientation-fixed UIImage)
|
|
817
|
+
finalProperties[kCGImagePropertyOrientation as String] = 1
|
|
818
|
+
|
|
813
819
|
// Add GPS location if available
|
|
814
820
|
if let location = location {
|
|
815
821
|
let formatter = DateFormatter()
|
|
@@ -829,10 +835,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
829
835
|
finalProperties[kCGImagePropertyGPSDictionary as String] = gpsDict
|
|
830
836
|
}
|
|
831
837
|
|
|
832
|
-
// Create or update TIFF dictionary for device info
|
|
838
|
+
// Create or update TIFF dictionary for device info and set orientation to Up
|
|
833
839
|
var tiffDict = finalProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] ?? [:]
|
|
834
840
|
tiffDict[kCGImagePropertyTIFFMake as String] = "Apple"
|
|
835
841
|
tiffDict[kCGImagePropertyTIFFModel as String] = UIDevice.current.model
|
|
842
|
+
tiffDict[kCGImagePropertyTIFFOrientation as String] = 1
|
|
836
843
|
finalProperties[kCGImagePropertyTIFFDictionary as String] = tiffDict
|
|
837
844
|
|
|
838
845
|
CGImageDestinationAddImage(destination, cgImage, finalProperties as CFDictionary)
|
|
@@ -841,7 +848,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
841
848
|
return mutableData as Data
|
|
842
849
|
}
|
|
843
850
|
|
|
844
|
-
return
|
|
851
|
+
return jpegDataAtQuality
|
|
845
852
|
}
|
|
846
853
|
|
|
847
854
|
@objc func captureSample(_ call: CAPPluginCall) {
|