react-native-rectangle-doc-scanner 10.46.0 → 10.47.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.
@@ -246,8 +246,20 @@ class CameraController(
246
246
  }
247
247
  }
248
248
 
249
+ // Frame rate throttling for better performance
250
+ private var lastAnalysisTimestamp: Long = 0
251
+ private val minAnalysisIntervalMs: Long = 150 // Process at most ~6-7 fps instead of 30 fps
252
+
249
253
  @androidx.annotation.OptIn(ExperimentalGetImage::class)
250
254
  private fun analyzeImage(imageProxy: ImageProxy) {
255
+ // Throttle frame processing to improve performance
256
+ val currentTime = System.currentTimeMillis()
257
+ if (currentTime - lastAnalysisTimestamp < minAnalysisIntervalMs) {
258
+ imageProxy.close()
259
+ return
260
+ }
261
+ lastAnalysisTimestamp = currentTime
262
+
251
263
  val mediaImage = imageProxy.image
252
264
  if (mediaImage == null) {
253
265
  imageProxy.close()
@@ -533,10 +545,23 @@ class CameraController(
533
545
  val centerX = viewWidth / 2f
534
546
  val centerY = viewHeight / 2f
535
547
 
536
- // CameraX already handles rotation via targetRotation
537
- // The buffer is already in the correct orientation
538
- val rotatedBufferWidth = bufferWidth
539
- val rotatedBufferHeight = bufferHeight
548
+ // For sensor=0 (tablet landscape), we need to manually rotate the buffer
549
+ // CameraX only handles rotation automatically for sensor=90 (phone portrait)
550
+ if (sensorOrientation == 0 && displayRotationDegrees != 0) {
551
+ matrix.postRotate(displayRotationDegrees.toFloat(), centerX, centerY)
552
+ }
553
+
554
+ // Calculate rotated buffer dimensions
555
+ val rotatedBufferWidth = if (sensorOrientation == 0 && (displayRotationDegrees == 90 || displayRotationDegrees == 270)) {
556
+ bufferHeight
557
+ } else {
558
+ bufferWidth
559
+ }
560
+ val rotatedBufferHeight = if (sensorOrientation == 0 && (displayRotationDegrees == 90 || displayRotationDegrees == 270)) {
561
+ bufferWidth
562
+ } else {
563
+ bufferHeight
564
+ }
540
565
 
541
566
  // Scale to fill the view while maintaining aspect ratio (center-crop).
542
567
  val scaleX = viewWidth.toFloat() / rotatedBufferWidth.toFloat()
@@ -694,14 +694,16 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
694
694
 
695
695
  /**
696
696
  * Overlay view for drawing detected rectangle
697
+ * Performance optimized: draws only corner points instead of filled polygon
697
698
  */
698
699
  private class OverlayView(context: Context) : View(context) {
699
700
  private var rectangle: Rectangle? = null
700
701
  private var overlayColor: Int = Color.parseColor("#80FFFFFF")
701
- private val paint = Paint().apply {
702
+ private val cornerPaint = Paint().apply {
702
703
  style = Paint.Style.FILL
703
- xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
704
+ isAntiAlias = true
704
705
  }
706
+ private val cornerRadius = 12f // Circle radius for corner points
705
707
 
706
708
  fun setRectangle(rect: Rectangle?, color: Int) {
707
709
  this.rectangle = rect
@@ -713,18 +715,13 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
713
715
  super.onDraw(canvas)
714
716
 
715
717
  rectangle?.let { rect ->
716
- paint.color = overlayColor
717
-
718
- // Draw the rectangle overlay (simplified - just a filled polygon)
719
- val path = android.graphics.Path().apply {
720
- moveTo(rect.topLeft.x.toFloat(), rect.topLeft.y.toFloat())
721
- lineTo(rect.topRight.x.toFloat(), rect.topRight.y.toFloat())
722
- lineTo(rect.bottomRight.x.toFloat(), rect.bottomRight.y.toFloat())
723
- lineTo(rect.bottomLeft.x.toFloat(), rect.bottomLeft.y.toFloat())
724
- close()
725
- }
718
+ cornerPaint.color = overlayColor
726
719
 
727
- canvas.drawPath(path, paint)
720
+ // Draw only corner points for better performance
721
+ canvas.drawCircle(rect.topLeft.x.toFloat(), rect.topLeft.y.toFloat(), cornerRadius, cornerPaint)
722
+ canvas.drawCircle(rect.topRight.x.toFloat(), rect.topRight.y.toFloat(), cornerRadius, cornerPaint)
723
+ canvas.drawCircle(rect.bottomLeft.x.toFloat(), rect.bottomLeft.y.toFloat(), cornerRadius, cornerPaint)
724
+ canvas.drawCircle(rect.bottomRight.x.toFloat(), rect.bottomRight.y.toFloat(), cornerRadius, cornerPaint)
728
725
  }
729
726
  }
730
727
  }
@@ -472,6 +472,7 @@ class DocumentDetector {
472
472
 
473
473
  /**
474
474
  * Evaluate rectangle quality in view coordinates (closer to iOS behavior).
475
+ * Improved thresholds for better detection accuracy.
475
476
  */
476
477
  fun evaluateRectangleQualityInView(
477
478
  rectangle: Rectangle,
@@ -483,18 +484,39 @@ class DocumentDetector {
483
484
  }
484
485
 
485
486
  val minDim = kotlin.math.min(viewWidth.toDouble(), viewHeight.toDouble())
486
- val angleThreshold = max(140.0, minDim * 0.18)
487
+
488
+ // Calculate actual edge lengths for better angle evaluation
489
+ val topEdgeLength = distance(rectangle.topLeft, rectangle.topRight)
490
+ val bottomEdgeLength = distance(rectangle.bottomLeft, rectangle.bottomRight)
491
+ val leftEdgeLength = distance(rectangle.topLeft, rectangle.bottomLeft)
492
+ val rightEdgeLength = distance(rectangle.topRight, rectangle.bottomRight)
493
+
494
+ // Use more lenient threshold: 12% of minimum dimension (reduced from 18%)
495
+ // This allows for more realistic perspective angles
496
+ val angleThreshold = max(80.0, minDim * 0.12)
487
497
 
488
498
  val topYDiff = abs(rectangle.topRight.y - rectangle.topLeft.y)
489
499
  val bottomYDiff = abs(rectangle.bottomLeft.y - rectangle.bottomRight.y)
490
500
  val leftXDiff = abs(rectangle.topLeft.x - rectangle.bottomLeft.x)
491
501
  val rightXDiff = abs(rectangle.topRight.x - rectangle.bottomRight.x)
492
502
 
493
- if (topYDiff > angleThreshold || bottomYDiff > angleThreshold || leftXDiff > angleThreshold || rightXDiff > angleThreshold) {
503
+ // Check if edges are too skewed (perspective too extreme)
504
+ if (topYDiff > angleThreshold || bottomYDiff > angleThreshold ||
505
+ leftXDiff > angleThreshold || rightXDiff > angleThreshold) {
506
+ return RectangleQuality.BAD_ANGLE
507
+ }
508
+
509
+ // Additional check: opposite edges should be somewhat similar in length
510
+ // Allow up to 60% difference (more lenient for perspective)
511
+ val topBottomRatio = if (bottomEdgeLength > 0) topEdgeLength / bottomEdgeLength else 0.0
512
+ val leftRightRatio = if (rightEdgeLength > 0) leftEdgeLength / rightEdgeLength else 0.0
513
+ if (topBottomRatio < 0.4 || topBottomRatio > 2.5 ||
514
+ leftRightRatio < 0.4 || leftRightRatio > 2.5) {
494
515
  return RectangleQuality.BAD_ANGLE
495
516
  }
496
517
 
497
- val margin = max(50.0, minDim * 0.05)
518
+ // More lenient margin check: 8% of minimum dimension (increased from 5%)
519
+ val margin = max(80.0, minDim * 0.08)
498
520
  if (rectangle.topLeft.y > margin ||
499
521
  rectangle.topRight.y > margin ||
500
522
  rectangle.bottomLeft.y < (viewHeight - margin) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "10.46.0",
3
+ "version": "10.47.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",