@capgo/camera-preview 7.4.0-alpha.3 → 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;
|
|
@@ -1776,8 +1777,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1776
1777
|
return;
|
|
1777
1778
|
}
|
|
1778
1779
|
|
|
1779
|
-
// Remove any existing focus indicator
|
|
1780
|
+
// Remove any existing focus indicator and cancel its animation
|
|
1780
1781
|
if (focusIndicatorView != null) {
|
|
1782
|
+
try {
|
|
1783
|
+
focusIndicatorView.clearAnimation();
|
|
1784
|
+
} catch (Exception ignore) {}
|
|
1781
1785
|
previewContainer.removeView(focusIndicatorView);
|
|
1782
1786
|
focusIndicatorView = null;
|
|
1783
1787
|
}
|
|
@@ -1811,6 +1815,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1811
1815
|
container.setBackground(drawable);
|
|
1812
1816
|
|
|
1813
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;
|
|
1814
1821
|
|
|
1815
1822
|
// Set initial state for smooth animation
|
|
1816
1823
|
focusIndicatorView.setAlpha(1f); // Start visible
|
|
@@ -1863,7 +1870,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1863
1870
|
new Runnable() {
|
|
1864
1871
|
@Override
|
|
1865
1872
|
public void run() {
|
|
1866
|
-
|
|
1873
|
+
// Ensure this runnable belongs to the latest indicator
|
|
1874
|
+
if (
|
|
1875
|
+
focusIndicatorView != null &&
|
|
1876
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1877
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1878
|
+
) {
|
|
1867
1879
|
// Smooth fade to semi-transparent
|
|
1868
1880
|
AlphaAnimation fadeToTransparent = new AlphaAnimation(1f, 0.4f);
|
|
1869
1881
|
fadeToTransparent.setDuration(400);
|
|
@@ -1885,7 +1897,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1885
1897
|
"showFocusIndicator: Fade to transparent ended, starting final fade out"
|
|
1886
1898
|
);
|
|
1887
1899
|
// Final smooth fade out and scale down
|
|
1888
|
-
if (
|
|
1900
|
+
if (
|
|
1901
|
+
focusIndicatorView != null &&
|
|
1902
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1903
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1904
|
+
) {
|
|
1889
1905
|
AnimationSet finalAnimation = new AnimationSet(false);
|
|
1890
1906
|
|
|
1891
1907
|
AlphaAnimation finalFadeOut = new AlphaAnimation(0.4f, 0f);
|
|
@@ -1933,8 +1949,13 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1933
1949
|
// Remove the focus indicator
|
|
1934
1950
|
if (
|
|
1935
1951
|
focusIndicatorView != null &&
|
|
1936
|
-
previewContainer != null
|
|
1952
|
+
previewContainer != null &&
|
|
1953
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1954
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1937
1955
|
) {
|
|
1956
|
+
try {
|
|
1957
|
+
focusIndicatorView.clearAnimation();
|
|
1958
|
+
} catch (Exception ignore) {}
|
|
1938
1959
|
previewContainer.removeView(focusIndicatorView);
|
|
1939
1960
|
focusIndicatorView = null;
|
|
1940
1961
|
}
|
|
@@ -1945,7 +1966,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1945
1966
|
}
|
|
1946
1967
|
);
|
|
1947
1968
|
|
|
1948
|
-
|
|
1969
|
+
if (
|
|
1970
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1971
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1972
|
+
) {
|
|
1973
|
+
focusIndicatorView.startAnimation(finalAnimation);
|
|
1974
|
+
}
|
|
1949
1975
|
}
|
|
1950
1976
|
}
|
|
1951
1977
|
|
|
@@ -1954,7 +1980,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1954
1980
|
}
|
|
1955
1981
|
);
|
|
1956
1982
|
|
|
1957
|
-
|
|
1983
|
+
if (
|
|
1984
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1985
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1986
|
+
) {
|
|
1987
|
+
focusIndicatorView.startAnimation(fadeToTransparent);
|
|
1988
|
+
}
|
|
1958
1989
|
}
|
|
1959
1990
|
}
|
|
1960
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) {
|