@capgo/camera-preview 7.4.0-alpha.21 → 7.4.0-alpha.23

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.
@@ -1281,11 +1281,116 @@ public class CameraPreview
1281
1281
  Log.d("CameraPreview", "12. PIXEL RATIO - " + pixelRatio);
1282
1282
  Log.d("CameraPreview", "========================");
1283
1283
 
1284
+ // Calculate logical values with proper rounding to avoid sub-pixel issues
1285
+ double logicalWidth = width / pixelRatio;
1286
+ double logicalHeight = height / pixelRatio;
1287
+ double logicalX = x / pixelRatio;
1288
+ double logicalY = relativeY / pixelRatio;
1289
+
1290
+ // Log exact calculations to debug one-pixel difference
1291
+ Log.d("CameraPreview", "========================");
1292
+ Log.d("CameraPreview", "FINAL POSITION CALCULATIONS:");
1293
+ Log.d(
1294
+ "CameraPreview",
1295
+ "Pixel values: x=" +
1296
+ x +
1297
+ ", y=" +
1298
+ relativeY +
1299
+ ", width=" +
1300
+ width +
1301
+ ", height=" +
1302
+ height
1303
+ );
1304
+ Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
1305
+ Log.d(
1306
+ "CameraPreview",
1307
+ "Logical values (exact): x=" +
1308
+ logicalX +
1309
+ ", y=" +
1310
+ logicalY +
1311
+ ", width=" +
1312
+ logicalWidth +
1313
+ ", height=" +
1314
+ logicalHeight
1315
+ );
1316
+ Log.d(
1317
+ "CameraPreview",
1318
+ "Logical values (rounded): x=" +
1319
+ Math.round(logicalX) +
1320
+ ", y=" +
1321
+ Math.round(logicalY) +
1322
+ ", width=" +
1323
+ Math.round(logicalWidth) +
1324
+ ", height=" +
1325
+ Math.round(logicalHeight)
1326
+ );
1327
+
1328
+ // Check if previewContainer has any padding or margin that might cause offset
1329
+ if (cameraXView != null) {
1330
+ View previewContainer = cameraXView.getPreviewContainer();
1331
+ if (previewContainer != null) {
1332
+ Log.d(
1333
+ "CameraPreview",
1334
+ "PreviewContainer padding: left=" +
1335
+ previewContainer.getPaddingLeft() +
1336
+ ", top=" +
1337
+ previewContainer.getPaddingTop() +
1338
+ ", right=" +
1339
+ previewContainer.getPaddingRight() +
1340
+ ", bottom=" +
1341
+ previewContainer.getPaddingBottom()
1342
+ );
1343
+ ViewGroup.LayoutParams params = previewContainer.getLayoutParams();
1344
+ if (params instanceof ViewGroup.MarginLayoutParams) {
1345
+ ViewGroup.MarginLayoutParams marginParams =
1346
+ (ViewGroup.MarginLayoutParams) params;
1347
+ Log.d(
1348
+ "CameraPreview",
1349
+ "PreviewContainer margins: left=" +
1350
+ marginParams.leftMargin +
1351
+ ", top=" +
1352
+ marginParams.topMargin +
1353
+ ", right=" +
1354
+ marginParams.rightMargin +
1355
+ ", bottom=" +
1356
+ marginParams.bottomMargin
1357
+ );
1358
+ }
1359
+ }
1360
+ }
1361
+ Log.d("CameraPreview", "========================");
1362
+
1284
1363
  JSObject result = new JSObject();
1285
- result.put("width", width / pixelRatio);
1286
- result.put("height", height / pixelRatio);
1287
- result.put("x", x / pixelRatio);
1288
- result.put("y", relativeY / pixelRatio);
1364
+ // Return values with proper rounding to avoid gaps
1365
+ // For positions (x, y): floor to avoid gaps at top/left
1366
+ // For dimensions (width, height): ceil to avoid gaps at bottom/right
1367
+ result.put("width", Math.floor(logicalWidth));
1368
+ result.put("height", Math.floor(logicalHeight));
1369
+ result.put("x", Math.ceil(logicalX));
1370
+ result.put("y", Math.ceil(logicalY));
1371
+
1372
+ // Log what we're returning
1373
+ Log.d(
1374
+ "CameraPreview",
1375
+ "Returning to JS - x: " +
1376
+ Math.ceil(logicalX) +
1377
+ " (from " +
1378
+ logicalX +
1379
+ "), y: " +
1380
+ Math.ceil(logicalY) +
1381
+ " (from " +
1382
+ logicalY +
1383
+ "), width: " +
1384
+ Math.floor(logicalWidth) +
1385
+ " (from " +
1386
+ logicalWidth +
1387
+ "), height: " +
1388
+ Math.floor(logicalHeight) +
1389
+ " (from " +
1390
+ logicalHeight +
1391
+ ")"
1392
+ );
1393
+
1289
1394
  call.resolve(result);
1290
1395
  bridge.releaseCall(call);
1291
1396
  cameraStartCallbackId = null; // Prevent re-use
@@ -1391,10 +1496,16 @@ public class CameraPreview
1391
1496
  float pixelRatio = metrics.density;
1392
1497
 
1393
1498
  JSObject ret = new JSObject();
