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.
@@ -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
- // Save or encode images
388
- val result = if (useBase64) {
389
- Arguments.createMap().apply {
390
- putString("croppedImage", ImageProcessor.bitmapToBase64(processed.croppedImage, quality))
391
- putString("initialImage", ImageProcessor.bitmapToBase64(processed.initialImage, quality))
392
- putMap("rectangleCoordinates", detectedRectangle?.toMap()?.toWritableMap())
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
- Arguments.createMap().apply {
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(result)
433
+ promise?.resolve(resultForPromise)
421
434
 
422
435
  // Then send event for backwards compatibility
423
- sendPictureTakenEvent(result)
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", event)
459
+ .receiveEvent(id, "onPictureTaken", data)
448
460
  }
449
461
 
450
462
  private fun sendRectangleDetectEvent(
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "4.3.0",
3
+ "version": "4.5.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -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}>