react-native-rectangle-doc-scanner 3.237.0 → 3.239.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.
@@ -106,6 +106,7 @@ class DocumentDetector {
106
106
  val blurredMat = Mat()
107
107
  val cannyMat = Mat()
108
108
  val morphMat = Mat()
109
+ val threshMat = Mat()
109
110
 
110
111
  try {
111
112
  // Convert to grayscale
@@ -124,57 +125,74 @@ class DocumentDetector {
124
125
  Imgproc.morphologyEx(cannyMat, morphMat, Imgproc.MORPH_CLOSE, kernel)
125
126
  kernel.release()
126
127
 
127
- // Find contours
128
- val contours = mutableListOf<MatOfPoint>()
129
- val hierarchy = Mat()
130
- Imgproc.findContours(
131
- morphMat,
132
- contours,
133
- hierarchy,
134
- Imgproc.RETR_EXTERNAL,
135
- Imgproc.CHAIN_APPROX_SIMPLE
136
- )
137
-
138
- // Find the largest contour that approximates to a quadrilateral
139
- var largestRectangle: Rectangle? = null
140
- var largestArea = 0.0
141
- val minArea = max(600.0, (srcMat.rows() * srcMat.cols()) * 0.001)
142
-
143
- for (contour in contours) {
144
- val contourArea = Imgproc.contourArea(contour)
145
-
146
- // Filter small contours
147
- if (contourArea < minArea) continue
148
-
149
- // Approximate contour to polygon
150
- val approx = MatOfPoint2f()
151
- val contour2f = MatOfPoint2f(*contour.toArray())
152
- val epsilon = 0.02 * Imgproc.arcLength(contour2f, true)
153
- Imgproc.approxPolyDP(contour2f, approx, epsilon, true)
154
-
155
- // Check if it's a quadrilateral
156
- if (approx.total() == 4L && Imgproc.isContourConvex(MatOfPoint(*approx.toArray()))) {
157
- val points = approx.toArray()
158
-
159
- if (contourArea > largestArea) {
160
- largestArea = contourArea
161
- largestRectangle = orderPoints(points)
128
+ fun findLargestRectangle(source: Mat): Rectangle? {
129
+ val contours = mutableListOf<MatOfPoint>()
130
+ val hierarchy = Mat()
131
+ Imgproc.findContours(
132
+ source,
133
+ contours,
134
+ hierarchy,
135
+ Imgproc.RETR_EXTERNAL,
136
+ Imgproc.CHAIN_APPROX_SIMPLE
137
+ )
138
+
139
+ var largestRectangle: Rectangle? = null
140
+ var largestArea = 0.0
141
+ val minArea = max(500.0, (srcMat.rows() * srcMat.cols()) * 0.0008)
142
+
143
+ for (contour in contours) {
144
+ val contourArea = Imgproc.contourArea(contour)
145
+ if (contourArea < minArea) continue
146
+
147
+ val approx = MatOfPoint2f()
148
+ val contour2f = MatOfPoint2f(*contour.toArray())
149
+ val epsilon = 0.018 * Imgproc.arcLength(contour2f, true)
150
+ Imgproc.approxPolyDP(contour2f, approx, epsilon, true)
151
+
152
+ if (approx.total() == 4L && Imgproc.isContourConvex(MatOfPoint(*approx.toArray()))) {
153
+ val points = approx.toArray()
154
+ if (contourArea > largestArea) {
155
+ largestArea = contourArea
156
+ largestRectangle = orderPoints(points)
157
+ }
162
158
  }
159
+
160
+ approx.release()
161
+ contour2f.release()
163
162
  }
164
163
 
165
- approx.release()
166
- contour2f.release()
164
+ hierarchy.release()
165
+ contours.forEach { it.release() }
166
+ return largestRectangle
167
167
  }
168
168
 
169
- hierarchy.release()
170
- contours.forEach { it.release() }
169
+ // First pass: Canny-based edges (good for strong edges).
170
+ var rectangle = findLargestRectangle(morphMat)
171
+
172
+ // Fallback: adaptive threshold (better for low-contrast cards).
173
+ if (rectangle == null) {
174
+ Imgproc.adaptiveThreshold(
175
+ blurredMat,
176
+ threshMat,
177
+ 255.0,
178
+ Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
179
+ Imgproc.THRESH_BINARY,
180
+ 15,
181
+ 2.0
182
+ )
183
+ val kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(3.0, 3.0))
184
+ Imgproc.morphologyEx(threshMat, morphMat, Imgproc.MORPH_CLOSE, kernel)
185
+ kernel.release()
186
+ rectangle = findLargestRectangle(morphMat)
187
+ }
171
188
 
172
- return largestRectangle
189
+ return rectangle
173
190
  } finally {
174
191
  grayMat.release()
175
192
  blurredMat.release()
176
193
  cannyMat.release()
177
194
  morphMat.release()
195
+ threshMat.release()
178
196
  }
179
197
  }
180
198
 
@@ -242,7 +260,7 @@ class DocumentDetector {
242
260
  }
243
261
 
244
262
  val minDim = kotlin.math.min(viewWidth.toDouble(), viewHeight.toDouble())
245
- val angleThreshold = max(40.0, minDim * 0.06)
263
+ val angleThreshold = max(60.0, minDim * 0.08)
246
264
 
247
265
  val topYDiff = abs(rectangle.topRight.y - rectangle.topLeft.y)
248
266
  val bottomYDiff = abs(rectangle.bottomLeft.y - rectangle.bottomRight.y)
@@ -253,7 +271,7 @@ class DocumentDetector {
253
271
  return RectangleQuality.BAD_ANGLE
254
272
  }
255
273
 
256
- val margin = max(80.0, minDim * 0.08)
274
+ val margin = max(120.0, minDim * 0.12)
257
275
  if (rectangle.topLeft.y > margin ||
258
276
  rectangle.topRight.y > margin ||
259
277
  rectangle.bottomLeft.y < (viewHeight - margin) ||
@@ -291,10 +291,11 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
291
291
  if (payload.rectangleCoordinates || payload.rectangleOnScreen) {
292
292
  lastRectangleRef.current = payload.rectangleCoordinates ?? payload.rectangleOnScreen ?? null;
293
293
  }
294
- const isGoodRectangle = payload.lastDetectionType === 0;
295
- const hasValidRectangle = isGoodRectangle && rectangleOnScreen;
296
- // 그리드를 표시할 조건: 좋은 품질의 사각형이 화면에 있을 때만
297
- if (hasValidRectangle) {
294
+ const hasAnyRectangle = react_native_1.Platform.OS === 'android'
295
+ ? Boolean(rectangleOnScreen || payload.rectangleCoordinates)
296
+ : payload.lastDetectionType === 0 && Boolean(rectangleOnScreen);
297
+ // 그리드를 표시할 조건: 사각형이 잡히면 품질과 무관하게 표시
298
+ if (hasAnyRectangle) {
298
299
  // 기존 타임아웃 클리어
299
300
  if (rectangleClearTimeoutRef.current) {
300
301
  clearTimeout(rectangleClearTimeoutRef.current);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.237.0",
3
+ "version": "3.239.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -397,11 +397,13 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
397
397
  lastRectangleRef.current = payload.rectangleCoordinates ?? payload.rectangleOnScreen ?? null;
398
398
  }
399
399
 
400
- const isGoodRectangle = payload.lastDetectionType === 0;
401
- const hasValidRectangle = isGoodRectangle && rectangleOnScreen;
400
+ const hasAnyRectangle =
401
+ Platform.OS === 'android'
402
+ ? Boolean(rectangleOnScreen || payload.rectangleCoordinates)
403
+ : payload.lastDetectionType === 0 && Boolean(rectangleOnScreen);
402
404
 
403
- // 그리드를 표시할 조건: 좋은 품질의 사각형이 화면에 있을 때만
404
- if (hasValidRectangle) {
405
+ // 그리드를 표시할 조건: 사각형이 잡히면 품질과 무관하게 표시
406
+ if (hasAnyRectangle) {
405
407
  // 기존 타임아웃 클리어
406
408
  if (rectangleClearTimeoutRef.current) {
407
409
  clearTimeout(rectangleClearTimeoutRef.current);