@capgo/camera-preview 7.15.0 → 7.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/android/build.gradle
CHANGED
|
@@ -51,9 +51,11 @@ dependencies {
|
|
|
51
51
|
implementation 'androidx.exifinterface:exifinterface:1.4.1'
|
|
52
52
|
implementation 'com.google.android.gms:play-services-location:21.3.0'
|
|
53
53
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
|
|
54
|
+
// In-memory EXIF writing (no temp files)
|
|
55
|
+
implementation 'org.apache.commons:commons-imaging:1.0.0-alpha6'
|
|
54
56
|
|
|
55
57
|
// CameraX dependencies
|
|
56
|
-
def camerax_version = "1.5.0
|
|
58
|
+
def camerax_version = "1.5.0"
|
|
57
59
|
implementation "androidx.camera:camera-core:${camerax_version}"
|
|
58
60
|
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
|
59
61
|
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
|
|
@@ -64,4 +66,3 @@ dependencies {
|
|
|
64
66
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
65
67
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
66
68
|
}
|
|
67
|
-
|
|
@@ -359,47 +359,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
359
359
|
fos.flush();
|
|
360
360
|
fos.close();
|
|
361
361
|
|
|
362
|
-
//
|
|
363
|
-
if ("image/jpeg".equals(mimeType) && sourceExif != null) {
|
|
364
|
-
try {
|
|
365
|
-
ExifInterface newExif = new ExifInterface(photo.getAbsolutePath());
|
|
366
|
-
for (String[] tag : EXIF_TAGS) {
|
|
367
|
-
String value = sourceExif.getAttribute(tag[0]);
|
|
368
|
-
if (value != null) newExif.setAttribute(tag[0], value);
|
|
369
|
-
}
|
|
370
|
-
// Normalize orientation for recompressed pixels
|
|
371
|
-
newExif.setAttribute(
|
|
372
|
-
ExifInterface.TAG_ORIENTATION,
|
|
373
|
-
Integer.toString(ExifInterface.ORIENTATION_NORMAL)
|
|
374
|
-
);
|
|
375
|
-
if (
|
|
376
|
-
finalWidth != null &&
|
|
377
|
-
finalHeight != null &&
|
|
378
|
-
finalWidth > 0 &&
|
|
379
|
-
finalHeight > 0
|
|
380
|
-
) {
|
|
381
|
-
newExif.setAttribute(
|
|
382
|
-
ExifInterface.TAG_PIXEL_X_DIMENSION,
|
|
383
|
-
String.valueOf(finalWidth)
|
|
384
|
-
);
|
|
385
|
-
newExif.setAttribute(
|
|
386
|
-
ExifInterface.TAG_PIXEL_Y_DIMENSION,
|
|
387
|
-
String.valueOf(finalHeight)
|
|
388
|
-
);
|
|
389
|
-
newExif.setAttribute(
|
|
390
|
-
ExifInterface.TAG_IMAGE_WIDTH,
|
|
391
|
-
String.valueOf(finalWidth)
|
|
392
|
-
);
|
|
393
|
-
newExif.setAttribute(
|
|
394
|
-
ExifInterface.TAG_IMAGE_LENGTH,
|
|
395
|
-
String.valueOf(finalHeight)
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
newExif.saveAttributes();
|
|
399
|
-
} catch (Exception ex) {
|
|
400
|
-
Log.w(TAG, "Failed to write EXIF to saved gallery image", ex);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
362
|
+
// No EXIF rewrite here; bytes already contain EXIF when needed
|
|
403
363
|
|
|
404
364
|
// Notify the gallery of the new image
|
|
405
365
|
MediaScannerConnection.scanFile(
|
|
@@ -1269,21 +1229,23 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1269
1229
|
@NonNull ImageCapture.OutputFileResults output
|
|
1270
1230
|
) {
|
|
1271
1231
|
try {
|
|
1272
|
-
byte[]
|
|
1232
|
+
byte[] originalCaptureBytes = imageStream.toByteArray();
|
|
1233
|
+
byte[] bytes = originalCaptureBytes; // will be replaced if we transform
|
|
1273
1234
|
int finalWidthOut = -1;
|
|
1274
1235
|
int finalHeightOut = -1;
|
|
1236
|
+
boolean transformedPixels = false;
|
|
1275
1237
|
|
|
1276
1238
|
ExifInterface exifInterface = new ExifInterface(
|
|
1277
|
-
new ByteArrayInputStream(
|
|
1239
|
+
new ByteArrayInputStream(originalCaptureBytes)
|
|
1278
1240
|
);
|
|
1279
1241
|
// Build EXIF JSON from captured bytes (location applied by metadata if provided)
|
|
1280
1242
|
JSONObject exifData = getExifData(exifInterface);
|
|
1281
1243
|
|
|
1282
1244
|
if (width != null || height != null) {
|
|
1283
1245
|
Bitmap bitmap = BitmapFactory.decodeByteArray(
|
|
1284
|
-
|
|
1246
|
+
originalCaptureBytes,
|
|
1285
1247
|
0,
|
|
1286
|
-
|
|
1248
|
+
originalCaptureBytes.length
|
|
1287
1249
|
);
|
|
1288
1250
|
bitmap = applyExifOrientation(bitmap, exifInterface);
|
|
1289
1251
|
Bitmap resizedBitmap = resizeBitmapToMaxDimensions(
|
|
@@ -1298,6 +1260,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1298
1260
|
stream
|
|
1299
1261
|
);
|
|
1300
1262
|
bytes = stream.toByteArray();
|
|
1263
|
+
transformedPixels = true;
|
|
1301
1264
|
|
|
1302
1265
|
// Update EXIF JSON to reflect new dimensions; no in-place EXIF write to bytes
|
|
1303
1266
|
try {
|
|
@@ -1315,9 +1278,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1315
1278
|
} else {
|
|
1316
1279
|
// No explicit size/ratio: crop to match current preview content
|
|
1317
1280
|
Bitmap originalBitmap = BitmapFactory.decodeByteArray(
|
|
1318
|
-
|
|
1281
|
+
originalCaptureBytes,
|
|
1319
1282
|
0,
|
|
1320
|
-
|
|
1283
|
+
originalCaptureBytes.length
|
|
1321
1284
|
);
|
|
1322
1285
|
originalBitmap = applyExifOrientation(
|
|
1323
1286
|
originalBitmap,
|
|
@@ -1331,6 +1294,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1331
1294
|
stream
|
|
1332
1295
|
);
|
|
1333
1296
|
bytes = stream.toByteArray();
|
|
1297
|
+
transformedPixels = true;
|
|
1334
1298
|
// Update EXIF JSON to reflect cropped dimensions; no in-place EXIF write to bytes
|
|
1335
1299
|
try {
|
|
1336
1300
|
exifData.put("PixelXDimension", previewCropped.getWidth());
|
|
@@ -1346,6 +1310,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1346
1310
|
finalHeightOut = previewCropped.getHeight();
|
|
1347
1311
|
}
|
|
1348
1312
|
|
|
1313
|
+
// After any transform, inject EXIF back into the in-memory JPEG bytes (no temp file)
|
|
1314
|
+
if (transformedPixels) {
|
|
1315
|
+
Integer fW = (finalWidthOut > 0) ? finalWidthOut : null;
|
|
1316
|
+
Integer fH = (finalHeightOut > 0) ? finalHeightOut : null;
|
|
1317
|
+
bytes = injectExifInMemory(
|
|
1318
|
+
bytes,
|
|
1319
|
+
originalCaptureBytes,
|
|
1320
|
+
fW,
|
|
1321
|
+
fH,
|
|
1322
|
+
/*normalizeOrientation*/true
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1349
1326
|
// Save to gallery asynchronously if requested, copy EXIF to file
|
|
1350
1327
|
if (saveToGallery) {
|
|
1351
1328
|
final byte[] finalBytes = bytes;
|
|
@@ -1375,41 +1352,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1375
1352
|
outFos.write(bytes);
|
|
1376
1353
|
outFos.close();
|
|
1377
1354
|
|
|
1378
|
-
//
|
|
1379
|
-
try {
|
|
1380
|
-
ExifInterface newExif = new ExifInterface(
|
|
1381
|
-
outFile.getAbsolutePath()
|
|
1382
|
-
);
|
|
1383
|
-
for (String[] tag : EXIF_TAGS) {
|
|
1384
|
-
String value = exifInterface.getAttribute(tag[0]);
|
|
1385
|
-
if (value != null) newExif.setAttribute(tag[0], value);
|
|
1386
|
-
}
|
|
1387
|
-
newExif.setAttribute(
|
|
1388
|
-
ExifInterface.TAG_ORIENTATION,
|
|
1389
|
-
Integer.toString(ExifInterface.ORIENTATION_NORMAL)
|
|
1390
|
-
);
|
|
1391
|
-
if (finalWidthOut > 0 && finalHeightOut > 0) {
|
|
1392
|
-
newExif.setAttribute(
|
|
1393
|
-
ExifInterface.TAG_PIXEL_X_DIMENSION,
|
|
1394
|
-
String.valueOf(finalWidthOut)
|
|
1395
|
-
);
|
|
1396
|
-
newExif.setAttribute(
|
|
1397
|
-
ExifInterface.TAG_PIXEL_Y_DIMENSION,
|
|
1398
|
-
String.valueOf(finalHeightOut)
|
|
1399
|
-
);
|
|
1400
|
-
newExif.setAttribute(
|
|
1401
|
-
ExifInterface.TAG_IMAGE_WIDTH,
|
|
1402
|
-
String.valueOf(finalWidthOut)
|
|
1403
|
-
);
|
|
1404
|
-
newExif.setAttribute(
|
|
1405
|
-
ExifInterface.TAG_IMAGE_LENGTH,
|
|
1406
|
-
String.valueOf(finalHeightOut)
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
newExif.saveAttributes();
|
|
1410
|
-
} catch (Exception ex) {
|
|
1411
|
-
Log.w(TAG, "Failed to embed EXIF into output file", ex);
|
|
1412
|
-
}
|
|
1355
|
+
// No EXIF rewrite here; bytes already contain EXIF when needed
|
|
1413
1356
|
|
|
1414
1357
|
// Return a file path; apps can convert via Capacitor.convertFileSrc on JS side
|
|
1415
1358
|
resultValue = outFile.getAbsolutePath();
|
|
@@ -1546,6 +1489,69 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1546
1489
|
return exifData;
|
|
1547
1490
|
}
|
|
1548
1491
|
|
|
1492
|
+
// Inject EXIF into a JPEG byte[] fully in-memory using Apache Commons Imaging (no temp files)
|
|
1493
|
+
// Copies EXIF from sourceJpeg (original capture) and updates orientation/dimensions if provided.
|
|
1494
|
+
private byte[] injectExifInMemory(
|
|
1495
|
+
byte[] targetJpeg,
|
|
1496
|
+
byte[] sourceJpegWithExif,
|
|
1497
|
+
Integer finalWidth,
|
|
1498
|
+
Integer finalHeight,
|
|
1499
|
+
boolean normalizeOrientation
|
|
1500
|
+
) {
|
|
1501
|
+
try {
|
|
1502
|
+
// Quick signature check for JPEG (FF D8 FF)
|
|
1503
|
+
if (
|
|
1504
|
+
targetJpeg == null ||
|
|
1505
|
+
targetJpeg.length < 3 ||
|
|
1506
|
+
(targetJpeg[0] & 0xFF) != 0xFF ||
|
|
1507
|
+
(targetJpeg[1] & 0xFF) != 0xD8 ||
|
|
1508
|
+
(targetJpeg[2] & 0xFF) != 0xFF
|
|
1509
|
+
) {
|
|
1510
|
+
return targetJpeg; // Not a JPEG; nothing to do
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Use Commons Imaging to read EXIF from the original capture bytes
|
|
1514
|
+
org.apache.commons.imaging.formats.jpeg.JpegImageMetadata jpegMetadata =
|
|
1515
|
+
(org.apache.commons.imaging.formats.jpeg.JpegImageMetadata) org.apache.commons.imaging.Imaging.getMetadata(
|
|
1516
|
+
sourceJpegWithExif
|
|
1517
|
+
);
|
|
1518
|
+
org.apache.commons.imaging.formats.tiff.TiffImageMetadata exif =
|
|
1519
|
+
jpegMetadata != null ? jpegMetadata.getExif() : null;
|
|
1520
|
+
|
|
1521
|
+
org.apache.commons.imaging.formats.tiff.write.TiffOutputSet outputSet =
|
|
1522
|
+
exif != null
|
|
1523
|
+
? exif.getOutputSet()
|
|
1524
|
+
: new org.apache.commons.imaging.formats.tiff.write.TiffOutputSet();
|
|
1525
|
+
|
|
1526
|
+
// Update orientation if requested (normalize to 1)
|
|
1527
|
+
if (normalizeOrientation) {
|
|
1528
|
+
org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory rootDir =
|
|
1529
|
+
outputSet.getOrCreateRootDirectory();
|
|
1530
|
+
rootDir.removeField(
|
|
1531
|
+
org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_ORIENTATION
|
|
1532
|
+
);
|
|
1533
|
+
rootDir.add(
|
|
1534
|
+
org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_ORIENTATION,
|
|
1535
|
+
(short) 1
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Optionally update dimensions here. Skipped to maximize compatibility with Commons Imaging 1.0-alpha3.
|
|
1540
|
+
|
|
1541
|
+
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
|
|
1542
|
+
new org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter()
|
|
1543
|
+
.updateExifMetadataLossless(
|
|
1544
|
+
new java.io.ByteArrayInputStream(targetJpeg),
|
|
1545
|
+
out,
|
|
1546
|
+
outputSet
|
|
1547
|
+
);
|
|
1548
|
+
return out.toByteArray();
|
|
1549
|
+
} catch (Throwable t) {
|
|
1550
|
+
Log.w(TAG, "injectExifInMemory: Failed to write EXIF in memory", t);
|
|
1551
|
+
return targetJpeg; // Fallback: return original bytes
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1549
1555
|
private static final String[][] EXIF_TAGS = new String[][] {
|
|
1550
1556
|
{ ExifInterface.TAG_APERTURE_VALUE, "ApertureValue" },
|
|
1551
1557
|
{ ExifInterface.TAG_ARTIST, "Artist" },
|