react-native-rectangle-doc-scanner 0.26.0 → 0.28.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.
@@ -133,7 +133,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
133
133
  try {
134
134
  // Report frame size for coordinate transformation
135
135
  updateFrameSize(frame.width, frame.height);
136
- const ratio = 480 / frame.width;
136
+ // Use higher resolution for better accuracy - 720p instead of 480p
137
+ const ratio = 720 / frame.width;
137
138
  const width = Math.floor(frame.width * ratio);
138
139
  const height = Math.floor(frame.height * ratio);
139
140
  step = 'resize';
@@ -149,26 +150,36 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
149
150
  step = 'cvtColor';
150
151
  reportStage(step);
151
152
  react_native_fast_opencv_1.OpenCV.invoke('cvtColor', mat, mat, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
152
- const morphologyKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
153
+ // Apply Gaussian blur to reduce noise
154
+ const gaussianKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
155
+ step = 'GaussianBlur';
156
+ reportStage(step);
157
+ react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
158
+ // Morphological operations to enhance edges
159
+ const morphologyKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 3, 3);
153
160
  step = 'getStructuringElement';
154
161
  reportStage(step);
155
162
  const element = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, morphologyKernel);
156
163
  step = 'morphologyEx';
157
164
  reportStage(step);
158
- react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.MORPH_OPEN, element);
159
- const gaussianKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
160
- step = 'GaussianBlur';
161
- reportStage(step);
162
- react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
165
+ react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.MORPH_CLOSE, element);
166
+ // Canny edge detection with optimized thresholds
163
167
  step = 'Canny';
164
168
  reportStage(step);
165
- react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 75, 100);
169
+ react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 50, 150);
170
+ // Dilate edges slightly to connect nearby contours
171
+ step = 'dilate';
172
+ reportStage(step);
173
+ const dilateKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 2, 2);
174
+ const dilateElement = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, dilateKernel);
175
+ react_native_fast_opencv_1.OpenCV.invoke('dilate', mat, mat, dilateElement);
166
176
  step = 'createContours';
167
177
  reportStage(step);
168
178
  const contours = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVectorOfVectors);
169
- react_native_fast_opencv_1.OpenCV.invoke('findContours', mat, contours, react_native_fast_opencv_1.RetrievalModes.RETR_LIST, react_native_fast_opencv_1.ContourApproximationModes.CHAIN_APPROX_SIMPLE);
179
+ react_native_fast_opencv_1.OpenCV.invoke('findContours', mat, contours, react_native_fast_opencv_1.RetrievalModes.RETR_EXTERNAL, react_native_fast_opencv_1.ContourApproximationModes.CHAIN_APPROX_SIMPLE);
170
180
  let best = null;
171
181
  let maxArea = 0;
182
+ const frameArea = width * height;
172
183
  step = 'toJSValue';
173
184
  reportStage(step);
174
185
  const contourVector = react_native_fast_opencv_1.OpenCV.toJSValue(contours);
@@ -180,11 +191,13 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
180
191
  step = `contour_${i}_area`;
181
192
  reportStage(step);
182
193
  const { value: area } = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour, false);
194
+ const areaRatio = area / frameArea;
183
195
  if (__DEV__) {
184
- console.log('[DocScanner] area ratio', area / (width * height));
196
+ console.log('[DocScanner] area ratio', areaRatio);
185
197
  }
