react-native-rectangle-doc-scanner 11.1.0 → 11.3.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.
@@ -280,10 +280,11 @@ class CameraController(
280
280
  }
281
281
 
282
282
  // Use the same rotation logic as updateTextureViewTransform
283
+ val tabletUpsideDownFix = if (sensorOrientation == 0 && displayRotationDegrees == 90) 180 else 0
283
284
  val effectiveRotation = if (sensorOrientation == 0) {
284
- displayRotationDegrees // Tablet: use display rotation (90°)
285
+ (displayRotationDegrees + tabletUpsideDownFix) % 360
285
286
  } else {
286
- sensorOrientation // Phone: use sensor orientation (90°)
287
+ sensorOrientation
287
288
  }
288
289
 
289
290
  Log.d(TAG, "[ANALYZE] Sensor: $sensorOrientation°, Display: $displayRotationDegrees°, Effective: $effectiveRotation°")
@@ -387,18 +388,30 @@ class CameraController(
387
388
  mlBox: android.graphics.Rect?
388
389
  ): Rectangle? {
389
390
  return try {
390
- if (mlBox != null) {
391
- val frameWidth = if (rotation == 90 || rotation == 270) height else width
392
- val frameHeight = if (rotation == 90 || rotation == 270) width else height
393
- val padX = (mlBox.width() * 0.25f).toInt().coerceAtLeast(32)
394
- val padY = (mlBox.height() * 0.25f).toInt().coerceAtLeast(32)
395
- val roi = android.graphics.Rect(
396
- (mlBox.left - padX).coerceAtLeast(0),
397
- (mlBox.top - padY).coerceAtLeast(0),
398
- (mlBox.right + padX).coerceAtMost(frameWidth),
399
- (mlBox.bottom + padY).coerceAtMost(frameHeight)
400
- )
401
- DocumentDetector.detectRectangleInYUVWithRoi(nv21, width, height, rotation, roi)
391
+ val frameWidth = if (rotation == 90 || rotation == 270) height else width
392
+ val frameHeight = if (rotation == 90 || rotation == 270) width else height
393
+ val frameArea = frameWidth.toLong() * frameHeight.toLong()
394
+ val roiRect = mlBox?.let { box ->
395
+ val boxArea = box.width().toLong() * box.height().toLong()
396
+ val aspect = if (box.height() > 0) box.width().toDouble() / box.height().toDouble() else 0.0
397
+ val isValidSize = boxArea >= (frameArea * 0.08)
398
+ val isValidAspect = aspect in 0.4..2.5
399
+ if (!isValidSize || !isValidAspect) {
400
+ null
401
+ } else {
402
+ val padX = (box.width() * 0.25f).toInt().coerceAtLeast(32)
403
+ val padY = (box.height() * 0.25f).toInt().coerceAtLeast(32)
404
+ android.graphics.Rect(
405
+ (box.left - padX).coerceAtLeast(0),
406
+ (box.top - padY).coerceAtLeast(0),
407
+ (box.right + padX).coerceAtMost(frameWidth),
408
+ (box.bottom + padY).coerceAtMost(frameHeight)
409
+ )
410
+ }
411
+ }
412
+
413
+ if (roiRect != null) {
414
+ DocumentDetector.detectRectangleInYUVWithRoi(nv21, width, height, rotation, roiRect)
402
415
  ?: DocumentDetector.detectRectangleInYUV(nv21, width, height, rotation)
403
416
  } else {
404
417
  DocumentDetector.detectRectangleInYUV(nv21, width, height, rotation)
@@ -259,7 +259,7 @@ class DocumentDetector {
259
259
 
260
260
  var largestRectangle: Rectangle? = null
261
261
  var bestScore = 0.0
262
- val minArea = max(450.0, (srcMat.rows() * srcMat.cols()) * 0.0007)
262
+ val minArea = max(350.0, (srcMat.rows() * srcMat.cols()) * 0.0005)
263
263
 
264
264
  debugStats.contours = contours.size
265
265
 
@@ -289,7 +289,7 @@ class DocumentDetector {
289
289
  val rect = Imgproc.minAreaRect(MatOfPoint2f(*points))
290
290
  val rectArea = rect.size.area()
291
291
  val rectangularity = if (rectArea > 1.0) contourArea / rectArea else 0.0
292
- if (rectangularity >= 0.6 && isCandidateValid(ordered, srcMat)) {
292
+ if (rectangularity >= 0.5 && isCandidateValid(ordered, srcMat)) {
293
293
  debugStats.candidates += 1
294
294
  val score = contourArea * rectangularity
295
295
  if (score > bestScore) {
@@ -313,7 +313,7 @@ class DocumentDetector {
313
313
  val rectArea = rotated.size.area()
314
314
  if (rectArea > 1.0) {
315
315
  val rectangularity = contourArea / rectArea
316
- if (rectangularity >= 0.6) {
316
+ if (rectangularity >= 0.5) {
317
317
  debugStats.candidates += 1
318
318
  val boxPoints = Array(4) { Point() }
319
319
  rotated.points(boxPoints)
@@ -497,16 +497,16 @@ class DocumentDetector {
497
497
  val rectHeight = max(leftEdgeLength, rightEdgeLength)
498
498
  val rectArea = rectWidth * rectHeight
499
499
 
500
- // Check if rectangle is too small (less than 15% of view area)
501
- // or too large (more than 85% - likely detecting screen instead of document)
500
+ // Check if rectangle is too small (less than 6% of view area)
501
+ // or too large (more than 95% - likely detecting screen instead of document)
502
502
  val areaRatio = rectArea / viewArea
503
- if (areaRatio < 0.15) {
503
+ if (areaRatio < 0.06) {
504
504
  if (BuildConfig.DEBUG) {
505
505
  Log.d(TAG, "[QUALITY] TOO_FAR (small): area=${String.format("%.1f", rectArea)}, ratio=${String.format("%.2f", areaRatio)}")
506
506
  }
507
507
  return RectangleQuality.TOO_FAR
508
508
  }
509
- if (areaRatio > 0.85) {
509
+ if (areaRatio > 0.95) {
510
510
  if (BuildConfig.DEBUG) {
511
511
  Log.d(TAG, "[QUALITY] TOO_FAR (large): area=${String.format("%.1f", rectArea)}, ratio=${String.format("%.2f", areaRatio)} - likely detecting screen")
512
512
  }
@@ -428,7 +428,7 @@ const VisionCameraScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor =
428
428
  const overlayIsActive = autoCapture ? isAutoCapturing : (detectedRectangle?.stableCounter ?? 0) > 0;
429
429
  return (react_1.default.createElement(react_native_1.View, { style: styles.container, onLayout: handleLayout },
430
430
  CameraComponent && device && hasPermission ? (react_1.default.createElement(CameraComponent, { ref: cameraRef, style: styles.scanner, device: device, isActive: true, photo: true, torch: enableTorch ? 'on' : 'off', frameProcessor: frameProcessor, frameProcessorFps: 10 })) : (react_1.default.createElement(react_native_1.View, { style: styles.scanner })),
431
- showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon, clipRect: detectedRectangle?.previewViewport ?? null })),
431
+ showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon, clipRect: react_native_1.Platform.OS === 'android' ? null : (detectedRectangle?.previewViewport ?? null) })),
432
432
  showManualCaptureButton && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: () => captureVision('manual') })),
433
433
  children));
434
434
  });
@@ -640,6 +640,7 @@ const NativeScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAU
640
640
  let rectangleOnScreen = normalizeRectangle(event.rectangleOnScreen ?? null);
641
641
  const density = react_native_1.PixelRatio.get();
642
642
  if (react_native_1.Platform.OS === 'android' &&
643
+ !rectangleOnScreen &&
643
644
  rectangleCoordinates &&
644
645
  event.imageSize &&
645
646
  event.previewSize &&
@@ -715,7 +716,7 @@ const NativeScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAU
715
716
  const detectionThreshold = autoCapture ? minStableFrames : 99999;
716
717
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
717
718
  react_1.default.createElement(react_native_document_scanner_1.default, { ref: scannerRef, style: styles.scanner, detectionCountBeforeCapture: detectionThreshold, overlayColor: overlayColor, enableTorch: enableTorch, quality: normalizedQuality, useBase64: useBase64, manualOnly: false, detectionConfig: detectionConfig, onPictureTaken: handlePictureTaken, onError: handleError, onRectangleDetect: handleRectangleDetect }),
718
- showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon, clipRect: detectedRectangle?.previewViewport ?? null })),
719
+ showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon, clipRect: react_native_1.Platform.OS === 'android' ? null : (detectedRectangle?.previewViewport ?? null) })),
719
720
  showManualCaptureButton && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: handleManualCapture })),
