react-native-rectangle-doc-scanner 0.42.0 → 0.43.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 +58 -34
- package/package.json +1 -1
- package/src/DocScanner.tsx +61 -38
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;
|
|
@@ -109,23 +110,44 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
109
110
|
const MIN_AREA_RATIO = 0.0035;
|
|
110
111
|
const MAX_AREA_RATIO = 0.9;
|
|
111
112
|
const MIN_EDGE_RATIO = 0.025;
|
|
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
|
-
|
|
120
|
+
const fallbackToAnchor = (resetHistory) => {
|
|
121
|
+
if (resetHistory) {
|
|
122
|
+
smoothingBufferRef.current = [];
|
|
123
|
+
lastMeasurementRef.current = null;
|
|
124
|
+
}
|
|
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
|
+
}
|
|
121
135
|
}
|
|
122
|
-
|
|
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,44 @@ 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
|
-
if (
|
|
181
|
-
setQuad(anchor);
|
|
192
|
+
const handled = fallbackToAnchor(true);
|
|
193
|
+
if (handled) {
|
|
182
194
|
return;
|
|
183
195
|
}
|
|
184
|
-
smoothingBufferRef.current = [];
|
|
185
|
-
anchorQuadRef.current = null;
|
|
186
|
-
lastQuadRef.current = null;
|
|
187
|
-
setQuad(null);
|
|
188
196
|
return;
|
|
189
197
|
}
|
|
190
198
|
if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
|
|
191
199
|
candidate = anchor;
|
|
200
|
+
smoothingBufferRef.current = nextHistory;
|
|
201
|
+
lastMeasurementRef.current = sanitized;
|
|
202
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
192
203
|
}
|
|
193
204
|
else if (delta <= BLEND_DISTANCE && centerDelta <= MAX_CENTER_DELTA && areaShift <= MAX_AREA_SHIFT) {
|
|
194
205
|
const normalizedDelta = Math.min(1, delta / BLEND_DISTANCE);
|
|
195
206
|
const adaptiveAlpha = 0.25 + normalizedDelta * 0.45; // 0.25..0.7 range
|
|
196
207
|
candidate = (0, quad_1.blendQuads)(anchor, candidate, adaptiveAlpha);
|
|
208
|
+
smoothingBufferRef.current = nextHistory;
|
|
209
|
+
lastMeasurementRef.current = sanitized;
|
|
210
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
197
211
|
}
|
|
198
212
|
else {
|
|
199
|
-
|
|
213
|
+
const handled = fallbackToAnchor(true);
|
|
214
|
+
if (handled) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
200
218
|
}
|
|
201
219
|
}
|
|
220
|
+
else {
|
|
221
|
+
smoothingBufferRef.current = nextHistory;
|
|
222
|
+
lastMeasurementRef.current = sanitized;
|
|
223
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
224
|
+
}
|
|
202
225
|
candidate = (0, quad_1.orderQuadPoints)(candidate);
|
|
203
226
|
anchorQuadRef.current = candidate;
|
|
204
227
|
lastQuadRef.current = candidate;
|
|
205
228
|
setQuad(candidate);
|
|
229
|
+
anchorMissesRef.current = 0;
|
|
206
230
|
}, []);
|
|
207
231
|
const reportError = (0, react_native_worklets_core_1.useRunOnJS)((step, error) => {
|
|
208
232
|
const message = error instanceof Error ? error.message : `${error}`;
|
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
|
|
|
@@ -125,28 +126,52 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
125
126
|
const MIN_AREA_RATIO = 0.0035;
|
|
126
127
|
const MAX_AREA_RATIO = 0.9;
|
|
127
128
|
const MIN_EDGE_RATIO = 0.025;
|
|
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,43 @@ 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
|
-
if (missingFrameCountRef.current <= 2) {
|
|
215
|
-
setQuad(anchor);
|
|
227
|
+
const handled = fallbackToAnchor(true);
|
|
228
|
+
if (handled) {
|
|
216
229
|
return;
|
|
217
230
|
}
|
|
218
|
-
|
|
219
|
-
smoothingBufferRef.current = [];
|
|
220
|
-
anchorQuadRef.current = null;
|
|
221
|
-
lastQuadRef.current = null;
|
|
222
|
-
setQuad(null);
|
|
223
231
|
return;
|
|
224
232
|
}
|
|
225
233
|
|
|
226
234
|
if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
|
|
227
235
|
candidate = anchor;
|
|
236
|
+
smoothingBufferRef.current = nextHistory;
|
|
237
|
+
lastMeasurementRef.current = sanitized;
|
|
238
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
228
239
|
} else if (delta <= BLEND_DISTANCE && centerDelta <= MAX_CENTER_DELTA && areaShift <= MAX_AREA_SHIFT) {
|
|
229
240
|
const normalizedDelta = Math.min(1, delta / BLEND_DISTANCE);
|
|
230
241
|
const adaptiveAlpha = 0.25 + normalizedDelta * 0.45; // 0.25..0.7 range
|
|
231
242
|
candidate = blendQuads(anchor, candidate, adaptiveAlpha);
|
|
243
|
+
smoothingBufferRef.current = nextHistory;
|
|
244
|
+
lastMeasurementRef.current = sanitized;
|
|
245
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
232
246
|
} else {
|
|
233
|
-
|
|
247
|
+
const handled = fallbackToAnchor(true);
|
|
248
|
+
if (handled) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
234
252
|
}
|
|
253
|
+
} else {
|
|
254
|
+
smoothingBufferRef.current = nextHistory;
|
|
255
|
+
lastMeasurementRef.current = sanitized;
|
|
256
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
235
257
|
}
|
|
236
258
|
|
|
237
259
|
candidate = orderQuadPoints(candidate);
|
|
238
260
|
anchorQuadRef.current = candidate;
|
|
239
261
|
lastQuadRef.current = candidate;
|
|
240
262
|
setQuad(candidate);
|
|
263
|
+
anchorMissesRef.current = 0;
|
|
241
264
|
}, []);
|
|
242
265
|
|
|
243
266
|
const reportError = useRunOnJS((step: string, error: unknown) => {
|