@capgo/camera-preview 7.8.0 → 7.8.1
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.
|
@@ -100,7 +100,48 @@ class CameraController: NSObject {
|
|
|
100
100
|
private func parseAspectRatio(_ aspectRatio: String) -> (width: CGFloat, height: CGFloat)? {
|
|
101
101
|
let components = aspectRatio.split(separator: ":").compactMap { Float(String($0)) }
|
|
102
102
|
guard components.count == 2 else { return nil }
|
|
103
|
-
|
|
103
|
+
|
|
104
|
+
// Check if device is in portrait orientation by looking at the current interface orientation
|
|
105
|
+
var isPortrait = false
|
|
106
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
107
|
+
print("[CameraPreview] parseAspectRatio - windowScene.interfaceOrientation: \(windowScene.interfaceOrientation)")
|
|
108
|
+
switch windowScene.interfaceOrientation {
|
|
109
|
+
case .portrait, .portraitUpsideDown:
|
|
110
|
+
isPortrait = true
|
|
111
|
+
case .landscapeLeft, .landscapeRight:
|
|
112
|
+
isPortrait = false
|
|
113
|
+
case .unknown:
|
|
114
|
+
// Fallback to device orientation
|
|
115
|
+
isPortrait = UIDevice.current.orientation.isPortrait
|
|
116
|
+
@unknown default:
|
|
117
|
+
isPortrait = UIDevice.current.orientation.isPortrait
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
// Fallback to device orientation
|
|
121
|
+
isPortrait = UIDevice.current.orientation.isPortrait
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let originalWidth = CGFloat(components[0])
|
|
125
|
+
let originalHeight = CGFloat(components[1])
|
|
126
|
+
print("[CameraPreview] parseAspectRatio - isPortrait: \(isPortrait) originalWidth: \(originalWidth) originalHeight: \(originalHeight)")
|
|
127
|
+
|
|
128
|
+
let finalWidth: CGFloat
|
|
129
|
+
let finalHeight: CGFloat
|
|
130
|
+
|
|
131
|
+
if isPortrait {
|
|
132
|
+
// For portrait mode, swap width and height to maintain portrait orientation
|
|
133
|
+
// 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
134
|
+
finalWidth = originalHeight
|
|
135
|
+
finalHeight = originalWidth
|
|
136
|
+
print("[CameraPreview] parseAspectRatio - Portrait mode: \(aspectRatio) -> \(finalWidth):\(finalHeight) (ratio: \(finalWidth/finalHeight))")
|
|
137
|
+
} else {
|
|
138
|
+
// For landscape mode, keep original orientation
|
|
139
|
+
finalWidth = originalWidth
|
|
140
|
+
finalHeight = originalHeight
|
|
141
|
+
print("[CameraPreview] parseAspectRatio - Landscape mode: \(aspectRatio) -> \(finalWidth):\(finalHeight) (ratio: \(finalWidth/finalHeight))")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (width: finalWidth, height: finalHeight)
|
|
104
145
|
}
|
|
105
146
|
}
|
|
106
147
|
|
|
@@ -340,6 +381,38 @@ extension CameraController {
|
|
|
340
381
|
}
|
|
341
382
|
}
|
|
342
383
|
|
|
384
|
+
/// Update the requested aspect ratio at runtime and reconfigure session/preview accordingly
|
|
385
|
+
func updateAspectRatio(_ aspectRatio: String?) {
|
|
386
|
+
// Update internal state
|
|
387
|
+
self.requestedAspectRatio = aspectRatio
|
|
388
|
+
|
|
389
|
+
// Reconfigure session preset to match the new ratio for optimal capture resolution
|
|
390
|
+
if let captureSession = self.captureSession {
|
|
391
|
+
captureSession.beginConfiguration()
|
|
392
|
+
self.configureSessionPreset(for: aspectRatio)
|
|
393
|
+
captureSession.commitConfiguration()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Update preview layer geometry on the main thread
|
|
397
|
+
DispatchQueue.main.async { [weak self] in
|
|
398
|
+
guard let self = self, let previewLayer = self.previewLayer else { return }
|
|
399
|
+
if let superlayer = previewLayer.superlayer {
|
|
400
|
+
let bounds = superlayer.bounds
|
|
401
|
+
if let aspect = aspectRatio {
|
|
402
|
+
let frame = self.calculateAspectRatioFrame(for: aspect, in: bounds)
|
|
403
|
+
previewLayer.frame = frame
|
|
404
|
+
previewLayer.videoGravity = .resizeAspectFill
|
|
405
|
+
} else {
|
|
406
|
+
previewLayer.frame = bounds
|
|
407
|
+
previewLayer.videoGravity = .resizeAspect
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Keep grid overlay in sync with preview
|
|
411
|
+
self.gridOverlayView?.frame = previewLayer.frame
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
343
416
|
private func setInitialZoom(level: Float?) {
|
|
344
417
|
let device = (currentCameraPosition == .rear) ? rearCamera : frontCamera
|
|
345
418
|
guard let device = device else {
|
|
@@ -544,7 +617,7 @@ extension CameraController {
|
|
|
544
617
|
}
|
|
545
618
|
|
|
546
619
|
private func updateVideoOrientationOnMainThread() {
|
|
547
|
-
|
|
620
|
+
var videoOrientation: AVCaptureVideoOrientation
|
|
548
621
|
|
|
549
622
|
// Use window scene interface orientation
|
|
550
623
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
@@ -695,7 +768,7 @@ extension CameraController {
|
|
|
695
768
|
}
|
|
696
769
|
|
|
697
770
|
func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
|
|
698
|
-
print("[CameraPreview] captureImage called - width: \(width ?? -1), height: \(height ?? -1)")
|
|
771
|
+
print("[CameraPreview] captureImage called - width: \(width ?? -1), height: \(height ?? -1), requestedAspectRatio: \(self.requestedAspectRatio ?? "nil")")
|
|
699
772
|
|
|
700
773
|
guard let photoOutput = self.photoOutput else {
|
|
701
774
|
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
@@ -764,18 +837,9 @@ extension CameraController {
|
|
|
764
837
|
print("[CameraPreview] Resized to max dimensions: \(finalImage.size.width)x\(finalImage.size.height)")
|
|
765
838
|
} else if let aspectRatio = self.requestedAspectRatio {
|
|
766
839
|
// No max dimensions specified, but aspect ratio is specified
|
|
767
|
-
//
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if let targetRatio = self.parseAspectRatio(aspectRatio) {
|
|
771
|
-
let targetAspectRatio = targetRatio.width / targetRatio.height
|
|
772
|
-
|
|
773
|
-
// Allow small tolerance for aspect ratio comparison
|
|
774
|
-
if abs(imageAspectRatio - targetAspectRatio) > 0.01 {
|
|
775
|
-
finalImage = self.cropImageToAspectRatio(image: image, aspectRatio: aspectRatio) ?? image
|
|
776
|
-
print("[CameraPreview] Cropped to match aspect ratio \(aspectRatio): \(finalImage.size.width)x\(finalImage.size.height)")
|
|
777
|
-
}
|
|
778
|
-
}
|
|
840
|
+
// Always apply aspect ratio cropping to ensure correct orientation
|
|
841
|
+
finalImage = self.cropImageToAspectRatio(image: image, aspectRatio: aspectRatio) ?? image
|
|
842
|
+
print("[CameraPreview] Applied aspect ratio cropping for \(aspectRatio): \(finalImage.size.width)x\(finalImage.size.height)")
|
|
779
843
|
}
|
|
780
844
|
|
|
781
845
|
completion(finalImage, photoData, metadata, nil)
|
|
@@ -857,13 +921,27 @@ extension CameraController {
|
|
|
857
921
|
|
|
858
922
|
func cropImageToAspectRatio(image: UIImage, aspectRatio: String) -> UIImage? {
|
|
859
923
|
guard let ratio = parseAspectRatio(aspectRatio) else {
|
|
924
|
+
print("[CameraPreview] cropImageToAspectRatio - Failed to parse aspect ratio: \(aspectRatio)")
|
|
860
925
|
return image
|
|
861
926
|
}
|
|
862
927
|
|
|
863
|
-
|
|
928
|
+
// Only normalize the image orientation if it's not already correct
|
|
929
|
+
let normalizedImage: UIImage
|
|
930
|
+
if image.imageOrientation == .up {
|
|
931
|
+
normalizedImage = image
|
|
932
|
+
print("[CameraPreview] cropImageToAspectRatio - Image already has correct orientation")
|
|
933
|
+
} else {
|
|
934
|
+
normalizedImage = image.fixedOrientation() ?? image
|
|
935
|
+
print("[CameraPreview] cropImageToAspectRatio - Normalized image orientation from \(image.imageOrientation.rawValue) to .up")
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
let imageSize = normalizedImage.size
|
|
864
939
|
let imageAspectRatio = imageSize.width / imageSize.height
|
|
865
940
|
let targetAspectRatio = ratio.width / ratio.height
|
|
866
941
|
|
|
942
|
+
print("[CameraPreview] cropImageToAspectRatio - Original image: \(imageSize.width)x\(imageSize.height) (ratio: \(imageAspectRatio))")
|
|
943
|
+
print("[CameraPreview] cropImageToAspectRatio - Target ratio: \(ratio.width):\(ratio.height) (ratio: \(targetAspectRatio))")
|
|
944
|
+
|
|
867
945
|
var cropRect: CGRect
|
|
868
946
|
|
|
869
947
|
if imageAspectRatio > targetAspectRatio {
|
|
@@ -871,19 +949,36 @@ extension CameraController {
|
|
|
871
949
|
let targetWidth = imageSize.height * targetAspectRatio
|
|
872
950
|
let xOffset = (imageSize.width - targetWidth) / 2
|
|
873
951
|
cropRect = CGRect(x: xOffset, y: 0, width: targetWidth, height: imageSize.height)
|
|
952
|
+
print("[CameraPreview] cropImageToAspectRatio - Horizontal crop: \(cropRect)")
|
|
874
953
|
} else {
|
|
875
954
|
// Image is taller than target - crop vertically (center crop)
|
|
876
955
|
let targetHeight = imageSize.width / targetAspectRatio
|
|
877
956
|
let yOffset = (imageSize.height - targetHeight) / 2
|
|
878
957
|
cropRect = CGRect(x: 0, y: yOffset, width: imageSize.width, height: targetHeight)
|
|
958
|
+
print("[CameraPreview] cropImageToAspectRatio - Vertical crop: \(cropRect) - Target height: \(targetHeight)")
|
|
879
959
|
}
|
|
880
960
|
|
|
881
|
-
|
|
961
|
+
// Validate crop rect is within image bounds
|
|
962
|
+
if cropRect.minX < 0 || cropRect.minY < 0 ||
|
|
963
|
+
cropRect.maxX > imageSize.width || cropRect.maxY > imageSize.height {
|
|
964
|
+
print("[CameraPreview] cropImageToAspectRatio - Warning: Crop rect \(cropRect) exceeds image bounds \(imageSize)")
|
|
965
|
+
// Adjust crop rect to fit within image bounds
|
|
966
|
+
cropRect = cropRect.intersection(CGRect(origin: .zero, size: imageSize))
|
|
967
|
+
print("[CameraPreview] cropImageToAspectRatio - Adjusted crop rect: \(cropRect)")
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
guard let cgImage = normalizedImage.cgImage,
|
|
882
971
|
let croppedCGImage = cgImage.cropping(to: cropRect) else {
|
|
972
|
+
print("[CameraPreview] cropImageToAspectRatio - Failed to crop image")
|
|
883
973
|
return nil
|
|
884
974
|
}
|
|
885
975
|
|
|
886
|
-
|
|
976
|
+
let croppedImage = UIImage(cgImage: croppedCGImage, scale: normalizedImage.scale, orientation: .up)
|
|
977
|
+
let finalAspectRatio = croppedImage.size.width / croppedImage.size.height
|
|
978
|
+
print("[CameraPreview] cropImageToAspectRatio - Final cropped image: \(croppedImage.size.width)x\(croppedImage.size.height) (ratio: \(finalAspectRatio))")
|
|
979
|
+
|
|
980
|
+
// Create the cropped image with normalized orientation
|
|
981
|
+
return croppedImage
|
|
887
982
|
}
|
|
888
983
|
|
|
889
984
|
func cropImageToMatchPreview(image: UIImage, previewLayer: AVCaptureVideoPreviewLayer) -> UIImage? {
|
|
@@ -1861,7 +1956,8 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
|
1861
1956
|
}
|
|
1862
1957
|
|
|
1863
1958
|
// Pass through original file data and metadata so callers can preserve EXIF
|
|
1864
|
-
|
|
1959
|
+
// Don't call fixedOrientation() here - let the completion block handle it after cropping
|
|
1960
|
+
self.photoCaptureCompletionBlock?(image, imageData, photo.metadata, nil)
|
|
1865
1961
|
}
|
|
1866
1962
|
}
|
|
1867
1963
|
|
|
@@ -310,6 +310,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
self.aspectRatio = newAspectRatio
|
|
313
|
+
|
|
314
|
+
// Propagate to camera controller so capture output and preview align
|
|
315
|
+
self.cameraController.updateAspectRatio(newAspectRatio)
|
|
316
|
+
|
|
313
317
|
DispatchQueue.main.async {
|
|
314
318
|
call.resolve(self.rawSetAspectRatio())
|
|
315
319
|
}
|