capacitor-plugin-camera-forked 3.0.99 → 3.0.101
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.
|
@@ -339,6 +339,9 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
339
339
|
|
|
340
340
|
camera.getCameraControl().startFocusAndMetering(initialFocus);
|
|
341
341
|
|
|
342
|
+
// Enable continuous auto-focus by starting a background focus monitoring
|
|
343
|
+
startContinuousAutoFocus();
|
|
344
|
+
|
|
342
345
|
Log.d("Camera", "Initialized responsive auto-focus with continuous monitoring");
|
|
343
346
|
} catch (Exception e) {
|
|
344
347
|
Log.e("Camera", "Failed to initialize responsive auto-focus: " + e.getMessage());
|
|
@@ -645,6 +648,15 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
645
648
|
response.put("autoFocusSuccessful", result.isFocusSuccessful());
|
|
646
649
|
response.put("x", x);
|
|
647
650
|
response.put("y", y);
|
|
651
|
+
|
|
652
|
+
// If focus failed, try a backup focus attempt to reduce need for multiple taps
|
|
653
|
+
if (!result.isFocusSuccessful()) {
|
|
654
|
+
Log.d("Camera", "Initial focus failed, attempting backup focus");
|
|
655
|
+
performBackupFocus(previewX, previewY);
|
|
656
|
+
} else {
|
|
657
|
+
// If manual focus was successful, maintain it with a follow-up action
|
|
658
|
+
maintainFocusAtPoint(previewX, previewY);
|
|
659
|
+
}
|
|
648
660
|
|
|
649
661
|
call.resolve(response);
|
|
650
662
|
} catch (Exception e) {
|
|
@@ -834,7 +846,9 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
834
846
|
.build();
|
|
835
847
|
|
|
836
848
|
camera.getCameraControl().startFocusAndMetering(restartAction);
|
|
837
|
-
|
|
849
|
+
|
|
850
|
+
// Restart the continuous auto-focus monitoring
|
|
851
|
+
startContinuousAutoFocus();
|
|
838
852
|
}
|
|
839
853
|
|
|
840
854
|
JSObject result = new JSObject();
|
|
@@ -1404,4 +1418,154 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
1404
1418
|
return count > 0 ? variance / count : 0.0;
|
|
1405
1419
|
}
|
|
1406
1420
|
|
|
1421
|
+
/**
|
|
1422
|
+
* Start continuous auto-focus monitoring for better focus stability
|
|
1423
|
+
*/
|
|
1424
|
+
private void startContinuousAutoFocus() {
|
|
1425
|
+
if (camera != null && previewView != null) {
|
|
1426
|
+
// Use a separate executor for continuous focus to avoid blocking
|
|
1427
|
+
ExecutorService focusExecutor = Executors.newSingleThreadExecutor();
|
|
1428
|
+
|
|
1429
|
+
focusExecutor.execute(new Runnable() {
|
|
1430
|
+
@Override
|
|
1431
|
+
public void run() {
|
|
1432
|
+
try {
|
|
1433
|
+
while (camera != null && camera.getCameraInfo().getCameraState().getValue().getType() == CameraState.Type.OPEN) {
|
|
1434
|
+
Thread.sleep(800); // Check every 800ms for faster transitions
|
|
1435
|
+
|
|
1436
|
+
getActivity().runOnUiThread(new Runnable() {
|
|
1437
|
+
@Override
|
|
1438
|
+
public void run() {
|
|
1439
|
+
try {
|
|
1440
|
+
// Trigger auto-focus at center to maintain continuous focus
|
|
1441
|
+
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
1442
|
+
float centerX = previewView.getWidth() / 2.0f;
|
|
1443
|
+
float centerY = previewView.getHeight() / 2.0f;
|
|
1444
|
+
MeteringPoint centerPoint = factory.createPoint(centerX, centerY);
|
|
1445
|
+
|
|
1446
|
+
FocusMeteringAction continuousAction = new FocusMeteringAction.Builder(centerPoint)
|
|
1447
|
+
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second for responsive transitions
|
|
1448
|
+
.build();
|
|
1449
|
+
|
|
1450
|
+
camera.getCameraControl().startFocusAndMetering(continuousAction);
|
|
1451
|
+
} catch (Exception e) {
|
|
1452
|
+
Log.d("Camera", "Continuous focus update failed: " + e.getMessage());
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
} catch (InterruptedException e) {
|
|
1458
|
+
Log.d("Camera", "Continuous auto-focus stopped");
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* Perform backup focus attempt if initial focus fails
|
|
1467
|
+
* This reduces the need for users to tap multiple times
|
|
1468
|
+
*/
|
|
1469
|
+
private void performBackupFocus(float previewX, float previewY) {
|
|
1470
|
+
if (camera == null || previewView == null) return;
|
|
1471
|
+
|
|
1472
|
+
// Wait a moment for the camera to settle
|
|
1473
|
+
ExecutorService backupFocusExecutor = Executors.newSingleThreadExecutor();
|
|
1474
|
+
backupFocusExecutor.execute(new Runnable() {
|
|
1475
|
+
@Override
|
|
1476
|
+
public void run() {
|
|
1477
|
+
try {
|
|
1478
|
+
Thread.sleep(200); // Wait 200ms for faster backup focus
|
|
1479
|
+
|
|
1480
|
+
getActivity().runOnUiThread(new Runnable() {
|
|
1481
|
+
@Override
|
|
1482
|
+
public void run() {
|
|
1483
|
+
try {
|
|
1484
|
+
if (camera != null && previewView != null) {
|
|
1485
|
+
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
1486
|
+
MeteringPoint backupPoint = factory.createPoint(previewX, previewY);
|
|
1487
|
+
|
|
1488
|
+
// Try with fast duration for responsive backup focus
|
|
1489
|
+
FocusMeteringAction backupAction = new FocusMeteringAction.Builder(backupPoint,
|
|
1490
|
+
FocusMeteringAction.FLAG_AF | FocusMeteringAction.FLAG_AE)
|
|
1491
|
+
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second for backup
|
|
1492
|
+
.build();
|
|
1493
|
+
|
|
1494
|
+
ListenableFuture<FocusMeteringResult> backupFuture =
|
|
1495
|
+
camera.getCameraControl().startFocusAndMetering(backupAction);
|
|
1496
|
+
|
|
1497
|
+
backupFuture.addListener(new Runnable() {
|
|
1498
|
+
@Override
|
|
1499
|
+
public void run() {
|
|
1500
|
+
try {
|
|
1501
|
+
FocusMeteringResult backupResult = backupFuture.get();
|
|
1502
|
+
if (backupResult.isFocusSuccessful()) {
|
|
1503
|
+
Log.d("Camera", "Backup focus successful");
|
|
1504
|
+
maintainFocusAtPoint(previewX, previewY);
|
|
1505
|
+
} else {
|
|
1506
|
+
Log.d("Camera", "Backup focus also failed");
|
|
1507
|
+
}
|
|
1508
|
+
} catch (Exception e) {
|
|
1509
|
+
Log.d("Camera", "Backup focus exception: " + e.getMessage());
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}, ContextCompat.getMainExecutor(getContext()));
|
|
1513
|
+
}
|
|
1514
|
+
} catch (Exception e) {
|
|
1515
|
+
Log.d("Camera", "Backup focus setup failed: " + e.getMessage());
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
} catch (InterruptedException e) {
|
|
1520
|
+
Log.d("Camera", "Backup focus interrupted");
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/**
|
|
1527
|
+
* Maintain focus at a specific point with repeated focus actions for stability
|
|
1528
|
+
*/
|
|
1529
|
+
private void maintainFocusAtPoint(float previewX, float previewY) {
|
|
1530
|
+
if (camera == null || previewView == null) return;
|
|
1531
|
+
|
|
1532
|
+
// Use a separate executor for focus maintenance
|
|
1533
|
+
ExecutorService focusMaintainExecutor = Executors.newSingleThreadExecutor();
|
|
1534
|
+
|
|
1535
|
+
focusMaintainExecutor.execute(new Runnable() {
|
|
1536
|
+
@Override
|
|
1537
|
+
public void run() {
|
|
1538
|
+
try {
|
|
1539
|
+
// Maintain focus for 2 seconds with quick refocus for responsive transitions
|
|
1540
|
+
for (int i = 0; i < 2; i++) {
|
|
1541
|
+
Thread.sleep(1000); // Wait 1 second between focus actions
|
|
1542
|
+
|
|
1543
|
+
getActivity().runOnUiThread(new Runnable() {
|
|
1544
|
+
@Override
|
|
1545
|
+
public void run() {
|
|
1546
|
+
try {
|
|
1547
|
+
if (camera != null && camera.getCameraInfo().getCameraState().getValue().getType() == CameraState.Type.OPEN) {
|
|
1548
|
+
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
1549
|
+
MeteringPoint maintainPoint = factory.createPoint(previewX, previewY);
|
|
1550
|
+
|
|
1551
|
+
FocusMeteringAction maintainAction = new FocusMeteringAction.Builder(maintainPoint)
|
|
1552
|
+
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second maintenance
|
|
1553
|
+
.build();
|
|
1554
|
+
|
|
1555
|
+
camera.getCameraControl().startFocusAndMetering(maintainAction);
|
|
1556
|
+
Log.d("Camera", "Maintaining focus at tapped point");
|
|
1557
|
+
}
|
|
1558
|
+
} catch (Exception e) {
|
|
1559
|
+
Log.d("Camera", "Focus maintenance failed: " + e.getMessage());
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
} catch (InterruptedException e) {
|
|
1565
|
+
Log.d("Camera", "Focus maintenance interrupted");
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1407
1571
|
}
|
|
@@ -1235,74 +1235,80 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1235
1235
|
private func calculateBlurScore(image: UIImage) -> Double {
|
|
1236
1236
|
guard let cgImage = image.cgImage else { return 0.0 }
|
|
1237
1237
|
|
|
1238
|
-
let
|
|
1239
|
-
let
|
|
1238
|
+
let width = cgImage.width
|
|
1239
|
+
let height = cgImage.height
|
|
1240
1240
|
|
|
1241
|
-
//
|
|
1242
|
-
let
|
|
1243
|
-
|
|
1244
|
-
|
|
1241
|
+
// Create bitmap context to access pixel data
|
|
1242
|
+
let bytesPerPixel = 4
|
|
1243
|
+
let bytesPerRow = bytesPerPixel * width
|
|
1244
|
+
let bitsPerComponent = 8
|
|
1245
1245
|
|
|
1246
|
-
guard let
|
|
1246
|
+
guard let context = CGContext(
|
|
1247
|
+
data: nil,
|
|
1248
|
+
width: width,
|
|
1249
|
+
height: height,
|
|
1250
|
+
bitsPerComponent: bitsPerComponent,
|
|
1251
|
+
bytesPerRow: bytesPerRow,
|
|
1252
|
+
space: CGColorSpaceCreateDeviceRGB(),
|
|
1253
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
|
1254
|
+
) else { return 0.0 }
|
|
1247
1255
|
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1250
|
-
0, -1, 0,
|
|
1251
|
-
-1, 4, -1,
|
|
1252
|
-
0, -1, 0
|
|
1253
|
-
]
|
|
1254
|
-
|
|
1255
|
-
let convolutionFilter = CIFilter(name: "CIConvolution3X3")!
|
|
1256
|
-
convolutionFilter.setValue(grayscaleImage, forKey: kCIInputImageKey)
|
|
1257
|
-
convolutionFilter.setValue(CIVector(values: laplacianKernel, count: 9), forKey: "inputWeights")
|
|
1258
|
-
|
|
1259
|
-
guard let filteredImage = convolutionFilter.outputImage else { return 0.0 }
|
|
1260
|
-
|
|
1261
|
-
// Calculate variance of the Laplacian
|
|
1262
|
-
let extent = filteredImage.extent
|
|
1263
|
-
let inputExtent = CIVector(x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height)
|
|
1264
|
-
|
|
1265
|
-
let averageFilter = CIFilter(name: "CIAreaAverage")!
|
|
1266
|
-
averageFilter.setValue(filteredImage, forKey: kCIInputImageKey)
|
|
1267
|
-
averageFilter.setValue(inputExtent, forKey: kCIInputExtentKey)
|
|
1268
|
-
|
|
1269
|
-
guard let averageImage = averageFilter.outputImage else { return 0.0 }
|
|
1270
|
-
|
|
1271
|
-
var bitmap = [UInt8](repeating: 0, count: 4)
|
|
1272
|
-
context.render(averageImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)
|
|
1256
|
+
// Draw image into context
|
|
1257
|
+
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
1273
1258
|
|
|
1274
|
-
let
|
|
1275
|
-
|
|
1276
|
-
// Calculate variance by processing small samples of the image
|
|
1277
|
-
let sampleSize = min(100, Int(extent.width), Int(extent.height))
|
|
1278
|
-
let stepX = extent.width / Double(sampleSize)
|
|
1279
|
-
let stepY = extent.height / Double(sampleSize)
|
|
1259
|
+
guard let pixelData = context.data else { return 0.0 }
|
|
1260
|
+
let data = pixelData.bindMemory(to: UInt8.self, capacity: width * height * bytesPerPixel)
|
|
1280
1261
|
|
|
1262
|
+
// Convert to grayscale and apply Laplacian kernel (same as Android/Web)
|
|
1281
1263
|
var variance = 0.0
|
|
1282
|
-
var
|
|
1264
|
+
var count = 0
|
|
1283
1265
|
|
|
1284
|
-
for
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
let
|
|
1266
|
+
// Sample every 4th pixel for performance (same as Android/Web)
|
|
1267
|
+
let step = 4
|
|
1268
|
+
for y in stride(from: step, to: height - step, by: step) {
|
|
1269
|
+
for x in stride(from: step, to: width - step, by: step) {
|
|
1270
|
+
let idx = (y * width + x) * bytesPerPixel
|
|
1289
1271
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1272
|
+
// Convert to grayscale using same formula as Android/Web
|
|
1273
|
+
let r = Double(data[idx])
|
|
1274
|
+
let g = Double(data[idx + 1])
|
|
1275
|
+
let b = Double(data[idx + 2])
|
|
1276
|
+
let gray = 0.299 * r + 0.587 * g + 0.114 * b
|
|
1293
1277
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1278
|
+
// Calculate neighbors for 3x3 Laplacian kernel
|
|
1279
|
+
let neighbors: [Double] = [
|
|
1280
|
+
// Top row
|
|
1281
|
+
getGrayscaleValue(data: data, x: x-1, y: y-1, width: width, bytesPerPixel: bytesPerPixel),
|
|
1282
|
+
getGrayscaleValue(data: data, x: x, y: y-1, width: width, bytesPerPixel: bytesPerPixel),
|
|
1283
|
+
getGrayscaleValue(data: data, x: x+1, y: y-1, width: width, bytesPerPixel: bytesPerPixel),
|
|
1284
|
+
// Middle row (left and right)
|
|
1285
|
+
getGrayscaleValue(data: data, x: x-1, y: y, width: width, bytesPerPixel: bytesPerPixel),
|
|
1286
|
+
getGrayscaleValue(data: data, x: x+1, y: y, width: width, bytesPerPixel: bytesPerPixel),
|
|
1287
|
+
// Bottom row
|
|
1288
|
+
getGrayscaleValue(data: data, x: x-1, y: y+1, width: width, bytesPerPixel: bytesPerPixel),
|
|
1289
|
+
getGrayscaleValue(data: data, x: x, y: y+1, width: width, bytesPerPixel: bytesPerPixel),
|
|
1290
|
+
getGrayscaleValue(data: data, x: x+1, y: y+1, width: width, bytesPerPixel: bytesPerPixel)
|
|
1291
|
+
]
|
|
1292
|
+
|
|
1293
|
+
// Apply 3x3 Laplacian kernel (matches Android/Web implementation)
|
|
1294
|
+
let laplacian = -neighbors[0] - neighbors[1] - neighbors[2] +
|
|
1295
|
+
-neighbors[3] + 8 * gray - neighbors[4] +
|
|
1296
|
+
-neighbors[5] - neighbors[6] - neighbors[7]
|
|
1297
|
+
|
|
1298
|
+
variance += laplacian * laplacian
|
|
1299
|
+
count += 1
|
|
1302
1300
|
}
|
|
1303
1301
|
}
|
|
1304
1302
|
|
|
1305
|
-
return
|
|
1303
|
+
return count > 0 ? variance / Double(count) : 0.0
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
private func getGrayscaleValue(data: UnsafePointer<UInt8>, x: Int, y: Int, width: Int, bytesPerPixel: Int) -> Double {
|
|
1307
|
+
let idx = (y * width + x) * bytesPerPixel
|
|
1308
|
+
let r = Double(data[idx])
|
|
1309
|
+
let g = Double(data[idx + 1])
|
|
1310
|
+
let b = Double(data[idx + 2])
|
|
1311
|
+
return 0.299 * r + 0.587 * g + 0.114 * b
|
|
1306
1312
|
}
|
|
1307
1313
|
|
|
1308
1314
|
}
|
package/package.json
CHANGED