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.
|
|
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() ->
|
|
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 =
|
|
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(
|
|
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
|
|
563
|
-
val
|
|
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
|
-
|
|
569
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
/**
|
package/dist/DocScanner.js
CHANGED
|
@@ -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 =
|
|
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
package/src/DocScanner.tsx
CHANGED
|
@@ -452,7 +452,10 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
|
|
|
452
452
|
[capture],
|
|
453
453
|
);
|
|
454
454
|
|
|
455
|
-
const overlayPolygon =
|
|
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;
|