720
721
  children));
721
722
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "11.1.0",
3
+ "version": "11.3.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -616,7 +616,7 @@ const VisionCameraScanner = forwardRef<DocScannerHandle, Props>(
616
616
  color={gridColor ?? overlayColor}
617
617
  lineWidth={gridLineWidth}
618
618
  polygon={overlayPolygon}
619
- clipRect={detectedRectangle?.previewViewport ?? null}
619
+ clipRect={Platform.OS === 'android' ? null : (detectedRectangle?.previewViewport ?? null)}
620
620
  />
621
621
  )}
622
622
  {showManualCaptureButton && (
@@ -896,6 +896,7 @@ const NativeScanner = forwardRef<DocScannerHandle, Props>(
896
896
 
897
897
  if (
898
898
  Platform.OS === 'android' &&
899
+ !rectangleOnScreen &&
899
900
  rectangleCoordinates &&
900
901
  event.imageSize &&
901
902
  event.previewSize &&
@@ -1017,7 +1018,7 @@ const NativeScanner = forwardRef<DocScannerHandle, Props>(
1017
1018
  color={gridColor ?? overlayColor}
1018
1019
  lineWidth={gridLineWidth}
1019
1020
  polygon={overlayPolygon}
1020
- clipRect={detectedRectangle?.previewViewport ?? null}
1021
+ clipRect={Platform.OS === 'android' ? null : (detectedRectangle?.previewViewport ?? null)}
1021
1022
  />
1022
1023
  )}
1023
1024
  {showManualCaptureButton && (