@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
- return (width: CGFloat(components[0]), height: CGFloat(components[1]))
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
- let videoOrientation: AVCaptureVideoOrientation
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
- // If we used session preset (low-res), image should already be correct aspect ratio
768
- // If we used high-res (shouldn't happen without max dimensions), crop it
769
- let imageAspectRatio = image.size.width / image.size.height
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
- let imageSize = image.size
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
- guard let cgImage = image.cgImage,
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
- return UIImage(cgImage: croppedCGImage, scale: image.scale, orientation: image.imageOrientation)
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
- self.photoCaptureCompletionBlock?(image.fixedOrientation(), imageData, photo.metadata, nil)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.8.0",
3
+ "version": "7.8.1",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {