@capgo/camera-preview 8.8.1 → 8.10.0
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 +63 -3
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +47 -2
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +176 -12
- package/dist/docs.json +112 -0
- package/dist/esm/definitions.d.ts +48 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +10 -1
- package/dist/esm/web.js +52 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +51 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +51 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +115 -2
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +78 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -427,6 +427,9 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
|
|
|
427
427
|
* [`getExposureCompensationRange()`](#getexposurecompensationrange)
|
|
428
428
|
* [`getExposureCompensation()`](#getexposurecompensation)
|
|
429
429
|
* [`setExposureCompensation(...)`](#setexposurecompensation)
|
|
430
|
+
* [`getWhiteBalanceModes()`](#getwhitebalancemodes)
|
|
431
|
+
* [`getWhiteBalanceMode()`](#getwhitebalancemode)
|
|
432
|
+
* [`setWhiteBalanceMode(...)`](#setwhitebalancemode)
|
|
430
433
|
* [`getSupportedVideoFrameRates()`](#getsupportedvideoframerates)
|
|
431
434
|
* [`getVideoFrameRate()`](#getvideoframerate)
|
|
432
435
|
* [`setVideoFrameRate(...)`](#setvideoframerate)
|
|
@@ -1379,6 +1382,52 @@ Sets the exposure compensation (EV bias). Value will be clamped to range.
|
|
|
1379
1382
|
--------------------
|
|
1380
1383
|
|
|
1381
1384
|
|
|
1385
|
+
### getWhiteBalanceModes()
|
|
1386
|
+
|
|
1387
|
+
```typescript
|
|
1388
|
+
getWhiteBalanceModes() => Promise<{ modes: WhiteBalanceMode[]; }>
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
Returns the white-balance modes supported by the active camera.
|
|
1392
|
+
Modes can include: 'AUTO', 'LOCK', 'CONTINUOUS'. `CUSTOM` is not listed
|
|
1393
|
+
until manual gains support is implemented.
|
|
1394
|
+
|
|
1395
|
+
**Returns:** <code>Promise<{ modes: WhiteBalanceMode[]; }></code>
|
|
1396
|
+
|
|
1397
|
+
--------------------
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
### getWhiteBalanceMode()
|
|
1401
|
+
|
|
1402
|
+
```typescript
|
|
1403
|
+
getWhiteBalanceMode() => Promise<{ mode: WhiteBalanceMode; }>
|
|
1404
|
+
```
|
|
1405
|
+
|
|
1406
|
+
Returns the current white-balance mode.
|
|
1407
|
+
|
|
1408
|
+
**Returns:** <code>Promise<{ mode: <a href="#whitebalancemode">WhiteBalanceMode</a>; }></code>
|
|
1409
|
+
|
|
1410
|
+
--------------------
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
### setWhiteBalanceMode(...)
|
|
1414
|
+
|
|
1415
|
+
```typescript
|
|
1416
|
+
setWhiteBalanceMode(options: { mode: WhiteBalanceMode; }) => Promise<void>
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
Sets the white-balance mode. `CONTINUOUS` keeps auto white balance running
|
|
1420
|
+
(recommended; avoids a warm/yellow cast), `LOCK` freezes the current gains,
|
|
1421
|
+
`AUTO` performs a one-time adjustment. `CUSTOM` is reserved and rejected
|
|
1422
|
+
until manual gains support is implemented.
|
|
1423
|
+
|
|
1424
|
+
| Param | Type |
|
|
1425
|
+
| ------------- | ------------------------------------------------------------------------ |
|
|
1426
|
+
| **`options`** | <code>{ mode: <a href="#whitebalancemode">WhiteBalanceMode</a>; }</code> |
|
|
1427
|
+
|
|
1428
|
+
--------------------
|
|
1429
|
+
|
|
1430
|
+
|
|
1382
1431
|
### getSupportedVideoFrameRates()
|
|
1383
1432
|
|
|
1384
1433
|
```typescript
|
|
@@ -1516,15 +1565,17 @@ Defines the options for capturing a picture.
|
|
|
1516
1565
|
| **`embedTimestamp`** | <code>boolean</code> | If true, the plugin will embed a timestamp in the top-right corner of the image. | <code>false</code> | 7.17.0 |
|
|
1517
1566
|
| **`embedLocation`** | <code>boolean</code> | If true, the plugin will embed the current location in the top-right corner of the image. Requires `withExifLocation` to be enabled. | <code>false</code> | 7.18.0 |
|
|
1518
1567
|
| **`photoQualityPrioritization`** | <code>'speed' \| 'balanced' \| 'quality'</code> | Sets the priority for photo quality vs. capture speed. - "speed": Prioritizes faster capture times, may reduce image quality. - "balanced": Aims for a balance between quality and speed. - "quality": Prioritizes image quality, may reduce capture speed. See https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/photoqualityprioritization for details. | <code>"speed"</code> | 7.21.0 |
|
|
1568
|
+
| **`mirrorFrontCamera`** | <code>boolean</code> | If true and the front camera is active, horizontally mirror the captured photo so it matches a selfie-style preview. Rear camera captures are never mirrored. Defaults to false to preserve the previous non-mirrored behavior. | <code>false</code> | 8.10.0 |
|
|
1519
1569
|
|
|
1520
1570
|
|
|
1521
1571
|
#### CameraSampleOptions
|
|
1522
1572
|
|
|
1523
1573
|
Defines the options for capturing a sample frame from the camera preview.
|
|
1524
1574
|
|
|
1525
|
-
| Prop
|
|
1526
|
-
|
|
|
1527
|
-
| **`quality`**
|
|
1575
|
+
| Prop | Type | Description | Default | Since |
|
|
1576
|
+
| ----------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------ |
|
|
1577
|
+
| **`quality`** | <code>number</code> | The quality of the captured sample, from 0 to 100. | <code>85</code> | |
|
|
1578
|
+
| **`mirrorFrontCamera`** | <code>boolean</code> | If true and the front camera is active, horizontally mirror the captured sample so it matches a selfie-style preview. Rear camera samples are never mirrored. Defaults to false to preserve the previous non-mirrored behavior. | <code>false</code> | 8.10.0 |
|
|
1528
1579
|
|
|
1529
1580
|
|
|
1530
1581
|
#### CameraPermissionStatus
|
|
@@ -1774,6 +1825,15 @@ Reusable exposure mode type for cross-platform support.
|
|
|
1774
1825
|
<code>'AUTO' | 'LOCK' | 'CONTINUOUS' | 'CUSTOM'</code>
|
|
1775
1826
|
|
|
1776
1827
|
|
|
1828
|
+
#### WhiteBalanceMode
|
|
1829
|
+
|
|
1830
|
+
Reusable white-balance mode type for cross-platform support.
|
|
1831
|
+
`CUSTOM` is reserved for a future manual white-balance gains API and is not
|
|
1832
|
+
returned by `getWhiteBalanceModes()` until implemented.
|
|
1833
|
+
|
|
1834
|
+
<code>'AUTO' | 'LOCK' | 'CONTINUOUS' | 'CUSTOM'</code>
|
|
1835
|
+
|
|
1836
|
+
|
|
1777
1837
|
### Enums
|
|
1778
1838
|
|
|
1779
1839
|
|
|
@@ -195,6 +195,49 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
@PluginMethod
|
|
199
|
+
public void getWhiteBalanceModes(PluginCall call) {
|
|
200
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
201
|
+
call.reject("Camera is not running");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
JSArray arr = new JSArray();
|
|
205
|
+
for (String m : cameraXView.getWhiteBalanceModes()) arr.put(m);
|
|
206
|
+
JSObject ret = new JSObject();
|
|
207
|
+
ret.put("modes", arr);
|
|
208
|
+
call.resolve(ret);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@PluginMethod
|
|
212
|
+
public void getWhiteBalanceMode(PluginCall call) {
|
|
213
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
214
|
+
call.reject("Camera is not running");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
JSObject ret = new JSObject();
|
|
218
|
+
ret.put("mode", cameraXView.getWhiteBalanceMode());
|
|
219
|
+
call.resolve(ret);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@PluginMethod
|
|
223
|
+
public void setWhiteBalanceMode(PluginCall call) {
|
|
224
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
225
|
+
call.reject("Camera is not running");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
String mode = call.getString("mode");
|
|
229
|
+
if (mode == null || mode.isEmpty()) {
|
|
230
|
+
call.reject("mode parameter is required");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
cameraXView.setWhiteBalanceMode(mode);
|
|
235
|
+
call.resolve();
|
|
236
|
+
} catch (Exception e) {
|
|
237
|
+
call.reject("Failed to set white balance mode: " + e.getMessage());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
198
241
|
@PluginMethod
|
|
199
242
|
public void getExposureModes(PluginCall call) {
|
|
200
243
|
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
@@ -484,8 +527,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
484
527
|
Integer height = call.getInt("height");
|
|
485
528
|
final boolean embedTimestamp = Boolean.TRUE.equals(call.getBoolean("embedTimestamp"));
|
|
486
529
|
final boolean embedLocation = Boolean.TRUE.equals(call.getBoolean("embedLocation"));
|
|
530
|
+
final boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera"));
|
|
487
531
|
|
|
488
|
-
cameraXView.capturePhoto(quality, saveToGallery, width, height, location, embedTimestamp, embedLocation);
|
|
532
|
+
cameraXView.capturePhoto(quality, saveToGallery, width, height, location, embedTimestamp, embedLocation, mirrorFrontCamera);
|
|
489
533
|
}
|
|
490
534
|
|
|
491
535
|
@PluginMethod
|
|
@@ -497,7 +541,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
497
541
|
bridge.saveCall(call);
|
|
498
542
|
sampleCallbackId = call.getCallbackId();
|
|
499
543
|
Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
|
|
500
|
-
|
|
544
|
+
final boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera"));
|
|
545
|
+
cameraXView.captureSample(quality, mirrorFrontCamera);
|
|
501
546
|
}
|
|
502
547
|
|
|
503
548
|
@PluginMethod
|
|
@@ -191,6 +191,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
191
191
|
private Runnable pendingFrameRateBindSuccess;
|
|
192
192
|
private java.util.function.Consumer<String> pendingFrameRateBindError;
|
|
193
193
|
private String currentExposureMode = "CONTINUOUS"; // Default behavior
|
|
194
|
+
private String currentWhiteBalanceMode = "CONTINUOUS"; // Default behavior
|
|
194
195
|
// Capture/stop coordination
|
|
195
196
|
private final Object captureLock = new Object();
|
|
196
197
|
private volatile boolean isCapturingPhoto = false;
|
|
@@ -1183,6 +1184,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1183
1184
|
}
|
|
1184
1185
|
|
|
1185
1186
|
resetExposureCompensationToDefault();
|
|
1187
|
+
reapplyCameraControlModes();
|
|
1186
1188
|
|
|
1187
1189
|
// Log details about the active camera
|
|
1188
1190
|
Log.d(TAG, "Use cases bound. Inspecting active camera and use cases.");
|
|
@@ -2024,7 +2026,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2024
2026
|
Integer height,
|
|
2025
2027
|
Location location,
|
|
2026
2028
|
final boolean embedTimestamp,
|
|
2027
|
-
final boolean embedLocation
|
|
2029
|
+
final boolean embedLocation,
|
|
2030
|
+
final boolean mirrorFrontCamera
|
|
2028
2031
|
) {
|
|
2029
2032
|
if (imageCapture == null) {
|
|
2030
2033
|
if (listener != null) {
|
|
@@ -2058,7 +2061,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2058
2061
|
", embedTimestamp: " +
|
|
2059
2062
|
embedTimestamp +
|
|
2060
2063
|
", embedLocation: " +
|
|
2061
|
-
embedLocation
|
|
2064
|
+
embedLocation +
|
|
2065
|
+
", mirrorFrontCamera: " +
|
|
2066
|
+
mirrorFrontCamera
|
|
2062
2067
|
);
|
|
2063
2068
|
|
|
2064
2069
|
boolean dispatched = false;
|
|
@@ -2114,6 +2119,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2114
2119
|
if (width != null || height != null) {
|
|
2115
2120
|
Bitmap bitmap = BitmapFactory.decodeByteArray(originalCaptureBytes, 0, originalCaptureBytes.length);
|
|
2116
2121
|
bitmap = applyExifOrientation(bitmap, exifInterface);
|
|
2122
|
+
bitmap = maybeMirrorFrontCameraBitmap(bitmap, mirrorFrontCamera);
|
|
2117
2123
|
Bitmap resizedBitmap = resizeBitmapToMaxDimensions(bitmap, width, height);
|
|
2118
2124
|
if (embedTimestamp || embedLocation) {
|
|
2119
2125
|
resizedBitmap = drawTimestampAndLocationOntoBitmap(
|
|
@@ -2142,6 +2148,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2142
2148
|
// No explicit size/ratio: crop to match current preview content
|
|
2143
2149
|
Bitmap originalBitmap = BitmapFactory.decodeByteArray(originalCaptureBytes, 0, originalCaptureBytes.length);
|
|
2144
2150
|
originalBitmap = applyExifOrientation(originalBitmap, exifInterface);
|
|
2151
|
+
originalBitmap = maybeMirrorFrontCameraBitmap(originalBitmap, mirrorFrontCamera);
|
|
2145
2152
|
Bitmap previewCropped = cropBitmapToMatchPreview(originalBitmap);
|
|
2146
2153
|
if (embedTimestamp || embedLocation) {
|
|
2147
2154
|
previewCropped = drawTimestampAndLocationOntoBitmap(
|
|
@@ -2399,6 +2406,29 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2399
2406
|
}
|
|
2400
2407
|
}
|
|
2401
2408
|
|
|
2409
|
+
private boolean shouldMirrorFrontCamera(boolean mirrorFrontCamera) {
|
|
2410
|
+
return mirrorFrontCamera && sessionConfig != null && "front".equals(sessionConfig.getPosition());
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
private Bitmap maybeMirrorFrontCameraBitmap(Bitmap bitmap, boolean mirrorFrontCamera) {
|
|
2414
|
+
if (bitmap == null || !shouldMirrorFrontCamera(mirrorFrontCamera)) {
|
|
2415
|
+
return bitmap;
|
|
2416
|
+
}
|
|
2417
|
+
return mirrorBitmapHorizontally(bitmap);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
private Bitmap mirrorBitmapHorizontally(Bitmap bitmap) {
|
|
2421
|
+
Matrix matrix = new Matrix();
|
|
2422
|
+
matrix.preScale(-1.0f, 1.0f);
|
|
2423
|
+
Bitmap mirrored = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
|
2424
|
+
if (mirrored != bitmap) {
|
|
2425
|
+
try {
|
|
2426
|
+
bitmap.recycle();
|
|
2427
|
+
} catch (Exception ignore) {}
|
|
2428
|
+
}
|
|
2429
|
+
return mirrored;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2402
2432
|
private Bitmap applyExifOrientation(Bitmap bitmap, ExifInterface exif) {
|
|
2403
2433
|
try {
|
|
2404
2434
|
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
|
@@ -2761,7 +2791,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2761
2791
|
// Note: We avoid temporary files for EXIF writes. When we transform pixels (resize/crop),
|
|
2762
2792
|
// we recompress JPEG in-memory and update EXIF info only in the returned JSON, not in the bytes.
|
|
2763
2793
|
|
|
2764
|
-
public void captureSample(int quality) {
|
|
2794
|
+
public void captureSample(int quality, final boolean mirrorFrontCamera) {
|
|
2765
2795
|
if (sampleImageCapture == null) {
|
|
2766
2796
|
if (listener != null) {
|
|
2767
2797
|
listener.onSampleTakenError("Camera not ready");
|
|
@@ -2773,7 +2803,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2773
2803
|
Log.d(TAG, "captureSample: Ignored because stop is pending");
|
|
2774
2804
|
return;
|
|
2775
2805
|
}
|
|
2776
|
-
Log.d(TAG, "captureSample: Starting sample capture with quality: " + quality);
|
|
2806
|
+
Log.d(TAG, "captureSample: Starting sample capture with quality: " + quality + ", mirrorFrontCamera: " + mirrorFrontCamera);
|
|
2777
2807
|
|
|
2778
2808
|
boolean dispatched = false;
|
|
2779
2809
|
try {
|
|
@@ -2795,6 +2825,20 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2795
2825
|
try {
|
|
2796
2826
|
// Convert ImageProxy to byte array
|
|
2797
2827
|
byte[] bytes = imageProxyToByteArray(image);
|
|
2828
|
+
if (shouldMirrorFrontCamera(mirrorFrontCamera)) {
|
|
2829
|
+
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
|
2830
|
+
if (bitmap != null) {
|
|
2831
|
+
Bitmap mirrored = mirrorBitmapHorizontally(bitmap);
|
|
2832
|
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
2833
|
+
mirrored.compress(Bitmap.CompressFormat.JPEG, quality, stream);
|
|
2834
|
+
bytes = stream.toByteArray();
|
|
2835
|
+
if (mirrored != bitmap) {
|
|
2836
|
+
try {
|
|
2837
|
+
bitmap.recycle();
|
|
2838
|
+
} catch (Exception ignore) {}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2798
2842
|
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
2799
2843
|
|
|
2800
2844
|
if (listener != null) {
|
|
@@ -3278,13 +3322,29 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3278
3322
|
|
|
3279
3323
|
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
3280
3324
|
public void setExposureMode(String mode) throws Exception {
|
|
3281
|
-
if (camera == null) {
|
|
3282
|
-
throw new Exception("Camera not initialized");
|
|
3283
|
-
}
|
|
3284
3325
|
if (mode == null) {
|
|
3285
3326
|
throw new Exception("mode is required");
|
|
3286
3327
|
}
|
|
3287
3328
|
String normalized = mode.toUpperCase(Locale.US);
|
|
3329
|
+
if (!"LOCK".equals(normalized) && !"CONTINUOUS".equals(normalized)) {
|
|
3330
|
+
throw new Exception("Unsupported exposure mode: " + mode);
|
|
3331
|
+
}
|
|
3332
|
+
final String modeToApply = normalized;
|
|
3333
|
+
mainExecutor.execute(() -> {
|
|
3334
|
+
try {
|
|
3335
|
+
applyExposureMode(modeToApply);
|
|
3336
|
+
currentExposureMode = modeToApply;
|
|
3337
|
+
} catch (Exception e) {
|
|
3338
|
+
Log.e(TAG, "setExposureMode: Failed to apply mode", e);
|
|
3339
|
+
}
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
3344
|
+
private void applyExposureMode(String normalized) throws Exception {
|
|
3345
|
+
if (camera == null) {
|
|
3346
|
+
throw new Exception("Camera not initialized");
|
|
3347
|
+
}
|
|
3288
3348
|
|
|
3289
3349
|
Camera2CameraControl c2 = Camera2CameraControl.from(camera.getCameraControl());
|
|
3290
3350
|
switch (normalized) {
|
|
@@ -3293,8 +3353,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3293
3353
|
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_LOCK, true)
|
|
3294
3354
|
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
|
|
3295
3355
|
.build();
|
|
3296
|
-
|
|
3297
|
-
currentExposureMode = "LOCK";
|
|
3356
|
+
c2.setCaptureRequestOptions(opts);
|
|
3298
3357
|
break;
|
|
3299
3358
|
}
|
|
3300
3359
|
case "CONTINUOUS": {
|
|
@@ -3302,12 +3361,117 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3302
3361
|
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_LOCK, false)
|
|
3303
3362
|
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
|
|
3304
3363
|
.build();
|
|
3305
|
-
|
|
3306
|
-
currentExposureMode = "CONTINUOUS";
|
|
3364
|
+
c2.setCaptureRequestOptions(opts);
|
|
3307
3365
|
break;
|
|
3308
3366
|
}
|
|
3309
3367
|
default:
|
|
3310
|
-
throw new Exception("Unsupported exposure mode: " +
|
|
3368
|
+
throw new Exception("Unsupported exposure mode: " + normalized);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// ===================== White Balance APIs =====================
|
|
3373
|
+
public java.util.List<String> getWhiteBalanceModes() {
|
|
3374
|
+
if (camera == null) {
|
|
3375
|
+
return Arrays.asList("LOCK", "CONTINUOUS");
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
java.util.List<String> modes = new java.util.ArrayList<>();
|
|
3379
|
+
try {
|
|
3380
|
+
Camera2CameraInfo c2Info = Camera2CameraInfo.from(camera.getCameraInfo());
|
|
3381
|
+
int[] available = c2Info.getCameraCharacteristic(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
|
|
3382
|
+
Boolean lockAvailable = c2Info.getCameraCharacteristic(CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE);
|
|
3383
|
+
|
|
3384
|
+
if (available != null) {
|
|
3385
|
+
for (int awbMode : available) {
|
|
3386
|
+
if (awbMode == CaptureRequest.CONTROL_AWB_MODE_AUTO && !modes.contains("CONTINUOUS")) {
|
|
3387
|
+
modes.add("CONTINUOUS");
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
if (Boolean.TRUE.equals(lockAvailable) && !modes.contains("LOCK")) {
|
|
3392
|
+
modes.add("LOCK");
|
|
3393
|
+
}
|
|
3394
|
+
} catch (Exception e) {
|
|
3395
|
+
Log.w(TAG, "getWhiteBalanceModes: Failed to query capabilities, using defaults", e);
|
|
3396
|
+
return Arrays.asList("LOCK", "CONTINUOUS");
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
if (modes.isEmpty()) {
|
|
3400
|
+
return Arrays.asList("LOCK", "CONTINUOUS");
|
|
3401
|
+
}
|
|
3402
|
+
return modes;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
public String getWhiteBalanceMode() {
|
|
3406
|
+
return currentWhiteBalanceMode;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
3410
|
+
public void setWhiteBalanceMode(String mode) throws Exception {
|
|
3411
|
+
if (mode == null) {
|
|
3412
|
+
throw new Exception("mode is required");
|
|
3413
|
+
}
|
|
3414
|
+
String normalized = mode.toUpperCase(Locale.US);
|
|
3415
|
+
if ("CUSTOM".equals(normalized)) {
|
|
3416
|
+
throw new Exception("CUSTOM white balance is not supported; manual gains are not yet exposed");
|
|
3417
|
+
}
|
|
3418
|
+
if (!"LOCK".equals(normalized) && !"AUTO".equals(normalized) && !"CONTINUOUS".equals(normalized)) {
|
|
3419
|
+
throw new Exception("Unsupported white balance mode: " + mode);
|
|
3420
|
+
}
|
|
3421
|
+
final String modeToApply = normalized;
|
|
3422
|
+
mainExecutor.execute(() -> {
|
|
3423
|
+
try {
|
|
3424
|
+
applyWhiteBalanceMode(modeToApply);
|
|
3425
|
+
currentWhiteBalanceMode = modeToApply;
|
|
3426
|
+
} catch (Exception e) {
|
|
3427
|
+
Log.e(TAG, "setWhiteBalanceMode: Failed to apply mode", e);
|
|
3428
|
+
}
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
3433
|
+
private void applyWhiteBalanceMode(String normalized) throws Exception {
|
|
3434
|
+
if (camera == null) {
|
|
3435
|
+
throw new Exception("Camera not initialized");
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
Camera2CameraControl c2 = Camera2CameraControl.from(camera.getCameraControl());
|
|
3439
|
+
switch (normalized) {
|
|
3440
|
+
case "LOCK": {
|
|
3441
|
+
CaptureRequestOptions opts = new CaptureRequestOptions.Builder()
|
|
3442
|
+
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_LOCK, true)
|
|
3443
|
+
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
|
|
3444
|
+
.build();
|
|
3445
|
+
c2.setCaptureRequestOptions(opts);
|
|
3446
|
+
break;
|
|
3447
|
+
}
|
|
3448
|
+
case "AUTO":
|
|
3449
|
+
case "CONTINUOUS": {
|
|
3450
|
+
CaptureRequestOptions opts = new CaptureRequestOptions.Builder()
|
|
3451
|
+
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_LOCK, false)
|
|
3452
|
+
.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
|
|
3453
|
+
.build();
|
|
3454
|
+
c2.setCaptureRequestOptions(opts);
|
|
3455
|
+
break;
|
|
3456
|
+
}
|
|
3457
|
+
default:
|
|
3458
|
+
throw new Exception("Unsupported white balance mode: " + normalized);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
private void reapplyCameraControlModes() {
|
|
3463
|
+
if (camera == null) {
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
try {
|
|
3467
|
+
applyExposureMode(currentExposureMode);
|
|
3468
|
+
} catch (Exception e) {
|
|
3469
|
+
Log.w(TAG, "reapplyCameraControlModes: Failed to reapply exposure mode", e);
|
|
3470
|
+
}
|
|
3471
|
+
try {
|
|
3472
|
+
applyWhiteBalanceMode(currentWhiteBalanceMode);
|
|
3473
|
+
} catch (Exception e) {
|
|
3474
|
+
Log.w(TAG, "reapplyCameraControlModes: Failed to reapply white balance mode", e);
|
|
3311
3475
|
}
|
|
3312
3476
|
}
|
|
3313
3477
|
|
package/dist/docs.json
CHANGED
|
@@ -1495,6 +1495,63 @@
|
|
|
1495
1495
|
"complexTypes": [],
|
|
1496
1496
|
"slug": "setexposurecompensation"
|
|
1497
1497
|
},
|
|
1498
|
+
{
|
|
1499
|
+
"name": "getWhiteBalanceModes",
|
|
1500
|
+
"signature": "() => Promise<{ modes: WhiteBalanceMode[]; }>",
|
|
1501
|
+
"parameters": [],
|
|
1502
|
+
"returns": "Promise<{ modes: WhiteBalanceMode[]; }>",
|
|
1503
|
+
"tags": [
|
|
1504
|
+
{
|
|
1505
|
+
"name": "platform",
|
|
1506
|
+
"text": "android, ios"
|
|
1507
|
+
}
|
|
1508
|
+
],
|
|
1509
|
+
"docs": "Returns the white-balance modes supported by the active camera.\nModes can include: 'AUTO', 'LOCK', 'CONTINUOUS'. `CUSTOM` is not listed\nuntil manual gains support is implemented.",
|
|
1510
|
+
"complexTypes": [
|
|
1511
|
+
"WhiteBalanceMode"
|
|
1512
|
+
],
|
|
1513
|
+
"slug": "getwhitebalancemodes"
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
"name": "getWhiteBalanceMode",
|
|
1517
|
+
"signature": "() => Promise<{ mode: WhiteBalanceMode; }>",
|
|
1518
|
+
"parameters": [],
|
|
1519
|
+
"returns": "Promise<{ mode: WhiteBalanceMode; }>",
|
|
1520
|
+
"tags": [
|
|
1521
|
+
{
|
|
1522
|
+
"name": "platform",
|
|
1523
|
+
"text": "android, ios"
|
|
1524
|
+
}
|
|
1525
|
+
],
|
|
1526
|
+
"docs": "Returns the current white-balance mode.",
|
|
1527
|
+
"complexTypes": [
|
|
1528
|
+
"WhiteBalanceMode"
|
|
1529
|
+
],
|
|
1530
|
+
"slug": "getwhitebalancemode"
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
"name": "setWhiteBalanceMode",
|
|
1534
|
+
"signature": "(options: { mode: WhiteBalanceMode; }) => Promise<void>",
|
|
1535
|
+
"parameters": [
|
|
1536
|
+
{
|
|
1537
|
+
"name": "options",
|
|
1538
|
+
"docs": "",
|
|
1539
|
+
"type": "{ mode: WhiteBalanceMode; }"
|
|
1540
|
+
}
|
|
1541
|
+
],
|
|
1542
|
+
"returns": "Promise<void>",
|
|
1543
|
+
"tags": [
|
|
1544
|
+
{
|
|
1545
|
+
"name": "platform",
|
|
1546
|
+
"text": "android, ios"
|
|
1547
|
+
}
|
|
1548
|
+
],
|
|
1549
|
+
"docs": "Sets the white-balance mode. `CONTINUOUS` keeps auto white balance running\n(recommended; avoids a warm/yellow cast), `LOCK` freezes the current gains,\n`AUTO` performs a one-time adjustment. `CUSTOM` is reserved and rejected\nuntil manual gains support is implemented.",
|
|
1550
|
+
"complexTypes": [
|
|
1551
|
+
"WhiteBalanceMode"
|
|
1552
|
+
],
|
|
1553
|
+
"slug": "setwhitebalancemode"
|
|
1554
|
+
},
|
|
1498
1555
|
{
|
|
1499
1556
|
"name": "getSupportedVideoFrameRates",
|
|
1500
1557
|
"signature": "() => Promise<{ frameRates: number[]; }>",
|
|
@@ -2286,6 +2343,22 @@
|
|
|
2286
2343
|
"docs": "Sets the priority for photo quality vs. capture speed.\n- \"speed\": Prioritizes faster capture times, may reduce image quality.\n- \"balanced\": Aims for a balance between quality and speed.\n- \"quality\": Prioritizes image quality, may reduce capture speed.\nSee https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/photoqualityprioritization for details.",
|
|
2287
2344
|
"complexTypes": [],
|
|
2288
2345
|
"type": "'speed' | 'balanced' | 'quality' | undefined"
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
"name": "mirrorFrontCamera",
|
|
2349
|
+
"tags": [
|
|
2350
|
+
{
|
|
2351
|
+
"text": "false",
|
|
2352
|
+
"name": "default"
|
|
2353
|
+
},
|
|
2354
|
+
{
|
|
2355
|
+
"text": "8.10.0",
|
|
2356
|
+
"name": "since"
|
|
2357
|
+
}
|
|
2358
|
+
],
|
|
2359
|
+
"docs": "If true and the front camera is active, horizontally mirror the captured photo\nso it matches a selfie-style preview. Rear camera captures are never mirrored.\nDefaults to false to preserve the previous non-mirrored behavior.",
|
|
2360
|
+
"complexTypes": [],
|
|
2361
|
+
"type": "boolean | undefined"
|
|
2289
2362
|
}
|
|
2290
2363
|
]
|
|
2291
2364
|
},
|
|
@@ -2307,6 +2380,22 @@
|
|
|
2307
2380
|
"docs": "The quality of the captured sample, from 0 to 100.",
|
|
2308
2381
|
"complexTypes": [],
|
|
2309
2382
|
"type": "number | undefined"
|
|
2383
|
+
},
|
|
2384
|
+
{
|
|
2385
|
+
"name": "mirrorFrontCamera",
|
|
2386
|
+
"tags": [
|
|
2387
|
+
{
|
|
2388
|
+
"text": "false",
|
|
2389
|
+
"name": "default"
|
|
2390
|
+
},
|
|
2391
|
+
{
|
|
2392
|
+
"text": "8.10.0",
|
|
2393
|
+
"name": "since"
|
|
2394
|
+
}
|
|
2395
|
+
],
|
|
2396
|
+
"docs": "If true and the front camera is active, horizontally mirror the captured sample\nso it matches a selfie-style preview. Rear camera samples are never mirrored.\nDefaults to false to preserve the previous non-mirrored behavior.",
|
|
2397
|
+
"complexTypes": [],
|
|
2398
|
+
"type": "boolean | undefined"
|
|
2310
2399
|
}
|
|
2311
2400
|
]
|
|
2312
2401
|
},
|
|
@@ -3210,6 +3299,29 @@
|
|
|
3210
3299
|
"complexTypes": []
|
|
3211
3300
|
}
|
|
3212
3301
|
]
|
|
3302
|
+
},
|
|
3303
|
+
{
|
|
3304
|
+
"name": "WhiteBalanceMode",
|
|
3305
|
+
"slug": "whitebalancemode",
|
|
3306
|
+
"docs": "Reusable white-balance mode type for cross-platform support.\n`CUSTOM` is reserved for a future manual white-balance gains API and is not\nreturned by `getWhiteBalanceModes()` until implemented.",
|
|
3307
|
+
"types": [
|
|
3308
|
+
{
|
|
3309
|
+
"text": "'AUTO'",
|
|
3310
|
+
"complexTypes": []
|
|
3311
|
+
},
|
|
3312
|
+
{
|
|
3313
|
+
"text": "'LOCK'",
|
|
3314
|
+
"complexTypes": []
|
|
3315
|
+
},
|
|
3316
|
+
{
|
|
3317
|
+
"text": "'CONTINUOUS'",
|
|
3318
|
+
"complexTypes": []
|
|
3319
|
+
},
|
|
3320
|
+
{
|
|
3321
|
+
"text": "'CUSTOM'",
|
|
3322
|
+
"complexTypes": []
|
|
3323
|
+
}
|
|
3324
|
+
]
|
|
3213
3325
|
}
|
|
3214
3326
|
],
|
|
3215
3327
|
"pluginConfigs": []
|
|
@@ -411,6 +411,14 @@ export interface CameraPreviewPictureOptions {
|
|
|
411
411
|
* @default "speed"
|
|
412
412
|
*/
|
|
413
413
|
photoQualityPrioritization?: 'speed' | 'balanced' | 'quality';
|
|
414
|
+
/**
|
|
415
|
+
* If true and the front camera is active, horizontally mirror the captured photo
|
|
416
|
+
* so it matches a selfie-style preview. Rear camera captures are never mirrored.
|
|
417
|
+
* Defaults to false to preserve the previous non-mirrored behavior.
|
|
418
|
+
* @default false
|
|
419
|
+
* @since 8.10.0
|
|
420
|
+
*/
|
|
421
|
+
mirrorFrontCamera?: boolean;
|
|
414
422
|
}
|
|
415
423
|
/** Represents EXIF data extracted from an image. */
|
|
416
424
|
export interface ExifData {
|
|
@@ -440,6 +448,14 @@ export interface CameraSampleOptions {
|
|
|
440
448
|
* @default 85
|
|
441
449
|
*/
|
|
442
450
|
quality?: number;
|
|
451
|
+
/**
|
|
452
|
+
* If true and the front camera is active, horizontally mirror the captured sample
|
|
453
|
+
* so it matches a selfie-style preview. Rear camera samples are never mirrored.
|
|
454
|
+
* Defaults to false to preserve the previous non-mirrored behavior.
|
|
455
|
+
* @default false
|
|
456
|
+
* @since 8.10.0
|
|
457
|
+
*/
|
|
458
|
+
mirrorFrontCamera?: boolean;
|
|
443
459
|
}
|
|
444
460
|
/**
|
|
445
461
|
* The available flash modes for the camera.
|
|
@@ -448,6 +464,12 @@ export interface CameraSampleOptions {
|
|
|
448
464
|
export type CameraPreviewFlashMode = 'off' | 'on' | 'auto' | 'torch';
|
|
449
465
|
/** Reusable exposure mode type for cross-platform support. */
|
|
450
466
|
export type ExposureMode = 'AUTO' | 'LOCK' | 'CONTINUOUS' | 'CUSTOM';
|
|
467
|
+
/**
|
|
468
|
+
* Reusable white-balance mode type for cross-platform support.
|
|
469
|
+
* `CUSTOM` is reserved for a future manual white-balance gains API and is not
|
|
470
|
+
* returned by `getWhiteBalanceModes()` until implemented.
|
|
471
|
+
*/
|
|
472
|
+
export type WhiteBalanceMode = 'AUTO' | 'LOCK' | 'CONTINUOUS' | 'CUSTOM';
|
|
451
473
|
/**
|
|
452
474
|
* Defines the options for setting the camera preview's opacity.
|
|
453
475
|
*/
|
|
@@ -1065,6 +1087,32 @@ export interface CameraPreviewPlugin {
|
|
|
1065
1087
|
setExposureCompensation(options: {
|
|
1066
1088
|
value: number;
|
|
1067
1089
|
}): Promise<void>;
|
|
1090
|
+
/**
|
|
1091
|
+
* Returns the white-balance modes supported by the active camera.
|
|
1092
|
+
* Modes can include: 'AUTO', 'LOCK', 'CONTINUOUS'. `CUSTOM` is not listed
|
|
1093
|
+
* until manual gains support is implemented.
|
|
1094
|
+
* @platform android, ios
|
|
1095
|
+
*/
|
|
1096
|
+
getWhiteBalanceModes(): Promise<{
|
|
1097
|
+
modes: WhiteBalanceMode[];
|
|
1098
|
+
}>;
|
|
1099
|
+
/**
|
|
1100
|
+
* Returns the current white-balance mode.
|
|
1101
|
+
* @platform android, ios
|
|
1102
|
+
*/
|
|
1103
|
+
getWhiteBalanceMode(): Promise<{
|
|
1104
|
+
mode: WhiteBalanceMode;
|
|
1105
|
+
}>;
|
|
1106
|
+
/**
|
|
1107
|
+
* Sets the white-balance mode. `CONTINUOUS` keeps auto white balance running
|
|
1108
|
+
* (recommended; avoids a warm/yellow cast), `LOCK` freezes the current gains,
|
|
1109
|
+
* `AUTO` performs a one-time adjustment. `CUSTOM` is reserved and rejected
|
|
1110
|
+
* until manual gains support is implemented.
|
|
1111
|
+
* @platform android, ios
|
|
1112
|
+
*/
|
|
1113
|
+
setWhiteBalanceMode(options: {
|
|
1114
|
+
mode: WhiteBalanceMode;
|
|
1115
|
+
}): Promise<void>;
|
|
1068
1116
|
/**
|
|
1069
1117
|
* Lists the video frame rates supported by the active camera for the current format.
|
|
1070
1118
|
* Supported values depend on the selected camera, lens, and video quality.
|