react-native-rectangle-doc-scanner 4.3.0 → 4.5.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 +7 -0
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentDetector.kt +28 -2
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentScannerView.kt +28 -16
- package/dist/FullDocScanner.js +1 -0
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +4 -0
|
@@ -227,6 +227,13 @@ class CameraController(
|
|
|
227
227
|
?: captureSizes?.maxByOrNull { it.width * it.height }
|
|
228
228
|
|
|
229
229
|
setupImageReaders()
|
|
230
|
+
Log.d(
|
|
231
|
+
TAG,
|
|
232
|
+
"[CAMERA2] view=${previewView.width}x${previewView.height} " +
|
|
233
|
+
"preview=${previewSize?.width}x${previewSize?.height} " +
|
|
234
|
+
"analysis=${analysisSize?.width}x${analysisSize?.height} " +
|
|
235
|
+
"capture=${captureSize?.width}x${captureSize?.height}"
|
|
236
|
+
)
|
|
230
237
|
|
|
231
238
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
232
239
|
Log.e(TAG, "[CAMERA2] Camera permission not granted")
|
|
@@ -179,6 +179,32 @@ class DocumentDetector {
|
|
|
179
179
|
Imgproc.morphologyEx(cannyMat, morphMat, Imgproc.MORPH_CLOSE, kernel)
|
|
180
180
|
kernel.release()
|
|
181
181
|
|
|
182
|
+
fun refineRectangle(gray: Mat, rectangle: Rectangle): Rectangle {
|
|
183
|
+
val maxX = (gray.cols() - 1).toDouble().coerceAtLeast(1.0)
|
|
184
|
+
val maxY = (gray.rows() - 1).toDouble().coerceAtLeast(1.0)
|
|
185
|
+
val points = MatOfPoint2f(
|
|
186
|
+
Point(rectangle.topLeft.x.coerceIn(0.0, maxX), rectangle.topLeft.y.coerceIn(0.0, maxY)),
|
|
187
|
+
Point(rectangle.topRight.x.coerceIn(0.0, maxX), rectangle.topRight.y.coerceIn(0.0, maxY)),
|
|
188
|
+
Point(rectangle.bottomLeft.x.coerceIn(0.0, maxX), rectangle.bottomLeft.y.coerceIn(0.0, maxY)),
|
|
189
|
+
Point(rectangle.bottomRight.x.coerceIn(0.0, maxX), rectangle.bottomRight.y.coerceIn(0.0, maxY))
|
|
190
|
+
)
|
|
191
|
+
val criteria = TermCriteria(TermCriteria.EPS + TermCriteria.MAX_ITER, 30, 0.01)
|
|
192
|
+
return try {
|
|
193
|
+
Imgproc.cornerSubPix(
|
|
194
|
+
gray,
|
|
195
|
+
points,
|
|
196
|
+
Size(5.0, 5.0),
|
|
197
|
+
Size(-1.0, -1.0),
|
|
198
|
+
criteria
|
|
199
|
+
)
|
|
200
|
+
orderPoints(points.toArray())
|
|
201
|
+
} catch (e: Exception) {
|
|
202
|
+
rectangle
|
|
203
|
+
} finally {
|
|
204
|
+
points.release()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
182
208
|
fun findLargestRectangle(source: Mat): Rectangle? {
|
|
183
209
|
val contours = mutableListOf<MatOfPoint>()
|
|
184
210
|
val hierarchy = Mat()
|
|
@@ -216,7 +242,7 @@ class DocumentDetector {
|
|
|
216
242
|
val points = quad.toArray()
|
|
217
243
|
if (contourArea > largestArea) {
|
|
218
244
|
largestArea = contourArea
|
|
219
|
-
largestRectangle = orderPoints(points)
|
|
245
|
+
largestRectangle = refineRectangle(grayMat, orderPoints(points))
|
|
220
246
|
}
|
|
221
247
|
} else {
|
|
222
248
|
// Fallback: use rotated bounding box when contour is near-rectangular.
|
|
@@ -230,7 +256,7 @@ class DocumentDetector {
|
|
|
230
256
|
val boxPoints = Array(4) { Point() }
|
|
231
257
|
rotated.points(boxPoints)
|
|
232
258
|
largestArea = contourArea
|
|
233
|
-
largestRectangle = orderPoints(boxPoints)
|
|
259
|
+
largestRectangle = refineRectangle(grayMat, orderPoints(boxPoints))
|
|
234
260
|
}
|
|
235
261
|
}
|
|
236
262
|
}
|
|
@@ -130,6 +130,13 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
130
130
|
initializeCameraWhenReady()
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
134
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
135
|
+
if (changed) {
|
|
136
|
+
Log.d(TAG, "[LAYOUT] View size: ${right - left}x${bottom - top}, PreviewView: ${previewView.width}x${previewView.height}")
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
133
140
|
private fun initializeCameraWhenReady() {
|
|
134
141
|
// If view is already laid out, start camera immediately
|
|
135
142
|
if (width > 0 && height > 0) {
|
|
@@ -384,13 +391,23 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
384
391
|
shouldCrop = shouldCrop
|
|
385
392
|
)
|
|
386
393
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
394
|
+
fun buildResult(
|
|
395
|
+
croppedPath: String,
|
|
396
|
+
initialPath: String,
|
|
397
|
+
rectangle: Rectangle?
|
|
398
|
+
): WritableMap {
|
|
399
|
+
return Arguments.createMap().apply {
|
|
400
|
+
putString("croppedImage", croppedPath)
|
|
401
|
+
putString("initialImage", initialPath)
|
|
402
|
+
putMap("rectangleCoordinates", rectangle?.toMap()?.toWritableMap())
|
|
393
403
|
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
val (resultForPromise, resultForEvent) = if (useBase64) {
|
|
407
|
+
val croppedBase64 = ImageProcessor.bitmapToBase64(processed.croppedImage, quality)
|
|
408
|
+
val initialBase64 = ImageProcessor.bitmapToBase64(processed.initialImage, quality)
|
|
409
|
+
buildResult(croppedBase64, initialBase64, detectedRectangle) to
|
|
410
|
+
buildResult(croppedBase64, initialBase64, detectedRectangle)
|
|
394
411
|
} else {
|
|
395
412
|
val timestamp = System.currentTimeMillis()
|
|
396
413
|
val croppedPath = ImageProcessor.saveBitmapToFile(
|
|
@@ -405,22 +422,18 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
405
422
|
"initial_img_$timestamp.jpeg",
|
|
406
423
|
quality
|
|
407
424
|
)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
putString("croppedImage", croppedPath)
|
|
411
|
-
putString("initialImage", initialPath)
|
|
412
|
-
putMap("rectangleCoordinates", detectedRectangle?.toMap()?.toWritableMap())
|
|
413
|
-
}
|
|
425
|
+
buildResult(croppedPath, initialPath, detectedRectangle) to
|
|
426
|
+
buildResult(croppedPath, initialPath, detectedRectangle)
|
|
414
427
|
}
|
|
415
428
|
|
|
416
429
|
withContext(Dispatchers.Main) {
|
|
417
430
|
Log.d(TAG, "Processing completed, resolving promise: ${promise != null}")
|
|
418
431
|
|
|
419
432
|
// Resolve promise first (if provided) - matches iOS behavior
|
|
420
|
-
promise?.resolve(
|
|
433
|
+
promise?.resolve(resultForPromise)
|
|
421
434
|
|
|
422
435
|
// Then send event for backwards compatibility
|
|
423
|
-
sendPictureTakenEvent(
|
|
436
|
+
sendPictureTakenEvent(resultForEvent)
|
|
424
437
|
|
|
425
438
|
isCapturing = false
|
|
426
439
|
|
|
@@ -442,9 +455,8 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
442
455
|
}
|
|
443
456
|
|
|
444
457
|
private fun sendPictureTakenEvent(data: WritableMap) {
|
|
445
|
-
val event = data.toHashMap().toWritableMap()
|
|
446
458
|
themedContext.getJSModule(RCTEventEmitter::class.java)
|
|
447
|
-
.receiveEvent(id, "onPictureTaken",
|
|
459
|
+
.receiveEvent(id, "onPictureTaken", data)
|
|
448
460
|
}
|
|
449
461
|
|
|
450
462
|
private fun sendRectangleDetectEvent(
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -573,6 +573,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
573
573
|
}, []);
|
|
574
574
|
const activePreviewImage = croppedImageData ? getActivePreviewImage(croppedImageData) : null;
|
|
575
575
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
576
|
+
react_native_1.Platform.OS === 'android' && (react_1.default.createElement(react_native_1.StatusBar, { translucent: true, backgroundColor: "transparent" })),
|
|
576
577
|
croppedImageData ? (
|
|
577
578
|
// check_DP: Show confirmation screen
|
|
578
579
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
NativeModules,
|
|
8
8
|
Platform,
|
|
9
9
|
StyleSheet,
|
|
10
|
+
StatusBar,
|
|
10
11
|
Text,
|
|
11
12
|
TouchableOpacity,
|
|
12
13
|
View,
|
|
@@ -775,6 +776,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
775
776
|
|
|
776
777
|
return (
|
|
777
778
|
<View style={styles.container}>
|
|
779
|
+
{Platform.OS === 'android' && (
|
|
780
|
+
<StatusBar translucent backgroundColor="transparent" />
|
|
781
|
+
)}
|
|
778
782
|
{croppedImageData ? (
|
|
779
783
|
// check_DP: Show confirmation screen
|
|
780
784
|
<View style={styles.confirmationContainer}>
|