@capgo/camera-preview 7.4.0-alpha.19 → 7.4.0-alpha.22
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.
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +235 -14
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +831 -252
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +35 -10
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.ahm.capacitor.camera.preview;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.content.res.Configuration;
|
|
4
5
|
import android.graphics.Bitmap;
|
|
5
6
|
import android.graphics.BitmapFactory;
|
|
6
7
|
import android.graphics.Color;
|
|
@@ -20,14 +21,7 @@ import android.util.Size;
|
|
|
20
21
|
import android.view.MotionEvent;
|
|
21
22
|
import android.view.View;
|
|
22
23
|
import android.view.ViewGroup;
|
|
23
|
-
import android.view.animation.AlphaAnimation;
|
|
24
|
-
import android.view.animation.Animation;
|
|
25
|
-
import android.view.animation.AnimationSet;
|
|
26
|
-
import android.view.animation.AnimationUtils;
|
|
27
|
-
import android.view.animation.ScaleAnimation;
|
|
28
24
|
import android.webkit.WebView;
|
|
29
|
-
import android.webkit.WebView;
|
|
30
|
-
import android.widget.FrameLayout;
|
|
31
25
|
import android.widget.FrameLayout;
|
|
32
26
|
import androidx.annotation.NonNull;
|
|
33
27
|
import androidx.annotation.OptIn;
|
|
@@ -78,7 +72,6 @@ import java.util.Set;
|
|
|
78
72
|
import java.util.concurrent.Executor;
|
|
79
73
|
import java.util.concurrent.ExecutorService;
|
|
80
74
|
import java.util.concurrent.Executors;
|
|
81
|
-
import java.util.concurrent.TimeUnit;
|
|
82
75
|
import org.json.JSONObject;
|
|
83
76
|
|
|
84
77
|
public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
@@ -142,6 +135,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
142
135
|
return isRunning;
|
|
143
136
|
}
|
|
144
137
|
|
|
138
|
+
public View getPreviewContainer() {
|
|
139
|
+
return previewContainer;
|
|
140
|
+
}
|
|
141
|
+
|
|
145
142
|
private void saveImageToGallery(byte[] data) {
|
|
146
143
|
try {
|
|
147
144
|
// Detect image format from byte array header
|
|
@@ -275,9 +272,24 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
275
272
|
previewContainer.setClickable(true);
|
|
276
273
|
previewContainer.setFocusable(true);
|
|
277
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
|
+
|
|
278
282
|
// Create and setup the preview view
|
|
279
283
|
previewView = new PreviewView(context);
|
|
280
|
-
|
|
284
|
+
// Match iOS behavior: FIT when no aspect ratio, FILL when aspect ratio is set
|
|
285
|
+
String initialAspectRatio = sessionConfig != null
|
|
286
|
+
? sessionConfig.getAspectRatio()
|
|
287
|
+
: null;
|
|
288
|
+
previewView.setScaleType(
|
|
289
|
+
(initialAspectRatio == null || initialAspectRatio.isEmpty())
|
|
290
|
+
? PreviewView.ScaleType.FIT_CENTER
|
|
291
|
+
: PreviewView.ScaleType.FILL_CENTER
|
|
292
|
+
);
|
|
281
293
|
// Also make preview view touchable as backup
|
|
282
294
|
previewView.setClickable(true);
|
|
283
295
|
previewView.setFocusable(true);
|
|
@@ -434,9 +446,45 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
434
446
|
int height = sessionConfig.getHeight();
|
|
435
447
|
String aspectRatio = sessionConfig.getAspectRatio();
|
|
436
448
|
|
|
449
|
+
// Get comprehensive display information
|
|
450
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
451
|
+
int screenWidthPx = metrics.widthPixels;
|
|
452
|
+
int screenHeightPx = metrics.heightPixels;
|
|
453
|
+
float density = metrics.density;
|
|
454
|
+
int screenWidthDp = (int) (screenWidthPx / density);
|
|
455
|
+
int screenHeightDp = (int) (screenHeightPx / density);
|
|
456
|
+
|
|
457
|
+
// Get WebView dimensions
|
|
458
|
+
int webViewWidth = webView != null ? webView.getWidth() : 0;
|
|
459
|
+
int webViewHeight = webView != null ? webView.getHeight() : 0;
|
|
460
|
+
|
|
461
|
+
// Get parent dimensions
|
|
462
|
+
ViewGroup parent = (ViewGroup) webView.getParent();
|
|
463
|
+
int parentWidth = parent != null ? parent.getWidth() : 0;
|
|
464
|
+
int parentHeight = parent != null ? parent.getHeight() : 0;
|
|
465
|
+
|
|
437
466
|
Log.d(
|
|
438
467
|
TAG,
|
|
439
|
-
"
|
|
468
|
+
"======================== CALCULATE PREVIEW LAYOUT PARAMS ========================"
|
|
469
|
+
);
|
|
470
|
+
Log.d(
|
|
471
|
+
TAG,
|
|
472
|
+
"Screen dimensions - Pixels: " +
|
|
473
|
+
screenWidthPx +
|
|
474
|
+
"x" +
|
|
475
|
+
screenHeightPx +
|
|
476
|
+
", DP: " +
|
|
477
|
+
screenWidthDp +
|
|
478
|
+
"x" +
|
|
479
|
+
screenHeightDp +
|
|
480
|
+
", Density: " +
|
|
481
|
+
density
|
|
482
|
+
);
|
|
483
|
+
Log.d(TAG, "WebView dimensions: " + webViewWidth + "x" + webViewHeight);
|
|
484
|
+
Log.d(TAG, "Parent dimensions: " + parentWidth + "x" + parentHeight);
|
|
485
|
+
Log.d(
|
|
486
|
+
TAG,
|
|
487
|
+
"SessionConfig values - x:" +
|
|
440
488
|
x +
|
|
441
489
|
" y:" +
|
|
442
490
|
y +
|
|
@@ -445,83 +493,97 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
445
493
|
" height:" +
|
|
446
494
|
height +
|
|
447
495
|
" aspectRatio:" +
|
|
448
|
-
aspectRatio
|
|
496
|
+
aspectRatio +
|
|
497
|
+
" isCentered:" +
|
|
498
|
+
sessionConfig.isCentered()
|
|
449
499
|
);
|
|
450
500
|
|
|
451
|
-
// Apply aspect ratio if specified
|
|
452
|
-
if (
|
|
501
|
+
// Apply aspect ratio if specified
|
|
502
|
+
if (
|
|
503
|
+
aspectRatio != null &&
|
|
504
|
+
!aspectRatio.isEmpty() &&
|
|
505
|
+
sessionConfig.isCentered()
|
|
506
|
+
) {
|
|
453
507
|
String[] ratios = aspectRatio.split(":");
|
|
454
508
|
if (ratios.length == 2) {
|
|
455
509
|
try {
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
int optimalHeight = (int) (width / ratio);
|
|
463
|
-
|
|
464
|
-
if (optimalHeight > height) {
|
|
465
|
-
// Height constraint is tighter, fit by height
|
|
466
|
-
optimalHeight = height;
|
|
467
|
-
optimalWidth = (int) (height * ratio);
|
|
468
|
-
}
|
|
510
|
+
// Match iOS logic exactly
|
|
511
|
+
double ratioWidth = Double.parseDouble(ratios[0]);
|
|
512
|
+
double ratioHeight = Double.parseDouble(ratios[1]);
|
|
513
|
+
boolean isPortrait =
|
|
514
|
+
context.getResources().getConfiguration().orientation ==
|
|
515
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
469
516
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
517
|
+
Log.d(
|
|
518
|
+
TAG,
|
|
519
|
+
"Aspect ratio parsing - Original: " +
|
|
520
|
+
aspectRatio +
|
|
521
|
+
" (width=" +
|
|
522
|
+
ratioWidth +
|
|
523
|
+
", height=" +
|
|
524
|
+
ratioHeight +
|
|
525
|
+
")"
|
|
526
|
+
);
|
|
527
|
+
Log.d(
|
|
528
|
+
TAG,
|
|
529
|
+
"Device orientation: " + (isPortrait ? "PORTRAIT" : "LANDSCAPE")
|
|
530
|
+
);
|
|
475
531
|
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
532
|
+
// iOS: let ratio = !isPortrait ? ratioParts[0] / ratioParts[1] : ratioParts[1] / ratioParts[0]
|
|
533
|
+
double ratio = !isPortrait
|
|
534
|
+
? (ratioWidth / ratioHeight)
|
|
535
|
+
: (ratioHeight / ratioWidth);
|
|
479
536
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
", newWidth=" +
|
|
489
|
-
width +
|
|
490
|
-
", screenWidth=" +
|
|
491
|
-
screenWidth +
|
|
492
|
-
", newX=" +
|
|
493
|
-
x
|
|
494
|
-
);
|
|
495
|
-
}
|
|
537
|
+
Log.d(
|
|
538
|
+
TAG,
|
|
539
|
+
"Computed ratio: " +
|
|
540
|
+
ratio +
|
|
541
|
+
" (iOS formula: " +
|
|
542
|
+
(!isPortrait ? "width/height" : "height/width") +
|
|
543
|
+
")"
|
|
544
|
+
);
|
|
496
545
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
y = (screenHeight - height) / 2;
|
|
501
|
-
Log.d(
|
|
502
|
-
TAG,
|
|
503
|
-
"calculatePreviewLayoutParams: Recentered Y after aspect ratio - " +
|
|
504
|
-
"oldHeight=" +
|
|
505
|
-
oldHeight +
|
|
506
|
-
", newHeight=" +
|
|
507
|
-
height +
|
|
508
|
-
", screenHeight=" +
|
|
509
|
-
screenHeight +
|
|
510
|
-
", newY=" +
|
|
511
|
-
y
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
546
|
+
// For centered mode with aspect ratio, calculate maximum size that fits
|
|
547
|
+
int availableWidth = metrics.widthPixels;
|
|
548
|
+
int availableHeight = metrics.heightPixels;
|
|
515
549
|
|
|
516
550
|
Log.d(
|
|
517
551
|
TAG,
|
|
518
|
-
"
|
|
519
|
-
|
|
520
|
-
" - new size: " +
|
|
521
|
-
width +
|
|
552
|
+
"Available space for preview: " +
|
|
553
|
+
availableWidth +
|
|
522
554
|
"x" +
|
|
523
|
-
|
|
555
|
+
availableHeight
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
// Calculate maximum size that fits the aspect ratio in available space
|
|
559
|
+
double maxWidthByHeight = availableHeight * ratio;
|
|
560
|
+
double maxHeightByWidth = availableWidth / ratio;
|
|
561
|
+
|
|
562
|
+
Log.d(
|
|
563
|
+
TAG,
|
|
564
|
+
"Aspect ratio calculations - maxWidthByHeight: " +
|
|
565
|
+
maxWidthByHeight +
|
|
566
|
+
", maxHeightByWidth: " +
|
|
567
|
+
maxHeightByWidth
|
|
524
568
|
);
|
|
569
|
+
|
|
570
|
+
if (maxWidthByHeight <= availableWidth) {
|
|
571
|
+
// Height is the limiting factor
|
|
572
|
+
width = (int) maxWidthByHeight;
|
|
573
|
+
height = availableHeight;
|
|
574
|
+
Log.d(TAG, "Height-limited sizing: " + width + "x" + height);
|
|
575
|
+
} else {
|
|
576
|
+
// Width is the limiting factor
|
|
577
|
+
width = availableWidth;
|
|
578
|
+
height = (int) maxHeightByWidth;
|
|
579
|
+
Log.d(TAG, "Width-limited sizing: " + width + "x" + height);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Center the preview
|
|
583
|
+
x = (availableWidth - width) / 2;
|
|
584
|
+
y = (availableHeight - height) / 2;
|
|
585
|
+
|
|
586
|
+
Log.d(TAG, "Auto-centered position: x=" + x + ", y=" + y);
|
|
525
587
|
} catch (NumberFormatException e) {
|
|
526
588
|
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
527
589
|
}
|
|
@@ -540,28 +602,20 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
540
602
|
|
|
541
603
|
Log.d(
|
|
542
604
|
TAG,
|
|
543
|
-
"
|
|
544
|
-
x +
|
|
545
|
-
" (leftMargin=" +
|
|
605
|
+
"Final layout params - Margins: left=" +
|
|
546
606
|
layoutParams.leftMargin +
|
|
547
|
-
"
|
|
548
|
-
y +
|
|
549
|
-
" (topMargin=" +
|
|
607
|
+
", top=" +
|
|
550
608
|
layoutParams.topMargin +
|
|
551
|
-
"
|
|
609
|
+
", Size: " +
|
|
610
|
+
width +
|
|
611
|
+
"x" +
|
|
612
|
+
height
|
|
552
613
|
);
|
|
553
|
-
|
|
554
614
|
Log.d(
|
|
555
615
|
TAG,
|
|
556
|
-
"
|
|
557
|
-
x +
|
|
558
|
-
" y:" +
|
|
559
|
-
y +
|
|
560
|
-
" width:" +
|
|
561
|
-
width +
|
|
562
|
-
" height:" +
|
|
563
|
-
height
|
|
616
|
+
"================================================================================"
|
|
564
617
|
);
|
|
618
|
+
|
|
565
619
|
return layoutParams;
|
|
566
620
|
}
|
|
567
621
|
|
|
@@ -623,13 +677,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
623
677
|
ResolutionSelector resolutionSelector =
|
|
624
678
|
resolutionSelectorBuilder.build();
|
|
625
679
|
|
|
680
|
+
int rotation = previewView != null && previewView.getDisplay() != null
|
|
681
|
+
? previewView.getDisplay().getRotation()
|
|
682
|
+
: android.view.Surface.ROTATION_0;
|
|
683
|
+
|
|
626
684
|
Preview preview = new Preview.Builder()
|
|
627
685
|
.setResolutionSelector(resolutionSelector)
|
|
686
|
+
.setTargetRotation(rotation)
|
|
628
687
|
.build();
|
|
629
688
|
imageCapture = new ImageCapture.Builder()
|
|
630
689
|
.setResolutionSelector(resolutionSelector)
|
|
631
690
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
632
691
|
.setFlashMode(currentFlashMode)
|
|
692
|
+
.setTargetRotation(rotation)
|
|
633
693
|
.build();
|
|
634
694
|
sampleImageCapture = imageCapture;
|
|
635
695
|
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
@@ -688,6 +748,50 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
688
748
|
if (previewResolution != null) {
|
|
689
749
|
currentPreviewResolution = previewResolution.getResolution();
|
|
690
750
|
Log.d(TAG, "Preview resolution: " + currentPreviewResolution);
|
|
751
|
+
|
|
752
|
+
// Log the actual aspect ratio of the selected resolution
|
|
753
|
+
if (currentPreviewResolution != null) {
|
|
754
|
+
double actualRatio =
|
|
755
|
+
(double) currentPreviewResolution.getWidth() /
|
|
756
|
+
(double) currentPreviewResolution.getHeight();
|
|
757
|
+
Log.d(
|
|
758
|
+
TAG,
|
|
759
|
+
"Actual preview aspect ratio: " +
|
|
760
|
+
actualRatio +
|
|
761
|
+
" (width=" +
|
|
762
|
+
currentPreviewResolution.getWidth() +
|
|
763
|
+
", height=" +
|
|
764
|
+
currentPreviewResolution.getHeight() +
|
|
765
|
+
")"
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
// Compare with requested ratio
|
|
769
|
+
if ("4:3".equals(sessionConfig.getAspectRatio())) {
|
|
770
|
+
double expectedRatio = 4.0 / 3.0;
|
|
771
|
+
double difference = Math.abs(actualRatio - expectedRatio);
|
|
772
|
+
Log.d(
|
|
773
|
+
TAG,
|
|
774
|
+
"4:3 ratio check - Expected: " +
|
|
775
|
+
expectedRatio +
|
|
776
|
+
", Actual: " +
|
|
777
|
+
actualRatio +
|
|
778
|
+
", Difference: " +
|
|
779
|
+
difference
|
|
780
|
+
);
|
|
781
|
+
} else if ("16:9".equals(sessionConfig.getAspectRatio())) {
|
|
782
|
+
double expectedRatio = 16.0 / 9.0;
|
|
783
|
+
double difference = Math.abs(actualRatio - expectedRatio);
|
|
784
|
+
Log.d(
|
|
785
|
+
TAG,
|
|
786
|
+
"16:9 ratio check - Expected: " +
|
|
787
|
+
expectedRatio +
|
|
788
|
+
", Actual: " +
|
|
789
|
+
actualRatio +
|
|
790
|
+
", Difference: " +
|
|
791
|
+
difference
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
691
795
|
}
|
|
692
796
|
ResolutionInfo imageCaptureResolution =
|
|
693
797
|
imageCapture.getResolutionInfo();
|
|
@@ -699,6 +803,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
699
803
|
);
|
|
700
804
|
}
|
|
701
805
|
|
|
806
|
+
// Update scale type based on aspect ratio whenever (re)binding
|
|
807
|
+
String ar = sessionConfig != null
|
|
808
|
+
? sessionConfig.getAspectRatio()
|
|
809
|
+
: null;
|
|
810
|
+
previewView.setScaleType(
|
|
811
|
+
(ar == null || ar.isEmpty())
|
|
812
|
+
? PreviewView.ScaleType.FIT_CENTER
|
|
813
|
+
: PreviewView.ScaleType.FILL_CENTER
|
|
814
|
+
);
|
|
815
|
+
|
|
702
816
|
// Set initial zoom if specified, prioritizing targetZoom over default zoomFactor
|
|
703
817
|
float initialZoom = sessionConfig.getTargetZoom() != 1.0f
|
|
704
818
|
? sessionConfig.getTargetZoom()
|
|
@@ -914,26 +1028,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
914
1028
|
|
|
915
1029
|
JSONObject exifData = getExifData(exifInterface);
|
|
916
1030
|
|
|
917
|
-
//
|
|
1031
|
+
// Determine final output: explicit size wins, then explicit aspectRatio,
|
|
1032
|
+
// otherwise crop to match what is visible in the preview (iOS parity)
|
|
918
1033
|
String captureAspectRatio = aspectRatio;
|
|
919
|
-
if (
|
|
920
|
-
width == null &&
|
|
921
|
-
height == null &&
|
|
922
|
-
aspectRatio == null &&
|
|
923
|
-
sessionConfig != null
|
|
924
|
-
) {
|
|
925
|
-
captureAspectRatio = sessionConfig.getAspectRatio();
|
|
926
|
-
// Default to "4:3" if no aspect ratio was set at all
|
|
927
|
-
if (captureAspectRatio == null) {
|
|
928
|
-
captureAspectRatio = "4:3";
|
|
929
|
-
}
|
|
930
|
-
Log.d(
|
|
931
|
-
TAG,
|
|
932
|
-
"capturePhoto: Using stored aspectRatio: " + captureAspectRatio
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Handle aspect ratio if no width/height specified
|
|
937
1034
|
if (
|
|
938
1035
|
width == null &&
|
|
939
1036
|
height == null &&
|
|
@@ -1027,14 +1124,22 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1027
1124
|
// Write EXIF data back to resized image
|
|
1028
1125
|
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
1029
1126
|
} else {
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1127
|
+
// No explicit size/ratio: crop to match current preview content
|
|
1128
|
+
Bitmap originalBitmap = BitmapFactory.decodeByteArray(
|
|
1129
|
+
bytes,
|
|
1130
|
+
0,
|
|
1131
|
+
bytes.length
|
|
1035
1132
|
);
|
|
1036
|
-
|
|
1037
|
-
|
|
1133
|
+
Bitmap previewCropped = cropBitmapToMatchPreview(originalBitmap);
|
|
1134
|
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
1135
|
+
previewCropped.compress(
|
|
1136
|
+
Bitmap.CompressFormat.JPEG,
|
|
1137
|
+
quality,
|
|
1138
|
+
stream
|
|
1139
|
+
);
|
|
1140
|
+
bytes = stream.toByteArray();
|
|
1141
|
+
// Preserve EXIF
|
|
1142
|
+
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
1038
1143
|
}
|
|
1039
1144
|
|
|
1040
1145
|
if (saveToGallery) {
|
|
@@ -1380,6 +1485,49 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1380
1485
|
return bytes;
|
|
1381
1486
|
}
|
|
1382
1487
|
|
|
1488
|
+
private Bitmap cropBitmapToMatchPreview(Bitmap image) {
|
|
1489
|
+
if (previewContainer == null || previewView == null) {
|
|
1490
|
+
return image;
|
|
1491
|
+
}
|
|
1492
|
+
int containerWidth = previewContainer.getWidth();
|
|
1493
|
+
int containerHeight = previewContainer.getHeight();
|
|
1494
|
+
if (containerWidth == 0 || containerHeight == 0) {
|
|
1495
|
+
return image;
|
|
1496
|
+
}
|
|
1497
|
+
// Compute preview aspect based on actual camera content bounds
|
|
1498
|
+
Rect bounds = getActualCameraBounds();
|
|
1499
|
+
int previewW = Math.max(1, bounds.width());
|
|
1500
|
+
int previewH = Math.max(1, bounds.height());
|
|
1501
|
+
float previewRatio = (float) previewW / (float) previewH;
|
|
1502
|
+
|
|
1503
|
+
int imgW = image.getWidth();
|
|
1504
|
+
int imgH = image.getHeight();
|
|
1505
|
+
float imgRatio = (float) imgW / (float) imgH;
|
|
1506
|
+
|
|
1507
|
+
int targetW = imgW;
|
|
1508
|
+
int targetH = imgH;
|
|
1509
|
+
if (imgRatio > previewRatio) {
|
|
1510
|
+
// Image wider than preview: crop width
|
|
1511
|
+
targetW = Math.round(imgH * previewRatio);
|
|
1512
|
+
} else if (imgRatio < previewRatio) {
|
|
1513
|
+
// Image taller than preview: crop height
|
|
1514
|
+
targetH = Math.round(imgW / previewRatio);
|
|
1515
|
+
}
|
|
1516
|
+
int x = Math.max(0, (imgW - targetW) / 2);
|
|
1517
|
+
int y = Math.max(0, (imgH - targetH) / 2);
|
|
1518
|
+
try {
|
|
1519
|
+
return Bitmap.createBitmap(
|
|
1520
|
+
image,
|
|
1521
|
+
x,
|
|
1522
|
+
y,
|
|
1523
|
+
Math.min(targetW, imgW - x),
|
|
1524
|
+
Math.min(targetH, imgH - y)
|
|
1525
|
+
);
|
|
1526
|
+
} catch (Exception ignore) {
|
|
1527
|
+
return image;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1383
1531
|
// not workin for xiaomi https://xiaomi.eu/community/threads/mi-11-ultra-unable-to-access-camera-lenses-in-apps-camera2-api.61456/
|
|
1384
1532
|
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1385
1533
|
public static List<
|
|
@@ -1698,12 +1846,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1698
1846
|
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
1699
1847
|
MeteringPoint point = factory.createPoint(x * viewWidth, y * viewHeight);
|
|
1700
1848
|
|
|
1701
|
-
// Create focus and metering action
|
|
1849
|
+
// Create focus and metering action (persistent, no auto-cancel) to match iOS behavior
|
|
1702
1850
|
FocusMeteringAction action = new FocusMeteringAction.Builder(
|
|
1703
1851
|
point,
|
|
1704
1852
|
FocusMeteringAction.FLAG_AF | FocusMeteringAction.FLAG_AE
|
|
1705
1853
|
)
|
|
1706
|
-
.
|
|
1854
|
+
.disableAutoCancel()
|
|
1707
1855
|
.build();
|
|
1708
1856
|
|
|
1709
1857
|
try {
|
|
@@ -1777,8 +1925,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1777
1925
|
}
|
|
1778
1926
|
|
|
1779
1927
|
// Create an elegant focus indicator
|
|
1780
|
-
|
|
1781
|
-
int size = (int) (
|
|
1928
|
+
FrameLayout container = new FrameLayout(context);
|
|
1929
|
+
int size = (int) (80 * context.getResources().getDisplayMetrics().density); // match iOS size
|
|
1782
1930
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size);
|
|
1783
1931
|
|
|
1784
1932
|
// Center the indicator on the touch point with bounds checking
|
|
@@ -1794,25 +1942,73 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1794
1942
|
Math.min((int) (y - size / 2), containerHeight - size)
|
|
1795
1943
|
);
|
|
1796
1944
|
|
|
1797
|
-
//
|
|
1798
|
-
GradientDrawable
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
);
|
|
1804
|
-
|
|
1805
|
-
|
|
1945
|
+
// iOS Camera style: square with mid-edge ticks
|
|
1946
|
+
GradientDrawable border = new GradientDrawable();
|
|
1947
|
+
border.setShape(GradientDrawable.RECTANGLE);
|
|
1948
|
+
int stroke = (int) (2 * context.getResources().getDisplayMetrics().density);
|
|
1949
|
+
border.setStroke(stroke, Color.YELLOW);
|
|
1950
|
+
border.setCornerRadius(0);
|
|
1951
|
+
border.setColor(Color.TRANSPARENT);
|
|
1952
|
+
container.setBackground(border);
|
|
1953
|
+
|
|
1954
|
+
// Add 4 tiny mid-edge ticks inside the square
|
|
1955
|
+
int tickLen = (int) (12 *
|
|
1956
|
+
context.getResources().getDisplayMetrics().density);
|
|
1957
|
+
int inset = stroke; // ticks should touch the sides
|
|
1958
|
+
// Top tick (perpendicular): vertical inward from top edge
|
|
1959
|
+
View topTick = new View(context);
|
|
1960
|
+
FrameLayout.LayoutParams topParams = new FrameLayout.LayoutParams(
|
|
1961
|
+
stroke,
|
|
1962
|
+
tickLen
|
|
1963
|
+
);
|
|
1964
|
+
topParams.leftMargin = (size - stroke) / 2;
|
|
1965
|
+
topParams.topMargin = inset;
|
|
1966
|
+
topTick.setLayoutParams(topParams);
|
|
1967
|
+
topTick.setBackgroundColor(Color.YELLOW);
|
|
1968
|
+
container.addView(topTick);
|
|
1969
|
+
// Bottom tick (perpendicular): vertical inward from bottom edge
|
|
1970
|
+
View bottomTick = new View(context);
|
|
1971
|
+
FrameLayout.LayoutParams bottomParams = new FrameLayout.LayoutParams(
|
|
1972
|
+
stroke,
|
|
1973
|
+
tickLen
|
|
1974
|
+
);
|
|
1975
|
+
bottomParams.leftMargin = (size - stroke) / 2;
|
|
1976
|
+
bottomParams.topMargin = size - inset - tickLen;
|
|
1977
|
+
bottomTick.setLayoutParams(bottomParams);
|
|
1978
|
+
bottomTick.setBackgroundColor(Color.YELLOW);
|
|
1979
|
+
container.addView(bottomTick);
|
|
1980
|
+
// Left tick (perpendicular): horizontal inward from left edge
|
|
1981
|
+
View leftTick = new View(context);
|
|
1982
|
+
FrameLayout.LayoutParams leftParams = new FrameLayout.LayoutParams(
|
|
1983
|
+
tickLen,
|
|
1984
|
+
stroke
|
|
1985
|
+
);
|
|
1986
|
+
leftParams.leftMargin = inset;
|
|
1987
|
+
leftParams.topMargin = (size - stroke) / 2;
|
|
1988
|
+
leftTick.setLayoutParams(leftParams);
|
|
1989
|
+
leftTick.setBackgroundColor(Color.YELLOW);
|
|
1990
|
+
container.addView(leftTick);
|
|
1991
|
+
// Right tick (perpendicular): horizontal inward from right edge
|
|
1992
|
+
View rightTick = new View(context);
|
|
1993
|
+
FrameLayout.LayoutParams rightParams = new FrameLayout.LayoutParams(
|
|
1994
|
+
tickLen,
|
|
1995
|
+
stroke
|
|
1996
|
+
);
|
|
1997
|
+
rightParams.leftMargin = size - inset - tickLen;
|
|
1998
|
+
rightParams.topMargin = (size - stroke) / 2;
|
|
1999
|
+
rightTick.setLayoutParams(rightParams);
|
|
2000
|
+
rightTick.setBackgroundColor(Color.YELLOW);
|
|
2001
|
+
container.addView(rightTick);
|
|
1806
2002
|
|
|
1807
2003
|
focusIndicatorView = container;
|
|
1808
2004
|
// Bump animation token; everything after this must validate against this token
|
|
1809
2005
|
final long thisAnimationId = ++focusIndicatorAnimationId;
|
|
1810
2006
|
final View thisIndicatorView = focusIndicatorView;
|
|
1811
2007
|
|
|
1812
|
-
// Set initial state for smooth animation
|
|
1813
|
-
focusIndicatorView.setAlpha(
|
|
1814
|
-
focusIndicatorView.setScaleX(1.
|
|
1815
|
-
focusIndicatorView.setScaleY(1.
|
|
2008
|
+
// Set initial state for smooth animation (mirror iOS)
|
|
2009
|
+
focusIndicatorView.setAlpha(0f);
|
|
2010
|
+
focusIndicatorView.setScaleX(1.5f);
|
|
2011
|
+
focusIndicatorView.setScaleY(1.5f);
|
|
1816
2012
|
focusIndicatorView.setVisibility(View.VISIBLE);
|
|
1817
2013
|
|
|
1818
2014
|
// Ensure container doesn't intercept touch events
|
|
@@ -1836,26 +2032,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1836
2032
|
// Force a layout pass to ensure the view is properly positioned
|
|
1837
2033
|
previewContainer.requestLayout();
|
|
1838
2034
|
|
|
1839
|
-
//
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
Animation.RELATIVE_TO_SELF,
|
|
1848
|
-
0.5f
|
|
1849
|
-
);
|
|
1850
|
-
scaleAnimation.setDuration(120);
|
|
1851
|
-
scaleAnimation.setInterpolator(
|
|
1852
|
-
new android.view.animation.OvershootInterpolator(1.2f)
|
|
1853
|
-
);
|
|
1854
|
-
|
|
1855
|
-
// Start the animation
|
|
1856
|
-
focusIndicatorView.startAnimation(scaleAnimation);
|
|
2035
|
+
// First phase: fade in and scale to 1.0 over 150ms
|
|
2036
|
+
focusIndicatorView
|
|
2037
|
+
.animate()
|
|
2038
|
+
.alpha(1f)
|
|
2039
|
+
.scaleX(1f)
|
|
2040
|
+
.scaleY(1f)
|
|
2041
|
+
.setDuration(150)
|
|
2042
|
+
.start();
|
|
1857
2043
|
|
|
1858
|
-
//
|
|
2044
|
+
// Phase 2: after 500ms, fade to 0.3 over 200ms
|
|
1859
2045
|
focusIndicatorView.postDelayed(
|
|
1860
2046
|
new Runnable() {
|
|
1861
2047
|
@Override
|
|
@@ -1868,37 +2054,66 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1868
2054
|
) {
|
|
1869
2055
|
focusIndicatorView
|
|
1870
2056
|
.animate()
|
|
1871
|
-
.alpha(
|
|
1872
|
-
.
|
|
1873
|
-
.scaleY(0.9f)
|
|
1874
|
-
.setDuration(180)
|
|
1875
|
-
.setInterpolator(
|
|
1876
|
-
new android.view.animation.AccelerateInterpolator()
|
|
1877
|
-
)
|
|
2057
|
+
.alpha(0.3f)
|
|
2058
|
+
.setDuration(200)
|
|
1878
2059
|
.withEndAction(
|
|
1879
2060
|
new Runnable() {
|
|
1880
2061
|
@Override
|
|
1881
2062
|
public void run() {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2063
|
+
// Phase 3: after 200ms more, fade out to 0 and scale to 0.8 over 300ms
|
|
2064
|
+
focusIndicatorView.postDelayed(
|
|
2065
|
+
new Runnable() {
|
|
2066
|
+
@Override
|
|
2067
|
+
public void run() {
|
|
2068
|
+
if (
|
|
2069
|
+
focusIndicatorView != null &&
|
|
2070
|
+
thisIndicatorView == focusIndicatorView &&
|
|
2071
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
2072
|
+
) {
|
|
2073
|
+
focusIndicatorView
|
|
2074
|
+
.animate()
|
|
2075
|
+
.alpha(0f)
|
|
2076
|
+
.scaleX(0.8f)
|
|
2077
|
+
.scaleY(0.8f)
|
|
2078
|
+
.setDuration(300)
|
|
2079
|
+
.setInterpolator(
|
|
2080
|
+
new android.view.animation.AccelerateInterpolator()
|
|
2081
|
+
)
|
|
2082
|
+
.withEndAction(
|
|
2083
|
+
new Runnable() {
|
|
2084
|
+
@Override
|
|
2085
|
+
public void run() {
|
|
2086
|
+
if (
|
|
2087
|
+
focusIndicatorView != null &&
|
|
2088
|
+
previewContainer != null &&
|
|
2089
|
+
thisIndicatorView == focusIndicatorView &&
|
|
2090
|
+
thisAnimationId ==
|
|
2091
|
+
focusIndicatorAnimationId
|
|
2092
|
+
) {
|
|
2093
|
+
try {
|
|
2094
|
+
focusIndicatorView.clearAnimation();
|
|
2095
|
+
} catch (Exception ignore) {}
|
|
2096
|
+
previewContainer.removeView(
|
|
2097
|
+
focusIndicatorView
|
|
2098
|
+
);
|
|
2099
|
+
focusIndicatorView = null;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
200
|
|
2108
|
+
);
|
|
1894
2109
|
}
|
|
1895
2110
|
}
|
|
1896
2111
|
);
|
|
1897
2112
|
}
|
|
1898
2113
|
}
|
|
1899
2114
|
},
|
|
1900
|
-
|
|
1901
|
-
);
|
|
2115
|
+
500
|
|
2116
|
+
);
|
|
1902
2117
|
}
|
|
1903
2118
|
|
|
1904
2119
|
public static List<Size> getSupportedPictureSizes(String facing) {
|
|
@@ -2149,13 +2364,48 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2149
2364
|
Float y,
|
|
2150
2365
|
Runnable callback
|
|
2151
2366
|
) {
|
|
2367
|
+
Log.d(
|
|
2368
|
+
TAG,
|
|
2369
|
+
"======================== SET ASPECT RATIO ========================"
|
|
2370
|
+
);
|
|
2371
|
+
Log.d(
|
|
2372
|
+
TAG,
|
|
2373
|
+
"Input parameters - aspectRatio: " +
|
|
2374
|
+
aspectRatio +
|
|
2375
|
+
", x: " +
|
|
2376
|
+
x +
|
|
2377
|
+
", y: " +
|
|
2378
|
+
y
|
|
2379
|
+
);
|
|
2380
|
+
|
|
2152
2381
|
if (sessionConfig == null) {
|
|
2382
|
+
Log.d(TAG, "SessionConfig is null, returning");
|
|
2153
2383
|
if (callback != null) callback.run();
|
|
2154
2384
|
return;
|
|
2155
2385
|
}
|
|
2156
2386
|
|
|
2157
2387
|
String currentAspectRatio = sessionConfig.getAspectRatio();
|
|
2158
2388
|
|
|
2389
|
+
// Get current display information
|
|
2390
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
2391
|
+
int screenWidthPx = metrics.widthPixels;
|
|
2392
|
+
int screenHeightPx = metrics.heightPixels;
|
|
2393
|
+
boolean isPortrait =
|
|
2394
|
+
context.getResources().getConfiguration().orientation ==
|
|
2395
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
2396
|
+
|
|
2397
|
+
Log.d(
|
|
2398
|
+
TAG,
|
|
2399
|
+
"Current screen: " +
|
|
2400
|
+
screenWidthPx +
|
|
2401
|
+
"x" +
|
|
2402
|
+
screenHeightPx +
|
|
2403
|
+
" (" +
|
|
2404
|
+
(isPortrait ? "PORTRAIT" : "LANDSCAPE") +
|
|
2405
|
+
")"
|
|
2406
|
+
);
|
|
2407
|
+
Log.d(TAG, "Current aspect ratio: " + currentAspectRatio);
|
|
2408
|
+
|
|
2159
2409
|
// Don't restart camera if aspect ratio hasn't changed and no position specified
|
|
2160
2410
|
if (
|
|
2161
2411
|
aspectRatio != null &&
|
|
@@ -2163,12 +2413,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2163
2413
|
x == null &&
|
|
2164
2414
|
y == null
|
|
2165
2415
|
) {
|
|
2166
|
-
Log.d(
|
|
2167
|
-
TAG,
|
|
2168
|
-
"setAspectRatio: Aspect ratio " +
|
|
2169
|
-
aspectRatio +
|
|
2170
|
-
" is already set and no position specified, skipping"
|
|
2171
|
-
);
|
|
2416
|
+
Log.d(TAG, "Aspect ratio unchanged and no position specified, skipping");
|
|
2172
2417
|
if (callback != null) callback.run();
|
|
2173
2418
|
return;
|
|
2174
2419
|
}
|
|
@@ -2176,22 +2421,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2176
2421
|
String currentGridMode = sessionConfig.getGridMode();
|
|
2177
2422
|
Log.d(
|
|
2178
2423
|
TAG,
|
|
2179
|
-
"
|
|
2180
|
-
currentAspectRatio +
|
|
2181
|
-
" to " +
|
|
2182
|
-
aspectRatio +
|
|
2183
|
-
(x != null && y != null
|
|
2184
|
-
? " at position (" + x + ", " + y + ")"
|
|
2185
|
-
: " with auto-centering") +
|
|
2186
|
-
", preserving grid mode: " +
|
|
2187
|
-
currentGridMode
|
|
2424
|
+
"Changing aspect ratio from " + currentAspectRatio + " to " + aspectRatio
|
|
2188
2425
|
);
|
|
2426
|
+
Log.d(TAG, "Auto-centering will be applied (matching iOS behavior)");
|
|
2189
2427
|
|
|
2428
|
+
// Match iOS behavior: when aspect ratio changes, always auto-center
|
|
2190
2429
|
sessionConfig = new CameraSessionConfiguration(
|
|
2191
2430
|
sessionConfig.getDeviceId(),
|
|
2192
2431
|
sessionConfig.getPosition(),
|
|
2193
|
-
|
|
2194
|
-
|
|
2432
|
+
-1, // Force auto-center X (iOS: self.posX = -1)
|
|
2433
|
+
-1, // Force auto-center Y (iOS: self.posY = -1)
|
|
2195
2434
|
sessionConfig.getWidth(),
|
|
2196
2435
|
sessionConfig.getHeight(),
|
|
2197
2436
|
sessionConfig.getPaddingBottom(),
|
|
@@ -2205,12 +2444,13 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2205
2444
|
aspectRatio,
|
|
2206
2445
|
currentGridMode
|
|
2207
2446
|
);
|
|
2447
|
+
sessionConfig.setCentered(true);
|
|
2208
2448
|
|
|
2209
2449
|
// Update layout and rebind camera with new aspect ratio
|
|
2210
2450
|
if (isRunning && previewContainer != null) {
|
|
2211
2451
|
mainExecutor.execute(() -> {
|
|
2212
|
-
// First update the UI layout
|
|
2213
|
-
updatePreviewLayoutForAspectRatio(aspectRatio,
|
|
2452
|
+
// First update the UI layout - always pass null for x,y to force auto-centering (matching iOS)
|
|
2453
|
+
updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
|
|
2214
2454
|
|
|
2215
2455
|
// Then rebind the camera with new aspect ratio configuration
|
|
2216
2456
|
Log.d(
|
|
@@ -2240,8 +2480,121 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2240
2480
|
previewContainer.post(callback);
|
|
2241
2481
|
}
|
|
2242
2482
|
}
|
|
2483
|
+
|
|
2484
|
+
Log.d(
|
|
2485
|
+
TAG,
|
|
2486
|
+
"=================================================================="
|
|
2487
|
+
);
|
|
2243
2488
|
});
|
|
2244
2489
|
} else {
|
|
2490
|
+
Log.d(TAG, "Camera not running, just saving configuration");
|
|
2491
|
+
Log.d(
|
|
2492
|
+
TAG,
|
|
2493
|
+
"=================================================================="
|
|
2494
|
+
);
|
|
2495
|
+
if (callback != null) callback.run();
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// Force aspect ratio recalculation (used during orientation changes)
|
|
2500
|
+
public void forceAspectRatioRecalculation(
|
|
2501
|
+
String aspectRatio,
|
|
2502
|
+
Float x,
|
|
2503
|
+
Float y,
|
|
2504
|
+
Runnable callback
|
|
2505
|
+
) {
|
|
2506
|
+
Log.d(
|
|
2507
|
+
TAG,
|
|
2508
|
+
"======================== FORCE ASPECT RATIO RECALCULATION ========================"
|
|
2509
|
+
);
|
|
2510
|
+
Log.d(
|
|
2511
|
+
TAG,
|
|
2512
|
+
"Input parameters - aspectRatio: " +
|
|
2513
|
+
aspectRatio +
|
|
2514
|
+
", x: " +
|
|
2515
|
+
x +
|
|
2516
|
+
", y: " +
|
|
2517
|
+
y
|
|
2518
|
+
);
|
|
2519
|
+
|
|
2520
|
+
if (sessionConfig == null) {
|
|
2521
|
+
Log.d(TAG, "SessionConfig is null, returning");
|
|
2522
|
+
if (callback != null) callback.run();
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
String currentGridMode = sessionConfig.getGridMode();
|
|
2527
|
+
Log.d(TAG, "Forcing aspect ratio recalculation for: " + aspectRatio);
|
|
2528
|
+
Log.d(TAG, "Auto-centering will be applied (matching iOS behavior)");
|
|
2529
|
+
|
|
2530
|
+
// Match iOS behavior: when aspect ratio changes, always auto-center
|
|
2531
|
+
sessionConfig = new CameraSessionConfiguration(
|
|
2532
|
+
sessionConfig.getDeviceId(),
|
|
2533
|
+
sessionConfig.getPosition(),
|
|
2534
|
+
-1, // Force auto-center X (iOS: self.posX = -1)
|
|
2535
|
+
-1, // Force auto-center Y (iOS: self.posY = -1)
|
|
2536
|
+
sessionConfig.getWidth(),
|
|
2537
|
+
sessionConfig.getHeight(),
|
|
2538
|
+
sessionConfig.getPaddingBottom(),
|
|
2539
|
+
sessionConfig.getToBack(),
|
|
2540
|
+
sessionConfig.getStoreToFile(),
|
|
2541
|
+
sessionConfig.getEnableOpacity(),
|
|
2542
|
+
sessionConfig.getEnableZoom(),
|
|
2543
|
+
sessionConfig.getDisableExifHeaderStripping(),
|
|
2544
|
+
sessionConfig.getDisableAudio(),
|
|
2545
|
+
sessionConfig.getZoomFactor(),
|
|
2546
|
+
aspectRatio,
|
|
2547
|
+
currentGridMode
|
|
2548
|
+
);
|
|
2549
|
+
sessionConfig.setCentered(true);
|
|
2550
|
+
|
|
2551
|
+
// Update layout and rebind camera with new aspect ratio
|
|
2552
|
+
if (isRunning && previewContainer != null) {
|
|
2553
|
+
mainExecutor.execute(() -> {
|
|
2554
|
+
// First update the UI layout - always pass null for x,y to force auto-centering (matching iOS)
|
|
2555
|
+
updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
|
|
2556
|
+
|
|
2557
|
+
// Then rebind the camera with new aspect ratio configuration
|
|
2558
|
+
Log.d(
|
|
2559
|
+
TAG,
|
|
2560
|
+
"forceAspectRatioRecalculation: Rebinding camera with aspect ratio: " +
|
|
2561
|
+
aspectRatio
|
|
2562
|
+
);
|
|
2563
|
+
bindCameraUseCases();
|
|
2564
|
+
|
|
2565
|
+
// Preserve grid mode and wait for completion
|
|
2566
|
+
if (gridOverlayView != null) {
|
|
2567
|
+
gridOverlayView.post(() -> {
|
|
2568
|
+
Log.d(
|
|
2569
|
+
TAG,
|
|
2570
|
+
"forceAspectRatioRecalculation: Re-applying grid mode: " +
|
|
2571
|
+
currentGridMode
|
|
2572
|
+
);
|
|
2573
|
+
gridOverlayView.setGridMode(currentGridMode);
|
|
2574
|
+
|
|
2575
|
+
// Wait one more frame for grid to be applied, then call callback
|
|
2576
|
+
if (callback != null) {
|
|
2577
|
+
gridOverlayView.post(callback);
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
} else {
|
|
2581
|
+
// No grid overlay, wait one frame for layout completion then call callback
|
|
2582
|
+
if (callback != null) {
|
|
2583
|
+
previewContainer.post(callback);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
Log.d(
|
|
2588
|
+
TAG,
|
|
2589
|
+
"=================================================================="
|
|
2590
|
+
);
|
|
2591
|
+
});
|
|
2592
|
+
} else {
|
|
2593
|
+
Log.d(TAG, "Camera not running, just saving configuration");
|
|
2594
|
+
Log.d(
|
|
2595
|
+
TAG,
|
|
2596
|
+
"=================================================================="
|
|
2597
|
+
);
|
|
2245
2598
|
if (callback != null) callback.run();
|
|
2246
2599
|
}
|
|
2247
2600
|
}
|
|
@@ -2352,15 +2705,40 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2352
2705
|
return new Rect(0, 0, containerWidth, containerHeight);
|
|
2353
2706
|
}
|
|
2354
2707
|
|
|
2355
|
-
//
|
|
2356
|
-
//
|
|
2357
|
-
|
|
2358
|
-
int
|
|
2708
|
+
// CameraX delivers preview in sensor orientation (always landscape)
|
|
2709
|
+
// But PreviewView internally rotates it to match device orientation
|
|
2710
|
+
// So we need to swap dimensions in portrait mode
|
|
2711
|
+
int cameraWidth = currentPreviewResolution.getWidth();
|
|
2712
|
+
int cameraHeight = currentPreviewResolution.getHeight();
|
|
2713
|
+
|
|
2714
|
+
// Check if we're in portrait mode
|
|
2715
|
+
boolean isPortrait =
|
|
2716
|
+
context.getResources().getConfiguration().orientation ==
|
|
2717
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
2718
|
+
|
|
2719
|
+
// Swap dimensions if in portrait mode to match how PreviewView displays it
|
|
2720
|
+
if (isPortrait) {
|
|
2721
|
+
int temp = cameraWidth;
|
|
2722
|
+
cameraWidth = cameraHeight;
|
|
2723
|
+
cameraHeight = temp;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// When we have an aspect ratio set, we use FILL_CENTER which scales to fill
|
|
2727
|
+
// the container while maintaining aspect ratio, potentially cropping
|
|
2728
|
+
boolean usesFillCenter =
|
|
2729
|
+
sessionConfig != null && sessionConfig.getAspectRatio() != null;
|
|
2359
2730
|
|
|
2360
|
-
// Calculate the scaling factor to fit the camera in the container
|
|
2361
2731
|
float widthScale = (float) containerWidth / cameraWidth;
|
|
2362
2732
|
float heightScale = (float) containerHeight / cameraHeight;
|
|
2363
|
-
float scale
|
|
2733
|
+
float scale;
|
|
2734
|
+
|
|
2735
|
+
if (usesFillCenter) {
|
|
2736
|
+
// FILL_CENTER uses max scale to fill the container
|
|
2737
|
+
scale = Math.max(widthScale, heightScale);
|
|
2738
|
+
} else {
|
|
2739
|
+
// FIT_CENTER uses min scale to fit within the container
|
|
2740
|
+
scale = Math.min(widthScale, heightScale);
|
|
2741
|
+
}
|
|
2364
2742
|
|
|
2365
2743
|
// Calculate the actual size of the camera content after scaling
|
|
2366
2744
|
int scaledWidth = Math.round(cameraWidth * scale);
|
|
@@ -2380,8 +2758,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2380
2758
|
cameraWidth +
|
|
2381
2759
|
"x" +
|
|
2382
2760
|
cameraHeight +
|
|
2761
|
+
" (swapped=" +
|
|
2762
|
+
isPortrait +
|
|
2763
|
+
")" +
|
|
2383
2764
|
", scale=" +
|
|
2384
2765
|
scale +
|
|
2766
|
+
" (fillCenter=" +
|
|
2767
|
+
usesFillCenter +
|
|
2768
|
+
")" +
|
|
2385
2769
|
", scaled=" +
|
|
2386
2770
|
scaledWidth +
|
|
2387
2771
|
"x" +
|
|
@@ -2395,10 +2779,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2395
2779
|
|
|
2396
2780
|
// Return the bounds relative to the container
|
|
2397
2781
|
return new Rect(
|
|
2398
|
-
offsetX,
|
|
2399
|
-
offsetY,
|
|
2400
|
-
offsetX + scaledWidth,
|
|
2401
|
-
offsetY + scaledHeight
|
|
2782
|
+
Math.max(0, offsetX),
|
|
2783
|
+
Math.max(0, offsetY),
|
|
2784
|
+
Math.min(containerWidth, offsetX + scaledWidth),
|
|
2785
|
+
Math.min(containerHeight, offsetY + scaledHeight)
|
|
2402
2786
|
);
|
|
2403
2787
|
}
|
|
2404
2788
|
|
|
@@ -2630,27 +3014,137 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2630
3014
|
) {
|
|
2631
3015
|
if (previewContainer == null || aspectRatio == null) return;
|
|
2632
3016
|
|
|
2633
|
-
|
|
3017
|
+
Log.d(
|
|
3018
|
+
TAG,
|
|
3019
|
+
"======================== UPDATE PREVIEW LAYOUT FOR ASPECT RATIO ========================"
|
|
3020
|
+
);
|
|
3021
|
+
Log.d(
|
|
3022
|
+
TAG,
|
|
3023
|
+
"Input parameters - aspectRatio: " +
|
|
3024
|
+
aspectRatio +
|
|
3025
|
+
", x: " +
|
|
3026
|
+
x +
|
|
3027
|
+
", y: " +
|
|
3028
|
+
y
|
|
3029
|
+
);
|
|
3030
|
+
|
|
3031
|
+
// Get comprehensive display information
|
|
3032
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
3033
|
+
int screenWidthPx = metrics.widthPixels;
|
|
3034
|
+
int screenHeightPx = metrics.heightPixels;
|
|
3035
|
+
float density = metrics.density;
|
|
3036
|
+
|
|
3037
|
+
// Get WebView dimensions
|
|
3038
|
+
int webViewWidth = webView.getWidth();
|
|
3039
|
+
int webViewHeight = webView.getHeight();
|
|
3040
|
+
|
|
3041
|
+
// Get current preview container info
|
|
3042
|
+
ViewGroup.LayoutParams currentParams = previewContainer.getLayoutParams();
|
|
3043
|
+
int currentWidth = currentParams != null ? currentParams.width : 0;
|
|
3044
|
+
int currentHeight = currentParams != null ? currentParams.height : 0;
|
|
3045
|
+
int currentX = 0;
|
|
3046
|
+
int currentY = 0;
|
|
3047
|
+
if (currentParams instanceof ViewGroup.MarginLayoutParams) {
|
|
3048
|
+
ViewGroup.MarginLayoutParams marginParams =
|
|
3049
|
+
(ViewGroup.MarginLayoutParams) currentParams;
|
|
3050
|
+
currentX = marginParams.leftMargin;
|
|
3051
|
+
currentY = marginParams.topMargin;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
Log.d(
|
|
3055
|
+
TAG,
|
|
3056
|
+
"Screen dimensions: " +
|
|
3057
|
+
screenWidthPx +
|
|
3058
|
+
"x" +
|
|
3059
|
+
screenHeightPx +
|
|
3060
|
+
" pixels, density: " +
|
|
3061
|
+
density
|
|
3062
|
+
);
|
|
3063
|
+
Log.d(TAG, "WebView dimensions: " + webViewWidth + "x" + webViewHeight);
|
|
3064
|
+
Log.d(
|
|
3065
|
+
TAG,
|
|
3066
|
+
"Current preview position: " +
|
|
3067
|
+
currentX +
|
|
3068
|
+
"," +
|
|
3069
|
+
currentY +
|
|
3070
|
+
" size: " +
|
|
3071
|
+
currentWidth +
|
|
3072
|
+
"x" +
|
|
3073
|
+
currentHeight
|
|
3074
|
+
);
|
|
3075
|
+
|
|
3076
|
+
// Parse aspect ratio as width:height (e.g., 4:3 -> r=4/3)
|
|
2634
3077
|
String[] ratios = aspectRatio.split(":");
|
|
2635
|
-
if (ratios.length != 2)
|
|
3078
|
+
if (ratios.length != 2) {
|
|
3079
|
+
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio);
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
2636
3082
|
|
|
2637
3083
|
try {
|
|
2638
|
-
//
|
|
2639
|
-
|
|
3084
|
+
// Match iOS logic exactly
|
|
3085
|
+
double ratioWidth = Double.parseDouble(ratios[0]);
|
|
3086
|
+
double ratioHeight = Double.parseDouble(ratios[1]);
|
|
3087
|
+
boolean isPortrait =
|
|
3088
|
+
context.getResources().getConfiguration().orientation ==
|
|
3089
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
3090
|
+
|
|
3091
|
+
Log.d(
|
|
3092
|
+
TAG,
|
|
3093
|
+
"Aspect ratio parsing - Original: " +
|
|
3094
|
+
aspectRatio +
|
|
3095
|
+
" (width=" +
|
|
3096
|
+
ratioWidth +
|
|
3097
|
+
", height=" +
|
|
3098
|
+
ratioHeight +
|
|
3099
|
+
")"
|
|
3100
|
+
);
|
|
3101
|
+
Log.d(
|
|
3102
|
+
TAG,
|
|
3103
|
+
"Device orientation: " + (isPortrait ? "PORTRAIT" : "LANDSCAPE")
|
|
3104
|
+
);
|
|
3105
|
+
|
|
3106
|
+
// iOS: let ratio = !isPortrait ? ratioParts[0] / ratioParts[1] : ratioParts[1] / ratioParts[0]
|
|
3107
|
+
double ratio = !isPortrait
|
|
3108
|
+
? (ratioWidth / ratioHeight)
|
|
3109
|
+
: (ratioHeight / ratioWidth);
|
|
3110
|
+
|
|
3111
|
+
Log.d(
|
|
3112
|
+
TAG,
|
|
3113
|
+
"Computed ratio: " +
|
|
3114
|
+
ratio +
|
|
3115
|
+
" (iOS formula: " +
|
|
3116
|
+
(!isPortrait ? "width/height" : "height/width") +
|
|
3117
|
+
")"
|
|
3118
|
+
);
|
|
2640
3119
|
|
|
2641
3120
|
// Get available space from webview dimensions
|
|
2642
|
-
int availableWidth =
|
|
2643
|
-
int availableHeight =
|
|
3121
|
+
int availableWidth = webViewWidth;
|
|
3122
|
+
int availableHeight = webViewHeight;
|
|
3123
|
+
|
|
3124
|
+
Log.d(
|
|
3125
|
+
TAG,
|
|
3126
|
+
"Available space from WebView: " +
|
|
3127
|
+
availableWidth +
|
|
3128
|
+
"x" +
|
|
3129
|
+
availableHeight
|
|
3130
|
+
);
|
|
2644
3131
|
|
|
2645
3132
|
// Calculate position and size
|
|
2646
3133
|
int finalX, finalY, finalWidth, finalHeight;
|
|
2647
3134
|
|
|
2648
3135
|
if (x != null && y != null) {
|
|
2649
|
-
//
|
|
3136
|
+
// Manual positioning mode
|
|
2650
3137
|
int webViewTopInset = getWebViewTopInset();
|
|
2651
3138
|
int webViewLeftInset = getWebViewLeftInset();
|
|
2652
3139
|
|
|
2653
|
-
|
|
3140
|
+
Log.d(
|
|
3141
|
+
TAG,
|
|
3142
|
+
"Manual positioning mode - WebView insets: left=" +
|
|
3143
|
+
webViewLeftInset +
|
|
3144
|
+
", top=" +
|
|
3145
|
+
webViewTopInset
|
|
3146
|
+
);
|
|
3147
|
+
|
|
2654
3148
|
finalX = Math.max(
|
|
2655
3149
|
0,
|
|
2656
3150
|
Math.min(x.intValue() + webViewLeftInset, availableWidth)
|
|
@@ -2664,6 +3158,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2664
3158
|
int maxWidth = availableWidth - finalX;
|
|
2665
3159
|
int maxHeight = availableHeight - finalY;
|
|
2666
3160
|
|
|
3161
|
+
Log.d(
|
|
3162
|
+
TAG,
|
|
3163
|
+
"Max available space from position: " + maxWidth + "x" + maxHeight
|
|
3164
|
+
);
|
|
3165
|
+
|
|
2667
3166
|
// Calculate optimal size while maintaining aspect ratio within available space
|
|
2668
3167
|
finalWidth = maxWidth;
|
|
2669
3168
|
finalHeight = (int) (maxWidth / ratio);
|
|
@@ -2672,76 +3171,147 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2672
3171
|
// Height constraint is tighter, fit by height
|
|
2673
3172
|
finalHeight = maxHeight;
|
|
2674
3173
|
finalWidth = (int) (maxHeight * ratio);
|
|
3174
|
+
Log.d(TAG, "Height-constrained sizing");
|
|
3175
|
+
} else {
|
|
3176
|
+
Log.d(TAG, "Width-constrained sizing");
|
|
2675
3177
|
}
|
|
2676
3178
|
|
|
2677
3179
|
// Ensure final position stays within bounds
|
|
2678
3180
|
finalX = Math.max(0, Math.min(finalX, availableWidth - finalWidth));
|
|
2679
3181
|
finalY = Math.max(0, Math.min(finalY, availableHeight - finalHeight));
|
|
2680
3182
|
} else {
|
|
2681
|
-
// Auto-center
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
3183
|
+
// Auto-center mode - match iOS behavior exactly
|
|
3184
|
+
Log.d(TAG, "Auto-center mode");
|
|
3185
|
+
|
|
3186
|
+
// Calculate maximum size that fits the aspect ratio in available space
|
|
3187
|
+
double maxWidthByHeight = availableHeight * ratio;
|
|
3188
|
+
double maxHeightByWidth = availableWidth / ratio;
|
|
3189
|
+
|
|
3190
|
+
Log.d(
|
|
3191
|
+
TAG,
|
|
3192
|
+
"Aspect ratio calculations - maxWidthByHeight: " +
|
|
3193
|
+
maxWidthByHeight +
|
|
3194
|
+
", maxHeightByWidth: " +
|
|
3195
|
+
maxHeightByWidth
|
|
3196
|
+
);
|
|
3197
|
+
|
|
3198
|
+
if (maxWidthByHeight <= availableWidth) {
|
|
3199
|
+
// Height is the limiting factor
|
|
3200
|
+
finalWidth = (int) maxWidthByHeight;
|
|
3201
|
+
finalHeight = availableHeight;
|
|
3202
|
+
Log.d(
|
|
3203
|
+
TAG,
|
|
3204
|
+
"Height-limited sizing: " + finalWidth + "x" + finalHeight
|
|
3205
|
+
);
|
|
3206
|
+
} else {
|
|
3207
|
+
// Width is the limiting factor
|
|
3208
|
+
finalWidth = availableWidth;
|
|
3209
|
+
finalHeight = (int) maxHeightByWidth;
|
|
3210
|
+
Log.d(TAG, "Width-limited sizing: " + finalWidth + "x" + finalHeight);
|
|
2694
3211
|
}
|
|
2695
3212
|
|
|
2696
|
-
// Center the
|
|
3213
|
+
// Center the preview
|
|
2697
3214
|
finalX = (availableWidth - finalWidth) / 2;
|
|
2698
3215
|
finalY = (availableHeight - finalHeight) / 2;
|
|
2699
3216
|
|
|
2700
3217
|
Log.d(
|
|
2701
3218
|
TAG,
|
|
2702
|
-
"
|
|
2703
|
-
ratio +
|
|
2704
|
-
", calculated size=" +
|
|
3219
|
+
"Auto-center mode: calculated size " +
|
|
2705
3220
|
finalWidth +
|
|
2706
3221
|
"x" +
|
|
2707
3222
|
finalHeight +
|
|
2708
|
-
"
|
|
2709
|
-
|
|
2710
|
-
"
|
|
2711
|
-
|
|
3223
|
+
" at position (" +
|
|
3224
|
+
finalX +
|
|
3225
|
+
", " +
|
|
3226
|
+
finalY +
|
|
3227
|
+
")"
|
|
2712
3228
|
);
|
|
2713
3229
|
}
|
|
2714
3230
|
|
|
3231
|
+
Log.d(
|
|
3232
|
+
TAG,
|
|
3233
|
+
"Final calculated layout - Position: (" +
|
|
3234
|
+
finalX +
|
|
3235
|
+
"," +
|
|
3236
|
+
finalY +
|
|
3237
|
+
"), Size: " +
|
|
3238
|
+
finalWidth +
|
|
3239
|
+
"x" +
|
|
3240
|
+
finalHeight
|
|
3241
|
+
);
|
|
3242
|
+
|
|
3243
|
+
// Calculate and log the actual displayed aspect ratio
|
|
3244
|
+
double displayedRatio = (double) finalWidth / (double) finalHeight;
|
|
3245
|
+
Log.d(
|
|
3246
|
+
TAG,
|
|
3247
|
+
"Displayed aspect ratio: " +
|
|
3248
|
+
displayedRatio +
|
|
3249
|
+
" (width=" +
|
|
3250
|
+
finalWidth +
|
|
3251
|
+
", height=" +
|
|
3252
|
+
finalHeight +
|
|
3253
|
+
")"
|
|
3254
|
+
);
|
|
3255
|
+
|
|
3256
|
+
// Compare with expected ratio based on orientation
|
|
3257
|
+
if (aspectRatio != null) {
|
|
3258
|
+
String[] parts = aspectRatio.split(":");
|
|
3259
|
+
if (parts.length == 2) {
|
|
3260
|
+
double expectedDisplayRatio = isPortrait
|
|
3261
|
+
? (ratioHeight / ratioWidth)
|
|
3262
|
+
: (ratioWidth / ratioHeight);
|
|
3263
|
+
double difference = Math.abs(displayedRatio - expectedDisplayRatio);
|
|
3264
|
+
Log.d(
|
|
3265
|
+
TAG,
|
|
3266
|
+
"Display ratio check - Expected: " +
|
|
3267
|
+
expectedDisplayRatio +
|
|
3268
|
+
", Actual: " +
|
|
3269
|
+
displayedRatio +
|
|
3270
|
+
", Difference: " +
|
|
3271
|
+
difference +
|
|
3272
|
+
" (tolerance should be < 0.01)"
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
|
|
2715
3277
|
// Update layout params
|
|
2716
|
-
ViewGroup.LayoutParams
|
|
2717
|
-
if (
|
|
3278
|
+
ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
|
|
3279
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
2718
3280
|
ViewGroup.MarginLayoutParams params =
|
|
2719
|
-
(ViewGroup.MarginLayoutParams)
|
|
3281
|
+
(ViewGroup.MarginLayoutParams) layoutParams;
|
|
2720
3282
|
params.width = finalWidth;
|
|
2721
3283
|
params.height = finalHeight;
|
|
2722
3284
|
params.leftMargin = finalX;
|
|
2723
3285
|
params.topMargin = finalY;
|
|
2724
3286
|
previewContainer.setLayoutParams(params);
|
|
2725
3287
|
previewContainer.requestLayout();
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
"updatePreviewLayoutForAspectRatio: Updated to " +
|
|
2729
|
-
finalWidth +
|
|
2730
|
-
"x" +
|
|
2731
|
-
finalHeight +
|
|
2732
|
-
" at (" +
|
|
2733
|
-
finalX +
|
|
2734
|
-
"," +
|
|
2735
|
-
finalY +
|
|
2736
|
-
")"
|
|
2737
|
-
);
|
|
3288
|
+
|
|
3289
|
+
Log.d(TAG, "Layout params applied successfully");
|
|
2738
3290
|
|
|
2739
3291
|
// Update grid overlay bounds after aspect ratio change
|
|
2740
|
-
previewContainer.post(() ->
|
|
3292
|
+
previewContainer.post(() -> {
|
|
3293
|
+
Log.d(
|
|
3294
|
+
TAG,
|
|
3295
|
+
"Post-layout verification - Actual position: " +
|
|
3296
|
+
previewContainer.getLeft() +
|
|
3297
|
+
"," +
|
|
3298
|
+
previewContainer.getTop() +
|
|
3299
|
+
", Actual size: " +
|
|
3300
|
+
previewContainer.getWidth() +
|
|
3301
|
+
"x" +
|
|
3302
|
+
previewContainer.getHeight()
|
|
3303
|
+
);
|
|
3304
|
+
updateGridOverlayBounds();
|
|
3305
|
+
});
|
|
2741
3306
|
}
|
|
2742
3307
|
} catch (NumberFormatException e) {
|
|
2743
3308
|
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
2744
3309
|
}
|
|
3310
|
+
|
|
3311
|
+
Log.d(
|
|
3312
|
+
TAG,
|
|
3313
|
+
"========================================================================================"
|
|
3314
|
+
);
|
|
2745
3315
|
}
|
|
2746
3316
|
|
|
2747
3317
|
private int getWebViewTopInset() {
|
|
@@ -2794,10 +3364,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2794
3364
|
int webViewTopInset = getWebViewTopInset();
|
|
2795
3365
|
int webViewLeftInset = getWebViewLeftInset();
|
|
2796
3366
|
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
int
|
|
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);
|
|
2801
3380
|
|
|
2802
3381
|
return new int[] { x, y, width, height };
|
|
2803
3382
|
}
|
|
@@ -2843,12 +3422,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2843
3422
|
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
2844
3423
|
MeteringPoint point = factory.createPoint(viewWidth / 2f, viewHeight / 2f);
|
|
2845
3424
|
|
|
2846
|
-
// Create focus and metering action
|
|
3425
|
+
// Create focus and metering action (persistent, no auto-cancel) to match iOS behavior
|
|
2847
3426
|
FocusMeteringAction action = new FocusMeteringAction.Builder(
|
|
2848
3427
|
point,
|
|
2849
3428
|
FocusMeteringAction.FLAG_AF | FocusMeteringAction.FLAG_AE
|
|
2850
3429
|
)
|
|
2851
|
-
.
|
|
3430
|
+
.disableAutoCancel()
|
|
2852
3431
|
.build();
|
|
2853
3432
|
|
|
2854
3433
|
try {
|