react-native-rectangle-doc-scanner 0.42.0 → 0.44.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.
@@ -95,7 +95,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
95
95
  const lastQuadRef = (0, react_1.useRef)(null);
96
96
  const smoothingBufferRef = (0, react_1.useRef)([]);
97
97
  const anchorQuadRef = (0, react_1.useRef)(null);
98
- const missingFrameCountRef = (0, react_1.useRef)(0);
98
+ const anchorMissesRef = (0, react_1.useRef)(0);
99
+ const anchorConfidenceRef = (0, react_1.useRef)(0);
99
100
  const lastMeasurementRef = (0, react_1.useRef)(null);
100
101
  const frameSizeRef = (0, react_1.useRef)(null);
101
102
  const MAX_HISTORY = 5;
@@ -106,26 +107,47 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
106
107
  const REJECT_CENTER_DELTA = 145;
107
108
  const MAX_AREA_SHIFT = 0.45;
108
109
  const HISTORY_RESET_DISTANCE = 70;
109
- const MIN_AREA_RATIO = 0.0035;
110
+ const MIN_AREA_RATIO = 0.0002;
110
111
  const MAX_AREA_RATIO = 0.9;
111
- const MIN_EDGE_RATIO = 0.025;
112
+ const MIN_EDGE_RATIO = 0.015;
113
+ const MAX_ANCHOR_MISSES = 12;
114
+ const MIN_CONFIDENCE_TO_HOLD = 2;
115
+ const MAX_ANCHOR_CONFIDENCE = 20;
112
116
  const updateQuad = (0, react_native_worklets_core_1.useRunOnJS)((value) => {
113
117
  if (__DEV__) {
114
118
  console.log('[DocScanner] quad', value);
115
119
  }
116
- if (!(0, quad_1.isValidQuad)(value)) {
117
- missingFrameCountRef.current += 1;
118
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
119
- setQuad(lastQuadRef.current);
120
- return;
120
+ const fallbackToAnchor = (resetHistory) => {
121
+ if (resetHistory) {
122
+ smoothingBufferRef.current = [];
123
+ lastMeasurementRef.current = null;
121
124
  }
122
- smoothingBufferRef.current = [];
125
+ const anchor = anchorQuadRef.current;
126
+ const anchorConfidence = anchorConfidenceRef.current;
127
+ if (anchor && anchorConfidence >= MIN_CONFIDENCE_TO_HOLD) {
128
+ anchorMissesRef.current += 1;
129
+ if (anchorMissesRef.current <= MAX_ANCHOR_MISSES) {
130
+ anchorConfidenceRef.current = Math.max(1, anchorConfidence - 1);
131
+ lastQuadRef.current = anchor;
132
+ setQuad(anchor);
133
+ return true;
134
+ }
135
+ }
136
+ anchorMissesRef.current = 0;
137
+ anchorConfidenceRef.current = 0;
123
138
  anchorQuadRef.current = null;
124
139
  lastQuadRef.current = null;
125
140
  setQuad(null);
141
+ return false;
142
+ };
143
+ if (!(0, quad_1.isValidQuad)(value)) {
144
+ const handled = fallbackToAnchor(false);
145
+ if (handled) {
146
+ return;
147
+ }
126
148
  return;
127
149
  }
128
- missingFrameCountRef.current = 0;
150
+ anchorMissesRef.current = 0;
129
151
  const ordered = (0, quad_1.orderQuadPoints)(value);
130
152
  const sanitized = (0, quad_1.sanitizeQuad)(ordered);
131
153
  const frameSize = frameSizeRef.current;
@@ -143,29 +165,20 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
143
165
  const edgesTooShort = minEdge < minEdgeThreshold;
144
166
  const aspectTooExtreme = aspectRatio > 7;
145
167
  if (areaTooSmall || areaTooLarge || edgesTooShort || aspectTooExtreme) {
146
- missingFrameCountRef.current += 1;
147
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
148
- setQuad(lastQuadRef.current);
168
+ const handled = fallbackToAnchor(true);
169
+ if (handled) {
149
170
  return;
150
171
  }
151
- smoothingBufferRef.current = [];
152
- anchorQuadRef.current = null;
153
- lastQuadRef.current = null;
154
- setQuad(null);
155
172
  return;
156
173
  }
157
174
  const lastMeasurement = lastMeasurementRef.current;
158
- if (lastMeasurement && (0, quad_1.quadDistance)(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE) {
159
- smoothingBufferRef.current = [];
160
- }
161
- lastMeasurementRef.current = sanitized;
162
- smoothingBufferRef.current.push(sanitized);
163
- if (smoothingBufferRef.current.length > MAX_HISTORY) {
164
- smoothingBufferRef.current.shift();
165
- }
166
- const history = smoothingBufferRef.current;
167
- const hasHistory = history.length >= 2;
168
- let candidate = hasHistory ? (0, quad_1.weightedAverageQuad)(history) : sanitized;
175
+ const shouldResetHistory = lastMeasurement && (0, quad_1.quadDistance)(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE;
176
+ const existingHistory = shouldResetHistory ? [] : smoothingBufferRef.current;
177
+ const nextHistory = existingHistory.length >= MAX_HISTORY
178
+ ? [...existingHistory.slice(existingHistory.length - (MAX_HISTORY - 1)), sanitized]
179
+ : [...existingHistory, sanitized];
180
+ const hasHistory = nextHistory.length >= 2;
181
+ let candidate = hasHistory ? (0, quad_1.weightedAverageQuad)(nextHistory) : sanitized;
169
182
  const anchor = anchorQuadRef.current;
170
183
  if (anchor && (0, quad_1.isValidQuad)(anchor)) {
171
184
  const delta = (0, quad_1.quadDistance)(candidate, anchor);
@@ -176,33 +189,47 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
176
189
  const centerDelta = Math.hypot(candidateCenter.x - anchorCenter.x, candidateCenter.y - anchorCenter.y);
177
190
  const areaShift = anchorArea > 0 ? Math.abs(anchorArea - candidateArea) / anchorArea : 0;
178
191
  if (centerDelta >= REJECT_CENTER_DELTA || areaShift > 1.2) {
179
- missingFrameCountRef.current += 1;
180
- if (missingFrameCountRef.current <= 2) {
181
- setQuad(anchor);
182
- return;
183
- }
184
- smoothingBufferRef.current = [];
185
- anchorQuadRef.current = null;
186
- lastQuadRef.current = null;
187
- setQuad(null);
192
+ smoothingBufferRef.current = [sanitized];
193
+ lastMeasurementRef.current = sanitized;
194
+ anchorQuadRef.current = candidate;
195
+ anchorConfidenceRef.current = 1;
196
+ anchorMissesRef.current = 0;
197
+ lastQuadRef.current = candidate;
198
+ setQuad(candidate);
188
199
  return;
189
200
  }
190
201
  if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
191
202
  candidate = anchor;
203
+ smoothingBufferRef.current = nextHistory;
204
+ lastMeasurementRef.current = sanitized;
205
+ anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
192
206
  }
193
207
  else if (delta <= BLEND_DISTANCE && centerDelta <= MAX_CENTER_DELTA && areaShift <= MAX_AREA_SHIFT) {
194
208
  const normalizedDelta = Math.min(1, delta / BLEND_DISTANCE);
195
209
  const adaptiveAlpha = 0.25 + normalizedDelta * 0.45; // 0.25..0.7 range
196
210
  candidate = (0, quad_1.blendQuads)(anchor, candidate, adaptiveAlpha);
211
+ smoothingBufferRef.current = nextHistory;
212
+ lastMeasurementRef.current = sanitized;
213
+ anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
197
214
  }
198
215
  else {
199
- smoothingBufferRef.current = [sanitized];
216
+ const handled = fallbackToAnchor(true);
217
+ if (handled) {
218
+ return;
219
+ }
220
+ return;
200
221
  }
201
222
  }
223
+ else {
224
+ smoothingBufferRef.current = nextHistory;
225
+ lastMeasurementRef.current = sanitized;
226
+ anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
227
+ }
202
228
  candidate = (0, quad_1.orderQuadPoints)(candidate);
203
229
  anchorQuadRef.current = candidate;
204
230
  lastQuadRef.current = candidate;
205
231
  setQuad(candidate);
232
+ anchorMissesRef.current = 0;
206
233
  }, []);
207
234
  const reportError = (0, react_native_worklets_core_1.useRunOnJS)((step, error) => {
208
235
  const message = error instanceof Error ? error.message : `${error}`;
@@ -286,7 +313,7 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
286
313
  console.log('[DocScanner] area', area, 'ratio', areaRatio);
287
314
  }
288
315
  // Skip if area ratio is too small or too large
289
- if (areaRatio < 0.005 || areaRatio > 0.95) {
316
+ if (areaRatio < 0.0002 || areaRatio > 0.99) {
290
317
  continue;
291
318
  }
292
319
  // Try to use convex hull for better corner detection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -110,7 +110,8 @@ export const DocScanner: React.FC<Props> = ({
110
110
  const lastQuadRef = useRef<Point[] | null>(null);
111
111
  const smoothingBufferRef = useRef<Point[][]>([]);
112
112
  const anchorQuadRef = useRef<Point[] | null>(null);
113
- const missingFrameCountRef = useRef(0);
113
+ const anchorMissesRef = useRef(0);
114
+ const anchorConfidenceRef = useRef(0);
114
115
  const lastMeasurementRef = useRef<Point[] | null>(null);
115
116
  const frameSizeRef = useRef<{ width: number; height: number } | null>(null);
116
117
 
@@ -122,31 +123,55 @@ export const DocScanner: React.FC<Props> = ({
122
123
  const REJECT_CENTER_DELTA = 145;
123
124
  const MAX_AREA_SHIFT = 0.45;
124
125
  const HISTORY_RESET_DISTANCE = 70;
125
- const MIN_AREA_RATIO = 0.0035;
126
+ const MIN_AREA_RATIO = 0.0002;
126
127
  const MAX_AREA_RATIO = 0.9;
127
- const MIN_EDGE_RATIO = 0.025;
128
+ const MIN_EDGE_RATIO = 0.015;
129
+ const MAX_ANCHOR_MISSES = 12;
130
+ const MIN_CONFIDENCE_TO_HOLD = 2;
131
+ const MAX_ANCHOR_CONFIDENCE = 20;
128
132
 
129
133
  const updateQuad = useRunOnJS((value: Point[] | null) => {
130
134
  if (__DEV__) {
131
135
  console.log('[DocScanner] quad', value);
132
136
  }
133
137
 
134
- if (!isValidQuad(value)) {
135
- missingFrameCountRef.current += 1;
138
+ const fallbackToAnchor = (resetHistory: boolean) => {
139
+ if (resetHistory) {
140
+ smoothingBufferRef.current = [];
141
+ lastMeasurementRef.current = null;
142
+ }
136
143
 
137
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
138
- setQuad(lastQuadRef.current);
139
- return;
144
+ const anchor = anchorQuadRef.current;
145
+ const anchorConfidence = anchorConfidenceRef.current;
146
+
147
+ if (anchor && anchorConfidence >= MIN_CONFIDENCE_TO_HOLD) {
148
+ anchorMissesRef.current += 1;
149
+
150
+ if (anchorMissesRef.current <= MAX_ANCHOR_MISSES) {
151
+ anchorConfidenceRef.current = Math.max(1, anchorConfidence - 1);
152
+ lastQuadRef.current = anchor;
153
+ setQuad(anchor);
154
+ return true;
155
+ }
140
156
  }
141
157
 
142
- smoothingBufferRef.current = [];
158
+ anchorMissesRef.current = 0;
159
+ anchorConfidenceRef.current = 0;
143
160
  anchorQuadRef.current = null;
144
161
  lastQuadRef.current = null;
145
162
  setQuad(null);
163
+ return false;
164
+ };
165
+
166
+ if (!isValidQuad(value)) {
167
+ const handled = fallbackToAnchor(false);
168
+ if (handled) {
169
+ return;
170
+ }
146
171
  return;
147
172
  }
148
173
 
149
- missingFrameCountRef.current = 0;
174
+ anchorMissesRef.current = 0;
150
175
 
151
176
  const ordered = orderQuadPoints(value);
152
177
  const sanitized = sanitizeQuad(ordered);
@@ -169,34 +194,24 @@ export const DocScanner: React.FC<Props> = ({
169
194
  const aspectTooExtreme = aspectRatio > 7;
170
195
 
171
196
  if (areaTooSmall || areaTooLarge || edgesTooShort || aspectTooExtreme) {
172
- missingFrameCountRef.current += 1;
173
-
174
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
175
- setQuad(lastQuadRef.current);
197
+ const handled = fallbackToAnchor(true);
198
+ if (handled) {
176
199
  return;
177
200
  }
178
-
179
- smoothingBufferRef.current = [];
180
- anchorQuadRef.current = null;
181
- lastQuadRef.current = null;
182
- setQuad(null);
183
201
  return;
184
202
  }
185
203
 
186
204
  const lastMeasurement = lastMeasurementRef.current;
187
- if (lastMeasurement && quadDistance(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE) {
188
- smoothingBufferRef.current = [];
189
- }
190
- lastMeasurementRef.current = sanitized;
205
+ const shouldResetHistory =
206
+ lastMeasurement && quadDistance(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE;
191
207
 
192
- smoothingBufferRef.current.push(sanitized);
193
- if (smoothingBufferRef.current.length > MAX_HISTORY) {
194
- smoothingBufferRef.current.shift();
195
- }
208
+ const existingHistory = shouldResetHistory ? [] : smoothingBufferRef.current;
209
+ const nextHistory = existingHistory.length >= MAX_HISTORY
210
+ ? [...existingHistory.slice(existingHistory.length - (MAX_HISTORY - 1)), sanitized]
211
+ : [...existingHistory, sanitized];
196
212
 
197
- const history = smoothingBufferRef.current;
198
- const hasHistory = history.length >= 2;
199
- let candidate = hasHistory ? weightedAverageQuad(history) : sanitized;
213
+ const hasHistory = nextHistory.length >= 2;
214
+ let candidate = hasHistory ? weightedAverageQuad(nextHistory) : sanitized;
200
215
 
201
216
  const anchor = anchorQuadRef.current;
202
217
  if (anchor && isValidQuad(anchor)) {
@@ -209,35 +224,46 @@ export const DocScanner: React.FC<Props> = ({
209
224
  const areaShift = anchorArea > 0 ? Math.abs(anchorArea - candidateArea) / anchorArea : 0;
210
225
 
211
226
  if (centerDelta >= REJECT_CENTER_DELTA || areaShift > 1.2) {
212
- missingFrameCountRef.current += 1;
213
-
214
- if (missingFrameCountRef.current <= 2) {
215
- setQuad(anchor);
216
- return;
217
- }
218
-
219
- smoothingBufferRef.current = [];
220
- anchorQuadRef.current = null;
221
- lastQuadRef.current = null;
222
- setQuad(null);
227
+ smoothingBufferRef.current = [sanitized];
228
+ lastMeasurementRef.current = sanitized;
229
+ anchorQuadRef.current = candidate;
230
+ anchorConfidenceRef.current = 1;
231
+ anchorMissesRef.current = 0;
232
+ lastQuadRef.current = candidate;
233
+ setQuad(candidate);
223
234
  return;
224
235
  }
225
236
 
226
237
  if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
227
238
  candidate = anchor;
239
+ smoothingBufferRef.current = nextHistory;
240
+ lastMeasurementRef.current = sanitized;
241
+ anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
228
242
  } else if (delta <= BLEND_DISTANCE && centerDelta <= MAX_CENTER_DELTA && areaShift <= MAX_AREA_SHIFT) {
229
243
  const normalizedDelta = Math.min(1, delta / BLEND_DISTANCE);
230
244
  const adaptiveAlpha = 0.25 + normalizedDelta * 0.45; // 0.25..0.7 range
231
245
  candidate = blendQuads(anchor, candidate, adaptiveAlpha);
246
+ smoothingBufferRef.current = nextHistory;
247
+ lastMeasurementRef.current = sanitized;
248
+ anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
232
249
  } else {
233
- smoothingBufferRef.current = [sanitized];
250
+ const handled = fallbackToAnchor(true);
251
+ if (handled) {
252
+ return;
253
+ }
254
+ return;
234
255
  }
256
+ } else {
257
+ smoothingBufferRef.current = nextHistory;
258
+ lastMeasurementRef.current = sanitized;
259
+ anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
235
260
  }
236
261
 
237
262
  candidate = orderQuadPoints(candidate);
238
263
  anchorQuadRef.current = candidate;
239
264
  lastQuadRef.current = candidate;
240
265
  setQuad(candidate);
266
+ anchorMissesRef.current = 0;
241
267
  }, []);
242
268
 
243
269
  const reportError = useRunOnJS((step: string, error: unknown) => {
@@ -342,7 +368,7 @@ export const DocScanner: React.FC<Props> = ({
342
368
  }
343
369
 
344
370
  // Skip if area ratio is too small or too large
345
- if (areaRatio < 0.005 || areaRatio > 0.95) {
371
+ if (areaRatio < 0.0002 || areaRatio > 0.99) {
346
372
  continue;
347
373
  }
348
374