@capgo/camera-preview 7.6.1-alpha.2 → 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.
|
@@ -72,10 +72,76 @@ class CameraController: NSObject {
|
|
|
72
72
|
// Track whether an aspect ratio was explicitly requested
|
|
73
73
|
var requestedAspectRatio: String?
|
|
74
74
|
|
|
75
|
+
private func calculateAspectRatioFrame(for aspectRatio: String, in bounds: CGRect) -> CGRect {
|
|
76
|
+
guard let ratio = parseAspectRatio(aspectRatio) else {
|
|
77
|
+
return bounds
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let targetAspectRatio = ratio.width / ratio.height
|
|
81
|
+
let viewAspectRatio = bounds.width / bounds.height
|
|
82
|
+
|
|
83
|
+
var frame: CGRect
|
|
84
|
+
|
|
85
|
+
if viewAspectRatio > targetAspectRatio {
|
|
86
|
+
// View is wider than target - fit by height
|
|
87
|
+
let targetWidth = bounds.height * targetAspectRatio
|
|
88
|
+
let xOffset = (bounds.width - targetWidth) / 2
|
|
89
|
+
frame = CGRect(x: xOffset, y: 0, width: targetWidth, height: bounds.height)
|
|
90
|
+
} else {
|
|
91
|
+
// View is taller than target - fit by width
|
|
92
|
+
let targetHeight = bounds.width / targetAspectRatio
|
|
93
|
+
let yOffset = (bounds.height - targetHeight) / 2
|
|
94
|
+
frame = CGRect(x: 0, y: yOffset, width: bounds.width, height: targetHeight)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return frame
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
private func parseAspectRatio(_ aspectRatio: String) -> (width: CGFloat, height: CGFloat)? {
|
|
76
101
|
let components = aspectRatio.split(separator: ":").compactMap { Float(String($0)) }
|
|
77
102
|
guard components.count == 2 else { return nil }
|
|
78
|
-
|
|
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)
|
|
79
145
|
}
|
|
80
146
|
}
|
|
81
147
|
|
|
@@ -315,6 +381,38 @@ extension CameraController {
|
|
|
315
381
|
}
|
|
316
382
|
}
|
|
317
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
|
+
|
|
318
416
|
private func setInitialZoom(level: Float?) {
|
|
319
417
|
let device = (currentCameraPosition == .rear) ? rearCamera : frontCamera
|
|
320
418
|
guard let device = device else {
|
|
@@ -429,9 +527,12 @@ extension CameraController {
|
|
|
429
527
|
// Configure video gravity and frame based on aspect ratio
|
|
430
528
|
if let aspectRatio = requestedAspectRatio {
|
|
431
529
|
// Calculate the frame based on requested aspect ratio
|
|
530
|
+
let frame = calculateAspectRatioFrame(for: aspectRatio, in: view.bounds)
|
|
531
|
+
previewLayer.frame = frame
|
|
432
532
|
previewLayer.videoGravity = .resizeAspectFill
|
|
433
533
|
} else {
|
|
434
534
|
// No specific aspect ratio requested - fill the entire view
|
|
535
|
+
previewLayer.frame = view.bounds
|
|
435
536
|
previewLayer.videoGravity = .resizeAspect
|
|
436
537
|
}
|
|
437
538
|
|
|
@@ -523,8 +624,6 @@ extension CameraController {
|
|
|
523
624
|
} else {
|
|
524
625
|
videoOrientation = .portrait
|
|
525
626
|
}
|
|
526
|
-
|
|
527
|
-
videoOrientation = .landscapeLeft
|
|
528
627
|
|
|
529
628
|
previewLayer?.connection?.videoOrientation = videoOrientation
|
|
530
629
|
dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
|
|
@@ -655,7 +754,7 @@ extension CameraController {
|
|
|
655
754
|
}
|
|
656
755
|
|
|
657
756
|
func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
|
|
658
|
-
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")")
|
|
659
758
|
|
|
660
759
|
guard let photoOutput = self.photoOutput else {
|
|
661
760
|
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
@@ -724,18 +823,9 @@ extension CameraController {
|
|
|
724
823
|
print("[CameraPreview] Resized to max dimensions: \(finalImage.size.width)x\(finalImage.size.height)")
|
|
725
824
|
} else if let aspectRatio = self.requestedAspectRatio {
|
|
726
825
|
// No max dimensions specified, but aspect ratio is specified
|
|
727
|
-
//
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if let targetRatio = self.parseAspectRatio(aspectRatio) {
|
|
731
|
-
let targetAspectRatio = targetRatio.width / targetRatio.height
|
|
732
|
-
|
|
733
|
-
// Allow small tolerance for aspect ratio comparison
|
|
734
|
-
if abs(imageAspectRatio - targetAspectRatio) > 0.01 {
|
|
735
|
-
finalImage = self.cropImageToAspectRatio(image: image, aspectRatio: aspectRatio) ?? image
|
|
736
|
-
print("[CameraPreview] Cropped to match aspect ratio \(aspectRatio): \(finalImage.size.width)x\(finalImage.size.height)")
|
|
737
|
-
}
|
|
738
|
-
}
|
|
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)")
|
|
739
829
|
}
|
|
740
830
|
|
|
741
831
|
completion(finalImage, photoData, metadata, nil)
|
|
@@ -817,13 +907,27 @@ extension CameraController {
|
|
|
817
907
|
|
|
818
908
|
func cropImageToAspectRatio(image: UIImage, aspectRatio: String) -> UIImage? {
|
|
819
909
|
guard let ratio = parseAspectRatio(aspectRatio) else {
|
|
910
|
+
print("[CameraPreview] cropImageToAspectRatio - Failed to parse aspect ratio: \(aspectRatio)")
|
|
820
911
|
return image
|
|
821
912
|
}
|
|
822
913
|
|
|
823
|
-
|
|
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
|
|
824
925
|
let imageAspectRatio = imageSize.width / imageSize.height
|
|
825
926
|
let targetAspectRatio = ratio.width / ratio.height
|
|
826
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
|
+
|
|
827
931
|
var cropRect: CGRect
|
|
828
932
|
|
|
829
933
|
if imageAspectRatio > targetAspectRatio {
|
|
@@ -831,19 +935,36 @@ extension CameraController {
|
|
|
831
935
|
let targetWidth = imageSize.height * targetAspectRatio
|
|
832
936
|
let xOffset = (imageSize.width - targetWidth) / 2
|
|
833
937
|
cropRect = CGRect(x: xOffset, y: 0, width: targetWidth, height: imageSize.height)
|
|
938
|
+
print("[CameraPreview] cropImageToAspectRatio - Horizontal crop: \(cropRect)")
|
|
834
939
|
} else {
|
|
835
940
|
// Image is taller than target - crop vertically (center crop)
|
|
836
941
|
let targetHeight = imageSize.width / targetAspectRatio
|
|
837
942
|
let yOffset = (imageSize.height - targetHeight) / 2
|
|
838
943
|
cropRect = CGRect(x: 0, y: yOffset, width: imageSize.width, height: targetHeight)
|
|
944
|
+
print("[CameraPreview] cropImageToAspectRatio - Vertical crop: \(cropRect) - Target height: \(targetHeight)")
|
|
839
945
|
}
|
|
840
946
|
|
|
841
|
-
|
|
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,
|
|
842
957
|
let croppedCGImage = cgImage.cropping(to: cropRect) else {
|
|
958
|
+
print("[CameraPreview] cropImageToAspectRatio - Failed to crop image")
|
|
843
959
|
return nil
|
|
844
960
|
}
|
|
845
961
|
|
|
846
|
-
|
|
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
|
|
847
968
|
}
|
|
848
969
|
|
|
849
970
|
func cropImageToMatchPreview(image: UIImage, previewLayer: AVCaptureVideoPreviewLayer) -> UIImage? {
|
|
@@ -1644,7 +1765,8 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
|
1644
1765
|
}
|
|
1645
1766
|
|
|
1646
1767
|
// Pass through original file data and metadata so callers can preserve EXIF
|
|
1647
|
-
|
|
1768
|
+
// Don't call fixedOrientation() here - let the completion block handle it after cropping
|
|
1769
|
+
self.photoCaptureCompletionBlock?(image, imageData, photo.metadata, nil)
|
|
1648
1770
|
}
|
|
1649
1771
|
}
|
|
1650
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
|
}
|