react-native-rectangle-doc-scanner 3.240.0 → 3.241.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.
@@ -7,6 +7,7 @@ import android.graphics.Bitmap
7
7
  import android.graphics.BitmapFactory
8
8
  import android.graphics.Matrix
9
9
  import android.graphics.SurfaceTexture
10
+ import android.graphics.Rect
10
11
  import android.graphics.RectF
11
12
  import android.graphics.ImageFormat
12
13
  import android.hardware.camera2.CameraCaptureSession
@@ -68,6 +69,7 @@ class CameraController(
68
69
  .enableMultipleObjects()
69
70
  .build()
70
71
  )
72
+ private var lastRefineTimestamp = 0L
71
73
 
72
74
  var onFrameAnalyzed: ((Rectangle?, Int, Int) -> Unit)? = null
73
75
 
@@ -379,13 +381,11 @@ class CameraController(
379
381
  val box = obj.boundingBox
380
382
  box.width() * box.height()
381
383
  }
382
- val rectangle = best?.boundingBox?.let { box ->
383
- Rectangle(
384
- Point(box.left.toDouble(), box.top.toDouble()),
385
- Point(box.right.toDouble(), box.top.toDouble()),
386
- Point(box.left.toDouble(), box.bottom.toDouble()),
387
- Point(box.right.toDouble(), box.bottom.toDouble())
388
- )
384
+ val mlBox = best?.boundingBox
385
+ val rectangle = when {
386
+ mlBox == null -> null
387
+ shouldRefineWithOpenCv() -> refineWithOpenCv(image, rotationDegrees, mlBox) ?: boxToRectangle(mlBox)
388
+ else -> boxToRectangle(mlBox)
389
389
  }
390
390
 
391
391
  val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) image.height else image.width
@@ -485,10 +485,7 @@ class CameraController(
485
485
 
486
486
  val matrix = Matrix()
487
487
  bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
488
- matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
489
-
490
- val scale = max(viewWidth / bufferWidth, viewHeight / bufferHeight)
491
- matrix.postScale(scale, scale, centerX, centerY)
488
+ matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.FILL)
492
489
  matrix.postRotate(rotation.toFloat(), centerX, centerY)
493
490
  previewView.setTransform(matrix)
494
491
  }
@@ -529,6 +526,100 @@ class CameraController(
529
526
  return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
530
527
  }
531
528
 
529
+ private fun shouldRefineWithOpenCv(): Boolean {
530
+ val now = System.currentTimeMillis()
531
+ if (now - lastRefineTimestamp < 200) {
532
+ return false
533
+ }
534
+ lastRefineTimestamp = now
535
+ return true
536
+ }
537
+
538
+ private fun refineWithOpenCv(image: Image, rotationDegrees: Int, mlBox: Rect): Rectangle? {
539
+ return try {
540
+ val nv21 = imageToNv21(image)
541
+ val openCvRect = DocumentDetector.detectRectangleInYUV(nv21, image.width, image.height, rotationDegrees)
542
+ if (openCvRect == null) {
543
+ null
544
+ } else {
545
+ val openRectBounds = rectangleBounds(openCvRect)
546
+ if (computeIoU(openRectBounds, mlBox) >= 0.2f) openCvRect else null
547
+ }
548
+ } catch (e: Exception) {
549
+ Log.w(TAG, "[CAMERA2] OpenCV refine failed", e)
550
+ null
551
+ }
552
+ }
553
+
554
+ private fun boxToRectangle(box: Rect): Rectangle {
555
+ return Rectangle(
556
+ Point(box.left.toDouble(), box.top.toDouble()),
557
+ Point(box.right.toDouble(), box.top.toDouble()),
558
+ Point(box.left.toDouble(), box.bottom.toDouble()),
559
+ Point(box.right.toDouble(), box.bottom.toDouble())
560
+ )
561
+ }
562
+
563
+ private fun rectangleBounds(rectangle: Rectangle): Rect {
564
+ val left = listOf(rectangle.topLeft.x, rectangle.bottomLeft.x, rectangle.topRight.x, rectangle.bottomRight.x).minOrNull() ?: 0.0
565
+ val right = listOf(rectangle.topLeft.x, rectangle.bottomLeft.x, rectangle.topRight.x, rectangle.bottomRight.x).maxOrNull() ?: 0.0
566
+ val top = listOf(rectangle.topLeft.y, rectangle.bottomLeft.y, rectangle.topRight.y, rectangle.bottomRight.y).minOrNull() ?: 0.0
567
+ val bottom = listOf(rectangle.topLeft.y, rectangle.bottomLeft.y, rectangle.topRight.y, rectangle.bottomRight.y).maxOrNull() ?: 0.0
568
+ return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
569
+ }
570
+
571
+ private fun computeIoU(a: Rect, b: Rect): Float {
572
+ val left = max(a.left, b.left)
573
+ val top = max(a.top, b.top)
574
+ val right = minOf(a.right, b.right)
575
+ val bottom = minOf(a.bottom, b.bottom)
576
+ if (right <= left || bottom <= top) return 0f
577
+ val intersection = (right - left).toFloat() * (bottom - top).toFloat()
578
+ val union = (a.width() * a.height() + b.width() * b.height() - intersection).toFloat()
579
+ return if (union <= 0f) 0f else intersection / union
580
+ }
581
+
582
+ private fun imageToNv21(image: Image): ByteArray {
583
+ val width = image.width
584
+ val height = image.height
585
+ val ySize = width * height
586
+ val uvSize = width * height / 2
587
+ val nv21 = ByteArray(ySize + uvSize)
588
+
589
+ val yBuffer = image.planes[0].buffer
590
+ val uBuffer = image.planes[1].buffer
591
+ val vBuffer = image.planes[2].buffer
592
+
593
+ val yRowStride = image.planes[0].rowStride
594
+ val yPixelStride = image.planes[0].pixelStride
595
+ var outputOffset = 0
596
+ for (row in 0 until height) {
597
+ var inputOffset = row * yRowStride
598
+ for (col in 0 until width) {
599
+ nv21[outputOffset++] = yBuffer.get(inputOffset)
600
+ inputOffset += yPixelStride
601
+ }
602
+ }
603
+
604
+ val uvRowStride = image.planes[1].rowStride
605
+ val uvPixelStride = image.planes[1].pixelStride
606
+ val vRowStride = image.planes[2].rowStride
607
+ val vPixelStride = image.planes[2].pixelStride
608
+ val uvHeight = height / 2
609
+ val uvWidth = width / 2
610
+ for (row in 0 until uvHeight) {
611
+ var uInputOffset = row * uvRowStride
612
+ var vInputOffset = row * vRowStride
613
+ for (col in 0 until uvWidth) {
614
+ nv21[outputOffset++] = vBuffer.get(vInputOffset)
615
+ nv21[outputOffset++] = uBuffer.get(uInputOffset)
616
+ uInputOffset += uvPixelStride
617
+ vInputOffset += vPixelStride
618
+ }
619
+ }
620
+
621
+ return nv21
622
+ }
532
623
  private fun hasCameraPermission(): Boolean {
533
624
  return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
534
625
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.240.0",
3
+ "version": "3.241.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",