@scr2em/capacitor-scanner 6.0.18 → 6.0.20
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 +4 -2
- package/android/src/main/java/com/leadliaion/capacitorscanner/CapacitorScannerPlugin.java +226 -194
- package/package.json +2 -1
- package/android/src/main/libs/rectangle-detector/build.gradle +0 -73
- package/android/src/main/libs/rectangle-detector/consumer-rules.pro +0 -0
- package/android/src/main/libs/rectangle-detector/proguard-rules.pro +0 -21
- package/android/src/main/libs/rectangle-detector/src/main/AndroidManifest.xml +0 -2
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/DetectionAccuracy.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/RectangleDetector.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/RectangleDetectorImpl.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/dto/DetectionResult.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/dto/Rectangle.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/extension/BitmapExt.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/strategy/AdaptiveThresholdStrategy.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/strategy/CannyAlgorithmStrategy.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/strategy/CompositeContourDetectionStrategy.kt +0 -0
- /package/android/src/main/{libs/rectangle-detector/src/main/java → java}/jp/co/smartbank/rectangledetector/strategy/ContourDetectionStrategy.kt +0 -0
package/android/build.gradle
CHANGED
|
@@ -26,7 +26,7 @@ buildscript {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
apply plugin: 'com.android.library'
|
|
29
|
-
|
|
29
|
+
apply plugin: 'kotlin-android'
|
|
30
30
|
android {
|
|
31
31
|
namespace "com.leadliaion.capacitorscanner"
|
|
32
32
|
compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
|
@@ -46,6 +46,9 @@ android {
|
|
|
46
46
|
lintOptions {
|
|
47
47
|
abortOnError false
|
|
48
48
|
}
|
|
49
|
+
kotlinOptions {
|
|
50
|
+
jvmTarget = '17'
|
|
51
|
+
}
|
|
49
52
|
compileOptions {
|
|
50
53
|
sourceCompatibility JavaVersion.VERSION_17
|
|
51
54
|
targetCompatibility JavaVersion.VERSION_17
|
|
@@ -76,7 +79,6 @@ dependencies {
|
|
|
76
79
|
|
|
77
80
|
// Exposed as 'api' so consuming apps get them transitively
|
|
78
81
|
api 'org.opencv:opencv:4.12.0'
|
|
79
|
-
implementation files('libs/rectangle-detector-release.aar')
|
|
80
82
|
api "androidx.camera:camera-core:1.5.1"
|
|
81
83
|
api "androidx.camera:camera-camera2:1.5.1"
|
|
82
84
|
api "androidx.camera:camera-lifecycle:1.5.1"
|
|
@@ -137,9 +137,11 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
137
137
|
private long lastRectangleProcessingTime = 0;
|
|
138
138
|
private long lastRectangleDetectionTime = 0; // Tracks when we last had a valid rectangle detection
|
|
139
139
|
private int processingFrequency = 10; // Default: 10 rectangle detections per second
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
/**
|
|
142
|
-
* Calculates the processing interval in milliseconds based on the desired
|
|
142
|
+
* Calculates the processing interval in milliseconds based on the desired
|
|
143
|
+
* frequency per second.
|
|
144
|
+
*
|
|
143
145
|
* @param frequency Number of processing operations per second
|
|
144
146
|
* @return Processing interval in milliseconds
|
|
145
147
|
*/
|
|
@@ -159,7 +161,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
159
161
|
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
|
|
160
162
|
.build());
|
|
161
163
|
|
|
162
|
-
// Initialize rectangle detector like the sample (use default constructor, not
|
|
164
|
+
// Initialize rectangle detector like the sample (use default constructor, not
|
|
165
|
+
// DetectionAccuracy.Passive)
|
|
163
166
|
try {
|
|
164
167
|
rectangleDetector = RectangleDetector.Companion.getInstance(DetectionAccuracy.Aggressive);
|
|
165
168
|
echo("Rectangle detector initialized successfully");
|
|
@@ -298,7 +301,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
298
301
|
|
|
299
302
|
private Camera camera;
|
|
300
303
|
|
|
301
|
-
private void bindCamera(@NonNull ProcessCameraProvider cameraProvider, PreviewView previewView, int lensFacing,
|
|
304
|
+
private void bindCamera(@NonNull ProcessCameraProvider cameraProvider, PreviewView previewView, int lensFacing,
|
|
305
|
+
PluginCall call) {
|
|
302
306
|
cameraProvider.unbindAll();
|
|
303
307
|
|
|
304
308
|
// Get preview view dimensions (like sample)
|
|
@@ -362,7 +366,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
362
366
|
|
|
363
367
|
try {
|
|
364
368
|
// Store the Camera instance for later use
|
|
365
|
-
camera = cameraProvider.bindToLifecycle(getActivity(), cameraSelector, preview, imageCapture,
|
|
369
|
+
camera = cameraProvider.bindToLifecycle(getActivity(), cameraSelector, preview, imageCapture,
|
|
370
|
+
imageAnalysis);
|
|
366
371
|
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
367
372
|
} catch (Exception e) {
|
|
368
373
|
echo("Failed to bind camera to lifecycle: " + e.getMessage());
|
|
@@ -387,7 +392,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
387
392
|
@ExperimentalGetImage
|
|
388
393
|
android.media.Image mediaImage = imageProxy.getImage();
|
|
389
394
|
if (mediaImage != null) {
|
|
390
|
-
InputImage image = InputImage.fromMediaImage(mediaImage,
|
|
395
|
+
InputImage image = InputImage.fromMediaImage(mediaImage,
|
|
396
|
+
imageProxy.getImageInfo().getRotationDegrees());
|
|
391
397
|
|
|
392
398
|
// Barcode detection
|
|
393
399
|
if (scanner != null) {
|
|
@@ -395,7 +401,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
395
401
|
.addOnSuccessListener(executor, barcodes -> {
|
|
396
402
|
processBarcodes(barcodes);
|
|
397
403
|
if (enabledDetectionTypes.contains("barcode")) {
|
|
398
|
-
displayDetectedBarcodes(barcodes, imageProxy.getWidth(), imageProxy.getHeight(),
|
|
404
|
+
displayDetectedBarcodes(barcodes, imageProxy.getWidth(), imageProxy.getHeight(),
|
|
405
|
+
imageProxy.getImageInfo().getRotationDegrees());
|
|
399
406
|
}
|
|
400
407
|
})
|
|
401
408
|
.addOnFailureListener(executor, e -> {
|
|
@@ -419,14 +426,16 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
419
426
|
// Convert ImageProxy to Bitmap using the sample's toBitmap method
|
|
420
427
|
bitmap = imageProxyToBitmap(imageProxy);
|
|
421
428
|
|
|
422
|
-
echo("Converting image for rectangle detection - bitmap size: " + bitmap.getWidth()
|
|
429
|
+
echo("Converting image for rectangle detection - bitmap size: " + bitmap.getWidth()
|
|
430
|
+
+ "x" + bitmap.getHeight());
|
|
423
431
|
|
|
424
432
|
long startTime = System.currentTimeMillis();
|
|
425
433
|
DetectionResult rawResult = rectangleDetector.detectRectangles(bitmap);
|
|
426
434
|
echo("Rectangle detection took " + (System.currentTimeMillis() - startTime) + "ms");
|
|
427
435
|
|
|
428
436
|
// Apply geometric filtering to eliminate false positives
|
|
429
|
-
List<Rectangle> filteredRectangles = filterRectangles(rawResult.getRectangles(),
|
|
437
|
+
List<Rectangle> filteredRectangles = filterRectangles(rawResult.getRectangles(),
|
|
438
|
+
rawResult.getImageSize());
|
|
430
439
|
|
|
431
440
|
// 2) record hit/miss in sliding window
|
|
432
441
|
boolean isHit = !filteredRectangles.isEmpty();
|
|
@@ -435,7 +444,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
435
444
|
detectionHistory.poll();
|
|
436
445
|
}
|
|
437
446
|
if (isHit) {
|
|
438
|
-
lastGoodRects = filteredRectangles;
|
|
447
|
+
lastGoodRects = filteredRectangles; // update last known good
|
|
439
448
|
echo("✅ Hit - saved " + filteredRectangles.size() + " good rectangles");
|
|
440
449
|
} else {
|
|
441
450
|
echo("❌ Miss - keeping " + lastGoodRects.size() + " previous rectangles");
|
|
@@ -448,12 +457,14 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
448
457
|
} else {
|
|
449
458
|
// For older Android versions, count manually
|
|
450
459
|
for (Boolean hit : detectionHistory) {
|
|
451
|
-
if (hit)
|
|
460
|
+
if (hit)
|
|
461
|
+
hits++;
|
|
452
462
|
}
|
|
453
463
|
}
|
|
454
464
|
boolean shouldShow = hits >= Math.ceil(PRESENCE_THRESHOLD * detectionHistory.size());
|
|
455
465
|
List<Rectangle> rectanglesToShow = shouldShow ? lastGoodRects : Collections.emptyList();
|
|
456
|
-
echo("Window: " + hits + "/" + detectionHistory.size() + " hits - shouldShow: "
|
|
466
|
+
echo("Window: " + hits + "/" + detectionHistory.size() + " hits - shouldShow: "
|
|
467
|
+
+ shouldShow);
|
|
457
468
|
|
|
458
469
|
// Create detection result with the rectangles we decided to show
|
|
459
470
|
detectionResult = new DetectionResult(rawResult.getImageSize(), rectanglesToShow);
|
|
@@ -464,7 +475,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
464
475
|
lastRectangleDetectionTime = currentTime;
|
|
465
476
|
}
|
|
466
477
|
|
|
467
|
-
// Process rectangle detection results to emit events if we're showing
|
|
478
|
+
// Process rectangle detection results to emit events if we're showing
|
|
479
|
+
// rectangles
|
|
468
480
|
if (!rectanglesToShow.isEmpty()) {
|
|
469
481
|
processRectangles(detectionResult);
|
|
470
482
|
}
|
|
@@ -474,7 +486,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
474
486
|
} catch (Exception e) {
|
|
475
487
|
echo("Failed to process image for rectangle detection: " + e.getMessage());
|
|
476
488
|
e.printStackTrace();
|
|
477
|
-
detectionResult = new DetectionResult(
|
|
489
|
+
detectionResult = new DetectionResult(
|
|
490
|
+
new Size(imageProxy.getWidth(), imageProxy.getHeight()), new ArrayList<>());
|
|
478
491
|
} finally {
|
|
479
492
|
// Cleanup bitmap resource in all code paths
|
|
480
493
|
if (bitmap != null) {
|
|
@@ -483,14 +496,17 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
483
496
|
}
|
|
484
497
|
} else {
|
|
485
498
|
// For frames we're not processing, reuse the last good detection result
|
|
486
|
-
// This ensures the UI stays smooth even though we're only detecting 10
|
|
499
|
+
// This ensures the UI stays smooth even though we're only detecting 10
|
|
500
|
+
// times/second
|
|
487
501
|
if (lastGoodDetectionResult != null &&
|
|
488
502
|
(currentTime - lastRectangleDetectionTime) < CACHE_MS) {
|
|
489
503
|
detectionResult = lastGoodDetectionResult;
|
|
490
|
-
echo("Reusing last good detection result (age: "
|
|
504
|
+
echo("Reusing last good detection result (age: "
|
|
505
|
+
+ (currentTime - lastRectangleProcessingTime) + "ms)");
|
|
491
506
|
} else {
|
|
492
507
|
// Create empty result if we have no recent good detection
|
|
493
|
-
detectionResult = new DetectionResult(
|
|
508
|
+
detectionResult = new DetectionResult(
|
|
509
|
+
new Size(imageProxy.getWidth(), imageProxy.getHeight()), new ArrayList<>());
|
|
494
510
|
}
|
|
495
511
|
}
|
|
496
512
|
|
|
@@ -524,8 +540,9 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
524
540
|
float imgWidth = imageSize.getWidth();
|
|
525
541
|
float imgHeight = imageSize.getHeight();
|
|
526
542
|
float imageArea = imgWidth * imgHeight;
|
|
527
|
-
|
|
528
|
-
// Sort rectangles by quality score (highest first) to prioritize better
|
|
543
|
+
|
|
544
|
+
// Sort rectangles by quality score (highest first) to prioritize better
|
|
545
|
+
// detections
|
|
529
546
|
List<Rectangle> sortedRectangles = new ArrayList<>(rectangles);
|
|
530
547
|
Collections.sort(sortedRectangles, (r1, r2) -> {
|
|
531
548
|
float score1 = calculateRectangleQualityScore(r1, imageSize);
|
|
@@ -540,31 +557,31 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
540
557
|
float areaRatio = rectArea / imageArea;
|
|
541
558
|
float rectangularity = calculateRectangularity(rect);
|
|
542
559
|
float aspectRatio = calculateAspectRatio(rect);
|
|
543
|
-
|
|
560
|
+
|
|
544
561
|
// Skip if too small relative to image (increased from 0.01 to 0.03)
|
|
545
562
|
if (areaRatio < 0.03f) {
|
|
546
563
|
echo("Filtered out tiny rectangle: " + areaRatio);
|
|
547
564
|
continue;
|
|
548
565
|
}
|
|
549
|
-
|
|
566
|
+
|
|
550
567
|
// Skip if too large (likely a false positive like the whole screen)
|
|
551
568
|
if (areaRatio > 0.8f) {
|
|
552
569
|
echo("Filtered out too large rectangle: " + areaRatio);
|
|
553
570
|
continue;
|
|
554
571
|
}
|
|
555
|
-
|
|
572
|
+
|
|
556
573
|
// Filter based on rectangularity (how close to being a perfect rectangle)
|
|
557
574
|
if (rectangularity < 0.8f) {
|
|
558
575
|
echo("Filtered out non-rectangular shape: " + rectangularity);
|
|
559
576
|
continue;
|
|
560
577
|
}
|
|
561
|
-
|
|
578
|
+
|
|
562
579
|
// Filter extreme aspect ratios (too narrow or too wide)
|
|
563
580
|
if (aspectRatio < 0.2f || aspectRatio > 5.0f) {
|
|
564
581
|
echo("Filtered out bad aspect ratio: " + aspectRatio);
|
|
565
582
|
continue;
|
|
566
583
|
}
|
|
567
|
-
|
|
584
|
+
|
|
568
585
|
// Check if this rectangle is a duplicate of one we already kept
|
|
569
586
|
boolean isDuplicate = false;
|
|
570
587
|
for (Rectangle existingRect : filteredRectangles) {
|
|
@@ -574,147 +591,153 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
574
591
|
break;
|
|
575
592
|
}
|
|
576
593
|
}
|
|
577
|
-
|
|
594
|
+
|
|
578
595
|
if (!isDuplicate) {
|
|
579
596
|
filteredRectangles.add(rect);
|
|
580
597
|
}
|
|
581
598
|
}
|
|
582
|
-
|
|
599
|
+
|
|
583
600
|
echo("Rectangle filtering: " + rectangles.size() + " → " + filteredRectangles.size() + " rectangles");
|
|
584
601
|
return filteredRectangles;
|
|
585
602
|
}
|
|
586
|
-
|
|
603
|
+
|
|
587
604
|
// Calculate area of rectangle
|
|
588
605
|
private float getRectangleArea(Rectangle rect) {
|
|
589
606
|
// Use shoelace formula to calculate area of quadrilateral
|
|
590
607
|
return 0.5f * Math.abs(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
608
|
+
(rect.getTopLeft().x * rect.getTopRight().y - rect.getTopRight().x * rect.getTopLeft().y) +
|
|
609
|
+
(rect.getTopRight().x * rect.getBottomRight().y
|
|
610
|
+
- rect.getBottomRight().x * rect.getTopRight().y)
|
|
611
|
+
+
|
|
612
|
+
(rect.getBottomRight().x * rect.getBottomLeft().y
|
|
613
|
+
- rect.getBottomLeft().x * rect.getBottomRight().y)
|
|
614
|
+
+
|
|
615
|
+
(rect.getBottomLeft().x * rect.getTopLeft().y - rect.getTopLeft().x * rect.getBottomLeft().y));
|
|
596
616
|
}
|
|
597
|
-
|
|
617
|
+
|
|
598
618
|
// Check if two rectangles are similar enough to be considered duplicates
|
|
599
619
|
private boolean isSimilarRectangle(Rectangle rect1, Rectangle rect2) {
|
|
600
620
|
// Calculate center points
|
|
601
|
-
float cx1 = (float) (rect1.getTopLeft().x + rect1.getTopRight().x + rect1.getBottomLeft().x
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
621
|
+
float cx1 = (float) (rect1.getTopLeft().x + rect1.getTopRight().x + rect1.getBottomLeft().x
|
|
622
|
+
+ rect1.getBottomRight().x) / 4;
|
|
623
|
+
float cy1 = (float) (rect1.getTopLeft().y + rect1.getTopRight().y + rect1.getBottomLeft().y
|
|
624
|
+
+ rect1.getBottomRight().y) / 4;
|
|
625
|
+
|
|
626
|
+
float cx2 = (float) (rect2.getTopLeft().x + rect2.getTopRight().x + rect2.getBottomLeft().x
|
|
627
|
+
+ rect2.getBottomRight().x) / 4;
|
|
628
|
+
float cy2 = (float) (rect2.getTopLeft().y + rect2.getTopRight().y + rect2.getBottomLeft().y
|
|
629
|
+
+ rect2.getBottomRight().y) / 4;
|
|
630
|
+
|
|
607
631
|
// Calculate distances between centers
|
|
608
632
|
float distanceBetweenCenters = (float) Math.sqrt(Math.pow(cx2 - cx1, 2) + Math.pow(cy2 - cy1, 2));
|
|
609
|
-
|
|
633
|
+
|
|
610
634
|
// Calculate diagonal lengths as reference
|
|
611
635
|
float diag1 = (float) Math.sqrt(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
);
|
|
636
|
+
Math.pow(rect1.getTopLeft().x - rect1.getBottomRight().x, 2) +
|
|
637
|
+
Math.pow(rect1.getTopLeft().y - rect1.getBottomRight().y, 2));
|
|
615
638
|
float diag2 = (float) Math.sqrt(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
639
|
+
Math.pow(rect2.getTopLeft().x - rect2.getBottomRight().x, 2) +
|
|
640
|
+
Math.pow(rect2.getTopLeft().y - rect2.getBottomRight().y, 2));
|
|
641
|
+
|
|
620
642
|
float avgDiag = (diag1 + diag2) / 2;
|
|
621
|
-
|
|
643
|
+
|
|
622
644
|
// Make duplicate detection more strict (reduced from 0.15 to 0.1)
|
|
623
|
-
// If centers are close relative to the rectangle size, they're likely
|
|
645
|
+
// If centers are close relative to the rectangle size, they're likely
|
|
646
|
+
// duplicates
|
|
624
647
|
return distanceBetweenCenters < (0.1f * avgDiag);
|
|
625
648
|
}
|
|
626
|
-
|
|
627
|
-
// Calculate how rectangular a shape is (1.0 = perfect rectangle, lower = less
|
|
649
|
+
|
|
650
|
+
// Calculate how rectangular a shape is (1.0 = perfect rectangle, lower = less
|
|
651
|
+
// rectangular)
|
|
628
652
|
private float calculateRectangularity(Rectangle rect) {
|
|
629
|
-
// Check angles between adjacent sides - should be close to 90 degrees in a
|
|
653
|
+
// Check angles between adjacent sides - should be close to 90 degrees in a
|
|
654
|
+
// rectangle
|
|
630
655
|
android.graphics.Point topLeft = rect.getTopLeft();
|
|
631
656
|
android.graphics.Point topRight = rect.getTopRight();
|
|
632
657
|
android.graphics.Point bottomRight = rect.getBottomRight();
|
|
633
658
|
android.graphics.Point bottomLeft = rect.getBottomLeft();
|
|
634
|
-
|
|
659
|
+
|
|
635
660
|
// Calculate vectors for the four sides
|
|
636
|
-
float[] side1x = {topRight.x - topLeft.x, topRight.y - topLeft.y};
|
|
637
|
-
float[] side2x = {bottomRight.x - topRight.x, bottomRight.y - topRight.y};
|
|
638
|
-
float[] side3x = {bottomLeft.x - bottomRight.x, bottomLeft.y - bottomRight.y};
|
|
639
|
-
float[] side4x = {topLeft.x - bottomLeft.x, topLeft.y - bottomLeft.y};
|
|
640
|
-
|
|
661
|
+
float[] side1x = { topRight.x - topLeft.x, topRight.y - topLeft.y };
|
|
662
|
+
float[] side2x = { bottomRight.x - topRight.x, bottomRight.y - topRight.y };
|
|
663
|
+
float[] side3x = { bottomLeft.x - bottomRight.x, bottomLeft.y - bottomRight.y };
|
|
664
|
+
float[] side4x = { topLeft.x - bottomLeft.x, topLeft.y - bottomLeft.y };
|
|
665
|
+
|
|
641
666
|
// Calculate angles between sides (dot product)
|
|
642
667
|
float angle1 = calculateAngle(side1x, side2x);
|
|
643
668
|
float angle2 = calculateAngle(side2x, side3x);
|
|
644
669
|
float angle3 = calculateAngle(side3x, side4x);
|
|
645
670
|
float angle4 = calculateAngle(side4x, side1x);
|
|
646
|
-
|
|
671
|
+
|
|
647
672
|
// For a perfect rectangle, all angles would be 90 degrees (or PI/2 radians)
|
|
648
673
|
// Calculate deviation from 90 degrees for each angle
|
|
649
|
-
float dev1 = Math.abs(angle1 - (float)(Math.PI/2));
|
|
650
|
-
float dev2 = Math.abs(angle2 - (float)(Math.PI/2));
|
|
651
|
-
float dev3 = Math.abs(angle3 - (float)(Math.PI/2));
|
|
652
|
-
float dev4 = Math.abs(angle4 - (float)(Math.PI/2));
|
|
653
|
-
|
|
674
|
+
float dev1 = Math.abs(angle1 - (float) (Math.PI / 2));
|
|
675
|
+
float dev2 = Math.abs(angle2 - (float) (Math.PI / 2));
|
|
676
|
+
float dev3 = Math.abs(angle3 - (float) (Math.PI / 2));
|
|
677
|
+
float dev4 = Math.abs(angle4 - (float) (Math.PI / 2));
|
|
678
|
+
|
|
654
679
|
// Calculate average deviation, normalized to 0-1 range
|
|
655
680
|
float avgDev = (dev1 + dev2 + dev3 + dev4) / 4;
|
|
656
|
-
|
|
681
|
+
|
|
657
682
|
// 1.0 = perfect rectangle, 0 = not rectangular at all
|
|
658
|
-
return Math.max(0, 1.0f - (avgDev / (float)(Math.PI/4)));
|
|
683
|
+
return Math.max(0, 1.0f - (avgDev / (float) (Math.PI / 4)));
|
|
659
684
|
}
|
|
660
|
-
|
|
685
|
+
|
|
661
686
|
// Calculate angle between two vectors in radians
|
|
662
687
|
private float calculateAngle(float[] v1, float[] v2) {
|
|
663
688
|
// Calculate dot product
|
|
664
689
|
float dotProduct = v1[0] * v2[0] + v1[1] * v2[1];
|
|
665
|
-
|
|
690
|
+
|
|
666
691
|
// Calculate magnitudes
|
|
667
|
-
float mag1 = (float)Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1]);
|
|
668
|
-
float mag2 = (float)Math.sqrt(v2[0] * v2[0] + v2[1] * v2[1]);
|
|
669
|
-
|
|
692
|
+
float mag1 = (float) Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1]);
|
|
693
|
+
float mag2 = (float) Math.sqrt(v2[0] * v2[0] + v2[1] * v2[1]);
|
|
694
|
+
|
|
670
695
|
// Avoid division by zero
|
|
671
|
-
if (mag1 == 0 || mag2 == 0)
|
|
672
|
-
|
|
696
|
+
if (mag1 == 0 || mag2 == 0)
|
|
697
|
+
return 0;
|
|
698
|
+
|
|
673
699
|
// Calculate angle
|
|
674
700
|
float cosAngle = dotProduct / (mag1 * mag2);
|
|
675
|
-
|
|
701
|
+
|
|
676
702
|
// Ensure value is in [-1,1] range to avoid domain errors with acos
|
|
677
703
|
cosAngle = Math.max(-1.0f, Math.min(1.0f, cosAngle));
|
|
678
|
-
|
|
679
|
-
return (float)Math.acos(cosAngle);
|
|
704
|
+
|
|
705
|
+
return (float) Math.acos(cosAngle);
|
|
680
706
|
}
|
|
681
|
-
|
|
707
|
+
|
|
682
708
|
// Calculate aspect ratio (width/height) using the average of opposite sides
|
|
683
709
|
private float calculateAspectRatio(Rectangle rect) {
|
|
684
710
|
android.graphics.Point topLeft = rect.getTopLeft();
|
|
685
711
|
android.graphics.Point topRight = rect.getTopRight();
|
|
686
712
|
android.graphics.Point bottomRight = rect.getBottomRight();
|
|
687
713
|
android.graphics.Point bottomLeft = rect.getBottomLeft();
|
|
688
|
-
|
|
689
|
-
float topWidth = (float)Math.sqrt(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
Math.pow(bottomRight.x - topRight.x, 2) +
|
|
706
|
-
Math.pow(bottomRight.y - topRight.y, 2)
|
|
707
|
-
);
|
|
708
|
-
|
|
714
|
+
|
|
715
|
+
float topWidth = (float) Math.sqrt(
|
|
716
|
+
Math.pow(topRight.x - topLeft.x, 2) +
|
|
717
|
+
Math.pow(topRight.y - topLeft.y, 2));
|
|
718
|
+
|
|
719
|
+
float bottomWidth = (float) Math.sqrt(
|
|
720
|
+
Math.pow(bottomRight.x - bottomLeft.x, 2) +
|
|
721
|
+
Math.pow(bottomRight.y - bottomLeft.y, 2));
|
|
722
|
+
|
|
723
|
+
float leftHeight = (float) Math.sqrt(
|
|
724
|
+
Math.pow(bottomLeft.x - topLeft.x, 2) +
|
|
725
|
+
Math.pow(bottomLeft.y - topLeft.y, 2));
|
|
726
|
+
|
|
727
|
+
float rightHeight = (float) Math.sqrt(
|
|
728
|
+
Math.pow(bottomRight.x - topRight.x, 2) +
|
|
729
|
+
Math.pow(bottomRight.y - topRight.y, 2));
|
|
730
|
+
|
|
709
731
|
float avgWidth = (topWidth + bottomWidth) / 2;
|
|
710
732
|
float avgHeight = (leftHeight + rightHeight) / 2;
|
|
711
|
-
|
|
733
|
+
|
|
712
734
|
// Avoid division by zero
|
|
713
|
-
if (avgHeight == 0)
|
|
714
|
-
|
|
735
|
+
if (avgHeight == 0)
|
|
736
|
+
return 0;
|
|
737
|
+
|
|
715
738
|
return avgWidth / avgHeight;
|
|
716
739
|
}
|
|
717
|
-
|
|
740
|
+
|
|
718
741
|
// Calculate a quality score for the rectangle (higher = better)
|
|
719
742
|
private float calculateRectangleQualityScore(Rectangle rect, Size imageSize) {
|
|
720
743
|
float rectangularity = calculateRectangularity(rect);
|
|
@@ -722,19 +745,21 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
722
745
|
float area = getRectangleArea(rect);
|
|
723
746
|
float imageArea = imageSize.getWidth() * imageSize.getHeight();
|
|
724
747
|
float areaRatio = area / imageArea;
|
|
725
|
-
|
|
726
|
-
// Ideal aspect ratio score (1.0 = golden ratio, decreases as we move away from
|
|
748
|
+
|
|
749
|
+
// Ideal aspect ratio score (1.0 = golden ratio, decreases as we move away from
|
|
750
|
+
// it)
|
|
727
751
|
float idealAspect = 1.6f; // Close to golden ratio
|
|
728
752
|
float aspectScore = 1.0f - Math.min(1.0f, Math.abs(aspectRatio - idealAspect) / 2.0f);
|
|
729
|
-
|
|
730
|
-
// Ideal area score (0 for tiny or huge rectangles, 1.0 for rectangles that take
|
|
753
|
+
|
|
754
|
+
// Ideal area score (0 for tiny or huge rectangles, 1.0 for rectangles that take
|
|
755
|
+
// up reasonable portion)
|
|
731
756
|
float areaScore = 0;
|
|
732
757
|
if (areaRatio > 0.05f && areaRatio < 0.7f) {
|
|
733
758
|
// Parabolic function that peaks at around 0.3 of the image area
|
|
734
759
|
areaScore = 1.0f - 4.0f * (areaRatio - 0.3f) * (areaRatio - 0.3f);
|
|
735
760
|
areaScore = Math.max(0, areaScore);
|
|
736
761
|
}
|
|
737
|
-
|
|
762
|
+
|
|
738
763
|
// Weighted combination of factors
|
|
739
764
|
return (0.5f * rectangularity) + (0.3f * aspectScore) + (0.2f * areaScore);
|
|
740
765
|
}
|
|
@@ -771,7 +796,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
771
796
|
data.put("scannedCode", rawValue);
|
|
772
797
|
data.put("format", BarcodeFormatHelper.barcodeFormatToString(format));
|
|
773
798
|
notifyListeners("barcodeScanned", data, true);
|
|
774
|
-
echo("Barcode " + rawValue + " scanned with format: "
|
|
799
|
+
echo("Barcode " + rawValue + " scanned with format: "
|
|
800
|
+
+ BarcodeFormatHelper.barcodeFormatToString(format));
|
|
775
801
|
}
|
|
776
802
|
}
|
|
777
803
|
}
|
|
@@ -792,7 +818,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
792
818
|
// Find the largest rectangle if multiple are detected
|
|
793
819
|
Rectangle largestRectangle = null;
|
|
794
820
|
float largestArea = 0;
|
|
795
|
-
|
|
821
|
+
|
|
796
822
|
for (Rectangle rectangle : rectangles) {
|
|
797
823
|
float area = getRectangleArea(rectangle);
|
|
798
824
|
if (area > largestArea) {
|
|
@@ -800,13 +826,13 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
800
826
|
largestRectangle = rectangle;
|
|
801
827
|
}
|
|
802
828
|
}
|
|
803
|
-
|
|
829
|
+
|
|
804
830
|
// Emit event with detected: true for the largest rectangle
|
|
805
831
|
JSObject data = new JSObject();
|
|
806
832
|
data.put("detected", true);
|
|
807
833
|
echo("Emitting rectangleDetected event with {detected: true} at " + currentTime);
|
|
808
834
|
notifyListeners("rectangleDetected", data, true);
|
|
809
|
-
|
|
835
|
+
|
|
810
836
|
// Update the last notification timestamp
|
|
811
837
|
lastRectangleNotificationTime = currentTime;
|
|
812
838
|
} else {
|
|
@@ -852,13 +878,13 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
852
878
|
for (DetectionResult result : recentDetectionResults) {
|
|
853
879
|
if (result != null && !result.getRectangles().isEmpty()) {
|
|
854
880
|
resultsWithRectangles++;
|
|
855
|
-
|
|
881
|
+
|
|
856
882
|
// Calculate quality score for this detection result
|
|
857
883
|
float qualityScore = 0;
|
|
858
884
|
for (Rectangle rect : result.getRectangles()) {
|
|
859
885
|
qualityScore += calculateRectangleQualityScore(rect, result.getImageSize());
|
|
860
886
|
}
|
|
861
|
-
|
|
887
|
+
|
|
862
888
|
// Factor in both number of rectangles and their quality
|
|
863
889
|
if (qualityScore > bestQualityScore) {
|
|
864
890
|
bestQualityScore = qualityScore;
|
|
@@ -945,8 +971,10 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
945
971
|
RectF srcRect = new RectF(0, 0, imageAnalysisWidth, imageAnalysisHeight);
|
|
946
972
|
RectF dstRect = new RectF(0, 0, viewWidth, viewHeight);
|
|
947
973
|
|
|
948
|
-
// Configure the matrix to scale and translate the source rectangle to fill the
|
|
949
|
-
//
|
|
974
|
+
// Configure the matrix to scale and translate the source rectangle to fill the
|
|
975
|
+
// destination,
|
|
976
|
+
// preserving aspect ratio. This is the equivalent of PreviewView's
|
|
977
|
+
// ScaleType.FILL_CENTER.
|
|
950
978
|
matrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.FILL);
|
|
951
979
|
|
|
952
980
|
// Apply the transformation to the barcode's bounding box
|
|
@@ -1028,21 +1056,21 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1028
1056
|
lastRectangleNotificationTime = 0;
|
|
1029
1057
|
lastRectangleProcessingTime = 0;
|
|
1030
1058
|
lastRectangleDetectionTime = 0;
|
|
1031
|
-
|
|
1059
|
+
|
|
1032
1060
|
// Clear detection histories and caches
|
|
1033
1061
|
enabledDetectionTypes.clear();
|
|
1034
1062
|
recentDetectionResults.clear();
|
|
1035
1063
|
detectionHistory.clear();
|
|
1036
1064
|
lastGoodRects = Collections.emptyList();
|
|
1037
|
-
|
|
1065
|
+
|
|
1038
1066
|
// Clear overlay rectangles
|
|
1039
1067
|
synchronized (overlayLock) {
|
|
1040
1068
|
currentBarcodeRects.clear();
|
|
1041
1069
|
currentRectangleRects.clear();
|
|
1042
1070
|
}
|
|
1043
|
-
|
|
1071
|
+
|
|
1044
1072
|
echo("All detection state variables have been reset");
|
|
1045
|
-
|
|
1073
|
+
|
|
1046
1074
|
call.resolve();
|
|
1047
1075
|
});
|
|
1048
1076
|
}
|
|
@@ -1062,7 +1090,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1062
1090
|
lastGoodRects = Collections.emptyList();
|
|
1063
1091
|
lastGoodRectangleTime = 0;
|
|
1064
1092
|
lastGoodDetectionResult = null;
|
|
1065
|
-
|
|
1093
|
+
|
|
1066
1094
|
// Clear previous detection overlays
|
|
1067
1095
|
if (detectionOverlay != null) {
|
|
1068
1096
|
getActivity().runOnUiThread(() -> {
|
|
@@ -1092,10 +1120,11 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1092
1120
|
// Get value as double and convert to float
|
|
1093
1121
|
double paddingValue = call.getData().getDouble("paddingRatio");
|
|
1094
1122
|
float paddingRatioFloat = (float) paddingValue;
|
|
1095
|
-
|
|
1123
|
+
|
|
1096
1124
|
// Validate that paddingRatio is between 0 and 1
|
|
1097
1125
|
if (paddingRatioFloat < 0 || paddingRatioFloat > 1) {
|
|
1098
|
-
echo("Invalid paddingRatio value: " + paddingRatioFloat
|
|
1126
|
+
echo("Invalid paddingRatio value: " + paddingRatioFloat
|
|
1127
|
+
+ " - must be between 0 and 1. Using default 0.01f");
|
|
1099
1128
|
paddingRatio = 0.01f;
|
|
1100
1129
|
} else {
|
|
1101
1130
|
paddingRatio = paddingRatioFloat;
|
|
@@ -1119,7 +1148,9 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1119
1148
|
}
|
|
1120
1149
|
|
|
1121
1150
|
if (enabledDetectionTypes.contains("barcode") || enabledDetectionTypes.contains("businessCard")) {
|
|
1122
|
-
|
|
1151
|
+
getActivity().runOnUiThread(() -> {
|
|
1152
|
+
setupDetectionOverlay();
|
|
1153
|
+
});
|
|
1123
1154
|
}
|
|
1124
1155
|
|
|
1125
1156
|
call.resolve(result);
|
|
@@ -1173,7 +1204,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1173
1204
|
|
|
1174
1205
|
// Decode the image into a Bitmap
|
|
1175
1206
|
options.inJustDecodeBounds = false;
|
|
1176
|
-
Bitmap originalBitmap = BitmapFactory.decodeByteArray(originalBytes, 0,
|
|
1207
|
+
Bitmap originalBitmap = BitmapFactory.decodeByteArray(originalBytes, 0,
|
|
1208
|
+
originalBytes.length, options);
|
|
1177
1209
|
if (originalBitmap == null) {
|
|
1178
1210
|
call.reject("Failed to decode captured image.");
|
|
1179
1211
|
return;
|
|
@@ -1189,13 +1221,12 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1189
1221
|
// Scale the overlay rect from preview coordinates to bitmap coordinates
|
|
1190
1222
|
float scaleX = (float) originalBitmap.getWidth() / previewView.getWidth();
|
|
1191
1223
|
float scaleY = (float) originalBitmap.getHeight() / previewView.getHeight();
|
|
1192
|
-
|
|
1224
|
+
|
|
1193
1225
|
RectF scaledRectF = new RectF(
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
);
|
|
1226
|
+
largestRectF.left * scaleX,
|
|
1227
|
+
largestRectF.top * scaleY,
|
|
1228
|
+
largestRectF.right * scaleX,
|
|
1229
|
+
largestRectF.bottom * scaleY);
|
|
1199
1230
|
|
|
1200
1231
|
// **FIX**: Un-mirror the coordinates for cropping if using the front camera
|
|
1201
1232
|
if (currentLensFacing == CameraSelector.LENS_FACING_FRONT) {
|
|
@@ -1204,35 +1235,34 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1204
1235
|
scaledRectF.left = bitmapWidth - scaledRectF.right;
|
|
1205
1236
|
scaledRectF.right = bitmapWidth - left;
|
|
1206
1237
|
}
|
|
1207
|
-
|
|
1238
|
+
|
|
1208
1239
|
// Calculate 1% padding based on the full image dimensions
|
|
1209
1240
|
float paddingX = originalBitmap.getWidth() * paddingRatio;
|
|
1210
1241
|
float paddingY = originalBitmap.getHeight() * paddingRatio;
|
|
1211
|
-
|
|
1242
|
+
|
|
1212
1243
|
// Apply 2% padding to the rectangle while keeping it within image bounds
|
|
1213
1244
|
RectF paddedRectF = new RectF(
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1245
|
+
Math.max(0, scaledRectF.left - paddingX),
|
|
1246
|
+
Math.max(0, scaledRectF.top - paddingY),
|
|
1247
|
+
Math.min(originalBitmap.getWidth(), scaledRectF.right + paddingX),
|
|
1248
|
+
Math.min(originalBitmap.getHeight(), scaledRectF.bottom + paddingY));
|
|
1249
|
+
|
|
1220
1250
|
// Convert scaled RectF to Rect (for cropping)
|
|
1221
1251
|
Rect largestRect = new Rect(
|
|
1222
1252
|
Math.round(paddedRectF.left),
|
|
1223
1253
|
Math.round(paddedRectF.top),
|
|
1224
1254
|
Math.round(paddedRectF.right),
|
|
1225
|
-
Math.round(paddedRectF.bottom)
|
|
1226
|
-
);
|
|
1255
|
+
Math.round(paddedRectF.bottom));
|
|
1227
1256
|
|
|
1228
1257
|
// Log dimensions for debugging
|
|
1229
|
-
echo("📏 Cropping - Preview: " + previewView.getWidth() + "x" + previewView.getHeight()
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1258
|
+
echo("📏 Cropping - Preview: " + previewView.getWidth() + "x" + previewView.getHeight()
|
|
1259
|
+
+
|
|
1260
|
+
", Bitmap: " + originalBitmap.getWidth() + "x" + originalBitmap.getHeight() +
|
|
1261
|
+
", Scale: " + scaleX + "x" + scaleY);
|
|
1262
|
+
echo("🔍 Original Rect: " + largestRectF.toString() +
|
|
1263
|
+
" → Scaled Rect: " + scaledRectF.toString() +
|
|
1264
|
+
" → Padded Rect: " + paddedRectF.toString());
|
|
1265
|
+
|
|
1236
1266
|
// Crop the image based on the largest rectangle
|
|
1237
1267
|
processedBitmap = cropBitmap(originalBitmap, largestRect);
|
|
1238
1268
|
} else {
|
|
@@ -1303,12 +1333,13 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1303
1333
|
}
|
|
1304
1334
|
|
|
1305
1335
|
// **SAMPLE METHOD**: Rotate bitmap if needed (exact sample logic)
|
|
1306
|
-
// Adjust the image's rotation based on its EXIF orientation
|
|
1336
|
+
// Adjust the image's rotation based on its EXIF orientation
|
|
1307
1337
|
private Bitmap rotateBitmap(Bitmap bitmap, int rotationDegrees) {
|
|
1308
1338
|
if (rotationDegrees != 0) {
|
|
1309
1339
|
Matrix matrix = new Matrix();
|
|
1310
1340
|
matrix.postRotate(rotationDegrees);
|
|
1311
|
-
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,
|
|
1341
|
+
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,
|
|
1342
|
+
true);
|
|
1312
1343
|
if (rotatedBitmap != bitmap) {
|
|
1313
1344
|
bitmap.recycle(); // Recycle original bitmap if a new one was created
|
|
1314
1345
|
}
|
|
@@ -1495,7 +1526,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1495
1526
|
case ImageFormat.JPEG -> toBitmapFromJpegFormatImage(imageProxy);
|
|
1496
1527
|
case ImageFormat.YUV_420_888 -> toBitmapFromYUVFormatImage(imageProxy);
|
|
1497
1528
|
default ->
|
|
1498
|
-
|
|
1529
|
+
throw new UnsupportedOperationException(
|
|
1530
|
+
"ImageProxy has unsupported image format: " + imageProxy.getFormat());
|
|
1499
1531
|
};
|
|
1500
1532
|
}
|
|
1501
1533
|
|
|
@@ -1506,13 +1538,14 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1506
1538
|
byte[] bytes = new byte[buffer.remaining()];
|
|
1507
1539
|
buffer.get(bytes);
|
|
1508
1540
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
|
1509
|
-
return rotateBitmap(cropBitmap(bitmap, imageProxy.getCropRect()),
|
|
1541
|
+
return rotateBitmap(cropBitmap(bitmap, imageProxy.getCropRect()),
|
|
1542
|
+
imageProxy.getImageInfo().getRotationDegrees());
|
|
1510
1543
|
}
|
|
1511
1544
|
|
|
1512
1545
|
// **SAMPLE METHOD**: Handle YUV format images (exact sample logic)
|
|
1513
1546
|
private Bitmap toBitmapFromYUVFormatImage(ImageProxy imageProxy) {
|
|
1514
1547
|
byte[] nv21 = toNV21ByteArray(imageProxy);
|
|
1515
|
-
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21,
|
|
1548
|
+
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21,
|
|
1516
1549
|
imageProxy.getCropRect().width(), imageProxy.getCropRect().height(), null);
|
|
1517
1550
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
1518
1551
|
yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 80, out);
|
|
@@ -1528,13 +1561,13 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1528
1561
|
int ySize = cropRect.width() * cropRect.height();
|
|
1529
1562
|
int uvSize = cropRect.width() * cropRect.height() / 4;
|
|
1530
1563
|
byte[] nv21 = new byte[ySize + uvSize * 2];
|
|
1531
|
-
|
|
1564
|
+
|
|
1532
1565
|
ImageProxy.PlaneProxy[] planes = imageProxy.getPlanes();
|
|
1533
1566
|
for (int planeIndex = 0; planeIndex < planes.length; planeIndex++) {
|
|
1534
1567
|
ImageProxy.PlaneProxy plane = planes[planeIndex];
|
|
1535
1568
|
int outputStride;
|
|
1536
1569
|
int outputOffset;
|
|
1537
|
-
|
|
1570
|
+
|
|
1538
1571
|
switch (planeIndex) {
|
|
1539
1572
|
case 0:
|
|
1540
1573
|
outputStride = 1;
|
|
@@ -1551,24 +1584,23 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1551
1584
|
default:
|
|
1552
1585
|
continue;
|
|
1553
1586
|
}
|
|
1554
|
-
|
|
1587
|
+
|
|
1555
1588
|
ByteBuffer planeBuffer = plane.getBuffer();
|
|
1556
1589
|
int rowStride = plane.getRowStride();
|
|
1557
1590
|
int pixelStride = plane.getPixelStride();
|
|
1558
|
-
|
|
1559
|
-
Rect planeCrop = (planeIndex == 0) ? cropRect
|
|
1560
|
-
new Rect(cropRect.left / 2, cropRect.top / 2, cropRect.right / 2, cropRect.bottom / 2);
|
|
1561
|
-
|
|
1591
|
+
|
|
1592
|
+
Rect planeCrop = (planeIndex == 0) ? cropRect
|
|
1593
|
+
: new Rect(cropRect.left / 2, cropRect.top / 2, cropRect.right / 2, cropRect.bottom / 2);
|
|
1594
|
+
|
|
1562
1595
|
int planeWidth = planeCrop.width();
|
|
1563
1596
|
int planeHeight = planeCrop.height();
|
|
1564
1597
|
byte[] rowBuffer = new byte[plane.getRowStride()];
|
|
1565
|
-
|
|
1566
|
-
int rowLength = (pixelStride == 1 && outputStride == 1) ? planeWidth :
|
|
1567
|
-
|
|
1568
|
-
|
|
1598
|
+
|
|
1599
|
+
int rowLength = (pixelStride == 1 && outputStride == 1) ? planeWidth : (planeWidth - 1) * pixelStride + 1;
|
|
1600
|
+
|
|
1569
1601
|
for (int row = 0; row < planeHeight; row++) {
|
|
1570
1602
|
planeBuffer.position((row + planeCrop.top) * rowStride + planeCrop.left * pixelStride);
|
|
1571
|
-
|
|
1603
|
+
|
|
1572
1604
|
if (pixelStride == 1 && outputStride == 1) {
|
|
1573
1605
|
planeBuffer.get(nv21, outputOffset, rowLength);
|
|
1574
1606
|
outputOffset += rowLength;
|
|
@@ -1581,7 +1613,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1581
1613
|
}
|
|
1582
1614
|
}
|
|
1583
1615
|
}
|
|
1584
|
-
|
|
1616
|
+
|
|
1585
1617
|
return nv21;
|
|
1586
1618
|
}
|
|
1587
1619
|
|
|
@@ -1599,7 +1631,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1599
1631
|
case ExifInterface.ORIENTATION_ROTATE_270:
|
|
1600
1632
|
return 270;
|
|
1601
1633
|
default:
|
|
1602
|
-
return 0;
|
|
1634
|
+
return 0; // No rotation
|
|
1603
1635
|
}
|
|
1604
1636
|
} catch (IOException e) {
|
|
1605
1637
|
e.printStackTrace();
|
|
@@ -1607,7 +1639,8 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1607
1639
|
return 0;
|
|
1608
1640
|
}
|
|
1609
1641
|
|
|
1610
|
-
private void displayDetectedRectangles(DetectionResult detectionResult, int imageWidth, int imageHeight,
|
|
1642
|
+
private void displayDetectedRectangles(DetectionResult detectionResult, int imageWidth, int imageHeight,
|
|
1643
|
+
int rotationDegrees) {
|
|
1611
1644
|
if (detectionOverlay == null || previewView == null) {
|
|
1612
1645
|
return;
|
|
1613
1646
|
}
|
|
@@ -1623,50 +1656,47 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1623
1656
|
|
|
1624
1657
|
echo("🔄 Sample coordinate transformation - Preview: " + previewWidth + "x" + previewHeight +
|
|
1625
1658
|
", Image: " + imageWidth + "x" + imageHeight + ", Rotation: " + rotationDegrees);
|
|
1626
|
-
|
|
1659
|
+
|
|
1627
1660
|
// Create preview size from actual preview dimensions
|
|
1628
1661
|
Size previewSize = new Size(previewWidth, previewHeight);
|
|
1629
|
-
|
|
1662
|
+
|
|
1630
1663
|
// Transform rectangles using sample's exact logic
|
|
1631
1664
|
RectF previewRectInImage = previewRectInImage(detectionResult.getImageSize(), previewSize);
|
|
1632
1665
|
List<Rectangle> transformedRectangles = new ArrayList<>();
|
|
1633
|
-
|
|
1666
|
+
|
|
1634
1667
|
for (Rectangle rectangle : detectionResult.getRectangles()) {
|
|
1635
1668
|
// Use separate scale ratios for width and height to fix vertical shifting
|
|
1636
1669
|
float scaleRatioWidth = previewSize.getWidth() / previewRectInImage.width();
|
|
1637
1670
|
float scaleRatioHeight = previewSize.getHeight() / previewRectInImage.height();
|
|
1638
|
-
|
|
1671
|
+
|
|
1639
1672
|
// Use the correct scale ratio for each dimension
|
|
1640
1673
|
android.graphics.Point transformedTopLeft = new android.graphics.Point(
|
|
1641
1674
|
Math.round((rectangle.getTopLeft().x - previewRectInImage.left) * scaleRatioWidth),
|
|
1642
|
-
Math.round((rectangle.getTopLeft().y - previewRectInImage.top) * scaleRatioHeight)
|
|
1643
|
-
);
|
|
1675
|
+
Math.round((rectangle.getTopLeft().y - previewRectInImage.top) * scaleRatioHeight));
|
|
1644
1676
|
android.graphics.Point transformedTopRight = new android.graphics.Point(
|
|
1645
1677
|
Math.round((rectangle.getTopRight().x - previewRectInImage.left) * scaleRatioWidth),
|
|
1646
|
-
Math.round((rectangle.getTopRight().y - previewRectInImage.top) * scaleRatioHeight)
|
|
1647
|
-
);
|
|
1678
|
+
Math.round((rectangle.getTopRight().y - previewRectInImage.top) * scaleRatioHeight));
|
|
1648
1679
|
android.graphics.Point transformedBottomLeft = new android.graphics.Point(
|
|
1649
1680
|
Math.round((rectangle.getBottomLeft().x - previewRectInImage.left) * scaleRatioWidth),
|
|
1650
|
-
Math.round((rectangle.getBottomLeft().y - previewRectInImage.top) * scaleRatioHeight)
|
|
1651
|
-
);
|
|
1681
|
+
Math.round((rectangle.getBottomLeft().y - previewRectInImage.top) * scaleRatioHeight));
|
|
1652
1682
|
android.graphics.Point transformedBottomRight = new android.graphics.Point(
|
|
1653
1683
|
Math.round((rectangle.getBottomRight().x - previewRectInImage.left) * scaleRatioWidth),
|
|
1654
|
-
Math.round((rectangle.getBottomRight().y - previewRectInImage.top) * scaleRatioHeight)
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
transformedRectangles.add(new Rectangle(transformedTopLeft, transformedTopRight,
|
|
1684
|
+
Math.round((rectangle.getBottomRight().y - previewRectInImage.top) * scaleRatioHeight));
|
|
1685
|
+
|
|
1686
|
+
transformedRectangles.add(new Rectangle(transformedTopLeft, transformedTopRight,
|
|
1658
1687
|
transformedBottomLeft, transformedBottomRight));
|
|
1659
1688
|
}
|
|
1660
|
-
|
|
1689
|
+
|
|
1661
1690
|
final List<RectF> overlayRects = new ArrayList<>();
|
|
1662
1691
|
for (Rectangle rect : transformedRectangles) {
|
|
1663
1692
|
// Create bounding box from transformed corner points
|
|
1664
1693
|
RectF transformedRect = getRectF(rect);
|
|
1665
1694
|
overlayRects.add(transformedRect);
|
|
1666
|
-
|
|
1667
|
-
echo("✅ Sample transformation: " +
|
|
1668
|
-
"Original corners: (" + detectionResult.getRectangles().get(overlayRects.size()-1).getTopLeft().x
|
|
1669
|
-
|
|
1695
|
+
|
|
1696
|
+
echo("✅ Sample transformation: " +
|
|
1697
|
+
"Original corners: (" + detectionResult.getRectangles().get(overlayRects.size() - 1).getTopLeft().x
|
|
1698
|
+
+ "," +
|
|
1699
|
+
detectionResult.getRectangles().get(overlayRects.size() - 1).getTopLeft().y + ") → " +
|
|
1670
1700
|
"Preview rect: " + transformedRect.toString());
|
|
1671
1701
|
}
|
|
1672
1702
|
|
|
@@ -1679,17 +1709,18 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1679
1709
|
@NonNull
|
|
1680
1710
|
private RectF getRectF(Rectangle rect) {
|
|
1681
1711
|
float minX = Math.min(Math.min(rect.getTopLeft().x, rect.getTopRight().x),
|
|
1682
|
-
|
|
1712
|
+
Math.min(rect.getBottomLeft().x, rect.getBottomRight().x));
|
|
1683
1713
|
float minY = Math.min(Math.min(rect.getTopLeft().y, rect.getTopRight().y),
|
|
1684
|
-
|
|
1714
|
+
Math.min(rect.getBottomLeft().y, rect.getBottomRight().y));
|
|
1685
1715
|
float maxX = Math.max(Math.max(rect.getTopLeft().x, rect.getTopRight().x),
|
|
1686
|
-
|
|
1716
|
+
Math.max(rect.getBottomLeft().x, rect.getBottomRight().x));
|
|
1687
1717
|
float maxY = Math.max(Math.max(rect.getTopLeft().y, rect.getTopRight().y),
|
|
1688
|
-
|
|
1718
|
+
Math.max(rect.getBottomLeft().y, rect.getBottomRight().y));
|
|
1689
1719
|
|
|
1690
1720
|
RectF boundingBox = new RectF(minX, minY, maxX, maxY);
|
|
1691
1721
|
|
|
1692
|
-
// **FIX**: Horizontally flip the coordinates if the front camera is active to
|
|
1722
|
+
// **FIX**: Horizontally flip the coordinates if the front camera is active to
|
|
1723
|
+
// match the mirrored preview
|
|
1693
1724
|
if (currentLensFacing == CameraSelector.LENS_FACING_FRONT) {
|
|
1694
1725
|
float left = boundingBox.left;
|
|
1695
1726
|
boundingBox.left = previewView.getWidth() - boundingBox.right;
|
|
@@ -1699,18 +1730,19 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
1699
1730
|
return boundingBox;
|
|
1700
1731
|
}
|
|
1701
1732
|
|
|
1702
|
-
// **SAMPLE METHOD**: Calculate preview rect in image coordinates (exact sample
|
|
1733
|
+
// **SAMPLE METHOD**: Calculate preview rect in image coordinates (exact sample
|
|
1734
|
+
// logic)
|
|
1703
1735
|
private RectF previewRectInImage(Size imageSize, Size previewSize) {
|
|
1704
1736
|
float widthRatio = (float) previewSize.getWidth() / imageSize.getWidth();
|
|
1705
1737
|
float heightRatio = (float) previewSize.getHeight() / imageSize.getHeight();
|
|
1706
|
-
|
|
1738
|
+
|
|
1707
1739
|
if (widthRatio > heightRatio) {
|
|
1708
1740
|
// Preview wider - image cropped top/bottom
|
|
1709
1741
|
float previewHeightInImage = previewSize.getHeight() / widthRatio;
|
|
1710
1742
|
float topOffset = (imageSize.getHeight() - previewHeightInImage) / 2;
|
|
1711
1743
|
return new RectF(0f, topOffset, imageSize.getWidth(), topOffset + previewHeightInImage);
|
|
1712
1744
|
} else {
|
|
1713
|
-
// Preview taller - image cropped left/right
|
|
1745
|
+
// Preview taller - image cropped left/right
|
|
1714
1746
|
float previewWidthInImage = previewSize.getWidth() / heightRatio;
|
|
1715
1747
|
float leftOffset = (imageSize.getWidth() - previewWidthInImage) / 2;
|
|
1716
1748
|
return new RectF(leftOffset, 0f, leftOffset + previewWidthInImage, imageSize.getHeight());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scr2em/capacitor-scanner",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.20",
|
|
4
4
|
"description": "scan codes",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"unpkg": "dist/plugin.js",
|
|
9
9
|
"files": [
|
|
10
10
|
"android/src/main/",
|
|
11
|
+
"android/libs/rectangle-detector-release.aar",
|
|
11
12
|
"android/build.gradle",
|
|
12
13
|
"dist/",
|
|
13
14
|
"ios/Sources",
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
plugins {
|
|
2
|
-
id 'com.android.library'
|
|
3
|
-
id 'kotlin-android'
|
|
4
|
-
id 'maven-publish'
|
|
5
|
-
id 'org.jetbrains.dokka'
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
group = 'com.github.smartbank-inc'
|
|
9
|
-
|
|
10
|
-
android {
|
|
11
|
-
namespace "com.smartbank.rectangledetector"
|
|
12
|
-
compileSdkVersion compile_sdk_version
|
|
13
|
-
|
|
14
|
-
defaultConfig {
|
|
15
|
-
minSdkVersion 23
|
|
16
|
-
targetSdkVersion target_sdk_version
|
|
17
|
-
versionCode version_code
|
|
18
|
-
versionName version_name
|
|
19
|
-
|
|
20
|
-
consumerProguardFiles "consumer-rules.pro"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
buildTypes {
|
|
24
|
-
release {
|
|
25
|
-
minifyEnabled false
|
|
26
|
-
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
compileOptions {
|
|
30
|
-
sourceCompatibility JavaVersion.VERSION_17
|
|
31
|
-
targetCompatibility JavaVersion.VERSION_17
|
|
32
|
-
}
|
|
33
|
-
kotlinOptions {
|
|
34
|
-
jvmTarget = '17'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
repositories {
|
|
39
|
-
google()
|
|
40
|
-
mavenCentral()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
dependencies {
|
|
44
|
-
implementation 'org.opencv:opencv:4.12.0'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
afterEvaluate {
|
|
48
|
-
tasks.register('androidSourcesJar', Jar) {
|
|
49
|
-
archiveClassifier.convention('sources')
|
|
50
|
-
archiveClassifier.set('sources')
|
|
51
|
-
from android.sourceSets.main.java.source
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
tasks.register('androidJavadocsJar', Jar) {
|
|
55
|
-
dependsOn dokkaJavadoc
|
|
56
|
-
archiveClassifier.convention('javadoc')
|
|
57
|
-
archiveClassifier.set('javadoc')
|
|
58
|
-
from dokkaJavadoc.outputDirectory
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
publishing {
|
|
62
|
-
publications {
|
|
63
|
-
release(MavenPublication) {
|
|
64
|
-
from components.release
|
|
65
|
-
groupId = 'com.github.smartbank-inc'
|
|
66
|
-
artifactId = 'android-rectangle-detector'
|
|
67
|
-
version = version_name
|
|
68
|
-
artifact androidSourcesJar
|
|
69
|
-
artifact androidJavadocsJar
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
File without changes
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Add project specific ProGuard rules here.
|
|
2
|
-
# You can control the set of applied configuration files using the
|
|
3
|
-
# proguardFiles setting in build.gradle.
|
|
4
|
-
#
|
|
5
|
-
# For more details, see
|
|
6
|
-
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
7
|
-
|
|
8
|
-
# If your project uses WebView with JS, uncomment the following
|
|
9
|
-
# and specify the fully qualified class name to the JavaScript interface
|
|
10
|
-
# class:
|
|
11
|
-
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
12
|
-
# public *;
|
|
13
|
-
#}
|
|
14
|
-
|
|
15
|
-
# Uncomment this to preserve the line number information for
|
|
16
|
-
# debugging stack traces.
|
|
17
|
-
#-keepattributes SourceFile,LineNumberTable
|
|
18
|
-
|
|
19
|
-
# If you keep the line number information, uncomment this to
|
|
20
|
-
# hide the original source file name.
|
|
21
|
-
#-renamesourcefileattribute SourceFile
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|