react-native-rectangle-doc-scanner 4.1.0 → 4.2.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.
@@ -34,6 +34,7 @@ import java.util.concurrent.atomic.AtomicReference
34
34
  import java.util.concurrent.atomic.AtomicBoolean
35
35
  import kotlin.math.abs
36
36
  import kotlin.math.max
37
+ import kotlin.math.min
37
38
 
38
39
  class CameraController(
39
40
  private val context: Context,
@@ -217,11 +218,13 @@ class CameraController(
217
218
  val previewSizes = streamConfigMap.getOutputSizes(SurfaceTexture::class.java)
218
219
  previewSize = chooseBestSize(previewSizes, viewAspect, null, preferClosestAspect = true)
219
220
 
221
+ val previewAspect = previewSize?.let { it.width.toDouble() / it.height.toDouble() } ?: viewAspect
220
222
  val analysisSizes = streamConfigMap.getOutputSizes(ImageFormat.YUV_420_888)
221
- analysisSize = chooseBestSize(analysisSizes, viewAspect, null, preferClosestAspect = true)
223
+ analysisSize = chooseBestSize(analysisSizes, previewAspect, null, preferClosestAspect = true)
222
224
 
223
225
  val captureSizes = streamConfigMap.getOutputSizes(ImageFormat.JPEG)
224
- captureSize = captureSizes?.maxByOrNull { it.width * it.height }
226
+ captureSize = chooseBestSize(captureSizes, previewAspect, null, preferClosestAspect = true)
227
+ ?: captureSizes?.maxByOrNull { it.width * it.height }
225
228
 
226
229
  setupImageReaders()
227
230
 
@@ -540,17 +543,22 @@ class CameraController(
540
543
  return sorted.first()
541
544
  }
542
545
 
546
+ fun aspectDiff(size: Size): Double {
547
+ val w = size.width.toDouble()
548
+ val h = size.height.toDouble()
549
+ val direct = abs(w / h - targetAspect)
550
+ val inverted = abs(h / w - targetAspect)
551
+ return min(direct, inverted)
552
+ }
553
+
543
554
  if (preferClosestAspect) {
544
555
  return capped.minWithOrNull(
545
- compareBy<Size> { abs(it.width.toDouble() / it.height.toDouble() - targetAspect) }
556
+ compareBy<Size> { aspectDiff(it) }
546
557
  .thenByDescending { it.width * it.height }
547
558
  )
548
559
  }
549
560
 
550
- val matching = capped.filter {
551
- val aspect = it.width.toDouble() / it.height.toDouble()
552
- abs(aspect - targetAspect) <= ANALYSIS_ASPECT_TOLERANCE
553
- }
561
+ val matching = capped.filter { aspectDiff(it) <= ANALYSIS_ASPECT_TOLERANCE }
554
562
 
555
563
  return matching.firstOrNull() ?: capped.first()
556
564
  }
@@ -192,7 +192,7 @@ class DocumentDetector {
192
192
 
193
193
  var largestRectangle: Rectangle? = null
194
194
  var largestArea = 0.0
195
- val minArea = max(500.0, (srcMat.rows() * srcMat.cols()) * 0.0008)
195
+ val minArea = max(300.0, (srcMat.rows() * srcMat.cols()) * 0.0005)
196
196
 
197
197
  for (contour in contours) {
198
198
  val contourArea = Imgproc.contourArea(contour)
@@ -200,11 +200,20 @@ class DocumentDetector {
200
200
 
201
201
  val approx = MatOfPoint2f()
202
202
  val contour2f = MatOfPoint2f(*contour.toArray())
203
- val epsilon = 0.018 * Imgproc.arcLength(contour2f, true)
203
+ val arcLength = Imgproc.arcLength(contour2f, true)
204
+ val epsilon = 0.018 * arcLength
204
205
  Imgproc.approxPolyDP(contour2f, approx, epsilon, true)
206
+ val relaxed = if (approx.total() != 4L) {
207
+ MatOfPoint2f().apply {
208
+ Imgproc.approxPolyDP(contour2f, this, 0.03 * arcLength, true)
209
+ }
210
+ } else {
211
+ null
212
+ }
213
+ val quad = if (relaxed?.total() == 4L) relaxed else approx
205
214
 
206
- if (approx.total() == 4L && Imgproc.isContourConvex(MatOfPoint(*approx.toArray()))) {
207
- val points = approx.toArray()
215
+ if (quad.total() == 4L && Imgproc.isContourConvex(MatOfPoint(*quad.toArray()))) {
216
+ val points = quad.toArray()
208
217
  if (contourArea > largestArea) {
209
218
  largestArea = contourArea
210
219
  largestRectangle = orderPoints(points)
@@ -212,6 +221,7 @@ class DocumentDetector {
212
221
  }
213
222
 
214
223
  approx.release()
224
+ relaxed?.release()
215
225
  contour2f.release()
216
226
  }
217
227
 
@@ -30,6 +30,7 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
30
30
  private val themedContext = context
31
31
  private val previewView: TextureView
32
32
  private val overlayView: OverlayView
33
+ private val useNativeOverlay = false
33
34
  private var cameraController: CameraController? = null
34
35
  private val lifecycleRegistry = LifecycleRegistry(this)
35
36
 
@@ -224,8 +225,10 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
224
225
  imageWidth: Int,
225
226
  imageHeight: Int
226
227
  ) {
227
- // Update overlay
228
- overlayView.setRectangle(rectangleOnScreen, overlayColor)
228
+ // Update native overlay only when explicitly enabled to avoid double-rendering with JS overlay
229
+ if (useNativeOverlay) {
230
+ overlayView.setRectangle(rectangleOnScreen, overlayColor)
231
+ }
229
232
 
230
233
  // Update stable counter based on quality
231
234
  if (rectangleCoordinates == null) {
@@ -327,14 +330,9 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
327
330
  // Detect rectangle in captured image
328
331
  val bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
329
332
  ?: throw IllegalStateException("decode_failed")
330
- var detectedRectangle = try {
331
- DocumentDetector.detectRectangle(bitmap)
332
- } catch (e: Exception) {
333
- Log.w(TAG, "Rectangle detection failed, using original image", e)
334
- null
335
- }
336
- if (detectedRectangle == null && lastDetectedImageWidth > 0 && lastDetectedImageHeight > 0) {
337
- val rectangleFromView = lastRectangleOnScreen?.let {
333
+ var detectedRectangle: Rectangle? = null
334
+ val rectangleFromView = if (lastDetectedImageWidth > 0 && lastDetectedImageHeight > 0) {
335
+ lastRectangleOnScreen?.let {
338
336
  viewToImageRectangle(
339
337
  it,
340
338
  width,
@@ -343,15 +341,35 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
343
341
  lastDetectedImageHeight
344
342
  )
345
343
  }
346
- val fallbackRect = rectangleFromView ?: lastDetectedRectangle
347
- if (fallbackRect != null) {
348
- detectedRectangle = scaleRectangleToBitmap(
349
- fallbackRect,
350
- lastDetectedImageWidth,
351
- lastDetectedImageHeight,
352
- bitmap.width,
353
- bitmap.height
354
- )
344
+ } else {
345
+ null
346
+ }
347
+ if (rectangleFromView != null) {
348
+ detectedRectangle = scaleRectangleToBitmap(
349
+ rectangleFromView,
350
+ lastDetectedImageWidth,
351
+ lastDetectedImageHeight,
352
+ bitmap.width,
353
+ bitmap.height
354
+ )
355
+ } else {
356
+ detectedRectangle = try {
357
+ DocumentDetector.detectRectangle(bitmap)
358
+ } catch (e: Exception) {
359
+ Log.w(TAG, "Rectangle detection failed, using original image", e)
360
+ null
361
+ }
362
+ if (detectedRectangle == null && lastDetectedImageWidth > 0 && lastDetectedImageHeight > 0) {
363
+ val fallbackRect = lastDetectedRectangle
364
+ if (fallbackRect != null) {
365
+ detectedRectangle = scaleRectangleToBitmap(
366
+ fallbackRect,
367
+ lastDetectedImageWidth,
368
+ lastDetectedImageHeight,
369
+ bitmap.width,
370
+ bitmap.height
371
+ )
372
+ }
355
373
  }
356
374
  }
357
375
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",