react-native-rectangle-doc-scanner 3.244.0 → 3.246.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.
@@ -48,6 +48,7 @@ class CameraController(
48
48
  private var previewSize: Size? = null
49
49
  private var analysisSize: Size? = null
50
50
  private var captureSize: Size? = null
51
+ private var sensorOrientation: Int = 0
51
52
 
52
53
  private var yuvReader: ImageReader? = null
53
54
  private var jpegReader: ImageReader? = null
@@ -207,6 +208,7 @@ class CameraController(
207
208
  val characteristics = cameraManager.getCameraCharacteristics(cameraId)
208
209
  val streamConfigMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
209
210
  ?: return
211
+ sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0
210
212
 
211
213
  val viewAspect = if (previewView.height == 0) {
212
214
  1.0
@@ -370,11 +372,35 @@ class CameraController(
370
372
  val rotationDegrees = computeRotationDegrees()
371
373
  val imageWidth = image.width
372
374
  val imageHeight = image.height
375
+ val nv21 = try {
376
+ imageToNv21(image)
377
+ } catch (e: Exception) {
378
+ Log.e(TAG, "[CAMERA2] Failed to read image buffer", e)
379
+ try {
380
+ image.close()
381
+ } catch (closeError: Exception) {
382
+ Log.w(TAG, "[CAMERA2] Failed to close image", closeError)
383
+ }
384
+ analysisInFlight.set(false)
385
+ return
386
+ } finally {
387
+ try {
388
+ image.close()
389
+ } catch (e: Exception) {
390
+ Log.w(TAG, "[CAMERA2] Failed to close image", e)
391
+ }
392
+ }
393
+
373
394
  val inputImage = try {
374
- InputImage.fromMediaImage(image, rotationDegrees)
395
+ InputImage.fromByteArray(
396
+ nv21,
397
+ imageWidth,
398
+ imageHeight,
399
+ rotationDegrees,
400
+ InputImage.IMAGE_FORMAT_NV21
401
+ )
375
402
  } catch (e: Exception) {
376
403
  Log.e(TAG, "[CAMERA2] Failed to create InputImage", e)
377
- image.close()
378
404
  analysisInFlight.set(false)
379
405
  return
380
406
  }
@@ -388,7 +414,9 @@ class CameraController(
388
414
  val mlBox = best?.boundingBox
389
415
  val rectangle = when {
390
416
  mlBox == null -> null
391
- shouldRefineWithOpenCv() -> refineWithOpenCv(image, rotationDegrees, mlBox) ?: boxToRectangle(mlBox)
417
+ shouldRefineWithOpenCv() ->
418
+ refineWithOpenCv(nv21, imageWidth, imageHeight, rotationDegrees, mlBox)
419
+ ?: boxToRectangle(mlBox)
392
420
  else -> boxToRectangle(mlBox)
393
421
  }
394
422
 
@@ -400,11 +428,6 @@ class CameraController(
400
428
  Log.e(TAG, "[CAMERA2] ML Kit detection failed", e)
401
429
  }
402
430
  .addOnCompleteListener {
403
- try {
404
- image.close()
405
- } catch (e: Exception) {
406
- Log.w(TAG, "[CAMERA2] Failed to close image", e)
407
- }
408
431
  analysisInFlight.set(false)
409
432
  }
410
433
  }
@@ -454,9 +477,6 @@ class CameraController(
454
477
  }
455
478
 
456
479
  private fun computeRotationDegrees(): Int {
457
- val cameraId = selectCameraId() ?: return 0
458
- val characteristics = cameraManager.getCameraCharacteristics(cameraId)
459
- val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0
460
480
  val displayRotation = displayRotationDegrees()
461
481
  return if (useFrontCamera) {
462
482
  (sensorOrientation + displayRotation) % 360
@@ -482,7 +502,7 @@ class CameraController(
482
502
  val preview = previewSize ?: return
483
503
  if (viewWidth == 0f || viewHeight == 0f) return
484
504
 
485
- val rotation = displayRotationDegrees()
505
+ val rotation = computeRotationDegrees()
486
506
  val bufferWidth = if (rotation == 90 || rotation == 270) preview.height.toFloat() else preview.width.toFloat()
487
507
  val bufferHeight = if (rotation == 90 || rotation == 270) preview.width.toFloat() else preview.height.toFloat()
488
508
 
@@ -557,16 +577,21 @@ class CameraController(
557
577
  return true
558
578
  }
559
579
 
560
- private fun refineWithOpenCv(image: Image, rotationDegrees: Int, mlBox: Rect): Rectangle? {
580
+ private fun refineWithOpenCv(
581
+ nv21: ByteArray,
582
+ imageWidth: Int,
583
+ imageHeight: Int,
584
+ rotationDegrees: Int,
585
+ mlBox: Rect
586
+ ): Rectangle? {
561
587
  return try {
562
- val nv21 = imageToNv21(image)
563
- val uprightWidth = if (rotationDegrees == 90 || rotationDegrees == 270) image.height else image.width
564
- val uprightHeight = if (rotationDegrees == 90 || rotationDegrees == 270) image.width else image.height
588
+ val uprightWidth = if (rotationDegrees == 90 || rotationDegrees == 270) imageHeight else imageWidth
589
+ val uprightHeight = if (rotationDegrees == 90 || rotationDegrees == 270) imageWidth else imageHeight
565
590
  val expanded = expandRect(mlBox, uprightWidth, uprightHeight, 0.2f)
566
591
  val openCvRect = DocumentDetector.detectRectangleInYUVWithRoi(
567
592
  nv21,
568
- image.width,
569
- image.height,
593
+ imageWidth,
594
+ imageHeight,
570
595
  rotationDegrees,
571
596
  expanded
572
597
  )
@@ -50,6 +50,9 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
50
50
  private var lastDetectionTimestamp: Long = 0L
51
51
  private var isCapturing = false
52
52
  private var isDetaching = false
53
+ private var lastDetectedRectangle: Rectangle? = null
54
+ private var lastDetectedImageWidth = 0
55
+ private var lastDetectedImageHeight = 0
53
56
 
54
57
  // Coroutine scope for async operations
55
58
  private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
@@ -186,6 +189,12 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
186
189
  }
187
190
  lastDetectionTimestamp = now
188
191
 
192
+ if (rectangle != null && imageWidth > 0 && imageHeight > 0) {
193
+ lastDetectedRectangle = rectangle
194
+ lastDetectedImageWidth = imageWidth
195
+ lastDetectedImageHeight = imageHeight
196
+ }
197
+
189
198
  val rectangleOnScreen = if (rectangle != null && width > 0 && height > 0) {
190
199
  DocumentDetector.transformRectangleToViewCoordinates(rectangle, imageWidth, imageHeight, width, height)
191
200
  } else {
@@ -313,12 +322,21 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
313
322
  // Detect rectangle in captured image
314
323
  val bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
315
324
  ?: throw IllegalStateException("decode_failed")
316
- val detectedRectangle = try {
325
+ var detectedRectangle = try {
317
326
  DocumentDetector.detectRectangle(bitmap)
318
327
  } catch (e: Exception) {
319
328
  Log.w(TAG, "Rectangle detection failed, using original image", e)
320
329
  null
321
330
  }
331
+ if (detectedRectangle == null && lastDetectedRectangle != null && lastDetectedImageWidth > 0 && lastDetectedImageHeight > 0) {
332
+ detectedRectangle = scaleRectangleToBitmap(
333
+ lastDetectedRectangle!!,
334
+ lastDetectedImageWidth,
335
+ lastDetectedImageHeight,
336
+ bitmap.width,
337
+ bitmap.height
338
+ )
339
+ }
322
340
 
323
341
  // Process image with detected rectangle
324
342
  val shouldCrop = detectedRectangle != null && stableCounter > 0
@@ -389,9 +407,7 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
389
407
  }
390
408
 
391
409
  private fun sendPictureTakenEvent(data: WritableMap) {
392
- val event = Arguments.createMap().apply {
393
- merge(data)
394
- }
410
+ val event = data.toHashMap().toWritableMap()
395
411
  themedContext.getJSModule(RCTEventEmitter::class.java)
396
412
  .receiveEvent(id, "onPictureTaken", event)
397
413
  }
@@ -582,6 +598,27 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
582
598
  }
583
599
  }
584
600
  }
601
+
602
+ private fun scaleRectangleToBitmap(
603
+ rectangle: Rectangle,
604
+ srcWidth: Int,
605
+ srcHeight: Int,
606
+ dstWidth: Int,
607
+ dstHeight: Int
608
+ ): Rectangle {
609
+ if (srcWidth == 0 || srcHeight == 0) return rectangle
610
+ val scaleX = dstWidth.toDouble() / srcWidth.toDouble()
611
+ val scaleY = dstHeight.toDouble() / srcHeight.toDouble()
612
+ fun mapPoint(point: Point): Point {
613
+ return Point(point.x * scaleX, point.y * scaleY)
614
+ }
615
+ return Rectangle(
616
+ mapPoint(rectangle.topLeft),
617
+ mapPoint(rectangle.topRight),
618
+ mapPoint(rectangle.bottomLeft),
619
+ mapPoint(rectangle.bottomRight)
620
+ )
621
+ }
585
622
  }
586
623
 
587
624
  /**
@@ -334,7 +334,9 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
334
334
  captureOriginRef.current = 'auto';
335
335
  },
336
336
  }), [capture]);
337
- const overlayPolygon = detectedRectangle?.rectangleOnScreen ?? detectedRectangle?.rectangleCoordinates ?? null;
337
+ const overlayPolygon = react_native_1.Platform.OS === 'android'
338
+ ? detectedRectangle?.rectangleOnScreen ?? null
339
+ : detectedRectangle?.rectangleOnScreen ?? detectedRectangle?.rectangleCoordinates ?? null;
338
340
  const overlayIsActive = autoCapture ? isAutoCapturing : (detectedRectangle?.stableCounter ?? 0) > 0;
339
341
  const detectionThreshold = autoCapture ? minStableFrames : 99999;
340
342
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.244.0",
3
+ "version": "3.246.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -452,7 +452,10 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
452
452
  [capture],
453
453
  );
454
454
 
455
- const overlayPolygon = detectedRectangle?.rectangleOnScreen ?? detectedRectangle?.rectangleCoordinates ?? null;
455
+ const overlayPolygon =
456
+ Platform.OS === 'android'
457
+ ? detectedRectangle?.rectangleOnScreen ?? null
458
+ : detectedRectangle?.rectangleOnScreen ?? detectedRectangle?.rectangleCoordinates ?? null;
456
459
  const overlayIsActive = autoCapture ? isAutoCapturing : (detectedRectangle?.stableCounter ?? 0) > 0;
457
460
 
458
461
  const detectionThreshold = autoCapture ? minStableFrames : 99999;