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.
- package/dist/DocScanner.js +66 -39
- package/package.json +1 -1
- package/src/DocScanner.tsx +69 -43
package/dist/DocScanner.js
CHANGED
|
@@ -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
|
|
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.
|
|
110
|
+
const MIN_AREA_RATIO = 0.0002;
|
|
110
111
|
const MAX_AREA_RATIO = 0.9;
|
|
111
|
-
const MIN_EDGE_RATIO = 0.
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return;
|
|
120
|
+
const fallbackToAnchor = (resetHistory) => {
|
|
121
|
+
if (resetHistory) {
|
|
122
|
+
smoothingBufferRef.current = [];
|
|
123
|
+
lastMeasurementRef.current = null;
|
|
121
124
|
}
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
if (
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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.
|
|
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
package/src/DocScanner.tsx
CHANGED
|
@@ -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
|
|
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.
|
|
126
|
+
const MIN_AREA_RATIO = 0.0002;
|
|
126
127
|
const MAX_AREA_RATIO = 0.9;
|
|
127
|
-
const MIN_EDGE_RATIO = 0.
|
|
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
|
-
|
|
135
|
-
|
|
138
|
+
const fallbackToAnchor = (resetHistory: boolean) => {
|
|
139
|
+
if (resetHistory) {
|
|
140
|
+
smoothingBufferRef.current = [];
|
|
141
|
+
lastMeasurementRef.current = null;
|
|
142
|
+
}
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
lastMeasurementRef.current = sanitized;
|
|
205
|
+
const shouldResetHistory =
|
|
206
|
+
lastMeasurement && quadDistance(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE;
|
|
191
207
|
|
|
192
|
-
smoothingBufferRef.current
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
198
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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.
|
|
371
|
+
if (areaRatio < 0.0002 || areaRatio > 0.99) {
|
|
346
372
|
continue;
|
|
347
373
|
}
|
|
348
374
|
|