@capgo/camera-preview 7.6.1-alpha.1 → 7.6.1-alpha.3
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 {
|
|
@@ -454,9 +527,12 @@ extension CameraController {
|
|
|
454
527
|
// Configure video gravity and frame based on aspect ratio
|
|
455
528
|
if let aspectRatio = requestedAspectRatio {
|
|
456
529
|
// Calculate the frame based on requested aspect ratio
|
|
530
|
+
let frame = calculateAspectRatioFrame(for: aspectRatio, in: view.bounds)
|
|
531
|
+
previewLayer.frame = frame
|
|
457
532
|
previewLayer.videoGravity = .resizeAspectFill
|
|
458
533
|
} else {
|
|
459
534
|
// No specific aspect ratio requested - fill the entire view
|
|
535
|
+
previewLayer.frame = view.bounds
|
|
460
536
|
previewLayer.videoGravity = .resizeAspect
|
|
461
537
|
}
|
|
462
538
|
|
|
@@ -527,7 +603,7 @@ extension CameraController {
|
|
|
527
603
|
}
|
|
528
604
|
|
|
529
605
|
private func updateVideoOrientationOnMainThread() {
|
|
530
|
-
|
|
606
|
+
var videoOrientation: AVCaptureVideoOrientation
|
|
531
607
|
|
|
532
608
|
// Use window scene interface orientation
|
|
533
609
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
@@ -678,7 +754,7 @@ extension CameraController {
|
|
|
678
754
|
}
|
|
679
755
|
|
|
680
756
|
func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
|
|
681
|
-
print("[CameraPreview] captureImage called - width: \(width ?? -1), height: \(height ?? -1)")
|
|
757
|
+
print("[CameraPreview] captureImage called - width: \(width ?? -1), height: \(height ?? -1), requestedAspectRatio: \(self.requestedAspectRatio ?? "nil")")
|
|
682
758
|
|
|
683
759
|
guard let photoOutput = self.photoOutput else {
|
|
684
760
|
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
@@ -747,18 +823,9 @@ extension CameraController {
|
|
|
747
823
|
print("[CameraPreview] Resized to max dimensions: \(finalImage.size.width)x\(finalImage.size.height)")
|
|
748
824
|
} else if let aspectRatio = self.requestedAspectRatio {
|
|
749
825
|
// No max dimensions specified, but aspect ratio is specified
|
|
750
|
-
//
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if let targetRatio = self.parseAspectRatio(aspectRatio) {
|
|
754
|
-
let targetAspectRatio = targetRatio.width / targetRatio.height
|
|
755
|
-
|
|
756
|
-
// Allow small tolerance for aspect ratio comparison
|
|
757
|
-
if abs(imageAspectRatio - targetAspectRatio) > 0.01 {
|
|
758
|
-
finalImage = self.cropImageToAspectRatio(image: image, aspectRatio: aspectRatio) ?? image
|
|
759
|
-
print("[CameraPreview] Cropped to match aspect ratio \(aspectRatio): \(finalImage.size.width)x\(finalImage.size.height)")
|
|
760
|
-
}
|
|
761
|
-
}
|
|
826
|
+
// Always apply aspect ratio cropping to ensure correct orientation
|
|
827
|
+
finalImage = self.cropImageToAspectRatio(image: image, aspectRatio: aspectRatio) ?? image
|
|
828
|
+
print("[CameraPreview] Applied aspect ratio cropping for \(aspectRatio): \(finalImage.size.width)x\(finalImage.size.height)")
|
|
762
829
|
}
|
|
763
830
|
|
|
764
831
|
completion(finalImage, photoData, metadata, nil)
|
|
@@ -840,13 +907,27 @@ extension CameraController {
|
|
|
840
907
|
|
|
841
908
|
func cropImageToAspectRatio(image: UIImage, aspectRatio: String) -> UIImage? {
|
|
842
909
|
guard let ratio = parseAspectRatio(aspectRatio) else {
|
|
910
|
+
print("[CameraPreview] cropImageToAspectRatio - Failed to parse aspect ratio: \(aspectRatio)")
|
|
843
911
|
return image
|
|
844
912
|
}
|
|
845
913
|
|
|
846
|
-
|
|
914
|
+
// Only normalize the image orientation if it's not already correct
|
|
915
|
+
let normalizedImage: UIImage
|
|
916
|
+
if image.imageOrientation == .up {
|
|
917
|
+
normalizedImage = image
|
|
918
|
+
print("[CameraPreview] cropImageToAspectRatio - Image already has correct orientation")
|
|
919
|
+
} else {
|
|
920
|
+
normalizedImage = image.fixedOrientation() ?? image
|
|
921
|
+
print("[CameraPreview] cropImageToAspectRatio - Normalized image orientation from \(image.imageOrientation.rawValue) to .up")
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
let imageSize = normalizedImage.size
|
|
847
925
|
let imageAspectRatio = imageSize.width / imageSize.height
|
|
848
926
|
let targetAspectRatio = ratio.width / ratio.height
|
|
849
927
|
|
|
928
|
+
print("[CameraPreview] cropImageToAspectRatio - Original image: \(imageSize.width)x\(imageSize.height) (ratio: \(imageAspectRatio))")
|
|
929
|
+
print("[CameraPreview] cropImageToAspectRatio - Target ratio: \(ratio.width):\(ratio.height) (ratio: \(targetAspectRatio))")
|
|
930
|
+
|
|
850
931
|
var cropRect: CGRect
|
|
851
932
|
|
|
852
933
|
if imageAspectRatio > targetAspectRatio {
|
|
@@ -854,19 +935,36 @@ extension CameraController {
|
|
|
854
935
|
let targetWidth = imageSize.height * targetAspectRatio
|
|
855
936
|
let xOffset = (imageSize.width - targetWidth) / 2
|
|
856
937
|
cropRect = CGRect(x: xOffset, y: 0, width: targetWidth, height: imageSize.height)
|
|
938
|
+
print("[CameraPreview] cropImageToAspectRatio - Horizontal crop: \(cropRect)")
|
|
857
939
|
} else {
|
|
858
940
|
// Image is taller than target - crop vertically (center crop)
|
|
859
941
|
let targetHeight = imageSize.width / targetAspectRatio
|
|
860
942
|
let yOffset = (imageSize.height - targetHeight) / 2
|
|
861
943
|
cropRect = CGRect(x: 0, y: yOffset, width: imageSize.width, height: targetHeight)
|
|
944
|
+
print("[CameraPreview] cropImageToAspectRatio - Vertical crop: \(cropRect) - Target height: \(targetHeight)")
|
|
862
945
|
}
|
|
863
946
|
|
|
864
|
-
|
|
947
|
+
// Validate crop rect is within image bounds
|
|
948
|
+
if cropRect.minX < 0 || cropRect.minY < 0 ||
|
|
949
|
+
cropRect.maxX > imageSize.width || cropRect.maxY > imageSize.height {
|
|
950
|
+
print("[CameraPreview] cropImageToAspectRatio - Warning: Crop rect \(cropRect) exceeds image bounds \(imageSize)")
|
|
951
|
+
// Adjust crop rect to fit within image bounds
|
|
952
|
+
cropRect = cropRect.intersection(CGRect(origin: .zero, size: imageSize))
|
|
953
|
+
print("[CameraPreview] cropImageToAspectRatio - Adjusted crop rect: \(cropRect)")
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
guard let cgImage = normalizedImage.cgImage,
|
|
865
957
|
let croppedCGImage = cgImage.cropping(to: cropRect) else {
|
|
958
|
+
print("[CameraPreview] cropImageToAspectRatio - Failed to crop image")
|
|
866
959
|
return nil
|
|
867
960
|
}
|
|
868
961
|
|
|
869
|
-
|
|
962
|
+
let croppedImage = UIImage(cgImage: croppedCGImage, scale: normalizedImage.scale, orientation: .up)
|
|
963
|
+
let finalAspectRatio = croppedImage.size.width / croppedImage.size.height
|
|
964
|
+
print("[CameraPreview] cropImageToAspectRatio - Final cropped image: \(croppedImage.size.width)x\(croppedImage.size.height) (ratio: \(finalAspectRatio))")
|
|
965
|
+
|
|
966
|
+
// Create the cropped image with normalized orientation
|
|
967
|
+
return croppedImage
|
|
870
968
|
}
|
|
871
969
|
|
|
872
970
|
func cropImageToMatchPreview(image: UIImage, previewLayer: AVCaptureVideoPreviewLayer) -> UIImage? {
|
|
@@ -1667,7 +1765,8 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
|
1667
1765
|
}
|
|
1668
1766
|
|
|
1669
1767
|
// Pass through original file data and metadata so callers can preserve EXIF
|
|
1670
|
-
|
|
1768
|
+
// Don't call fixedOrientation() here - let the completion block handle it after cropping
|
|
1769
|
+
self.photoCaptureCompletionBlock?(image, imageData, photo.metadata, nil)
|
|
1671
1770
|
}
|
|
1672
1771
|
}
|
|
1673
1772
|
|
|
@@ -305,6 +305,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
self.aspectRatio = newAspectRatio
|
|
308
|
+
|
|
309
|
+
// Propagate to camera controller so capture output and preview align
|
|
310
|
+
self.cameraController.updateAspectRatio(newAspectRatio)
|
|
311
|
+
|
|
308
312
|
DispatchQueue.main.async {
|
|
309
313
|
call.resolve(self.rawSetAspectRatio())
|
|
310
314
|
}
|