186
- // Lower threshold to detect smaller documents
187
- if (area < width * height * 0.0001) {
198
+ // Filter by area: document should be at least 5% and at most 95% of frame
199
+ // This prevents detecting tiny noise or the entire frame
200
+ if (areaRatio < 0.05 || areaRatio > 0.95) {
188
201
  continue;
189
202
  }
190
203
  step = `contour_${i}_arcLength`;
@@ -192,10 +205,11 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
192
205
  const { value: perimeter } = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
193
206
  const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
194
207
  let approxArray = [];
195
- let usedBoundingRect = false;
196
- let epsilonBase = 0.006 * perimeter;
197
- for (let attempt = 0; attempt < 10; attempt += 1) {
198
- const epsilon = epsilonBase * (1 + attempt);
208
+ // Start with smaller epsilon for more accurate corner detection
209
+ // Try epsilon values from 0.5% to 5% of perimeter
210
+ const epsilonValues = [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05];
211
+ for (let attempt = 0; attempt < epsilonValues.length; attempt += 1) {
212
+ const epsilon = epsilonValues[attempt] * perimeter;
199
213
  step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
200
214
  reportStage(step);
201
215
  react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, epsilon, true);
@@ -210,41 +224,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
210
224
  approxArray = candidate;
211
225
  break;
212
226
  }
213
- if (approxArray.length === 0 || Math.abs(candidate.length - 4) < Math.abs(approxArray.length - 4)) {
214
- approxArray = candidate;
215
- }
216
- }
217
- if (approxArray.length !== 4) {
218
- // fallback: boundingRect (axis-aligned) so we always have 4 points
219
- try {
220
- const rect = react_native_fast_opencv_1.OpenCV.invoke('boundingRect', contour);
221
- // Convert the rect object to JS value to get actual coordinates
222
- const rectJS = react_native_fast_opencv_1.OpenCV.toJSValue(rect);
223
- const rectValue = rectJS?.value ?? rectJS;
224
- const rectX = rectValue?.x ?? 0;
225
- const rectY = rectValue?.y ?? 0;
226
- const rectW = rectValue?.width ?? 0;
227
- const rectH = rectValue?.height ?? 0;
228
- // Validate that we have a valid rectangle
229
- if (rectW > 0 && rectH > 0) {
230
- approxArray = [
231
- { x: rectX, y: rectY },
232
- { x: rectX + rectW, y: rectY },
233
- { x: rectX + rectW, y: rectY + rectH },
234
- { x: rectX, y: rectY + rectH },
235
- ];
236
- usedBoundingRect = true;
237
- if (__DEV__) {
238
- console.log('[DocScanner] using boundingRect fallback:', approxArray);
239
- }
240
- }
241
- }
242
- catch (err) {
243
- if (__DEV__) {
244
- console.warn('[DocScanner] boundingRect fallback failed:', err);
245
- }
246
- }
247
227
  }
228
+ // Only proceed if we found exactly 4 corners
248
229
  if (approxArray.length !== 4) {
249
230
  continue;
250
231
  }
@@ -266,23 +247,21 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
266
247
  x: pt.x / ratio,
267
248
  y: pt.y / ratio,
268
249
  }));
