@capgo/camera-preview 7.4.0-beta.15 → 7.4.0-beta.17
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/README.md +9 -8
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +118 -25
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +160 -75
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +24 -7
- package/dist/docs.json +12 -0
- package/dist/esm/definitions.d.ts +7 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.js +43 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +43 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +43 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +59 -8
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +107 -95
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -725,14 +725,15 @@ Represents EXIF data extracted from an image.
|
|
|
725
725
|
|
|
726
726
|
Defines the options for capturing a picture.
|
|
727
727
|
|
|
728
|
-
| Prop | Type | Description
|
|
729
|
-
| ---------------------- | ------------------------------------------------------- |
|
|
730
|
-
| **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used.
|
|
731
|
-
| **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used.
|
|
732
|
-
| **`
|
|
733
|
-
| **`
|
|
734
|
-
| **`
|
|
735
|
-
| **`
|
|
728
|
+
| Prop | Type | Description | Default | Since |
|
|
729
|
+
| ---------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----- |
|
|
730
|
+
| **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | | |
|
|
731
|
+
| **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | | |
|
|
732
|
+
| **`aspectRatio`** | <code>string</code> | The desired aspect ratio of the captured image (e.g., '4:3', '16:9'). If specified without width/height, captures the largest possible image with this ratio. Cannot be used together with width or height - the capture will be rejected with an error. | | 7.7.0 |
|
|
733
|
+
| **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> | |
|
|
734
|
+
| **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> | |
|
|
735
|
+
| **`saveToGallery`** | <code>boolean</code> | If true, the captured image will be saved to the user's gallery. | <code>false</code> | 7.5.0 |
|
|
736
|
+
| **`withExifLocation`** | <code>boolean</code> | If true, the plugin will attempt to add GPS location data to the image's EXIF metadata. This may prompt the user for location permissions. | <code>false</code> | 7.6.0 |
|
|
736
737
|
|
|
737
738
|
|
|
738
739
|
#### CameraSampleOptions
|
|
@@ -9,6 +9,7 @@ import android.location.Location;
|
|
|
9
9
|
import android.util.DisplayMetrics;
|
|
10
10
|
import android.util.Log;
|
|
11
11
|
import android.util.Size;
|
|
12
|
+
import android.view.View;
|
|
12
13
|
import android.view.ViewGroup;
|
|
13
14
|
import android.webkit.WebView;
|
|
14
15
|
import com.ahm.capacitor.camera.preview.model.CameraDevice;
|
|
@@ -171,8 +172,9 @@ public class CameraPreview
|
|
|
171
172
|
final boolean saveToGallery = call.getBoolean("saveToGallery", false);
|
|
172
173
|
Integer width = call.getInt("width");
|
|
173
174
|
Integer height = call.getInt("height");
|
|
175
|
+
String aspectRatio = call.getString("aspectRatio");
|
|
174
176
|
|
|
175
|
-
cameraXView.capturePhoto(quality, saveToGallery, width, height, location);
|
|
177
|
+
cameraXView.capturePhoto(quality, saveToGallery, width, height, aspectRatio, location);
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
@PluginMethod
|
|
@@ -574,6 +576,41 @@ public class CameraPreview
|
|
|
574
576
|
|
|
575
577
|
// Calculate pixel ratio
|
|
576
578
|
float pixelRatio = metrics.density;
|
|
579
|
+
|
|
580
|
+
// The key insight: JavaScript coordinates are relative to the WebView's viewport
|
|
581
|
+
// If the WebView is positioned below the status bar (webViewLocationOnScreen[1] > 0),
|
|
582
|
+
// we need to add that offset when placing native views
|
|
583
|
+
int webViewTopInset = webViewLocationOnScreen[1];
|
|
584
|
+
boolean isEdgeToEdgeActive = webViewLocationOnScreen[1] > 0;
|
|
585
|
+
|
|
586
|
+
// Log all the positioning information for debugging
|
|
587
|
+
Log.d("CameraPreview", "WebView Position Debug:");
|
|
588
|
+
Log.d("CameraPreview", " - webView.getTop(): " + webViewTop);
|
|
589
|
+
Log.d("CameraPreview", " - webView.getLeft(): " + webViewLeft);
|
|
590
|
+
Log.d("CameraPreview", " - webView locationInWindow: (" + webViewLocationInWindow[0] + ", " + webViewLocationInWindow[1] + ")");
|
|
591
|
+
Log.d("CameraPreview", " - webView locationOnScreen: (" + webViewLocationOnScreen[0] + ", " + webViewLocationOnScreen[1] + ")");
|
|
592
|
+
Log.d("CameraPreview", " - parent locationInWindow: (" + parentLocationInWindow[0] + ", " + parentLocationInWindow[1] + ")");
|
|
593
|
+
Log.d("CameraPreview", " - parent locationOnScreen: (" + parentLocationOnScreen[0] + ", " + parentLocationOnScreen[1] + ")");
|
|
594
|
+
|
|
595
|
+
// Check if WebView has margins
|
|
596
|
+
View webView = getBridge().getWebView();
|
|
597
|
+
ViewGroup.LayoutParams webViewLayoutParams = webView.getLayoutParams();
|
|
598
|
+
if (webViewLayoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
599
|
+
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) webViewLayoutParams;
|
|
600
|
+
Log.d("CameraPreview", " - webView margins: left=" + marginParams.leftMargin +
|
|
601
|
+
", top=" + marginParams.topMargin +
|
|
602
|
+
", right=" + marginParams.rightMargin +
|
|
603
|
+
", bottom=" + marginParams.bottomMargin);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Check WebView padding
|
|
607
|
+
Log.d("CameraPreview", " - webView padding: left=" + webView.getPaddingLeft() +
|
|
608
|
+
", top=" + webView.getPaddingTop() +
|
|
609
|
+
", right=" + webView.getPaddingRight() +
|
|
610
|
+
", bottom=" + webView.getPaddingBottom());
|
|
611
|
+
|
|
612
|
+
Log.d("CameraPreview", " - Using webViewTopInset: " + webViewTopInset);
|
|
613
|
+
Log.d("CameraPreview", " - isEdgeToEdgeActive: " + isEdgeToEdgeActive);
|
|
577
614
|
|
|
578
615
|
// Calculate position - center if x or y is -1
|
|
579
616
|
int computedX;
|
|
@@ -588,7 +625,13 @@ public class CameraPreview
|
|
|
588
625
|
: getBridge().getWebView().getHeight();
|
|
589
626
|
computedHeight -= (int) (paddingBottom * pixelRatio);
|
|
590
627
|
|
|
591
|
-
Log.d("CameraPreview", "
|
|
628
|
+
Log.d("CameraPreview", "========================");
|
|
629
|
+
Log.d("CameraPreview", "POSITIONING CALCULATIONS:");
|
|
630
|
+
Log.d("CameraPreview", "1. INPUT - x: " + x + ", y: " + y + ", width: " + width + ", height: " + height);
|
|
631
|
+
Log.d("CameraPreview", "2. PIXEL RATIO: " + pixelRatio);
|
|
632
|
+
Log.d("CameraPreview", "3. SCREEN - width: " + metrics.widthPixels + ", height: " + metrics.heightPixels);
|
|
633
|
+
Log.d("CameraPreview", "4. WEBVIEW - width: " + getBridge().getWebView().getWidth() + ", height: " + getBridge().getWebView().getHeight());
|
|
634
|
+
Log.d("CameraPreview", "5. COMPUTED DIMENSIONS - width: " + computedWidth + ", height: " + computedHeight);
|
|
592
635
|
|
|
593
636
|
if (x == -1) {
|
|
594
637
|
// Center horizontally
|
|
@@ -617,20 +660,51 @@ public class CameraPreview
|
|
|
617
660
|
}
|
|
618
661
|
|
|
619
662
|
if (y == -1) {
|
|
620
|
-
// Center vertically
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
663
|
+
// Center vertically
|
|
664
|
+
if (isEdgeToEdgeActive) {
|
|
665
|
+
// When WebView is offset from top, center within the available space
|
|
666
|
+
// The camera should be centered in the full screen, not just the WebView area
|
|
667
|
+
computedY = (metrics.heightPixels - computedHeight) / 2;
|
|
668
|
+
Log.d(
|
|
669
|
+
"CameraPreview",
|
|
670
|
+
"Centering vertically with WebView offset: screenHeight=" +
|
|
671
|
+
metrics.heightPixels +
|
|
672
|
+
", webViewTop=" +
|
|
673
|
+
webViewTopInset +
|
|
674
|
+
", computedHeight=" +
|
|
675
|
+
computedHeight +
|
|
676
|
+
", computedY=" +
|
|
677
|
+
computedY
|
|
678
|
+
);
|
|
679
|
+
} else {
|
|
680
|
+
// Normal mode - use full screen height
|
|
681
|
+
computedY = (metrics.heightPixels - computedHeight) / 2;
|
|
682
|
+
Log.d(
|
|
683
|
+
"CameraPreview",
|
|
684
|
+
"Centering vertically (normal): screenHeight=" +
|
|
685
|
+
metrics.heightPixels +
|
|
686
|
+
", computedHeight=" +
|
|
687
|
+
computedHeight +
|
|
688
|
+
", computedY=" +
|
|
689
|
+
computedY
|
|
690
|
+
);
|
|
691
|
+
}
|
|
632
692
|
} else {
|
|
633
693
|
computedY = (int) (y * pixelRatio);
|
|
694
|
+
// If edge-to-edge is active, JavaScript Y is relative to WebView content area
|
|
695
|
+
// We need to add the inset to get absolute screen position
|
|
696
|
+
if (isEdgeToEdgeActive) {
|
|
697
|
+
computedY += webViewTopInset;
|
|
698
|
+
Log.d(
|
|
699
|
+
"CameraPreview",
|
|
700
|
+
"Edge-to-edge adjustment: Y position " +
|
|
701
|
+
(int)(y * pixelRatio) +
|
|
702
|
+
" + inset " +
|
|
703
|
+
webViewTopInset +
|
|
704
|
+
" = " +
|
|
705
|
+
computedY
|
|
706
|
+
);
|
|
707
|
+
}
|
|
634
708
|
Log.d(
|
|
635
709
|
"CameraPreview",
|
|
636
710
|
"Using provided Y position: " +
|
|
@@ -638,10 +712,15 @@ public class CameraPreview
|
|
|
638
712
|
" * " +
|
|
639
713
|
pixelRatio +
|
|
640
714
|
" = " +
|
|
641
|
-
computedY
|
|
715
|
+
computedY +
|
|
716
|
+
(isEdgeToEdgeActive ? " (adjusted for edge-to-edge)" : "")
|
|
642
717
|
);
|
|
643
718
|
}
|
|
644
719
|
|
|
720
|
+
Log.d(
|
|
721
|
+
"CameraPreview",
|
|
722
|
+
"2b. EDGE-TO-EDGE - " + (isEdgeToEdgeActive ? "ACTIVE (inset=" + webViewTopInset + ")" : "INACTIVE")
|
|
723
|
+
);
|
|
645
724
|
Log.d(
|
|
646
725
|
"CameraPreview",
|
|
647
726
|
"3. COMPUTED POSITION - x=" + computedX + ", y=" + computedY
|
|
@@ -776,18 +855,16 @@ public class CameraPreview
|
|
|
776
855
|
.getDisplayMetrics();
|
|
777
856
|
float pixelRatio = metrics.density;
|
|
778
857
|
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
// and
|
|
858
|
+
// When WebView is offset from the top (e.g., below status bar),
|
|
859
|
+
// we need to convert between JavaScript coordinates (relative to WebView)
|
|
860
|
+
// and native coordinates (relative to screen)
|
|
861
|
+
WebView webView = getBridge().getWebView();
|
|
782
862
|
int webViewTopInset = 0;
|
|
783
863
|
boolean isEdgeToEdgeActive = false;
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
webView
|
|
787
|
-
|
|
788
|
-
) {
|
|
789
|
-
webViewTopInset =
|
|
790
|
-
((ViewGroup.MarginLayoutParams) webView.getLayoutParams()).topMargin;
|
|
864
|
+
if (webView != null) {
|
|
865
|
+
int[] location = new int[2];
|
|
866
|
+
webView.getLocationOnScreen(location);
|
|
867
|
+
webViewTopInset = location[1];
|
|
791
868
|
isEdgeToEdgeActive = webViewTopInset > 0;
|
|
792
869
|
}
|
|
793
870
|
|
|
@@ -972,8 +1049,24 @@ public class CameraPreview
|
|
|
972
1049
|
.getDisplayMetrics();
|
|
973
1050
|
float pixelRatio = metrics.density;
|
|
974
1051
|
|
|
1052
|
+
// Check if edge-to-edge mode is active
|
|
1053
|
+
WebView webView = getBridge().getWebView();
|
|
1054
|
+
int webViewTopInset = 0;
|
|
1055
|
+
boolean isEdgeToEdgeActive = false;
|
|
1056
|
+
if (webView != null) {
|
|
1057
|
+
int[] location = new int[2];
|
|
1058
|
+
webView.getLocationOnScreen(location);
|
|
1059
|
+
webViewTopInset = location[1];
|
|
1060
|
+
isEdgeToEdgeActive = webViewTopInset > 0;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
975
1063
|
int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
|
|
976
1064
|
int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
|
|
1065
|
+
|
|
1066
|
+
// Add edge-to-edge inset to Y if active
|
|
1067
|
+
if (isEdgeToEdgeActive && y > 0) {
|
|
1068
|
+
y += webViewTopInset;
|
|
1069
|
+
}
|
|
977
1070
|
int width = (widthParam != null && widthParam > 0)
|
|
978
1071
|
? (int) (widthParam * pixelRatio)
|
|
979
1072
|
: 0;
|
|
@@ -360,6 +360,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
360
360
|
Log.d(TAG, "setupPreviewView: Setting grid mode to: " + currentGridMode);
|
|
361
361
|
gridOverlayView.setGridMode(currentGridMode);
|
|
362
362
|
});
|
|
363
|
+
|
|
364
|
+
// Add a layout listener to update grid bounds when preview view changes size
|
|
365
|
+
previewView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
366
|
+
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
|
|
367
|
+
Log.d(TAG, "PreviewView layout changed, updating grid bounds");
|
|
368
|
+
updateGridOverlayBounds();
|
|
369
|
+
}
|
|
370
|
+
});
|
|
363
371
|
|
|
364
372
|
ViewGroup parent = (ViewGroup) webView.getParent();
|
|
365
373
|
if (parent != null) {
|
|
@@ -480,6 +488,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
480
488
|
|
|
481
489
|
if (height != oldHeight) {
|
|
482
490
|
int screenHeight = metrics.heightPixels;
|
|
491
|
+
// Always center based on full screen height
|
|
483
492
|
y = (screenHeight - height) / 2;
|
|
484
493
|
Log.d(
|
|
485
494
|
TAG,
|
|
@@ -516,27 +525,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
516
525
|
height
|
|
517
526
|
);
|
|
518
527
|
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
// Don't add insets if centered or full-screen
|
|
524
|
-
if (sessionConfig.isCentered() || (x == 0 && y == 0)) {
|
|
525
|
-
layoutParams.leftMargin = x;
|
|
526
|
-
layoutParams.topMargin = y;
|
|
527
|
-
Log.d(
|
|
528
|
-
TAG,
|
|
529
|
-
"calculatePreviewLayoutParams: Centered/Full-screen mode - keeping position without insets. isCentered=" +
|
|
530
|
-
sessionConfig.isCentered()
|
|
531
|
-
);
|
|
532
|
-
} else {
|
|
533
|
-
layoutParams.leftMargin = x + webViewLeftInset;
|
|
534
|
-
layoutParams.topMargin = y + webViewTopInset;
|
|
535
|
-
Log.d(
|
|
536
|
-
TAG,
|
|
537
|
-
"calculatePreviewLayoutParams: Positioned mode - applying insets"
|
|
538
|
-
);
|
|
539
|
-
}
|
|
528
|
+
// The X and Y positions passed from CameraPreview already include webView insets
|
|
529
|
+
// when edge-to-edge is active, so we don't need to add them again here
|
|
530
|
+
layoutParams.leftMargin = x;
|
|
531
|
+
layoutParams.topMargin = y;
|
|
540
532
|
|
|
541
533
|
Log.d(
|
|
542
534
|
TAG,
|
|
@@ -562,18 +554,6 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
562
554
|
" height:" +
|
|
563
555
|
height
|
|
564
556
|
);
|
|
565
|
-
Log.d(
|
|
566
|
-
TAG,
|
|
567
|
-
"calculatePreviewLayoutParams: Final margins - leftMargin:" +
|
|
568
|
-
layoutParams.leftMargin +
|
|
569
|
-
" topMargin:" +
|
|
570
|
-
layoutParams.topMargin +
|
|
571
|
-
" (WebView insets: left=" +
|
|
572
|
-
webViewLeftInset +
|
|
573
|
-
", top=" +
|
|
574
|
-
webViewTopInset +
|
|
575
|
-
")"
|
|
576
|
-
);
|
|
577
557
|
return layoutParams;
|
|
578
558
|
}
|
|
579
559
|
|
|
@@ -766,6 +746,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
766
746
|
actualHeight
|
|
767
747
|
);
|
|
768
748
|
|
|
749
|
+
// Update grid overlay bounds after camera is started
|
|
750
|
+
updateGridOverlayBounds();
|
|
751
|
+
|
|
769
752
|
listener.onCameraStarted(
|
|
770
753
|
actualWidth,
|
|
771
754
|
actualHeight,
|
|
@@ -852,9 +835,18 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
852
835
|
final boolean saveToGallery,
|
|
853
836
|
Integer width,
|
|
854
837
|
Integer height,
|
|
838
|
+
String aspectRatio,
|
|
855
839
|
Location location
|
|
856
840
|
) {
|
|
857
|
-
Log.d(TAG, "capturePhoto: Starting photo capture with quality: " + quality);
|
|
841
|
+
Log.d(TAG, "capturePhoto: Starting photo capture with quality: " + quality + ", aspectRatio: " + aspectRatio);
|
|
842
|
+
|
|
843
|
+
// Check for conflicting parameters
|
|
844
|
+
if (aspectRatio != null && (width != null || height != null)) {
|
|
845
|
+
if (listener != null) {
|
|
846
|
+
listener.onPictureTakenError("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.");
|
|
847
|
+
}
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
858
850
|
|
|
859
851
|
if (imageCapture == null) {
|
|
860
852
|
if (listener != null) {
|
|
@@ -902,7 +894,63 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
902
894
|
|
|
903
895
|
JSONObject exifData = getExifData(exifInterface);
|
|
904
896
|
|
|
905
|
-
|
|
897
|
+
// Handle aspect ratio if no width/height specified
|
|
898
|
+
if (width == null && height == null && aspectRatio != null && !aspectRatio.isEmpty()) {
|
|
899
|
+
// Get the original image dimensions
|
|
900
|
+
Bitmap originalBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
|
901
|
+
int originalWidth = originalBitmap.getWidth();
|
|
902
|
+
int originalHeight = originalBitmap.getHeight();
|
|
903
|
+
|
|
904
|
+
// Parse aspect ratio
|
|
905
|
+
String[] ratios = aspectRatio.split(":");
|
|
906
|
+
if (ratios.length == 2) {
|
|
907
|
+
try {
|
|
908
|
+
float widthRatio = Float.parseFloat(ratios[0]);
|
|
909
|
+
float heightRatio = Float.parseFloat(ratios[1]);
|
|
910
|
+
|
|
911
|
+
// For capture in portrait orientation, swap the aspect ratio (16:9 becomes 9:16)
|
|
912
|
+
boolean isPortrait = originalHeight > originalWidth;
|
|
913
|
+
float targetAspectRatio = isPortrait ? heightRatio / widthRatio : widthRatio / heightRatio;
|
|
914
|
+
float originalAspectRatio = (float) originalWidth / originalHeight;
|
|
915
|
+
|
|
916
|
+
int targetWidth, targetHeight;
|
|
917
|
+
|
|
918
|
+
if (originalAspectRatio > targetAspectRatio) {
|
|
919
|
+
// Original is wider than target - fit by height
|
|
920
|
+
targetHeight = originalHeight;
|
|
921
|
+
targetWidth = (int) (targetHeight * targetAspectRatio);
|
|
922
|
+
} else {
|
|
923
|
+
// Original is taller than target - fit by width
|
|
924
|
+
targetWidth = originalWidth;
|
|
925
|
+
targetHeight = (int) (targetWidth / targetAspectRatio);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Center crop the image
|
|
929
|
+
int xOffset = (originalWidth - targetWidth) / 2;
|
|
930
|
+
int yOffset = (originalHeight - targetHeight) / 2;
|
|
931
|
+
|
|
932
|
+
Bitmap croppedBitmap = Bitmap.createBitmap(
|
|
933
|
+
originalBitmap,
|
|
934
|
+
xOffset,
|
|
935
|
+
yOffset,
|
|
936
|
+
targetWidth,
|
|
937
|
+
targetHeight
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
941
|
+
croppedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
|
|
942
|
+
bytes = stream.toByteArray();
|
|
943
|
+
|
|
944
|
+
// Write EXIF data back to cropped image
|
|
945
|
+
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
946
|
+
|
|
947
|
+
originalBitmap.recycle();
|
|
948
|
+
croppedBitmap.recycle();
|
|
949
|
+
} catch (NumberFormatException e) {
|
|
950
|
+
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
} else if (width != null && height != null) {
|
|
906
954
|
Bitmap bitmap = BitmapFactory.decodeByteArray(
|
|
907
955
|
bytes,
|
|
908
956
|
0,
|
|
@@ -1543,6 +1591,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1543
1591
|
throw new Exception("Preview view not initialized");
|
|
1544
1592
|
}
|
|
1545
1593
|
|
|
1594
|
+
// Validate that coordinates are within bounds (0-1 range)
|
|
1595
|
+
if (x < 0f || x > 1f || y < 0f || y > 1f) {
|
|
1596
|
+
Log.w(TAG, "setFocus: Coordinates out of bounds - x: " + x + ", y: " + y);
|
|
1597
|
+
throw new Exception("Focus coordinates must be between 0 and 1");
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1546
1600
|
// Cancel any ongoing focus operation
|
|
1547
1601
|
if (currentFocusFuture != null && !currentFocusFuture.isDone()) {
|
|
1548
1602
|
Log.d(TAG, "setFocus: Cancelling previous focus operation");
|
|
@@ -1551,16 +1605,18 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1551
1605
|
|
|
1552
1606
|
int viewWidth = previewView.getWidth();
|
|
1553
1607
|
int viewHeight = previewView.getHeight();
|
|
1554
|
-
|
|
1555
|
-
float indicatorY = y * viewHeight;
|
|
1556
|
-
showFocusIndicator(indicatorX, indicatorY);
|
|
1557
|
-
|
|
1608
|
+
|
|
1558
1609
|
if (viewWidth <= 0 || viewHeight <= 0) {
|
|
1559
1610
|
throw new Exception(
|
|
1560
1611
|
"Preview view has invalid dimensions: " + viewWidth + "x" + viewHeight
|
|
1561
1612
|
);
|
|
1562
1613
|
}
|
|
1563
1614
|
|
|
1615
|
+
// Only show focus indicator after validation passes
|
|
1616
|
+
float indicatorX = x * viewWidth;
|
|
1617
|
+
float indicatorY = y * viewHeight;
|
|
1618
|
+
showFocusIndicator(indicatorX, indicatorY);
|
|
1619
|
+
|
|
1564
1620
|
// Create MeteringPoint using the preview view
|
|
1565
1621
|
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
1566
1622
|
MeteringPoint point = factory.createPoint(x * viewWidth, y * viewHeight);
|
|
@@ -2576,19 +2632,30 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2576
2632
|
|
|
2577
2633
|
// Wait for camera rebinding to complete, then call callback
|
|
2578
2634
|
if (callback != null) {
|
|
2579
|
-
previewContainer.post(() ->
|
|
2635
|
+
previewContainer.post(() -> {
|
|
2636
|
+
updateGridOverlayBounds();
|
|
2637
|
+
previewContainer.post(callback);
|
|
2638
|
+
});
|
|
2639
|
+
} else {
|
|
2640
|
+
previewContainer.post(() -> updateGridOverlayBounds());
|
|
2580
2641
|
}
|
|
2581
2642
|
} else {
|
|
2582
2643
|
// No camera rebinding needed, wait for layout to complete then call callback
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2644
|
+
previewContainer.post(() -> {
|
|
2645
|
+
updateGridOverlayBounds();
|
|
2646
|
+
if (callback != null) {
|
|
2647
|
+
callback.run();
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2586
2650
|
}
|
|
2587
2651
|
} else {
|
|
2588
2652
|
// No sessionConfig, just wait for layout then call callback
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2653
|
+
previewContainer.post(() -> {
|
|
2654
|
+
updateGridOverlayBounds();
|
|
2655
|
+
if (callback != null) {
|
|
2656
|
+
callback.run();
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2592
2659
|
}
|
|
2593
2660
|
} else {
|
|
2594
2661
|
Log.w(
|
|
@@ -2727,6 +2794,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2727
2794
|
finalY +
|
|
2728
2795
|
")"
|
|
2729
2796
|
);
|
|
2797
|
+
|
|
2798
|
+
// Update grid overlay bounds after aspect ratio change
|
|
2799
|
+
previewContainer.post(() -> updateGridOverlayBounds());
|
|
2730
2800
|
}
|
|
2731
2801
|
} catch (NumberFormatException e) {
|
|
2732
2802
|
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
@@ -2736,10 +2806,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2736
2806
|
private int getWebViewTopInset() {
|
|
2737
2807
|
try {
|
|
2738
2808
|
if (webView != null) {
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2809
|
+
// Get the actual WebView position on screen
|
|
2810
|
+
int[] location = new int[2];
|
|
2811
|
+
webView.getLocationOnScreen(location);
|
|
2812
|
+
return location[1]; // Y position is the top inset
|
|
2743
2813
|
}
|
|
2744
2814
|
} catch (Exception e) {
|
|
2745
2815
|
Log.w(TAG, "Failed to get WebView top inset", e);
|
|
@@ -2750,10 +2820,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2750
2820
|
private int getWebViewLeftInset() {
|
|
2751
2821
|
try {
|
|
2752
2822
|
if (webView != null) {
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2823
|
+
// Get the actual WebView position on screen for consistency
|
|
2824
|
+
int[] location = new int[2];
|
|
2825
|
+
webView.getLocationOnScreen(location);
|
|
2826
|
+
return location[0]; // X position is the left inset
|
|
2757
2827
|
}
|
|
2758
2828
|
} catch (Exception e) {
|
|
2759
2829
|
Log.w(TAG, "Failed to get WebView left inset", e);
|
|
@@ -2769,33 +2839,48 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2769
2839
|
return new int[] { 0, 0, 0, 0 }; // x, y, width, height
|
|
2770
2840
|
}
|
|
2771
2841
|
|
|
2772
|
-
|
|
2773
|
-
int
|
|
2842
|
+
// Get actual camera preview bounds (accounts for letterboxing/pillarboxing)
|
|
2843
|
+
int actualX = getPreviewX();
|
|
2844
|
+
int actualY = getPreviewY();
|
|
2845
|
+
int actualWidth = getPreviewWidth();
|
|
2846
|
+
int actualHeight = getPreviewHeight();
|
|
2774
2847
|
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2848
|
+
// Convert to logical pixels for JavaScript
|
|
2849
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
2850
|
+
float pixelRatio = metrics.density;
|
|
2778
2851
|
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2852
|
+
// Remove WebView insets from coordinates
|
|
2853
|
+
int webViewTopInset = getWebViewTopInset();
|
|
2854
|
+
int webViewLeftInset = getWebViewLeftInset();
|
|
2782
2855
|
|
|
2783
|
-
|
|
2784
|
-
|
|
2856
|
+
int x = Math.max(
|
|
2857
|
+
0,
|
|
2858
|
+
(int) ((actualX - webViewLeftInset) / pixelRatio)
|
|
2859
|
+
);
|
|
2860
|
+
int y = Math.max(
|
|
2861
|
+
0,
|
|
2862
|
+
(int) ((actualY - webViewTopInset) / pixelRatio)
|
|
2863
|
+
);
|
|
2864
|
+
int width = (int) (actualWidth / pixelRatio);
|
|
2865
|
+
int height = (int) (actualHeight / pixelRatio);
|
|
2785
2866
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2867
|
+
return new int[] { x, y, width, height };
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
private void updateGridOverlayBounds() {
|
|
2871
|
+
if (gridOverlayView != null && previewView != null) {
|
|
2872
|
+
// Get the actual camera bounds
|
|
2873
|
+
Rect cameraBounds = getActualCameraBounds();
|
|
2874
|
+
|
|
2875
|
+
// Update the grid overlay with the camera bounds
|
|
2876
|
+
gridOverlayView.setCameraBounds(cameraBounds);
|
|
2877
|
+
|
|
2878
|
+
Log.d(
|
|
2879
|
+
TAG,
|
|
2880
|
+
"updateGridOverlayBounds: Updated grid bounds to " +
|
|
2881
|
+
cameraBounds.toString()
|
|
2793
2882
|
);
|
|
2794
|
-
width = (int) (params.width / pixelRatio);
|
|
2795
|
-
height = (int) (params.height / pixelRatio);
|
|
2796
2883
|
}
|
|
2797
|
-
|
|
2798
|
-
return new int[] { x, y, width, height };
|
|
2799
2884
|
}
|
|
2800
2885
|
|
|
2801
2886
|
private void triggerAutoFocus() {
|
|
@@ -3,6 +3,7 @@ package com.ahm.capacitor.camera.preview;
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import android.graphics.Canvas;
|
|
5
5
|
import android.graphics.Paint;
|
|
6
|
+
import android.graphics.Rect;
|
|
6
7
|
import android.util.AttributeSet;
|
|
7
8
|
import android.view.View;
|
|
8
9
|
|
|
@@ -10,6 +11,7 @@ public class GridOverlayView extends View {
|
|
|
10
11
|
|
|
11
12
|
private Paint gridPaint;
|
|
12
13
|
private String gridMode = "none";
|
|
14
|
+
private Rect cameraBounds = null;
|
|
13
15
|
|
|
14
16
|
public GridOverlayView(Context context) {
|
|
15
17
|
super(context);
|
|
@@ -38,6 +40,11 @@ public class GridOverlayView extends View {
|
|
|
38
40
|
gridPaint.setAntiAlias(true);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
public void setCameraBounds(Rect bounds) {
|
|
44
|
+
this.cameraBounds = bounds;
|
|
45
|
+
invalidate();
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
public void setGridMode(String mode) {
|
|
42
49
|
String previousMode = this.gridMode;
|
|
43
50
|
this.gridMode = mode != null ? mode : "none";
|
|
@@ -62,34 +69,44 @@ public class GridOverlayView extends View {
|
|
|
62
69
|
return;
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
// Use camera bounds if available, otherwise use full view bounds
|
|
73
|
+
int left = 0;
|
|
74
|
+
int top = 0;
|
|
65
75
|
int width = getWidth();
|
|
66
76
|
int height = getHeight();
|
|
67
77
|
|
|
78
|
+
if (cameraBounds != null) {
|
|
79
|
+
left = cameraBounds.left;
|
|
80
|
+
top = cameraBounds.top;
|
|
81
|
+
width = cameraBounds.width();
|
|
82
|
+
height = cameraBounds.height();
|
|
83
|
+
}
|
|
84
|
+
|
|
68
85
|
if (width <= 0 || height <= 0) {
|
|
69
86
|
return;
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
if ("3x3".equals(gridMode)) {
|
|
73
|
-
drawGrid(canvas, width, height, 3);
|
|
90
|
+
drawGrid(canvas, left, top, width, height, 3);
|
|
74
91
|
} else if ("4x4".equals(gridMode)) {
|
|
75
|
-
drawGrid(canvas, width, height, 4);
|
|
92
|
+
drawGrid(canvas, left, top, width, height, 4);
|
|
76
93
|
}
|
|
77
94
|
}
|
|
78
95
|
|
|
79
|
-
private void drawGrid(Canvas canvas, int width, int height, int divisions) {
|
|
96
|
+
private void drawGrid(Canvas canvas, int left, int top, int width, int height, int divisions) {
|
|
80
97
|
float stepX = (float) width / divisions;
|
|
81
98
|
float stepY = (float) height / divisions;
|
|
82
99
|
|
|
83
100
|
// Draw vertical lines
|
|
84
101
|
for (int i = 1; i < divisions; i++) {
|
|
85
|
-
float x = i * stepX;
|
|
86
|
-
canvas.drawLine(x,
|
|
102
|
+
float x = left + (i * stepX);
|
|
103
|
+
canvas.drawLine(x, top, x, top + height, gridPaint);
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
// Draw horizontal lines
|
|
90
107
|
for (int i = 1; i < divisions; i++) {
|
|
91
|
-
float y = i * stepY;
|
|
92
|
-
canvas.drawLine(
|
|
108
|
+
float y = top + (i * stepY);
|
|
109
|
+
canvas.drawLine(left, y, left + width, y, gridPaint);
|
|
93
110
|
}
|
|
94
111
|
}
|
|
95
112
|
}
|