1394
- ret.put("x", cameraXView.getPreviewX() / pixelRatio);
1395
- ret.put("y", cameraXView.getPreviewY() / pixelRatio);
1396
- ret.put("width", cameraXView.getPreviewWidth() / pixelRatio);
1397
- ret.put("height", cameraXView.getPreviewHeight() / pixelRatio);
1499
+ // Use same rounding strategy as start method
1500
+ double x = cameraXView.getPreviewX() / pixelRatio;
1501
+ double y = cameraXView.getPreviewY() / pixelRatio;
1502
+ double width = cameraXView.getPreviewWidth() / pixelRatio;
1503
+ double height = cameraXView.getPreviewHeight() / pixelRatio;
1504
+
1505
+ ret.put("x", Math.ceil(x));
1506
+ ret.put("y", Math.ceil(y));
1507
+ ret.put("width", Math.floor(width));
1508
+ ret.put("height", Math.floor(height));
1398
1509
  call.resolve(ret);
1399
1510
  }
1400
1511
 
@@ -135,6 +135,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
135
135
  return isRunning;
136
136
  }
137
137
 
138
+ public View getPreviewContainer() {
139
+ return previewContainer;
140
+ }
141
+
138
142
  private void saveImageToGallery(byte[] data) {
139
143
  try {
140
144
  // Detect image format from byte array header
@@ -268,6 +272,13 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
268
272
  previewContainer.setClickable(true);
269
273
  previewContainer.setFocusable(true);
270
274
 
275
+ // Disable any potential drawing artifacts that might cause 1px offset
276
+ previewContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
277
+
278
+ // Ensure no clip bounds that might cause visual offset
279
+ previewContainer.setClipChildren(false);
280
+ previewContainer.setClipToPadding(false);
281
+
271
282
  // Create and setup the preview view
272
283
  previewView = new PreviewView(context);
273
284
  // Match iOS behavior: FIT when no aspect ratio, FILL when aspect ratio is set
@@ -3353,10 +3364,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
3353
3364
  int webViewTopInset = getWebViewTopInset();
3354
3365
  int webViewLeftInset = getWebViewLeftInset();
3355
3366
 
3356
- int x = Math.max(0, (int) ((actualX - webViewLeftInset) / pixelRatio));
3357
- int y = Math.max(0, (int) ((actualY - webViewTopInset) / pixelRatio));
3358
- int width = (int) (actualWidth / pixelRatio);
3359
- int height = (int) (actualHeight / pixelRatio);
3367
+ // Use proper rounding strategy to avoid gaps:
3368
+ // - For positions (x, y): floor to avoid gaps at top/left
3369
+ // - For dimensions (width, height): ceil to avoid gaps at bottom/right
3370
+ int x = Math.max(
3371
+ 0,
3372
+ (int) Math.ceil((actualX - webViewLeftInset) / pixelRatio)
3373
+ );
3374
+ int y = Math.max(
3375
+ 0,
3376
+ (int) Math.ceil((actualY - webViewTopInset) / pixelRatio)
3377
+ );
3378
+ int width = (int) Math.floor(actualWidth / pixelRatio);
3379
+ int height = (int) Math.floor(actualHeight / pixelRatio);
3360
3380
 
3361
3381
  return new int[] { x, y, width, height };
3362
3382
  }
@@ -504,6 +504,33 @@ extension CameraController {
504
504
  dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
505
505
  photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
506
506
  }
507
+
508
+ private func setDefaultZoomAfterFlip() {
509
+ let device = (currentCameraPosition == .rear) ? rearCamera : frontCamera
510
+ guard let device = device else {
511
+ print("[CameraPreview] No device available for default zoom after flip")
512
+ return
513
+ }
514
+
515
+ // Set zoom to 1.0x in UI terms, accounting for display multiplier
516
+ let multiplier = self.getDisplayZoomMultiplier()
517
+ let targetUIZoom: Float = 1.0 // We want 1.0x in the UI
518
+ let nativeZoom = multiplier != 1.0 ? (targetUIZoom / multiplier) : targetUIZoom
519
+
520
+ let minZoom = device.minAvailableVideoZoomFactor
521
+ let maxZoom = min(device.maxAvailableVideoZoomFactor, saneMaxZoomFactor)
522
+ let clampedZoom = max(minZoom, min(CGFloat(nativeZoom), maxZoom))
523
+
524
+ do {
525
+ try device.lockForConfiguration()
526
+ device.videoZoomFactor = clampedZoom
527
+ device.unlockForConfiguration()
528
+ self.zoomFactor = clampedZoom
529
+ print("[CameraPreview] Set default zoom after flip: UI=\(targetUIZoom)x, native=\(clampedZoom), multiplier=\(multiplier)")
530
+ } catch {
531
+ print("[CameraPreview] Failed to set default zoom after flip: \(error)")
532
+ }
533
+ }
507
534
 
508
535
  func switchCameras() throws {
509
536
  guard let currentCameraPosition = currentCameraPosition,
@@ -594,6 +621,11 @@ extension CameraController {
594
621
 
595
622
  // Update video orientation
596
623
  self.updateVideoOrientation()
624
+
625
+ // Set default 1.0 zoom level after camera switch to prevent iOS 18+ zoom jumps
626
+ DispatchQueue.main.async { [weak self] in
627
+ self?.setDefaultZoomAfterFlip()
628
+ }
597
629
  }
598
630
 
599
631
  func captureImage(width: Int?, height: Int?, aspectRatio: String?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.4.0-alpha.21",
3
+ "version": "7.4.0-alpha.23",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {