@capgo/camera-preview 7.4.0-alpha.18 → 7.4.0-alpha.21
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 +116 -6
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +807 -248
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +35 -10
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +3 -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 {
|
|
@@ -277,7 +270,15 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
277
270
|
|
|
278
271
|
// Create and setup the preview view
|
|
279
272
|
previewView = new PreviewView(context);
|
|
280
|
-
|
|
273
|
+
// Match iOS behavior: FIT when no aspect ratio, FILL when aspect ratio is set
|
|
274
|
+
String initialAspectRatio = sessionConfig != null
|
|
275
|
+
? sessionConfig.getAspectRatio()
|
|
276
|
+
: null;
|
|
277
|
+
previewView.setScaleType(
|
|
278
|
+
(initialAspectRatio == null || initialAspectRatio.isEmpty())
|
|
279
|
+
? PreviewView.ScaleType.FIT_CENTER
|
|
280
|
+
: PreviewView.ScaleType.FILL_CENTER
|
|
281
|
+
);
|
|
281
282
|
// Also make preview view touchable as backup
|
|
282
283
|
previewView.setClickable(true);
|
|
283
284
|
previewView.setFocusable(true);
|
|
@@ -434,9 +435,45 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
434
435
|
int height = sessionConfig.getHeight();
|
|
435
436
|
String aspectRatio = sessionConfig.getAspectRatio();
|
|
436
437
|
|
|
438
|
+
// Get comprehensive display information
|
|
439
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
440
|
+
int screenWidthPx = metrics.widthPixels;
|
|
441
|
+
int screenHeightPx = metrics.heightPixels;
|
|
442
|
+
float density = metrics.density;
|
|
443
|
+
int screenWidthDp = (int) (screenWidthPx / density);
|
|
444
|
+
int screenHeightDp = (int) (screenHeightPx / density);
|
|
445
|
+
|
|
446
|
+
// Get WebView dimensions
|
|
447
|
+
int webViewWidth = webView != null ? webView.getWidth() : 0;
|
|
448
|
+
int webViewHeight = webView != null ? webView.getHeight() : 0;
|
|
449
|
+
|
|
450
|
+
// Get parent dimensions
|
|
451
|
+
ViewGroup parent = (ViewGroup) webView.getParent();
|
|
452
|
+
int parentWidth = parent != null ? parent.getWidth() : 0;
|
|
453
|
+
int parentHeight = parent != null ? parent.getHeight() : 0;
|
|
454
|
+
|
|
455
|
+
Log.d(
|
|
456
|
+
TAG,
|
|
457
|
+
"======================== CALCULATE PREVIEW LAYOUT PARAMS ========================"
|
|
458
|
+
);
|
|
437
459
|
Log.d(
|
|
438
460
|
TAG,
|
|
439
|
-
"
|
|
461
|
+
"Screen dimensions - Pixels: " +
|
|
462
|
+
screenWidthPx +
|
|
463
|
+
"x" +
|
|
464
|
+
screenHeightPx +
|
|
465
|
+
", DP: " +
|
|
466
|
+
screenWidthDp +
|
|
467
|
+
"x" +
|
|
468
|
+
screenHeightDp +
|
|
469
|
+
", Density: " +
|
|
470
|
+
density
|
|
471
|
+
);
|
|
472
|
+
Log.d(TAG, "WebView dimensions: " + webViewWidth + "x" + webViewHeight);
|
|
473
|
+
Log.d(TAG, "Parent dimensions: " + parentWidth + "x" + parentHeight);
|
|
474
|
+
Log.d(
|
|
475
|
+
TAG,
|
|
476
|
+
"SessionConfig values - x:" +
|
|
440
477
|
x +
|
|
441
478
|
" y:" +
|
|
442
479
|
y +
|
|
@@ -445,83 +482,97 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
445
482
|
" height:" +
|
|
446
483
|
height +
|
|
447
484
|
" aspectRatio:" +
|
|
448
|
-
aspectRatio
|
|
485
|
+
aspectRatio +
|
|
486
|
+
" isCentered:" +
|
|
487
|
+
sessionConfig.isCentered()
|
|
449
488
|
);
|
|
450
489
|
|
|
451
|
-
// Apply aspect ratio if specified
|
|
452
|
-
if (
|
|
490
|
+
// Apply aspect ratio if specified
|
|
491
|
+
if (
|
|
492
|
+
aspectRatio != null &&
|
|
493
|
+
!aspectRatio.isEmpty() &&
|
|
494
|
+
sessionConfig.isCentered()
|
|
495
|
+
) {
|
|
453
496
|
String[] ratios = aspectRatio.split(":");
|
|
454
497
|
if (ratios.length == 2) {
|
|
455
498
|
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
|
-
}
|
|
499
|
+
// Match iOS logic exactly
|
|
500
|
+
double ratioWidth = Double.parseDouble(ratios[0]);
|
|
501
|
+
double ratioHeight = Double.parseDouble(ratios[1]);
|
|
502
|
+
boolean isPortrait =
|
|
503
|
+
context.getResources().getConfiguration().orientation ==
|
|
504
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
469
505
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
506
|
+
Log.d(
|
|
507
|
+
TAG,
|
|
508
|
+
"Aspect ratio parsing - Original: " +
|
|
509
|
+
aspectRatio +
|
|
510
|
+
" (width=" +
|
|
511
|
+
ratioWidth +
|
|
512
|
+
", height=" +
|
|
513
|
+
ratioHeight +
|
|
514
|
+
")"
|
|
515
|
+
);
|
|
516
|
+
Log.d(
|
|
517
|
+
TAG,
|
|
518
|
+
"Device orientation: " + (isPortrait ? "PORTRAIT" : "LANDSCAPE")
|
|
519
|
+
);
|
|
475
520
|
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
521
|
+
// iOS: let ratio = !isPortrait ? ratioParts[0] / ratioParts[1] : ratioParts[1] / ratioParts[0]
|
|
522
|
+
double ratio = !isPortrait
|
|
523
|
+
? (ratioWidth / ratioHeight)
|
|
524
|
+
: (ratioHeight / ratioWidth);
|
|
479
525
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
", newWidth=" +
|
|
489
|
-
width +
|
|
490
|
-
", screenWidth=" +
|
|
491
|
-
screenWidth +
|
|
492
|
-
", newX=" +
|
|
493
|
-
x
|
|
494
|
-
);
|
|
495
|
-
}
|
|
526
|
+
Log.d(
|
|
527
|
+
TAG,
|
|
528
|
+
"Computed ratio: " +
|
|
529
|
+
ratio +
|
|
530
|
+
" (iOS formula: " +
|
|
531
|
+
(!isPortrait ? "width/height" : "height/width") +
|
|
532
|
+
")"
|
|
533
|
+
);
|
|
496
534
|
|
|
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
|
-
}
|
|
535
|
+
// For centered mode with aspect ratio, calculate maximum size that fits
|
|
536
|
+
int availableWidth = metrics.widthPixels;
|
|
537
|
+
int availableHeight = metrics.heightPixels;
|
|
515
538
|
|
|
516
539
|
Log.d(
|
|
517
540
|
TAG,
|
|
518
|
-
"
|
|
519
|
-
|
|
520
|
-
" - new size: " +
|
|
521
|
-
width +
|
|
541
|
+
"Available space for preview: " +
|
|
542
|
+
availableWidth +
|
|
522
543
|
"x" +
|
|
523
|
-
|
|
544
|
+
availableHeight
|
|
524
545
|
);
|
|
546
|
+
|
|
547
|
+
// Calculate maximum size that fits the aspect ratio in available space
|
|
548
|
+
double maxWidthByHeight = availableHeight * ratio;
|
|
549
|
+
double maxHeightByWidth = availableWidth / ratio;
|
|
550
|
+
|
|
551
|
+
Log.d(
|
|
552
|
+
TAG,
|
|
553
|
+
"Aspect ratio calculations - maxWidthByHeight: " +
|
|
554
|
+
maxWidthByHeight +
|
|
555
|
+
", maxHeightByWidth: " +
|
|
556
|
+
maxHeightByWidth
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
if (maxWidthByHeight <= availableWidth) {
|
|
560
|
+
// Height is the limiting factor
|
|
561
|
+
width = (int) maxWidthByHeight;
|
|
562
|
+
height = availableHeight;
|
|
563
|
+
Log.d(TAG, "Height-limited sizing: " + width + "x" + height);
|
|
564
|
+
} else {
|
|
565
|
+
// Width is the limiting factor
|
|
566
|
+
width = availableWidth;
|
|
567
|
+
height = (int) maxHeightByWidth;
|
|
568
|
+
Log.d(TAG, "Width-limited sizing: " + width + "x" + height);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Center the preview
|
|
572
|
+
x = (availableWidth - width) / 2;
|
|
573
|
+
y = (availableHeight - height) / 2;
|
|
574
|
+
|
|
575
|
+
Log.d(TAG, "Auto-centered position: x=" + x + ", y=" + y);
|
|
525
576
|
} catch (NumberFormatException e) {
|
|
526
577
|
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
527
578
|
}
|
|
@@ -540,28 +591,20 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
540
591
|
|
|
541
592
|
Log.d(
|
|
542
593
|
TAG,
|
|
543
|
-
"
|
|
544
|
-
x +
|
|
545
|
-
" (leftMargin=" +
|
|
594
|
+
"Final layout params - Margins: left=" +
|
|
546
595
|
layoutParams.leftMargin +
|
|
547
|
-
"
|
|
548
|
-
y +
|
|
549
|
-
" (topMargin=" +
|
|
596
|
+
", top=" +
|
|
550
597
|
layoutParams.topMargin +
|
|
551
|
-
"
|
|
598
|
+
", Size: " +
|
|
599
|
+
width +
|
|
600
|
+
"x" +
|
|
601
|
+
height
|
|
552
602
|
);
|
|
553
|
-
|
|
554
603
|
Log.d(
|
|
555
604
|
TAG,
|
|
556
|
-
"
|
|
557
|
-
x +
|
|
558
|
-
" y:" +
|
|
559
|
-
y +
|
|
560
|
-
" width:" +
|
|
561
|
-
width +
|
|
562
|
-
" height:" +
|
|
563
|
-
height
|
|
605
|
+
"================================================================================"
|
|
564
606
|
);
|
|
607
|
+
|
|
565
608
|
return layoutParams;
|
|
566
609
|
}
|
|
567
610
|
|
|
@@ -623,13 +666,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
623
666
|
ResolutionSelector resolutionSelector =
|
|
624
667
|
resolutionSelectorBuilder.build();
|
|
625
668
|
|
|
669
|
+
int rotation = previewView != null && previewView.getDisplay() != null
|
|
670
|
+
? previewView.getDisplay().getRotation()
|
|
671
|
+
: android.view.Surface.ROTATION_0;
|
|
672
|
+
|
|
626
673
|
Preview preview = new Preview.Builder()
|
|
627
674
|
.setResolutionSelector(resolutionSelector)
|
|
675
|
+
.setTargetRotation(rotation)
|
|
628
676
|
.build();
|
|
629
677
|
imageCapture = new ImageCapture.Builder()
|
|
630
678
|
.setResolutionSelector(resolutionSelector)
|
|
631
679
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
632
680
|
.setFlashMode(currentFlashMode)
|
|
681
|
+
.setTargetRotation(rotation)
|
|
633
682
|
.build();
|
|
634
683
|
sampleImageCapture = imageCapture;
|
|
635
684
|
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
@@ -688,6 +737,50 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
688
737
|
if (previewResolution != null) {
|
|
689
738
|
currentPreviewResolution = previewResolution.getResolution();
|
|
690
739
|
Log.d(TAG, "Preview resolution: " + currentPreviewResolution);
|
|
740
|
+
|
|
741
|
+
// Log the actual aspect ratio of the selected resolution
|
|
742
|
+
if (currentPreviewResolution != null) {
|
|
743
|
+
double actualRatio =
|
|
744
|
+
(double) currentPreviewResolution.getWidth() /
|
|
745
|
+
(double) currentPreviewResolution.getHeight();
|
|
746
|
+
Log.d(
|
|
747
|
+
TAG,
|
|
748
|
+
"Actual preview aspect ratio: " +
|
|
749
|
+
actualRatio +
|
|
750
|
+
" (width=" +
|
|
751
|
+
currentPreviewResolution.getWidth() +
|
|
752
|
+
", height=" +
|
|
753
|
+
currentPreviewResolution.getHeight() +
|
|
754
|
+
")"
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// Compare with requested ratio
|
|
758
|
+
if ("4:3".equals(sessionConfig.getAspectRatio())) {
|
|
759
|
+
double expectedRatio = 4.0 / 3.0;
|
|
760
|
+
double difference = Math.abs(actualRatio - expectedRatio);
|
|
761
|
+
Log.d(
|
|
762
|
+
TAG,
|
|
763
|
+
"4:3 ratio check - Expected: " +
|
|
764
|
+
expectedRatio +
|
|
765
|
+
", Actual: " +
|
|
766
|
+
actualRatio +
|
|
767
|
+
", Difference: " +
|
|
768
|
+
difference
|
|
769
|
+
);
|
|
770
|
+
} else if ("16:9".equals(sessionConfig.getAspectRatio())) {
|
|
771
|
+
double expectedRatio = 16.0 / 9.0;
|
|
772
|
+
double difference = Math.abs(actualRatio - expectedRatio);
|
|
773
|
+
Log.d(
|
|
774
|
+
TAG,
|
|
775
|
+
"16:9 ratio check - Expected: " +
|
|
776
|
+
expectedRatio +
|
|
777
|
+
", Actual: " +
|
|
778
|
+
actualRatio +
|
|
779
|
+
", Difference: " +
|
|
780
|
+
difference
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
691
784
|
}
|
|
692
785
|
ResolutionInfo imageCaptureResolution =
|
|
693
786
|
imageCapture.getResolutionInfo();
|
|
@@ -699,6 +792,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
699
792
|
);
|
|
700
793
|
}
|
|
701
794
|
|
|
795
|
+
// Update scale type based on aspect ratio whenever (re)binding
|
|
796
|
+
String ar = sessionConfig != null
|
|
797
|
+
? sessionConfig.getAspectRatio()
|
|
798
|
+
: null;
|
|
799
|
+
previewView.setScaleType(
|
|
800
|
+
(ar == null || ar.isEmpty())
|
|
801
|
+
? PreviewView.ScaleType.FIT_CENTER
|
|
802
|
+
: PreviewView.ScaleType.FILL_CENTER
|
|
803
|
+
);
|
|
804
|
+
|
|
702
805
|
// Set initial zoom if specified, prioritizing targetZoom over default zoomFactor
|
|
703
806
|
float initialZoom = sessionConfig.getTargetZoom() != 1.0f
|
|
704
807
|
? sessionConfig.getTargetZoom()
|
|
@@ -914,26 +1017,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
914
1017
|
|
|
915
1018
|
JSONObject exifData = getExifData(exifInterface);
|
|
916
1019
|
|
|
917
|
-
//
|
|
1020
|
+
// Determine final output: explicit size wins, then explicit aspectRatio,
|
|
1021
|
+
// otherwise crop to match what is visible in the preview (iOS parity)
|
|
918
1022
|
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
1023
|
if (
|
|
938
1024
|
width == null &&
|
|
939
1025
|
height == null &&
|
|
@@ -1027,14 +1113,22 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1027
1113
|
// Write EXIF data back to resized image
|
|
1028
1114
|
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
1029
1115
|
} else {
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1116
|
+
// No explicit size/ratio: crop to match current preview content
|
|
1117
|
+
Bitmap originalBitmap = BitmapFactory.decodeByteArray(
|
|
1118
|
+
bytes,
|
|
1119
|
+
0,
|
|
1120
|
+
bytes.length
|
|
1121
|
+
);
|
|
1122
|
+
Bitmap previewCropped = cropBitmapToMatchPreview(originalBitmap);
|
|
1123
|
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
1124
|
+
previewCropped.compress(
|
|
1125
|
+
Bitmap.CompressFormat.JPEG,
|
|
1126
|
+
quality,
|
|
1127
|
+
stream
|
|
1035
1128
|
);
|
|
1036
|
-
|
|
1037
|
-
|
|
1129
|
+
bytes = stream.toByteArray();
|
|
1130
|
+
// Preserve EXIF
|
|
1131
|
+
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
1038
1132
|
}
|
|
1039
1133
|
|
|
1040
1134
|
if (saveToGallery) {
|
|
@@ -1380,6 +1474,49 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1380
1474
|
return bytes;
|
|
1381
1475
|
}
|
|
1382
1476
|
|
|
1477
|
+
private Bitmap cropBitmapToMatchPreview(Bitmap image) {
|
|
1478
|
+
if (previewContainer == null || previewView == null) {
|
|
1479
|
+
return image;
|
|
1480
|
+
}
|
|
1481
|
+
int containerWidth = previewContainer.getWidth();
|
|
1482
|
+
int containerHeight = previewContainer.getHeight();
|
|
1483
|
+
if (containerWidth == 0 || containerHeight == 0) {
|
|
1484
|
+
return image;
|
|
1485
|
+
}
|
|
1486
|
+
// Compute preview aspect based on actual camera content bounds
|
|
1487
|
+
Rect bounds = getActualCameraBounds();
|
|
1488
|
+
int previewW = Math.max(1, bounds.width());
|
|
1489
|
+
int previewH = Math.max(1, bounds.height());
|
|
1490
|
+
float previewRatio = (float) previewW / (float) previewH;
|
|
1491
|
+
|
|
1492
|
+
int imgW = image.getWidth();
|
|
1493
|
+
int imgH = image.getHeight();
|
|
1494
|
+
float imgRatio = (float) imgW / (float) imgH;
|
|
1495
|
+
|
|
1496
|
+
int targetW = imgW;
|
|
1497
|
+
int targetH = imgH;
|
|
1498
|
+
if (imgRatio > previewRatio) {
|
|
1499
|
+
// Image wider than preview: crop width
|
|
1500
|
+
targetW = Math.round(imgH * previewRatio);
|
|
1501
|
+
} else if (imgRatio < previewRatio) {
|
|
1502
|
+
// Image taller than preview: crop height
|
|
1503
|
+
targetH = Math.round(imgW / previewRatio);
|
|
1504
|
+
}
|
|
1505
|
+
int x = Math.max(0, (imgW - targetW) / 2);
|
|
1506
|
+
int y = Math.max(0, (imgH - targetH) / 2);
|
|
1507
|
+
try {
|
|
1508
|
+
return Bitmap.createBitmap(
|
|
1509
|
+
image,
|
|
1510
|
+
x,
|
|
1511
|
+
y,
|
|
1512
|
+
Math.min(targetW, imgW - x),
|
|
1513
|
+
Math.min(targetH, imgH - y)
|
|
1514
|
+
);
|
|
1515
|
+
} catch (Exception ignore) {
|
|
1516
|
+
return image;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1383
1520
|
// not workin for xiaomi https://xiaomi.eu/community/threads/mi-11-ultra-unable-to-access-camera-lenses-in-apps-camera2-api.61456/
|
|
1384
1521
|
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1385
1522
|
public static List<
|
|
@@ -1698,12 +1835,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1698
1835
|
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
1699
1836
|
MeteringPoint point = factory.createPoint(x * viewWidth, y * viewHeight);
|
|
1700
1837
|
|
|
1701
|
-
// Create focus and metering action
|
|
1838
|
+
// Create focus and metering action (persistent, no auto-cancel) to match iOS behavior
|
|
1702
1839
|
FocusMeteringAction action = new FocusMeteringAction.Builder(
|
|
1703
1840
|
point,
|
|
1704
1841
|
FocusMeteringAction.FLAG_AF | FocusMeteringAction.FLAG_AE
|
|
1705
1842
|
)
|
|
1706
|
-
.
|
|
1843
|
+
.disableAutoCancel()
|
|
1707
1844
|
.build();
|
|
1708
1845
|
|
|
1709
1846
|
try {
|
|
@@ -1777,8 +1914,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1777
1914
|
}
|
|
1778
1915
|
|
|
1779
1916
|
// Create an elegant focus indicator
|
|
1780
|
-
|
|
1781
|
-
int size = (int) (
|
|
1917
|
+
FrameLayout container = new FrameLayout(context);
|
|
1918
|
+
int size = (int) (80 * context.getResources().getDisplayMetrics().density); // match iOS size
|
|
1782
1919
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size);
|
|
1783
1920
|
|
|
1784
1921
|
// Center the indicator on the touch point with bounds checking
|
|
@@ -1794,25 +1931,73 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1794
1931
|
Math.min((int) (y - size / 2), containerHeight - size)
|
|
1795
1932
|
);
|
|
1796
1933
|
|
|
1797
|
-
//
|
|
1798
|
-
GradientDrawable
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
);
|
|
1804
|
-
|
|
1805
|
-
|
|
1934
|
+
// iOS Camera style: square with mid-edge ticks
|
|
1935
|
+
GradientDrawable border = new GradientDrawable();
|
|
1936
|
+
border.setShape(GradientDrawable.RECTANGLE);
|
|
1937
|
+
int stroke = (int) (2 * context.getResources().getDisplayMetrics().density);
|
|
1938
|
+
border.setStroke(stroke, Color.YELLOW);
|
|
1939
|
+
border.setCornerRadius(0);
|
|
1940
|
+
border.setColor(Color.TRANSPARENT);
|
|
1941
|
+
container.setBackground(border);
|
|
1942
|
+
|
|
1943
|
+
// Add 4 tiny mid-edge ticks inside the square
|
|
1944
|
+
int tickLen = (int) (12 *
|
|
1945
|
+
context.getResources().getDisplayMetrics().density);
|
|
1946
|
+
int inset = stroke; // ticks should touch the sides
|
|
1947
|
+
// Top tick (perpendicular): vertical inward from top edge
|
|
1948
|
+
View topTick = new View(context);
|
|
1949
|
+
FrameLayout.LayoutParams topParams = new FrameLayout.LayoutParams(
|
|
1950
|
+
stroke,
|
|
1951
|
+
tickLen
|
|
1952
|
+
);
|
|
1953
|
+
topParams.leftMargin = (size - stroke) / 2;
|
|
1954
|
+
topParams.topMargin = inset;
|
|
1955
|
+
topTick.setLayoutParams(topParams);
|
|
1956
|
+
topTick.setBackgroundColor(Color.YELLOW);
|
|
1957
|
+
container.addView(topTick);
|
|
1958
|
+
// Bottom tick (perpendicular): vertical inward from bottom edge
|
|
1959
|
+
View bottomTick = new View(context);
|
|
1960
|
+
FrameLayout.LayoutParams bottomParams = new FrameLayout.LayoutParams(
|
|
1961
|
+
stroke,
|
|
1962
|
+
tickLen
|
|
1963
|
+
);
|
|
1964
|
+
bottomParams.leftMargin = (size - stroke) / 2;
|
|
1965
|
+
bottomParams.topMargin = size - inset - tickLen;
|
|
1966
|
+
bottomTick.setLayoutParams(bottomParams);
|
|
1967
|
+
bottomTick.setBackgroundColor(Color.YELLOW);
|
|
1968
|
+
container.addView(bottomTick);
|
|
1969
|
+
// Left tick (perpendicular): horizontal inward from left edge
|
|
1970
|
+
View leftTick = new View(context);
|
|
1971
|
+
FrameLayout.LayoutParams leftParams = new FrameLayout.LayoutParams(
|
|
1972
|
+
tickLen,
|
|
1973
|
+
stroke
|
|
1974
|
+
);
|
|
1975
|
+
leftParams.leftMargin = inset;
|
|
1976
|
+
leftParams.topMargin = (size - stroke) / 2;
|
|
1977
|
+
leftTick.setLayoutParams(leftParams);
|
|
1978
|
+
leftTick.setBackgroundColor(Color.YELLOW);
|
|
1979
|
+
container.addView(leftTick);
|
|
1980
|
+
// Right tick (perpendicular): horizontal inward from right edge
|
|
1981
|
+
View rightTick = new View(context);
|
|
1982
|
+
FrameLayout.LayoutParams rightParams = new FrameLayout.LayoutParams(
|
|
1983
|
+
tickLen,
|
|
1984
|
+
stroke
|
|
1985
|
+
);
|
|
1986
|
+
rightParams.leftMargin = size - inset - tickLen;
|
|
1987
|
+
rightParams.topMargin = (size - stroke) / 2;
|
|
1988
|
+
rightTick.setLayoutParams(rightParams);
|
|
1989
|
+
rightTick.setBackgroundColor(Color.YELLOW);
|
|
1990
|
+
container.addView(rightTick);
|
|
1806
1991
|
|
|
1807
1992
|
focusIndicatorView = container;
|
|
1808
1993
|
// Bump animation token; everything after this must validate against this token
|
|
1809
1994
|
final long thisAnimationId = ++focusIndicatorAnimationId;
|
|
1810
1995
|
final View thisIndicatorView = focusIndicatorView;
|
|
1811
1996
|
|
|
1812
|
-
// Set initial state for smooth animation
|
|
1813
|
-
focusIndicatorView.setAlpha(
|
|
1814
|
-
focusIndicatorView.setScaleX(1.
|
|
1815
|
-
focusIndicatorView.setScaleY(1.
|
|
1997
|
+
// Set initial state for smooth animation (mirror iOS)
|
|
1998
|
+
focusIndicatorView.setAlpha(0f);
|
|
1999
|
+
focusIndicatorView.setScaleX(1.5f);
|
|
2000
|
+
focusIndicatorView.setScaleY(1.5f);
|
|
1816
2001
|
focusIndicatorView.setVisibility(View.VISIBLE);
|
|
1817
2002
|
|
|
1818
2003
|
// Ensure container doesn't intercept touch events
|
|
@@ -1836,26 +2021,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1836
2021
|
// Force a layout pass to ensure the view is properly positioned
|
|
1837
2022
|
previewContainer.requestLayout();
|
|
1838
2023
|
|
|
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);
|
|
2024
|
+
// First phase: fade in and scale to 1.0 over 150ms
|
|
2025
|
+
focusIndicatorView
|
|
2026
|
+
.animate()
|
|
2027
|
+
.alpha(1f)
|
|
2028
|
+
.scaleX(1f)
|
|
2029
|
+
.scaleY(1f)
|
|
2030
|
+
.setDuration(150)
|
|
2031
|
+
.start();
|
|
1857
2032
|
|
|
1858
|
-
//
|
|
2033
|
+
// Phase 2: after 500ms, fade to 0.3 over 200ms
|
|
1859
2034
|
focusIndicatorView.postDelayed(
|
|
1860
2035
|
new Runnable() {
|
|
1861
2036
|
@Override
|
|
@@ -1868,37 +2043,66 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1868
2043
|
) {
|
|
1869
2044
|
focusIndicatorView
|
|
1870
2045
|
.animate()
|
|
1871
|
-
.alpha(
|
|
1872
|
-
.
|
|
1873
|
-
.scaleY(0.9f)
|
|
1874
|
-
.setDuration(180)
|
|
1875
|
-
.setInterpolator(
|
|
1876
|
-
new android.view.animation.AccelerateInterpolator()
|
|
1877
|
-
)
|
|
2046
|
+
.alpha(0.3f)
|
|
2047
|
+
.setDuration(200)
|
|
1878
2048
|
.withEndAction(
|
|
1879
2049
|
new Runnable() {
|
|
1880
2050
|
@Override
|
|
1881
2051
|
public void run() {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2052
|
+
// Phase 3: after 200ms more, fade out to 0 and scale to 0.8 over 300ms
|
|
2053
|
+
focusIndicatorView.postDelayed(
|
|
2054
|
+
new Runnable() {
|
|
2055
|
+
@Override
|
|
2056
|
+
public void run() {
|
|
2057
|
+
if (
|
|
2058
|
+
focusIndicatorView != null &&
|
|
2059
|
+
thisIndicatorView == focusIndicatorView &&
|
|
2060
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
2061
|
+
) {
|
|
2062
|
+
focusIndicatorView
|
|
2063
|
+
.animate()
|
|
2064
|
+
.alpha(0f)
|
|
2065
|
+
.scaleX(0.8f)
|
|
2066
|
+
.scaleY(0.8f)
|
|
2067
|
+
.setDuration(300)
|
|
2068
|
+
.setInterpolator(
|
|
2069
|
+
new android.view.animation.AccelerateInterpolator()
|
|
2070
|
+
)
|
|
2071
|
+
.withEndAction(
|
|
2072
|
+
new Runnable() {
|
|
2073
|
+
@Override
|
|
2074
|
+
public void run() {
|
|
2075
|
+
if (
|
|
2076
|
+
focusIndicatorView != null &&
|
|
2077
|
+
previewContainer != null &&
|
|
2078
|
+
thisIndicatorView == focusIndicatorView &&
|
|
2079
|
+
thisAnimationId ==
|
|
2080
|
+
focusIndicatorAnimationId
|
|
2081
|
+
) {
|
|
2082
|
+
try {
|
|
2083
|
+
focusIndicatorView.clearAnimation();
|
|
2084
|
+
} catch (Exception ignore) {}
|
|
2085
|
+
previewContainer.removeView(
|
|
2086
|
+
focusIndicatorView
|
|
2087
|
+
);
|
|
2088
|
+
focusIndicatorView = null;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
200
|
|
2097
|
+
);
|
|
1894
2098
|
}
|
|
1895
2099
|
}
|
|
1896
2100
|
);
|
|
1897
2101
|
}
|
|
1898
2102
|
}
|
|
1899
2103
|
},
|
|
1900
|
-
|
|
1901
|
-
);
|
|
2104
|
+
500
|
|
2105
|
+
);
|
|
1902
2106
|
}
|
|
1903
2107
|
|
|
1904
2108
|
public static List<Size> getSupportedPictureSizes(String facing) {
|
|
@@ -2149,13 +2353,48 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2149
2353
|
Float y,
|
|
2150
2354
|
Runnable callback
|
|
2151
2355
|
) {
|
|
2356
|
+
Log.d(
|
|
2357
|
+
TAG,
|
|
2358
|
+
"======================== SET ASPECT RATIO ========================"
|
|
2359
|
+
);
|
|
2360
|
+
Log.d(
|
|
2361
|
+
TAG,
|
|
2362
|
+
"Input parameters - aspectRatio: " +
|
|
2363
|
+
aspectRatio +
|
|
2364
|
+
", x: " +
|
|
2365
|
+
x +
|
|
2366
|
+
", y: " +
|
|
2367
|
+
y
|
|
2368
|
+
);
|
|
2369
|
+
|
|
2152
2370
|
if (sessionConfig == null) {
|
|
2371
|
+
Log.d(TAG, "SessionConfig is null, returning");
|
|
2153
2372
|
if (callback != null) callback.run();
|
|
2154
2373
|
return;
|
|
2155
2374
|
}
|
|
2156
2375
|
|
|
2157
2376
|
String currentAspectRatio = sessionConfig.getAspectRatio();
|
|
2158
2377
|
|
|
2378
|
+
// Get current display information
|
|
2379
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
2380
|
+
int screenWidthPx = metrics.widthPixels;
|
|
2381
|
+
int screenHeightPx = metrics.heightPixels;
|
|
2382
|
+
boolean isPortrait =
|
|
2383
|
+
context.getResources().getConfiguration().orientation ==
|
|
2384
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
2385
|
+
|
|
2386
|
+
Log.d(
|
|
2387
|
+
TAG,
|
|
2388
|
+
"Current screen: " +
|
|
2389
|
+
screenWidthPx +
|
|
2390
|
+
"x" +
|
|
2391
|
+
screenHeightPx +
|
|
2392
|
+
" (" +
|
|
2393
|
+
(isPortrait ? "PORTRAIT" : "LANDSCAPE") +
|
|
2394
|
+
")"
|
|
2395
|
+
);
|
|
2396
|
+
Log.d(TAG, "Current aspect ratio: " + currentAspectRatio);
|
|
2397
|
+
|
|
2159
2398
|
// Don't restart camera if aspect ratio hasn't changed and no position specified
|
|
2160
2399
|
if (
|
|
2161
2400
|
aspectRatio != null &&
|
|
@@ -2163,12 +2402,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2163
2402
|
x == null &&
|
|
2164
2403
|
y == null
|
|
2165
2404
|
) {
|
|
2166
|
-
Log.d(
|
|
2167
|
-
TAG,
|
|
2168
|
-
"setAspectRatio: Aspect ratio " +
|
|
2169
|
-
aspectRatio +
|
|
2170
|
-
" is already set and no position specified, skipping"
|
|
2171
|
-
);
|
|
2405
|
+
Log.d(TAG, "Aspect ratio unchanged and no position specified, skipping");
|
|
2172
2406
|
if (callback != null) callback.run();
|
|
2173
2407
|
return;
|
|
2174
2408
|
}
|
|
@@ -2176,22 +2410,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2176
2410
|
String currentGridMode = sessionConfig.getGridMode();
|
|
2177
2411
|
Log.d(
|
|
2178
2412
|
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
|
|
2413
|
+
"Changing aspect ratio from " + currentAspectRatio + " to " + aspectRatio
|
|
2188
2414
|
);
|
|
2415
|
+
Log.d(TAG, "Auto-centering will be applied (matching iOS behavior)");
|
|
2189
2416
|
|
|
2417
|
+
// Match iOS behavior: when aspect ratio changes, always auto-center
|
|
2190
2418
|
sessionConfig = new CameraSessionConfiguration(
|
|
2191
2419
|
sessionConfig.getDeviceId(),
|
|
2192
2420
|
sessionConfig.getPosition(),
|
|
2193
|
-
|
|
2194
|
-
|
|
2421
|
+
-1, // Force auto-center X (iOS: self.posX = -1)
|
|
2422
|
+
-1, // Force auto-center Y (iOS: self.posY = -1)
|
|
2195
2423
|
sessionConfig.getWidth(),
|
|
2196
2424
|
sessionConfig.getHeight(),
|
|
2197
2425
|
sessionConfig.getPaddingBottom(),
|
|
@@ -2205,12 +2433,13 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2205
2433
|
aspectRatio,
|
|
2206
2434
|
currentGridMode
|
|
2207
2435
|
);
|
|
2436
|
+
sessionConfig.setCentered(true);
|
|
2208
2437
|
|
|
2209
2438
|
// Update layout and rebind camera with new aspect ratio
|
|
2210
2439
|
if (isRunning && previewContainer != null) {
|
|
2211
2440
|
mainExecutor.execute(() -> {
|
|
2212
|
-
// First update the UI layout
|
|
2213
|
-
updatePreviewLayoutForAspectRatio(aspectRatio,
|
|
2441
|
+
// First update the UI layout - always pass null for x,y to force auto-centering (matching iOS)
|
|
2442
|
+
updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
|
|
2214
2443
|
|
|
2215
2444
|
// Then rebind the camera with new aspect ratio configuration
|
|
2216
2445
|
Log.d(
|
|
@@ -2240,8 +2469,121 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2240
2469
|
previewContainer.post(callback);
|
|
2241
2470
|
}
|
|
2242
2471
|
}
|
|
2472
|
+
|
|
2473
|
+
Log.d(
|
|
2474
|
+
TAG,
|
|
2475
|
+
"=================================================================="
|
|
2476
|
+
);
|
|
2243
2477
|
});
|
|
2244
2478
|
} else {
|
|
2479
|
+
Log.d(TAG, "Camera not running, just saving configuration");
|
|
2480
|
+
Log.d(
|
|
2481
|
+
TAG,
|
|
2482
|
+
"=================================================================="
|
|
2483
|
+
);
|
|
2484
|
+
if (callback != null) callback.run();
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// Force aspect ratio recalculation (used during orientation changes)
|
|
2489
|
+
public void forceAspectRatioRecalculation(
|
|
2490
|
+
String aspectRatio,
|
|
2491
|
+
Float x,
|
|
2492
|
+
Float y,
|
|
2493
|
+
Runnable callback
|
|
2494
|
+
) {
|
|
2495
|
+
Log.d(
|
|
2496
|
+
TAG,
|
|
2497
|
+
"======================== FORCE ASPECT RATIO RECALCULATION ========================"
|
|
2498
|
+
);
|
|
2499
|
+
Log.d(
|
|
2500
|
+
TAG,
|
|
2501
|
+
"Input parameters - aspectRatio: " +
|
|
2502
|
+
aspectRatio +
|
|
2503
|
+
", x: " +
|
|
2504
|
+
x +
|
|
2505
|
+
", y: " +
|
|
2506
|
+
y
|
|
2507
|
+
);
|
|
2508
|
+
|
|
2509
|
+
if (sessionConfig == null) {
|
|
2510
|
+
Log.d(TAG, "SessionConfig is null, returning");
|
|
2511
|
+
if (callback != null) callback.run();
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
String currentGridMode = sessionConfig.getGridMode();
|
|
2516
|
+
Log.d(TAG, "Forcing aspect ratio recalculation for: " + aspectRatio);
|
|
2517
|
+
Log.d(TAG, "Auto-centering will be applied (matching iOS behavior)");
|
|
2518
|
+
|
|
2519
|
+
// Match iOS behavior: when aspect ratio changes, always auto-center
|
|
2520
|
+
sessionConfig = new CameraSessionConfiguration(
|
|
2521
|
+
sessionConfig.getDeviceId(),
|
|
2522
|
+
sessionConfig.getPosition(),
|
|
2523
|
+
-1, // Force auto-center X (iOS: self.posX = -1)
|
|
2524
|
+
-1, // Force auto-center Y (iOS: self.posY = -1)
|
|
2525
|
+
sessionConfig.getWidth(),
|
|
2526
|
+
sessionConfig.getHeight(),
|
|
2527
|
+
sessionConfig.getPaddingBottom(),
|
|
2528
|
+
sessionConfig.getToBack(),
|
|
2529
|
+
sessionConfig.getStoreToFile(),
|
|
2530
|
+
sessionConfig.getEnableOpacity(),
|
|
2531
|
+
sessionConfig.getEnableZoom(),
|
|
2532
|
+
sessionConfig.getDisableExifHeaderStripping(),
|
|
2533
|
+
sessionConfig.getDisableAudio(),
|
|
2534
|
+
sessionConfig.getZoomFactor(),
|
|
2535
|
+
aspectRatio,
|
|
2536
|
+
currentGridMode
|
|
2537
|
+
);
|
|
2538
|
+
sessionConfig.setCentered(true);
|
|
2539
|
+
|
|
2540
|
+
// Update layout and rebind camera with new aspect ratio
|
|
2541
|
+
if (isRunning && previewContainer != null) {
|
|
2542
|
+
mainExecutor.execute(() -> {
|
|
2543
|
+
// First update the UI layout - always pass null for x,y to force auto-centering (matching iOS)
|
|
2544
|
+
updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
|
|
2545
|
+
|
|
2546
|
+
// Then rebind the camera with new aspect ratio configuration
|
|
2547
|
+
Log.d(
|
|
2548
|
+
TAG,
|
|
2549
|
+
"forceAspectRatioRecalculation: Rebinding camera with aspect ratio: " +
|
|
2550
|
+
aspectRatio
|
|
2551
|
+
);
|
|
2552
|
+
bindCameraUseCases();
|
|
2553
|
+
|
|
2554
|
+
// Preserve grid mode and wait for completion
|
|
2555
|
+
if (gridOverlayView != null) {
|
|
2556
|
+
gridOverlayView.post(() -> {
|
|
2557
|
+
Log.d(
|
|
2558
|
+
TAG,
|
|
2559
|
+
"forceAspectRatioRecalculation: Re-applying grid mode: " +
|
|
2560
|
+
currentGridMode
|
|
2561
|
+
);
|
|
2562
|
+
gridOverlayView.setGridMode(currentGridMode);
|
|
2563
|
+
|
|
2564
|
+
// Wait one more frame for grid to be applied, then call callback
|
|
2565
|
+
if (callback != null) {
|
|
2566
|
+
gridOverlayView.post(callback);
|
|
2567
|
+
}
|
|
2568
|
+
});
|
|
2569
|
+
} else {
|
|
2570
|
+
// No grid overlay, wait one frame for layout completion then call callback
|
|
2571
|
+
if (callback != null) {
|
|
2572
|
+
previewContainer.post(callback);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
Log.d(
|
|
2577
|
+
TAG,
|
|
2578
|
+
"=================================================================="
|
|
2579
|
+
);
|
|
2580
|
+
});
|
|
2581
|
+
} else {
|
|
2582
|
+
Log.d(TAG, "Camera not running, just saving configuration");
|
|
2583
|
+
Log.d(
|
|
2584
|
+
TAG,
|
|
2585
|
+
"=================================================================="
|
|
2586
|
+
);
|
|
2245
2587
|
if (callback != null) callback.run();
|
|
2246
2588
|
}
|
|
2247
2589
|
}
|
|
@@ -2352,15 +2694,40 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2352
2694
|
return new Rect(0, 0, containerWidth, containerHeight);
|
|
2353
2695
|
}
|
|
2354
2696
|
|
|
2355
|
-
//
|
|
2356
|
-
//
|
|
2357
|
-
|
|
2358
|
-
int
|
|
2697
|
+
// CameraX delivers preview in sensor orientation (always landscape)
|
|
2698
|
+
// But PreviewView internally rotates it to match device orientation
|
|
2699
|
+
// So we need to swap dimensions in portrait mode
|
|
2700
|
+
int cameraWidth = currentPreviewResolution.getWidth();
|
|
2701
|
+
int cameraHeight = currentPreviewResolution.getHeight();
|
|
2702
|
+
|
|
2703
|
+
// Check if we're in portrait mode
|
|
2704
|
+
boolean isPortrait =
|
|
2705
|
+
context.getResources().getConfiguration().orientation ==
|
|
2706
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
2707
|
+
|
|
2708
|
+
// Swap dimensions if in portrait mode to match how PreviewView displays it
|
|
2709
|
+
if (isPortrait) {
|
|
2710
|
+
int temp = cameraWidth;
|
|
2711
|
+
cameraWidth = cameraHeight;
|
|
2712
|
+
cameraHeight = temp;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
// When we have an aspect ratio set, we use FILL_CENTER which scales to fill
|
|
2716
|
+
// the container while maintaining aspect ratio, potentially cropping
|
|
2717
|
+
boolean usesFillCenter =
|
|
2718
|
+
sessionConfig != null && sessionConfig.getAspectRatio() != null;
|
|
2359
2719
|
|
|
2360
|
-
// Calculate the scaling factor to fit the camera in the container
|
|
2361
2720
|
float widthScale = (float) containerWidth / cameraWidth;
|
|
2362
2721
|
float heightScale = (float) containerHeight / cameraHeight;
|
|
2363
|
-
float scale
|
|
2722
|
+
float scale;
|
|
2723
|
+
|
|
2724
|
+
if (usesFillCenter) {
|
|
2725
|
+
// FILL_CENTER uses max scale to fill the container
|
|
2726
|
+
scale = Math.max(widthScale, heightScale);
|
|
2727
|
+
} else {
|
|
2728
|
+
// FIT_CENTER uses min scale to fit within the container
|
|
2729
|
+
scale = Math.min(widthScale, heightScale);
|
|
2730
|
+
}
|
|
2364
2731
|
|
|
2365
2732
|
// Calculate the actual size of the camera content after scaling
|
|
2366
2733
|
int scaledWidth = Math.round(cameraWidth * scale);
|
|
@@ -2380,8 +2747,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2380
2747
|
cameraWidth +
|
|
2381
2748
|
"x" +
|
|
2382
2749
|
cameraHeight +
|
|
2750
|
+
" (swapped=" +
|
|
2751
|
+
isPortrait +
|
|
2752
|
+
")" +
|
|
2383
2753
|
", scale=" +
|
|
2384
2754
|
scale +
|
|
2755
|
+
" (fillCenter=" +
|
|
2756
|
+
usesFillCenter +
|
|
2757
|
+
")" +
|
|
2385
2758
|
", scaled=" +
|
|
2386
2759
|
scaledWidth +
|
|
2387
2760
|
"x" +
|
|
@@ -2395,10 +2768,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2395
2768
|
|
|
2396
2769
|
// Return the bounds relative to the container
|
|
2397
2770
|
return new Rect(
|
|
2398
|
-
offsetX,
|
|
2399
|
-
offsetY,
|
|
2400
|
-
offsetX + scaledWidth,
|
|
2401
|
-
offsetY + scaledHeight
|
|
2771
|
+
Math.max(0, offsetX),
|
|
2772
|
+
Math.max(0, offsetY),
|
|
2773
|
+
Math.min(containerWidth, offsetX + scaledWidth),
|
|
2774
|
+
Math.min(containerHeight, offsetY + scaledHeight)
|
|
2402
2775
|
);
|
|
2403
2776
|
}
|
|
2404
2777
|
|
|
@@ -2630,27 +3003,137 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2630
3003
|
) {
|
|
2631
3004
|
if (previewContainer == null || aspectRatio == null) return;
|
|
2632
3005
|
|
|
2633
|
-
|
|
3006
|
+
Log.d(
|
|
3007
|
+
TAG,
|
|
3008
|
+
"======================== UPDATE PREVIEW LAYOUT FOR ASPECT RATIO ========================"
|
|
3009
|
+
);
|
|
3010
|
+
Log.d(
|
|
3011
|
+
TAG,
|
|
3012
|
+
"Input parameters - aspectRatio: " +
|
|
3013
|
+
aspectRatio +
|
|
3014
|
+
", x: " +
|
|
3015
|
+
x +
|
|
3016
|
+
", y: " +
|
|
3017
|
+
y
|
|
3018
|
+
);
|
|
3019
|
+
|
|
3020
|
+
// Get comprehensive display information
|
|
3021
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
3022
|
+
int screenWidthPx = metrics.widthPixels;
|
|
3023
|
+
int screenHeightPx = metrics.heightPixels;
|
|
3024
|
+
float density = metrics.density;
|
|
3025
|
+
|
|
3026
|
+
// Get WebView dimensions
|
|
3027
|
+
int webViewWidth = webView.getWidth();
|
|
3028
|
+
int webViewHeight = webView.getHeight();
|
|
3029
|
+
|
|
3030
|
+
// Get current preview container info
|
|
3031
|
+
ViewGroup.LayoutParams currentParams = previewContainer.getLayoutParams();
|
|
3032
|
+
int currentWidth = currentParams != null ? currentParams.width : 0;
|
|
3033
|
+
int currentHeight = currentParams != null ? currentParams.height : 0;
|
|
3034
|
+
int currentX = 0;
|
|
3035
|
+
int currentY = 0;
|
|
3036
|
+
if (currentParams instanceof ViewGroup.MarginLayoutParams) {
|
|
3037
|
+
ViewGroup.MarginLayoutParams marginParams =
|
|
3038
|
+
(ViewGroup.MarginLayoutParams) currentParams;
|
|
3039
|
+
currentX = marginParams.leftMargin;
|
|
3040
|
+
currentY = marginParams.topMargin;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
Log.d(
|
|
3044
|
+
TAG,
|
|
3045
|
+
"Screen dimensions: " +
|
|
3046
|
+
screenWidthPx +
|
|
3047
|
+
"x" +
|
|
3048
|
+
screenHeightPx +
|
|
3049
|
+
" pixels, density: " +
|
|
3050
|
+
density
|
|
3051
|
+
);
|
|
3052
|
+
Log.d(TAG, "WebView dimensions: " + webViewWidth + "x" + webViewHeight);
|
|
3053
|
+
Log.d(
|
|
3054
|
+
TAG,
|
|
3055
|
+
"Current preview position: " +
|
|
3056
|
+
currentX +
|
|
3057
|
+
"," +
|
|
3058
|
+
currentY +
|
|
3059
|
+
" size: " +
|
|
3060
|
+
currentWidth +
|
|
3061
|
+
"x" +
|
|
3062
|
+
currentHeight
|
|
3063
|
+
);
|
|
3064
|
+
|
|
3065
|
+
// Parse aspect ratio as width:height (e.g., 4:3 -> r=4/3)
|
|
2634
3066
|
String[] ratios = aspectRatio.split(":");
|
|
2635
|
-
if (ratios.length != 2)
|
|
3067
|
+
if (ratios.length != 2) {
|
|
3068
|
+
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio);
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
2636
3071
|
|
|
2637
3072
|
try {
|
|
2638
|
-
//
|
|
2639
|
-
|
|
3073
|
+
// Match iOS logic exactly
|
|
3074
|
+
double ratioWidth = Double.parseDouble(ratios[0]);
|
|
3075
|
+
double ratioHeight = Double.parseDouble(ratios[1]);
|
|
3076
|
+
boolean isPortrait =
|
|
3077
|
+
context.getResources().getConfiguration().orientation ==
|
|
3078
|
+
Configuration.ORIENTATION_PORTRAIT;
|
|
3079
|
+
|
|
3080
|
+
Log.d(
|
|
3081
|
+
TAG,
|
|
3082
|
+
"Aspect ratio parsing - Original: " +
|
|
3083
|
+
aspectRatio +
|
|
3084
|
+
" (width=" +
|
|
3085
|
+
ratioWidth +
|
|
3086
|
+
", height=" +
|
|
3087
|
+
ratioHeight +
|
|
3088
|
+
")"
|
|
3089
|
+
);
|
|
3090
|
+
Log.d(
|
|
3091
|
+
TAG,
|
|
3092
|
+
"Device orientation: " + (isPortrait ? "PORTRAIT" : "LANDSCAPE")
|
|
3093
|
+
);
|
|
3094
|
+
|
|
3095
|
+
// iOS: let ratio = !isPortrait ? ratioParts[0] / ratioParts[1] : ratioParts[1] / ratioParts[0]
|
|
3096
|
+
double ratio = !isPortrait
|
|
3097
|
+
? (ratioWidth / ratioHeight)
|
|
3098
|
+
: (ratioHeight / ratioWidth);
|
|
3099
|
+
|
|
3100
|
+
Log.d(
|
|
3101
|
+
TAG,
|
|
3102
|
+
"Computed ratio: " +
|
|
3103
|
+
ratio +
|
|
3104
|
+
" (iOS formula: " +
|
|
3105
|
+
(!isPortrait ? "width/height" : "height/width") +
|
|
3106
|
+
")"
|
|
3107
|
+
);
|
|
2640
3108
|
|
|
2641
3109
|
// Get available space from webview dimensions
|
|
2642
|
-
int availableWidth =
|
|
2643
|
-
int availableHeight =
|
|
3110
|
+
int availableWidth = webViewWidth;
|
|
3111
|
+
int availableHeight = webViewHeight;
|
|
3112
|
+
|
|
3113
|
+
Log.d(
|
|
3114
|
+
TAG,
|
|
3115
|
+
"Available space from WebView: " +
|
|
3116
|
+
availableWidth +
|
|
3117
|
+
"x" +
|
|
3118
|
+
availableHeight
|
|
3119
|
+
);
|
|
2644
3120
|
|
|
2645
3121
|
// Calculate position and size
|
|
2646
3122
|
int finalX, finalY, finalWidth, finalHeight;
|
|
2647
3123
|
|
|
2648
3124
|
if (x != null && y != null) {
|
|
2649
|
-
//
|
|
3125
|
+
// Manual positioning mode
|
|
2650
3126
|
int webViewTopInset = getWebViewTopInset();
|
|
2651
3127
|
int webViewLeftInset = getWebViewLeftInset();
|
|
2652
3128
|
|
|
2653
|
-
|
|
3129
|
+
Log.d(
|
|
3130
|
+
TAG,
|
|
3131
|
+
"Manual positioning mode - WebView insets: left=" +
|
|
3132
|
+
webViewLeftInset +
|
|
3133
|
+
", top=" +
|
|
3134
|
+
webViewTopInset
|
|
3135
|
+
);
|
|
3136
|
+
|
|
2654
3137
|
finalX = Math.max(
|
|
2655
3138
|
0,
|
|
2656
3139
|
Math.min(x.intValue() + webViewLeftInset, availableWidth)
|
|
@@ -2664,6 +3147,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2664
3147
|
int maxWidth = availableWidth - finalX;
|
|
2665
3148
|
int maxHeight = availableHeight - finalY;
|
|
2666
3149
|
|
|
3150
|
+
Log.d(
|
|
3151
|
+
TAG,
|
|
3152
|
+
"Max available space from position: " + maxWidth + "x" + maxHeight
|
|
3153
|
+
);
|
|
3154
|
+
|
|
2667
3155
|
// Calculate optimal size while maintaining aspect ratio within available space
|
|
2668
3156
|
finalWidth = maxWidth;
|
|
2669
3157
|
finalHeight = (int) (maxWidth / ratio);
|
|
@@ -2672,76 +3160,147 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2672
3160
|
// Height constraint is tighter, fit by height
|
|
2673
3161
|
finalHeight = maxHeight;
|
|
2674
3162
|
finalWidth = (int) (maxHeight * ratio);
|
|
3163
|
+
Log.d(TAG, "Height-constrained sizing");
|
|
3164
|
+
} else {
|
|
3165
|
+
Log.d(TAG, "Width-constrained sizing");
|
|
2675
3166
|
}
|
|
2676
3167
|
|
|
2677
3168
|
// Ensure final position stays within bounds
|
|
2678
3169
|
finalX = Math.max(0, Math.min(finalX, availableWidth - finalWidth));
|
|
2679
3170
|
finalY = Math.max(0, Math.min(finalY, availableHeight - finalHeight));
|
|
2680
3171
|
} else {
|
|
2681
|
-
// Auto-center
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
3172
|
+
// Auto-center mode - match iOS behavior exactly
|
|
3173
|
+
Log.d(TAG, "Auto-center mode");
|
|
3174
|
+
|
|
3175
|
+
// Calculate maximum size that fits the aspect ratio in available space
|
|
3176
|
+
double maxWidthByHeight = availableHeight * ratio;
|
|
3177
|
+
double maxHeightByWidth = availableWidth / ratio;
|
|
3178
|
+
|
|
3179
|
+
Log.d(
|
|
3180
|
+
TAG,
|
|
3181
|
+
"Aspect ratio calculations - maxWidthByHeight: " +
|
|
3182
|
+
maxWidthByHeight +
|
|
3183
|
+
", maxHeightByWidth: " +
|
|
3184
|
+
maxHeightByWidth
|
|
3185
|
+
);
|
|
3186
|
+
|
|
3187
|
+
if (maxWidthByHeight <= availableWidth) {
|
|
3188
|
+
// Height is the limiting factor
|
|
3189
|
+
finalWidth = (int) maxWidthByHeight;
|
|
3190
|
+
finalHeight = availableHeight;
|
|
3191
|
+
Log.d(
|
|
3192
|
+
TAG,
|
|
3193
|
+
"Height-limited sizing: " + finalWidth + "x" + finalHeight
|
|
3194
|
+
);
|
|
3195
|
+
} else {
|
|
3196
|
+
// Width is the limiting factor
|
|
3197
|
+
finalWidth = availableWidth;
|
|
3198
|
+
finalHeight = (int) maxHeightByWidth;
|
|
3199
|
+
Log.d(TAG, "Width-limited sizing: " + finalWidth + "x" + finalHeight);
|
|
2694
3200
|
}
|
|
2695
3201
|
|
|
2696
|
-
// Center the
|
|
3202
|
+
// Center the preview
|
|
2697
3203
|
finalX = (availableWidth - finalWidth) / 2;
|
|
2698
3204
|
finalY = (availableHeight - finalHeight) / 2;
|
|
2699
3205
|
|
|
2700
3206
|
Log.d(
|
|
2701
3207
|
TAG,
|
|
2702
|
-
"
|
|
2703
|
-
ratio +
|
|
2704
|
-
", calculated size=" +
|
|
3208
|
+
"Auto-center mode: calculated size " +
|
|
2705
3209
|
finalWidth +
|
|
2706
3210
|
"x" +
|
|
2707
3211
|
finalHeight +
|
|
2708
|
-
"
|
|
2709
|
-
|
|
2710
|
-
"
|
|
2711
|
-
|
|
3212
|
+
" at position (" +
|
|
3213
|
+
finalX +
|
|
3214
|
+
", " +
|
|
3215
|
+
finalY +
|
|
3216
|
+
")"
|
|
2712
3217
|
);
|
|
2713
3218
|
}
|
|
2714
3219
|
|
|
3220
|
+
Log.d(
|
|
3221
|
+
TAG,
|
|
3222
|
+
"Final calculated layout - Position: (" +
|
|
3223
|
+
finalX +
|
|
3224
|
+
"," +
|
|
3225
|
+
finalY +
|
|
3226
|
+
"), Size: " +
|
|
3227
|
+
finalWidth +
|
|
3228
|
+
"x" +
|
|
3229
|
+
finalHeight
|
|
3230
|
+
);
|
|
3231
|
+
|
|
3232
|
+
// Calculate and log the actual displayed aspect ratio
|
|
3233
|
+
double displayedRatio = (double) finalWidth / (double) finalHeight;
|
|
3234
|
+
Log.d(
|
|
3235
|
+
TAG,
|
|
3236
|
+
"Displayed aspect ratio: " +
|
|
3237
|
+
displayedRatio +
|
|
3238
|
+
" (width=" +
|
|
3239
|
+
finalWidth +
|
|
3240
|
+
", height=" +
|
|
3241
|
+
finalHeight +
|
|
3242
|
+
")"
|
|
3243
|
+
);
|
|
3244
|
+
|
|
3245
|
+
// Compare with expected ratio based on orientation
|
|
3246
|
+
if (aspectRatio != null) {
|
|
3247
|
+
String[] parts = aspectRatio.split(":");
|
|
3248
|
+
if (parts.length == 2) {
|
|
3249
|
+
double expectedDisplayRatio = isPortrait
|
|
3250
|
+
? (ratioHeight / ratioWidth)
|
|
3251
|
+
: (ratioWidth / ratioHeight);
|
|
3252
|
+
double difference = Math.abs(displayedRatio - expectedDisplayRatio);
|
|
3253
|
+
Log.d(
|
|
3254
|
+
TAG,
|
|
3255
|
+
"Display ratio check - Expected: " +
|
|
3256
|
+
expectedDisplayRatio +
|
|
3257
|
+
", Actual: " +
|
|
3258
|
+
displayedRatio +
|
|
3259
|
+
", Difference: " +
|
|
3260
|
+
difference +
|
|
3261
|
+
" (tolerance should be < 0.01)"
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
|
|
2715
3266
|
// Update layout params
|
|
2716
|
-
ViewGroup.LayoutParams
|
|
2717
|
-
if (
|
|
3267
|
+
ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
|
|
3268
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
2718
3269
|
ViewGroup.MarginLayoutParams params =
|
|
2719
|
-
(ViewGroup.MarginLayoutParams)
|
|
3270
|
+
(ViewGroup.MarginLayoutParams) layoutParams;
|
|
2720
3271
|
params.width = finalWidth;
|
|
2721
3272
|
params.height = finalHeight;
|
|
2722
3273
|
params.leftMargin = finalX;
|
|
2723
3274
|
params.topMargin = finalY;
|
|
2724
3275
|
previewContainer.setLayoutParams(params);
|
|
2725
3276
|
previewContainer.requestLayout();
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
"updatePreviewLayoutForAspectRatio: Updated to " +
|
|
2729
|
-
finalWidth +
|
|
2730
|
-
"x" +
|
|
2731
|
-
finalHeight +
|
|
2732
|
-
" at (" +
|
|
2733
|
-
finalX +
|
|
2734
|
-
"," +
|
|
2735
|
-
finalY +
|
|
2736
|
-
")"
|
|
2737
|
-
);
|
|
3277
|
+
|
|
3278
|
+
Log.d(TAG, "Layout params applied successfully");
|
|
2738
3279
|
|
|
2739
3280
|
// Update grid overlay bounds after aspect ratio change
|
|
2740
|
-
previewContainer.post(() ->
|
|
3281
|
+
previewContainer.post(() -> {
|
|
3282
|
+
Log.d(
|
|
3283
|
+
TAG,
|
|
3284
|
+
"Post-layout verification - Actual position: " +
|
|
3285
|
+
previewContainer.getLeft() +
|
|
3286
|
+
"," +
|
|
3287
|
+
previewContainer.getTop() +
|
|
3288
|
+
", Actual size: " +
|
|
3289
|
+
previewContainer.getWidth() +
|
|
3290
|
+
"x" +
|
|
3291
|
+
previewContainer.getHeight()
|
|
3292
|
+
);
|
|
3293
|
+
updateGridOverlayBounds();
|
|
3294
|
+
});
|
|
2741
3295
|
}
|
|
2742
3296
|
} catch (NumberFormatException e) {
|
|
2743
3297
|
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
2744
3298
|
}
|
|
3299
|
+
|
|
3300
|
+
Log.d(
|
|
3301
|
+
TAG,
|
|
3302
|
+
"========================================================================================"
|
|
3303
|
+
);
|
|
2745
3304
|
}
|
|
2746
3305
|
|
|
2747
3306
|
private int getWebViewTopInset() {
|
|
@@ -2843,12 +3402,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2843
3402
|
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
2844
3403
|
MeteringPoint point = factory.createPoint(viewWidth / 2f, viewHeight / 2f);
|
|
2845
3404
|
|
|
2846
|
-
// Create focus and metering action
|
|
3405
|
+
// Create focus and metering action (persistent, no auto-cancel) to match iOS behavior
|
|
2847
3406
|
FocusMeteringAction action = new FocusMeteringAction.Builder(
|
|
2848
3407
|
point,
|
|
2849
3408
|
FocusMeteringAction.FLAG_AF | FocusMeteringAction.FLAG_AE
|
|
2850
3409
|
)
|
|
2851
|
-
.
|
|
3410
|
+
.disableAutoCancel()
|
|
2852
3411
|
.build();
|
|
2853
3412
|
|
|
2854
3413
|
try {
|