@capgo/camera-preview 7.4.0-beta.3 → 7.4.0-beta.5

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.
Files changed (30) hide show
  1. package/README.md +66 -23
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/file-system.probe +0 -0
  12. package/android/src/main/AndroidManifest.xml +1 -1
  13. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +54 -27
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +132 -19
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +80 -0
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +19 -5
  17. package/dist/docs.json +100 -3
  18. package/dist/esm/definitions.d.ts +44 -2
  19. package/dist/esm/definitions.js.map +1 -1
  20. package/dist/esm/web.d.ts +16 -2
  21. package/dist/esm/web.js +177 -78
  22. package/dist/esm/web.js.map +1 -1
  23. package/dist/plugin.cjs.js +177 -78
  24. package/dist/plugin.cjs.js.map +1 -1
  25. package/dist/plugin.js +177 -78
  26. package/dist/plugin.js.map +1 -1
  27. package/ios/Sources/CapgoCameraPreview/CameraController.swift +78 -20
  28. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  29. package/ios/Sources/CapgoCameraPreview/Plugin.swift +155 -37
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -220,6 +220,8 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
220
220
  * [`capture(...)`](#capture)
221
221
  * [`captureSample(...)`](#capturesample)
222
222
  * [`getSupportedFlashModes()`](#getsupportedflashmodes)
223
+ * [`setAspectRatio(...)`](#setaspectratio)
224
+ * [`getAspectRatio()`](#getaspectratio)
223
225
  * [`getHorizontalFov()`](#gethorizontalfov)
224
226
  * [`getSupportedPictureSizes()`](#getsupportedpicturesizes)
225
227
  * [`setFlashMode(...)`](#setflashmode)
@@ -249,7 +251,7 @@ The main interface for the CameraPreview plugin.
249
251
  ### start(...)
250
252
 
251
253
  ```typescript
252
- start(options: CameraPreviewOptions) => Promise<void>
254
+ start(options: CameraPreviewOptions) => Promise<{ width: number; height: number; x: number; y: number; }>
253
255
  ```
254
256
 
255
257
  Starts the camera preview.
@@ -258,6 +260,8 @@ Starts the camera preview.
258
260
  | ------------- | --------------------------------------------------------------------- | ------------------------------------------- |
259
261
  | **`options`** | <code><a href="#camerapreviewoptions">CameraPreviewOptions</a></code> | - The configuration for the camera preview. |
260
262
 
263
+ **Returns:** <code>Promise&lt;{ width: number; height: number; x: number; y: number; }&gt;</code>
264
+
261
265
  **Since:** 0.0.1
262
266
 
263
267
  --------------------
@@ -329,6 +333,38 @@ Gets the flash modes supported by the active camera.
329
333
  --------------------
330
334
 
331
335
 
336
+ ### setAspectRatio(...)
337
+
338
+ ```typescript
339
+ setAspectRatio(options: { aspectRatio: '4:3' | '16:9' | 'fill'; }) => Promise<void>
340
+ ```
341
+
342
+ Set the aspect ratio of the camera preview.
343
+
344
+ | Param | Type | Description |
345
+ | ------------- | -------------------------------------------------------- | --------------------------- |
346
+ | **`options`** | <code>{ aspectRatio: '4:3' \| '16:9' \| 'fill'; }</code> | - The desired aspect ratio. |
347
+
348
+ **Since:** 7.4.0
349
+
350
+ --------------------
351
+
352
+
353
+ ### getAspectRatio()
354
+
355
+ ```typescript
356
+ getAspectRatio() => Promise<{ aspectRatio: '4:3' | '16:9' | 'fill'; }>
357
+ ```
358
+
359
+ Gets the current aspect ratio of the camera preview.
360
+
361
+ **Returns:** <code>Promise&lt;{ aspectRatio: '4:3' | '16:9' | 'fill'; }&gt;</code>
362
+
363
+ **Since:** 7.4.0
364
+
365
+ --------------------
366
+
367
+
332
368
  ### getHorizontalFov()
333
369
 
334
370
  ```typescript
@@ -568,28 +604,30 @@ Gets the ID of the currently active camera device.
568
604
 
569
605
  Defines the configuration options for starting the camera preview.
570
606
 
571
- | Prop | Type | Description | Default |
572
- | ---------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- |
573
- | **`parent`** | <code>string</code> | The parent element to attach the video preview to. | |
574
- | **`className`** | <code>string</code> | A CSS class name to add to the preview element. | |
575
- | **`width`** | <code>number</code> | The width of the preview in pixels. Defaults to the screen width. | |
576
- | **`height`** | <code>number</code> | The height of the preview in pixels. Defaults to the screen height. | |
577
- | **`x`** | <code>number</code> | The horizontal origin of the preview, in pixels. | |
578
- | **`y`** | <code>number</code> | The vertical origin of the preview, in pixels. | |
579
- | **`includeSafeAreaInsets`** | <code>boolean</code> | Adjusts the y-position to account for safe areas (e.g., notches). | <code>false</code> |
580
- | **`toBack`** | <code>boolean</code> | If true, places the preview behind the webview. | <code>true</code> |
581
- | **`paddingBottom`** | <code>number</code> | Bottom padding for the preview, in pixels. | |
582
- | **`rotateWhenOrientationChanged`** | <code>boolean</code> | Whether to rotate the preview when the device orientation changes. | <code>true</code> |
583
- | **`position`** | <code>string</code> | The camera to use. | <code>"rear"</code> |
584
- | **`storeToFile`** | <code>boolean</code> | If true, saves the captured image to a file and returns the file path. If false, returns a base64 encoded string. | <code>false</code> |
585
- | **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> |
586
- | **`enableHighResolution`** | <code>boolean</code> | If true, enables high-resolution image capture. | <code>false</code> |
587
- | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> |
588
- | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> |
589
- | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> |
590
- | **`enableZoom`** | <code>boolean</code> | If true, enables pinch-to-zoom functionality on the preview. | <code>false</code> |
591
- | **`enableVideoMode`** | <code>boolean</code> | If true, uses the video-optimized preset for the camera session. | <code>false</code> |
592
- | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | |
607
+ | Prop | Type | Description | Default | Since |
608
+ | ---------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------- | ----- |
609
+ | **`parent`** | <code>string</code> | The parent element to attach the video preview to. | | |
610
+ | **`className`** | <code>string</code> | A CSS class name to add to the preview element. | | |
611
+ | **`width`** | <code>number</code> | The width of the preview in pixels. Defaults to the screen width. | | |
612
+ | **`height`** | <code>number</code> | The height of the preview in pixels. Defaults to the screen height. | | |
613
+ | **`x`** | <code>number</code> | The horizontal origin of the preview, in pixels. | | |
614
+ | **`y`** | <code>number</code> | The vertical origin of the preview, in pixels. | | |
615
+ | **`aspectRatio`** | <code>'4:3' \| '16:9' \| 'fill'</code> | The aspect ratio of the camera preview, '4:3' or '16:9'. If not set, the camera will use the default aspect ratio. | | 2.0.0 |
616
+ | **`gridMode`** | <code><a href="#gridmode">GridMode</a></code> | The grid overlay to display on the camera preview. | <code>"none"</code> | 2.1.0 |
617
+ | **`includeSafeAreaInsets`** | <code>boolean</code> | Adjusts the y-position to account for safe areas (e.g., notches). | <code>false</code> | |
618
+ | **`toBack`** | <code>boolean</code> | If true, places the preview behind the webview. | <code>true</code> | |
619
+ | **`paddingBottom`** | <code>number</code> | Bottom padding for the preview, in pixels. | | |
620
+ | **`rotateWhenOrientationChanged`** | <code>boolean</code> | Whether to rotate the preview when the device orientation changes. | <code>true</code> | |
621
+ | **`position`** | <code>string</code> | The camera to use. | <code>"rear"</code> | |
622
+ | **`storeToFile`** | <code>boolean</code> | If true, saves the captured image to a file and returns the file path. If false, returns a base64 encoded string. | <code>false</code> | |
623
+ | **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> | |
624
+ | **`enableHighResolution`** | <code>boolean</code> | If true, enables high-resolution image capture. | <code>false</code> | |
625
+ | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> | |
626
+ | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> | |
627
+ | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> | |
628
+ | **`enableZoom`** | <code>boolean</code> | If true, enables pinch-to-zoom functionality on the preview. | <code>false</code> | |
629
+ | **`enableVideoMode`** | <code>boolean</code> | If true, uses the video-optimized preset for the camera session. | <code>false</code> | |
630
+ | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | | |
593
631
 
594
632
 
595
633
  #### ExifData
@@ -693,6 +731,11 @@ Represents the detailed information of the currently active lens.
693
731
  ### Type Aliases
694
732
 
695
733
 
734
+ #### GridMode
735
+
736
+ <code>"none" | "3x3" | "4x4"</code>
737
+
738
+
696
739
  #### CameraPosition
697
740
 
698
741
  <code>"rear" | "front"</code>
Binary file
@@ -1,7 +1,7 @@
1
1
 
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
3
  <uses-permission android:name="android.permission.CAMERA" />
4
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
4
5
  <uses-feature android:name="android.hardware.camera" />
5
6
  <uses-feature android:name="android.hardware.camera.autofocus" />
6
7
  </manifest>
7
-
@@ -30,6 +30,14 @@ import org.json.JSONObject;
30
30
  import android.location.Location;
31
31
  import com.getcapacitor.Logger;
32
32
 
33
+ interface CameraPreviewListener {
34
+ void onPictureTaken(String base64, JSONObject exif);
35
+ void onPictureTakenError(String message);
36
+ void onCameraStarted(int width, int height, int x, int y);
37
+ void onCameraStopped();
38
+ void onCaptureStarted();
39
+ }
40
+
33
41
  @CapacitorPlugin(
34
42
  name = "CameraPreview",
35
43
  permissions = {
@@ -49,7 +57,7 @@ import com.getcapacitor.Logger;
49
57
  )
50
58
  public class CameraPreview
51
59
  extends Plugin
52
- implements CameraXView.CameraXViewListener {
60
+ implements CameraPreviewListener {
53
61
 
54
62
  static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
55
63
  static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
@@ -146,7 +154,7 @@ public class CameraPreview
146
154
  final boolean saveToGallery = call.getBoolean("saveToGallery", false);
147
155
  Integer width = call.getInt("width");
148
156
  Integer height = call.getInt("height");
149
-
157
+
150
158
  cameraXView.capturePhoto(quality, saveToGallery, width, height, location);
151
159
  }
152
160
 
@@ -291,7 +299,7 @@ public class CameraPreview
291
299
  }
292
300
  rear.put("supportedPictureSizes", rearSizesJs);
293
301
  supportedPictureSizesResult.put(rear);
294
-
302
+
295
303
  List<Size> frontSizes = CameraXView.getSupportedPictureSizes("front");
296
304
  JSObject front = new JSObject();
297
305
  front.put("facing", "front");
@@ -304,7 +312,7 @@ public class CameraPreview
304
312
  }
305
313
  front.put("supportedPictureSizes", frontSizesJs);
306
314
  supportedPictureSizesResult.put(front);
307
-
315
+
308
316
  JSObject ret = new JSObject();
309
317
  ret.put("supportedPictureSizes", supportedPictureSizesResult);
310
318
  call.resolve(ret);
@@ -389,7 +397,9 @@ public class CameraPreview
389
397
  final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
390
398
  final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
391
399
  final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
392
-
400
+ final String aspectRatio = call.getString("aspectRatio", "fill");
401
+ final String gridMode = call.getString("gridMode", "none");
402
+
393
403
  float targetZoom = 1.0f;
394
404
  // Check if the selected device is a physical ultra-wide
395
405
  if (originalDeviceId != null) {
@@ -401,7 +411,7 @@ public class CameraPreview
401
411
  Log.d("CameraPreview", "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera.");
402
412
  targetZoom = 0.5f;
403
413
  // Force the use of the logical camera by clearing the specific deviceId
404
- deviceId = null;
414
+ deviceId = null;
405
415
  break;
406
416
  }
407
417
  }
@@ -412,7 +422,7 @@ public class CameraPreview
412
422
 
413
423
  previousOrientationRequest = getBridge().getActivity().getRequestedOrientation();
414
424
  cameraXView = new CameraXView(getContext(), getBridge().getWebView());
415
- cameraXView.setListener(this);
425
+ cameraXView.setListener((CameraXView.CameraXViewListener) this);
416
426
 
417
427
  String finalDeviceId = deviceId;
418
428
  float finalTargetZoom = targetZoom;
@@ -427,9 +437,9 @@ public class CameraPreview
427
437
  int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : getBridge().getWebView().getHeight();
428
438
  computedHeight -= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, metrics);
429
439
 
430
- CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
440
+ CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f, aspectRatio, gridMode);
431
441
  config.setTargetZoom(finalTargetZoom);
432
-
442
+
433
443
  bridge.saveCall(call);
434
444
  cameraStartCallbackId = call.getCallbackId();
435
445
  cameraXView.startSession(config);
@@ -463,33 +473,50 @@ public class CameraPreview
463
473
  }
464
474
 
465
475
  @Override
466
- public void onSampleTaken(String result) {
467
- JSObject jsObject = new JSObject();
468
- jsObject.put("value", result);
469
- bridge.getSavedCall(snapshotCallbackId).resolve(jsObject);
476
+ public void onCaptureStarted() {
477
+ Log.i("CameraPreview", "Capture started");
470
478
  }
471
479
 
472
480
  @Override
473
- public void onSampleTakenError(String message) {
474
- bridge.getSavedCall(snapshotCallbackId).reject(message);
481
+ public void onCameraStarted(int width, int height, int x, int y) {
482
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
483
+ if (call != null) {
484
+ JSObject result = new JSObject();
485
+ result.put("width", width);
486
+ result.put("height", height);
487
+ result.put("x", x);
488
+ result.put("y", y);
489
+ call.resolve(result);
490
+ bridge.releaseCall(call);
491
+ cameraStartCallbackId = null; // Prevent re-use
492
+ }
475
493
  }
476
494
 
477
495
  @Override
478
- public void onCameraStarted() {
479
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
480
- if (pluginCall != null) {
481
- pluginCall.resolve();
482
- bridge.releaseCall(pluginCall);
483
- }
496
+ public void onCameraStopped() {
497
+ // This method is no longer needed as onCameraStarted handles the promise resolution.
484
498
  }
485
499
 
486
- @Override
487
- public void onCameraStartError(String message) {
488
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
489
- if (pluginCall != null) {
490
- pluginCall.reject(message);
491
- bridge.releaseCall(pluginCall);
500
+ @PluginMethod
501
+ public void setAspectRatio(PluginCall call) {
502
+ if (cameraXView == null || !cameraXView.isRunning()) {
503
+ call.reject("Camera is not running");
504
+ return;
492
505
  }
506
+ String aspectRatio = call.getString("aspectRatio", "fill");
507
+ cameraXView.setAspectRatio(aspectRatio);
508
+ call.resolve();
493
509
  }
494
510
 
511
+ @PluginMethod
512
+ public void getAspectRatio(PluginCall call) {
513
+ if (cameraXView == null || !cameraXView.isRunning()) {
514
+ call.reject("Camera is not running");
515
+ return;
516
+ }
517
+ String aspectRatio = cameraXView.getAspectRatio();
518
+ JSObject ret = new JSObject();
519
+ ret.put("aspectRatio", aspectRatio);
520
+ call.resolve(ret);
521
+ }
495
522
  }
@@ -18,6 +18,8 @@ import androidx.camera.core.ImageCapture;
18
18
  import androidx.camera.core.ImageCaptureException;
19
19
  import androidx.camera.core.ImageProxy;
20
20
  import androidx.camera.core.Preview;
21
+ import androidx.camera.core.AspectRatio;
22
+ import androidx.camera.core.resolutionselector.AspectRatioStrategy;
21
23
  import androidx.camera.core.resolutionselector.ResolutionSelector;
22
24
  import androidx.camera.core.resolutionselector.ResolutionStrategy;
23
25
  import androidx.camera.lifecycle.ProcessCameraProvider;
@@ -27,6 +29,7 @@ import androidx.lifecycle.Lifecycle;
27
29
  import androidx.lifecycle.LifecycleOwner;
28
30
  import androidx.lifecycle.LifecycleRegistry;
29
31
  import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
32
+ import android.widget.FrameLayout;
30
33
  import com.ahm.capacitor.camera.preview.model.LensInfo;
31
34
  import com.ahm.capacitor.camera.preview.model.ZoomFactors;
32
35
  import com.google.common.util.concurrent.ListenableFuture;
@@ -61,8 +64,12 @@ import android.graphics.Bitmap;
61
64
  import android.graphics.Matrix;
62
65
  import java.io.ByteArrayOutputStream;
63
66
  import android.location.Location;
67
+ import android.widget.FrameLayout;
68
+ import androidx.lifecycle.LifecycleObserver;
69
+ import androidx.lifecycle.OnLifecycleEvent;
70
+ import android.util.Rational;
64
71
 
65
- public class CameraXView implements LifecycleOwner {
72
+ public class CameraXView implements LifecycleOwner, LifecycleObserver {
66
73
  private static final String TAG = "CameraPreview CameraXView";
67
74
 
68
75
  public interface CameraXViewListener {
@@ -79,6 +86,8 @@ public class CameraXView implements LifecycleOwner {
79
86
  private ImageCapture imageCapture;
80
87
  private ImageCapture sampleImageCapture;
81
88
  private PreviewView previewView;
89
+ private GridOverlayView gridOverlayView;
90
+ private FrameLayout previewContainer;
82
91
  private CameraSelector currentCameraSelector;
83
92
  private String currentDeviceId;
84
93
  private int currentFlashMode = ImageCapture.FLASH_MODE_OFF;
@@ -179,23 +188,47 @@ public class CameraXView implements LifecycleOwner {
179
188
  if (sessionConfig.isToBack()) {
180
189
  webView.setBackgroundColor(android.graphics.Color.TRANSPARENT);
181
190
  }
191
+
192
+ // Create a container to hold both the preview and grid overlay
193
+ previewContainer = new FrameLayout(context);
194
+
195
+ // Create and setup the preview view
182
196
  previewView = new PreviewView(context);
183
197
  previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
198
+ previewContainer.addView(previewView, new FrameLayout.LayoutParams(
199
+ FrameLayout.LayoutParams.MATCH_PARENT,
200
+ FrameLayout.LayoutParams.MATCH_PARENT
201
+ ));
202
+
203
+ // Create and setup the grid overlay
204
+ gridOverlayView = new GridOverlayView(context);
205
+ gridOverlayView.setGridMode(sessionConfig.getGridMode());
206
+ previewContainer.addView(gridOverlayView, new FrameLayout.LayoutParams(
207
+ FrameLayout.LayoutParams.MATCH_PARENT,
208
+ FrameLayout.LayoutParams.MATCH_PARENT
209
+ ));
210
+
184
211
  ViewGroup parent = (ViewGroup) webView.getParent();
185
212
  if (parent != null) {
186
- parent.addView(previewView, new ViewGroup.LayoutParams(sessionConfig.getWidth(), sessionConfig.getHeight()));
213
+ parent.addView(previewContainer, new ViewGroup.LayoutParams(sessionConfig.getWidth(), sessionConfig.getHeight()));
187
214
  if(sessionConfig.isToBack()) webView.bringToFront();
188
215
  }
189
216
  }
190
217
 
191
218
  private void removePreviewView() {
192
- if (previewView != null) {
193
- ViewGroup parent = (ViewGroup) previewView.getParent();
219
+ if (previewContainer != null) {
220
+ ViewGroup parent = (ViewGroup) previewContainer.getParent();
194
221
  if (parent != null) {
195
- parent.removeView(previewView);
222
+ parent.removeView(previewContainer);
196
223
  }
224
+ previewContainer = null;
225
+ }
226
+ if (previewView != null) {
197
227
  previewView = null;
198
228
  }
229
+ if (gridOverlayView != null) {
230
+ gridOverlayView = null;
231
+ }
199
232
  webView.setBackgroundColor(android.graphics.Color.WHITE);
200
233
  }
201
234
 
@@ -206,9 +239,22 @@ public class CameraXView implements LifecycleOwner {
206
239
  try {
207
240
  Log.d(TAG, "Building camera selector with deviceId: " + sessionConfig.getDeviceId() + " and position: " + sessionConfig.getPosition());
208
241
  currentCameraSelector = buildCameraSelector();
209
- ResolutionSelector resolutionSelector = new ResolutionSelector.Builder()
210
- .setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY)
211
- .build();
242
+
243
+ ResolutionSelector.Builder resolutionSelectorBuilder = new ResolutionSelector.Builder()
244
+ .setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY);
245
+
246
+ if (sessionConfig.getAspectRatio() != null) {
247
+ int aspectRatio;
248
+ if ("16:9".equals(sessionConfig.getAspectRatio())) {
249
+ aspectRatio = AspectRatio.RATIO_16_9;
250
+ } else { // "4:3"
251
+ aspectRatio = AspectRatio.RATIO_4_3;
252
+ }
253
+ resolutionSelectorBuilder.setAspectRatioStrategy(new AspectRatioStrategy(aspectRatio, AspectRatioStrategy.FALLBACK_RULE_AUTO));
254
+ }
255
+
256
+ ResolutionSelector resolutionSelector = resolutionSelectorBuilder.build();
257
+
212
258
  Preview preview = new Preview.Builder().setResolutionSelector(resolutionSelector).build();
213
259
  imageCapture = new ImageCapture.Builder().setResolutionSelector(resolutionSelector).setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY).setFlashMode(currentFlashMode).build();
214
260
  sampleImageCapture = imageCapture;
@@ -347,13 +393,18 @@ public class CameraXView implements LifecycleOwner {
347
393
  @Override
348
394
  public void onImageSaved(@NonNull ImageCapture.OutputFileResults output) {
349
395
  try {
350
- byte[] bytes = Files.readAllBytes(tempFile.toPath());
351
- ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());
396
+ // Read file using FileInputStream for compatibility
397
+ byte[] bytes = new byte[(int) tempFile.length()];
398
+ java.io.FileInputStream fis = new java.io.FileInputStream(tempFile);
399
+ fis.read(bytes);
400
+ fis.close();
352
401
 
402
+ ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());
403
+
353
404
  if (location != null) {
354
405
  exifInterface.setGpsInfo(location);
355
406
  }
356
-
407
+
357
408
  JSONObject exifData = getExifData(exifInterface);
358
409
 
359
410
  if (width != null && height != null) {
@@ -363,13 +414,13 @@ public class CameraXView implements LifecycleOwner {
363
414
  resizedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
364
415
  bytes = stream.toByteArray();
365
416
  }
366
-
417
+
367
418
  if (saveToGallery) {
368
419
  saveImageToGallery(bytes);
369
420
  }
370
421
 
371
422
  String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
372
-
423
+
373
424
  tempFile.delete();
374
425
 
375
426
  if (listener != null) {
@@ -389,7 +440,7 @@ public class CameraXView implements LifecycleOwner {
389
440
  private Bitmap resizeBitmap(Bitmap bitmap, int width, int height) {
390
441
  return Bitmap.createScaledBitmap(bitmap, width, height, true);
391
442
  }
392
-
443
+
393
444
  private JSONObject getExifData(ExifInterface exifInterface) {
394
445
  JSONObject exifData = new JSONObject();
395
446
  try {
@@ -615,7 +666,7 @@ public class CameraXView implements LifecycleOwner {
615
666
  for (CameraInfo cameraInfo : cameraProvider.getAvailableCameraInfos()) {
616
667
  String logicalCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
617
668
  String position = isBackCamera(cameraInfo) ? "rear" : "front";
618
-
669
+
619
670
  // Add logical camera
620
671
  float minZoom = Objects.requireNonNull(cameraInfo.getZoomState().getValue()).getMinZoomRatio();
621
672
  float maxZoom = cameraInfo.getZoomState().getValue().getMaxZoomRatio();
@@ -651,7 +702,7 @@ public class CameraXView implements LifecycleOwner {
651
702
  if (focalLengths[0] < 3.0f) deviceType = "ultraWide";
652
703
  else if (focalLengths[0] > 5.0f) deviceType = "telephoto";
653
704
  }
654
-
705
+
655
706
  float physicalMinZoom = 1.0f;
656
707
  float physicalMaxZoom = 1.0f;
657
708
  if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
@@ -661,11 +712,11 @@ public class CameraXView implements LifecycleOwner {
661
712
  physicalMaxZoom = zoomRange.getUpper();
662
713
  }
663
714
  }
664
-
715
+
665
716
  String label = "Physical " + deviceType + " (" + position + ")";
666
717
  List<LensInfo> physicalLenses = new ArrayList<>();
667
718
  physicalLenses.add(new LensInfo(focalLengths != null ? focalLengths[0] : 4.25f, deviceType, 1.0f, physicalMaxZoom));
668
-
719
+
669
720
  devices.add(new com.ahm.capacitor.camera.preview.model.CameraDevice(
670
721
  physicalId, label, position, physicalLenses, physicalMinZoom, physicalMaxZoom, false
671
722
  ));
@@ -969,7 +1020,9 @@ public class CameraXView implements LifecycleOwner {
969
1020
  sessionConfig.isEnableZoom(), // enableZoom
970
1021
  sessionConfig.isDisableExifHeaderStripping(), // disableExifHeaderStripping
971
1022
  sessionConfig.isDisableAudio(), // disableAudio
972
- sessionConfig.getZoomFactor() // zoomFactor
1023
+ sessionConfig.getZoomFactor(), // zoomFactor
1024
+ sessionConfig.getAspectRatio(), // aspectRatio
1025
+ sessionConfig.getGridMode() // gridMode
973
1026
  );
974
1027
 
975
1028
  // Clear current device ID to force position-based selection
@@ -987,4 +1040,64 @@ public class CameraXView implements LifecycleOwner {
987
1040
  previewView.setAlpha(opacity);
988
1041
  }
989
1042
  }
1043
+
1044
+ private void updateLayoutParams() {
1045
+ if (sessionConfig == null) return;
1046
+
1047
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
1048
+ sessionConfig.getWidth(),
1049
+ sessionConfig.getHeight()
1050
+ );
1051
+ layoutParams.leftMargin = sessionConfig.getX();
1052
+ layoutParams.topMargin = sessionConfig.getY();
1053
+
1054
+ if (sessionConfig.getAspectRatio() != null && !sessionConfig.getAspectRatio().equals("fill")) {
1055
+ String[] ratios = sessionConfig.getAspectRatio().split(":");
1056
+ float ratio = Float.parseFloat(ratios[0]) / Float.parseFloat(ratios[1]);
1057
+ if (sessionConfig.getWidth() > 0) {
1058
+ layoutParams.height = (int) (sessionConfig.getWidth() / ratio);
1059
+ } else if (sessionConfig.getHeight() > 0) {
1060
+ layoutParams.width = (int) (sessionConfig.getHeight() * ratio);
1061
+ }
1062
+ } else {
1063
+ previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
1064
+ }
1065
+
1066
+ previewView.setLayoutParams(layoutParams);
1067
+
1068
+ if (listener != null) {
1069
+ listener.onCameraStarted();
1070
+ }
1071
+ }
1072
+
1073
+ public String getAspectRatio() {
1074
+ if (sessionConfig != null) {
1075
+ return sessionConfig.getAspectRatio();
1076
+ }
1077
+ return "fill";
1078
+ }
1079
+
1080
+ public void setAspectRatio(String aspectRatio) {
1081
+ if (sessionConfig != null) {
1082
+ sessionConfig = new CameraSessionConfiguration(
1083
+ sessionConfig.getDeviceId(),
1084
+ sessionConfig.getPosition(),
1085
+ sessionConfig.getX(),
1086
+ sessionConfig.getY(),
1087
+ sessionConfig.getWidth(),
1088
+ sessionConfig.getHeight(),
1089
+ sessionConfig.getPaddingBottom(),
1090
+ sessionConfig.getToBack(),
1091
+ sessionConfig.getStoreToFile(),
1092
+ sessionConfig.getEnableOpacity(),
1093
+ sessionConfig.getEnableZoom(),
1094
+ sessionConfig.getDisableExifHeaderStripping(),
1095
+ sessionConfig.getDisableAudio(),
1096
+ sessionConfig.getZoomFactor(),
1097
+ aspectRatio,
1098
+ sessionConfig.getGridMode()
1099
+ );
1100
+ updateLayoutParams();
1101
+ }
1102
+ }
990
1103
  }