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