react-native-rectangle-doc-scanner 0.31.0 → 0.33.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.
@@ -184,7 +184,10 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
184
184
  reportStage(step);
185
185
  const { value: area } = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour, false);
186
186
  // Skip extremely small contours, but keep threshold very low to allow distant documents
187
- if (area < 200) {
187
+ if (typeof area !== 'number' || !isFinite(area)) {
188
+ continue;
189
+ }
190
+ if (area < 50) {
188
191
  continue;
189
192
  }
190
193
  step = `contour_${i}_area`; // ratio stage
@@ -193,12 +196,15 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
193
196
  if (__DEV__) {
194
197
  console.log('[DocScanner] area', area, 'ratio', areaRatio);
195
198
  }
196
- if (areaRatio < 0.00002 || areaRatio > 0.99) {
199
+ if (areaRatio < 0.000005 || areaRatio > 0.995) {
197
200
  continue;
198
201
  }
202
+ // Use convex hull to simplify contour before polygon approximation
203
+ const hull = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
204
+ react_native_fast_opencv_1.OpenCV.invoke('convexHull', contour, hull);
199
205
  step = `contour_${i}_arcLength`;
200
206
  reportStage(step);
201
- const { value: perimeter } = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
207
+ const { value: perimeter } = react_native_fast_opencv_1.OpenCV.invoke('arcLength', hull, true);
202
208
  const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
203
209
  let approxArray = [];
204
210
  // Start with smaller epsilon for more accurate corner detection
@@ -208,7 +214,7 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
208
214
  const epsilon = epsilonValues[attempt] * perimeter;
209
215
  step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
210
216
  reportStage(step);
211
- react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, epsilon, true);
217
+ react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', hull, approx, epsilon, true);
212
218
  step = `contour_${i}_toJS_attempt_${attempt}`;
213
219
  reportStage(step);
214
220
  const approxValue = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
@@ -222,27 +228,35 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
222
228
  }
223
229
  }
