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