react-native-rectangle-doc-scanner 4.0.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.
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/CameraController.kt +15 -7
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentDetector.kt +14 -4
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentScannerView.kt +37 -19
- package/dist/FullDocScanner.js +1 -1
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +1 -1
|
@@ -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,
|
|
223
|
+
analysisSize = chooseBestSize(analysisSizes, previewAspect, null, preferClosestAspect = true)
|
|
222
224
|
|
|
223
225
|
val captureSizes = streamConfigMap.getOutputSizes(ImageFormat.JPEG)
|
|
224
|
-
captureSize = captureSizes
|
|
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> {
|
|
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(
|
|
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
|
|
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 (
|
|
207
|
-
val points =
|
|
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
|
-
|
|
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 =
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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/dist/FullDocScanner.js
CHANGED
|
@@ -527,7 +527,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
527
527
|
const stableCounter = event.stableCounter ?? 0;
|
|
528
528
|
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
529
529
|
const hasRectangle = Boolean(rectangleCoordinates);
|
|
530
|
-
const captureReady = hasRectangle && event.lastDetectionType === 0 && stableCounter >= 1;
|
|
530
|
+
const captureReady = hasRectangle && (react_native_1.Platform.OS === 'android' || (event.lastDetectionType === 0 && stableCounter >= 1));
|
|
531
531
|
const scheduleClear = (ref, clearFn) => {
|
|
532
532
|
if (ref.current) {
|
|
533
533
|
clearTimeout(ref.current);
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -718,7 +718,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
718
718
|
const stableCounter = event.stableCounter ?? 0;
|
|
719
719
|
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
720
720
|
const hasRectangle = Boolean(rectangleCoordinates);
|
|
721
|
-
const captureReady = hasRectangle && event.lastDetectionType === 0 && stableCounter >= 1;
|
|
721
|
+
const captureReady = hasRectangle && (Platform.OS === 'android' || (event.lastDetectionType === 0 && stableCounter >= 1));
|
|
722
722
|
|
|
723
723
|
const scheduleClear = (
|
|
724
724
|
ref: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
|