224
230
  if (approxArray.length !== 4) {
225
- // fallback: boundingRect (axis-aligned) so we always have 4 points
231
+ // fallback: rotated rectangle using minAreaRect
226
232
  try {
227
- const rect = react_native_fast_opencv_1.OpenCV.invoke('boundingRect', contour);
233
+ const rect = react_native_fast_opencv_1.OpenCV.invoke('minAreaRect', contour);
228
234
  const rectValue = rect?.value ?? rect;
229
- const rectX = rectValue.x ?? rectValue?.topLeft?.x ?? 0;
230
- const rectY = rectValue.y ?? rectValue?.topLeft?.y ?? 0;
231
- const rectW = rectValue.width ?? rectValue?.size?.width ?? 0;
232
- const rectH = rectValue.height ?? rectValue?.size?.height ?? 0;
233
- approxArray = [
234
- { x: rectX, y: rectY },
235
- { x: rectX + rectW, y: rectY },
236
- { x: rectX + rectW, y: rectY + rectH },
237
- { x: rectX, y: rectY + rectH },
238
- ];
239
- if (__DEV__) {
240
- console.log('[DocScanner] boundingRect fallback', approxArray);
235
+ const centerX = rectValue.centerX ?? rectValue.center?.x ?? 0;
236
+ const centerY = rectValue.centerY ?? rectValue.center?.y ?? 0;
237
+ const rectW = rectValue.width ?? rectValue.size?.width ?? 0;
238
+ const rectH = rectValue.height ?? rectValue.size?.height ?? 0;
239
+ const angleDeg = rectValue.angle ?? rectValue.rotation ?? 0;
240
+ if (rectW > 0 && rectH > 0) {
241
+ const angleRad = (angleDeg * Math.PI) / 180;
242
+ const cosA = Math.cos(angleRad);
243
+ const sinA = Math.sin(angleRad);
244
+ const halfW = rectW / 2;
245
+ const halfH = rectH / 2;
246
+ approxArray = [
247
+ { x: centerX - halfW * cosA + halfH * sinA, y: centerY - halfW * sinA - halfH * cosA },
248
+ { x: centerX + halfW * cosA + halfH * sinA, y: centerY + halfW * sinA - halfH * cosA },
249
+ { x: centerX + halfW * cosA - halfH * sinA, y: centerY + halfW * sinA + halfH * cosA },
250
+ { x: centerX - halfW * cosA - halfH * sinA, y: centerY - halfW * sinA + halfH * cosA },
251
+ ];
252
+ if (__DEV__) {
253
+ console.log('[DocScanner] minAreaRect fallback', rectValue, approxArray);
254
+ }
241
255
  }
242
256
  }
243
257
  catch (err) {
244
258
  if (__DEV__) {
245
- console.warn('[DocScanner] boundingRect fallback failed', err);
259
+ console.warn('[DocScanner] minAreaRect fallback failed', err);
246
260
  }
247
261
  }
248
262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -208,7 +208,11 @@ export const DocScanner: React.FC<Props> = ({
208
208
  const { value: area } = OpenCV.invoke('contourArea', contour, false);
209
209
 
210
210
  // Skip extremely small contours, but keep threshold very low to allow distant documents
211
- if (area < 200) {
211
+ if (typeof area !== 'number' || !isFinite(area)) {
212
+ continue;
213
+ }
214
+
215
+ if (area < 50) {
212
216
  continue;
213
217
  }
214
218
 
@@ -220,13 +224,17 @@ export const DocScanner: React.FC<Props> = ({
220
224
  console.log('[DocScanner] area', area, 'ratio', areaRatio);
221
225
  }
222
226
 
223
- if (areaRatio < 0.00002 || areaRatio > 0.99) {
227
+ if (areaRatio < 0.000005 || areaRatio > 0.995) {
224
228
  continue;
225
229
  }
226
230
 
231
+ // Use convex hull to simplify contour before polygon approximation
232
+ const hull = OpenCV.createObject(ObjectType.PointVector);
233
+ OpenCV.invoke('convexHull', contour, hull);
234
+
227
235
  step = `contour_${i}_arcLength`;
228
236
  reportStage(step);
229
- const { value: perimeter } = OpenCV.invoke('arcLength', contour, true);
237
+ const { value: perimeter } = OpenCV.invoke('arcLength', hull, true);
230
238
  const approx = OpenCV.createObject(ObjectType.PointVector);
231
239
 
232
240
  let approxArray: Array<{ x: number; y: number }> = [];
@@ -239,7 +247,7 @@ export const DocScanner: React.FC<Props> = ({
239
247
  const epsilon = epsilonValues[attempt] * perimeter;
240
248
  step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
241
249
  reportStage(step);
242
- OpenCV.invoke('approxPolyDP', contour, approx, epsilon, true);
250
+ OpenCV.invoke('approxPolyDP', hull, approx, epsilon, true);
243
251
 
244
252
  step = `contour_${i}_toJS_attempt_${attempt}`;
245
253
  reportStage(step);
@@ -257,28 +265,38 @@ export const DocScanner: React.FC<Props> = ({
257
265
  }
258
266
 
259
267
  if (approxArray.length !== 4) {
260
- // fallback: boundingRect (axis-aligned) so we always have 4 points
268
+ // fallback: rotated rectangle using minAreaRect
261
269
  try {
262
- const rect = OpenCV.invoke('boundingRect', contour);
270
+ const rect = OpenCV.invoke('minAreaRect', contour);
263
271
  const rectValue = rect?.value ?? rect;
264
- const rectX = rectValue.x ?? rectValue?.topLeft?.x ?? 0;
265
- const rectY = rectValue.y ?? rectValue?.topLeft?.y ?? 0;
266
- const rectW = rectValue.width ?? rectValue?.size?.width ?? 0;
267
- const rectH = rectValue.height ?? rectValue?.size?.height ?? 0;
268
-
269
- approxArray = [
270
- { x: rectX, y: rectY },
271
- { x: rectX + rectW, y: rectY },
272
- { x: rectX + rectW, y: rectY + rectH },
273
- { x: rectX, y: rectY + rectH },
274
- ];
275
272
 
276
- if (__DEV__) {
277
- console.log('[DocScanner] boundingRect fallback', approxArray);
273
+ const centerX = rectValue.centerX ?? rectValue.center?.x ?? 0;
274
+ const centerY = rectValue.centerY ?? rectValue.center?.y ?? 0;
275
+ const rectW = rectValue.width ?? rectValue.size?.width ?? 0;
276
+ const rectH = rectValue.height ?? rectValue.size?.height ?? 0;
277
+ const angleDeg = rectValue.angle ?? rectValue.rotation ?? 0;
278
+
279
+ if (rectW > 0 && rectH > 0) {
280
+ const angleRad = (angleDeg * Math.PI) / 180;
281
+ const cosA = Math.cos(angleRad);
282
+ const sinA = Math.sin(angleRad);
283
+ const halfW = rectW / 2;
284
+ const halfH = rectH / 2;
285
+
286
+ approxArray = [
287
+ { x: centerX - halfW * cosA + halfH * sinA, y: centerY - halfW * sinA - halfH * cosA },
288
+ { x: centerX + halfW * cosA + halfH * sinA, y: centerY + halfW * sinA - halfH * cosA },
289
+ { x: centerX + halfW * cosA - halfH * sinA, y: centerY + halfW * sinA + halfH * cosA },
290
+ { x: centerX - halfW * cosA - halfH * sinA, y: centerY - halfW * sinA + halfH * cosA },
291
+ ];
292
+
293
+ if (__DEV__) {
294
+ console.log('[DocScanner] minAreaRect fallback', rectValue, approxArray);
295
+ }
278
296
  }
279
297
  } catch (err) {
280
298
  if (__DEV__) {
281
- console.warn('[DocScanner] boundingRect fallback failed', err);
299
+ console.warn('[DocScanner] minAreaRect fallback failed', err);
282
300
  }
283
301
  }
284
302
  }