269
- // Skip convexity check for boundingRect (always forms a valid rectangle)
270
- if (!usedBoundingRect) {
271
- try {
272
- if (!isConvexQuadrilateral(points)) {
273
- if (__DEV__) {
274
- console.log('[DocScanner] not convex, skipping:', points);
275
- }
276
- continue;
277
- }
278
- }
279
- catch (err) {
250
+ // Verify the quadrilateral is convex (valid document shape)
251
+ try {
252
+ if (!isConvexQuadrilateral(points)) {
280
253
  if (__DEV__) {
281
- console.warn('[DocScanner] convex check error:', err, 'points:', points);
254
+ console.log('[DocScanner] not convex, skipping:', points);
282
255
  }
283
256
  continue;
284
257
  }
285
258
  }
259
+ catch (err) {
260
+ if (__DEV__) {
261
+ console.warn('[DocScanner] convex check error:', err, 'points:', points);
262
+ }
263
+ continue;
264
+ }
286
265
  if (area > maxArea) {
287
266
  best = points;
288
267
  maxArea = area;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -147,7 +147,8 @@ export const DocScanner: React.FC<Props> = ({
147
147
  // Report frame size for coordinate transformation
148
148
  updateFrameSize(frame.width, frame.height);
149
149
 
150
- const ratio = 480 / frame.width;
150
+ // Use higher resolution for better accuracy - 720p instead of 480p
151
+ const ratio = 720 / frame.width;
151
152
  const width = Math.floor(frame.width * ratio);
152
153
  const height = Math.floor(frame.height * ratio);
153
154
  step = 'resize';
@@ -166,29 +167,41 @@ export const DocScanner: React.FC<Props> = ({
166
167
  reportStage(step);
167
168
  OpenCV.invoke('cvtColor', mat, mat, ColorConversionCodes.COLOR_BGR2GRAY);
168
169
 
169
- const morphologyKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
170
+ // Apply Gaussian blur to reduce noise
171
+ const gaussianKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
172
+ step = 'GaussianBlur';
173
+ reportStage(step);
174
+ OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
175
+
176
+ // Morphological operations to enhance edges
177
+ const morphologyKernel = OpenCV.createObject(ObjectType.Size, 3, 3);
170
178
  step = 'getStructuringElement';
171
179
  reportStage(step);
172
180
  const element = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, morphologyKernel);
173
181
  step = 'morphologyEx';
174
182
  reportStage(step);
175
- OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.MORPH_OPEN, element);
183
+ OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.MORPH_CLOSE, element);
176
184
 
177
- const gaussianKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
178
- step = 'GaussianBlur';
179
- reportStage(step);
180
- OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
185
+ // Canny edge detection with optimized thresholds
181
186
  step = 'Canny';
182
187
  reportStage(step);
183
- OpenCV.invoke('Canny', mat, mat, 75, 100);
188
+ OpenCV.invoke('Canny', mat, mat, 50, 150);
189
+
190
+ // Dilate edges slightly to connect nearby contours
191
+ step = 'dilate';
192
+ reportStage(step);
193
+ const dilateKernel = OpenCV.createObject(ObjectType.Size, 2, 2);
194
+ const dilateElement = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, dilateKernel);
195
+ OpenCV.invoke('dilate', mat, mat, dilateElement);
184
196
 
185
197
  step = 'createContours';
186
198
  reportStage(step);
187
199
  const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
188
- OpenCV.invoke('findContours', mat, contours, RetrievalModes.RETR_LIST, ContourApproximationModes.CHAIN_APPROX_SIMPLE);
200
+ OpenCV.invoke('findContours', mat, contours, RetrievalModes.RETR_EXTERNAL, ContourApproximationModes.CHAIN_APPROX_SIMPLE);
189
201
 
190
202
  let best: Point[] | null = null;
191
203
  let maxArea = 0;
204
+ const frameArea = width * height;
192
205
 
193
206
  step = 'toJSValue';
194
207
  reportStage(step);
@@ -203,13 +216,15 @@ export const DocScanner: React.FC<Props> = ({
203
216
  step = `contour_${i}_area`;
204
217
  reportStage(step);
205
218
  const { value: area } = OpenCV.invoke('contourArea', contour, false);
219
+ const areaRatio = area / frameArea;
206
220
 
207
221
  if (__DEV__) {
208
- console.log('[DocScanner] area ratio', area / (width * height));
222
+ console.log('[DocScanner] area ratio', areaRatio);
209
223
  }
210
224
 
211
- // Lower threshold to detect smaller documents
212
- if (area < width * height * 0.0001) {
225
+ // Filter by area: document should be at least 5% and at most 95% of frame
226
+ // This prevents detecting tiny noise or the entire frame
227
+ if (areaRatio < 0.05 || areaRatio > 0.95) {
213
228
  continue;
214
229
  }
215
230
 
@@ -219,11 +234,13 @@ export const DocScanner: React.FC<Props> = ({
219
234
  const approx = OpenCV.createObject(ObjectType.PointVector);
220
235
 
221
236
  let approxArray: Array<{ x: number; y: number }> = [];
222
- let usedBoundingRect = false;
223
- let epsilonBase = 0.006 * perimeter;
224
237
 
225
- for (let attempt = 0; attempt < 10; attempt += 1) {
226
- const epsilon = epsilonBase * (1 + attempt);
238
+ // Start with smaller epsilon for more accurate corner detection
239
+ // Try epsilon values from 0.5% to 5% of perimeter
240
+ const epsilonValues = [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05];
241
+
242
+ for (let attempt = 0; attempt < epsilonValues.length; attempt += 1) {
243
+ const epsilon = epsilonValues[attempt] * perimeter;
227
244
  step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
228
245
  reportStage(step);
229
246
  OpenCV.invoke('approxPolyDP', contour, approx, epsilon, true);
@@ -241,46 +258,9 @@ export const DocScanner: React.FC<Props> = ({
241
258
  approxArray = candidate as Array<{ x: number; y: number }>;
242
259
  break;
243
260
  }
244
-
245
- if (approxArray.length === 0 || Math.abs(candidate.length - 4) < Math.abs(approxArray.length - 4)) {
246
- approxArray = candidate as Array<{ x: number; y: number }>;
247
- }
248
- }
249
-
250
- if (approxArray.length !== 4) {
251
- // fallback: boundingRect (axis-aligned) so we always have 4 points
252
- try {
253
- const rect = OpenCV.invoke('boundingRect', contour);
254
- // Convert the rect object to JS value to get actual coordinates
255
- const rectJS = OpenCV.toJSValue(rect);
256
- const rectValue = rectJS?.value ?? rectJS;
257
-
258
- const rectX = rectValue?.x ?? 0;
259
- const rectY = rectValue?.y ?? 0;
260
- const rectW = rectValue?.width ?? 0;
261
- const rectH = rectValue?.height ?? 0;
262
-
263
- // Validate that we have a valid rectangle
264
- if (rectW > 0 && rectH > 0) {
265
- approxArray = [
266
- { x: rectX, y: rectY },
267
- { x: rectX + rectW, y: rectY },
268
- { x: rectX + rectW, y: rectY + rectH },
269
- { x: rectX, y: rectY + rectH },
270
- ];
271
- usedBoundingRect = true;
272
-
273
- if (__DEV__) {
274
- console.log('[DocScanner] using boundingRect fallback:', approxArray);
275
- }
276
- }
277
- } catch (err) {
278
- if (__DEV__) {
279
- console.warn('[DocScanner] boundingRect fallback failed:', err);
280
- }
281
- }
282
261
  }
283
262
 
263
+ // Only proceed if we found exactly 4 corners
284
264
  if (approxArray.length !== 4) {
285
265
  continue;
286
266
  }
@@ -307,21 +287,19 @@ export const DocScanner: React.FC<Props> = ({
307
287
  y: pt.y / ratio,
308
288
  }));
309
289
 
310
- // Skip convexity check for boundingRect (always forms a valid rectangle)
311
- if (!usedBoundingRect) {
312
- try {
313
- if (!isConvexQuadrilateral(points)) {
314
- if (__DEV__) {
315
- console.log('[DocScanner] not convex, skipping:', points);
316
- }
317
- continue;
318
- }
319
- } catch (err) {
290
+ // Verify the quadrilateral is convex (valid document shape)
291
+ try {
292
+ if (!isConvexQuadrilateral(points)) {
320
293
  if (__DEV__) {
321
- console.warn('[DocScanner] convex check error:', err, 'points:', points);
294
+ console.log('[DocScanner] not convex, skipping:', points);
322
295
  }
323
296
  continue;
324
297
  }
298
+ } catch (err) {
299
+ if (__DEV__) {
300
+ console.warn('[DocScanner] convex check error:', err, 'points:', points);
301
+ }
302
+ continue;
325
303
  }
326
304
 
327
305
  if (area > maxArea) {