react-native-rectangle-doc-scanner 0.41.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 +64 -27
- package/package.json +1 -1
- package/src/DocScanner.tsx +68 -29
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;
|
|
@@ -103,28 +104,50 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
103
104
|
const SNAP_CENTER_DISTANCE = 12;
|
|
104
105
|
const BLEND_DISTANCE = 65; // pixels; softly ease between similar shapes
|
|
105
106
|
const MAX_CENTER_DELTA = 85;
|
|
107
|
+
const REJECT_CENTER_DELTA = 145;
|
|
106
108
|
const MAX_AREA_SHIFT = 0.45;
|
|
107
109
|
const HISTORY_RESET_DISTANCE = 70;
|
|
108
110
|
const MIN_AREA_RATIO = 0.0035;
|
|
109
111
|
const MAX_AREA_RATIO = 0.9;
|
|
110
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;
|
|
111
116
|
const updateQuad = (0, react_native_worklets_core_1.useRunOnJS)((value) => {
|
|
112
117
|
if (__DEV__) {
|
|
113
118
|
console.log('[DocScanner] quad', value);
|
|
114
119
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
135
|
}
|
|
121
|
-
|
|
136
|
+
anchorMissesRef.current = 0;
|
|
137
|
+
anchorConfidenceRef.current = 0;
|
|
122
138
|
anchorQuadRef.current = null;
|
|
123
139
|
lastQuadRef.current = null;
|
|
124
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
|
+
}
|
|
125
148
|
return;
|
|
126
149
|
}
|
|
127
|
-
|
|
150
|
+
anchorMissesRef.current = 0;
|
|
128
151
|
const ordered = (0, quad_1.orderQuadPoints)(value);
|
|
129
152
|
const sanitized = (0, quad_1.sanitizeQuad)(ordered);
|
|
130
153
|
const frameSize = frameSizeRef.current;
|
|
@@ -142,29 +165,20 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
142
165
|
const edgesTooShort = minEdge < minEdgeThreshold;
|
|
143
166
|
const aspectTooExtreme = aspectRatio > 7;
|
|
144
167
|
if (areaTooSmall || areaTooLarge || edgesTooShort || aspectTooExtreme) {
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
setQuad(lastQuadRef.current);
|
|
168
|
+
const handled = fallbackToAnchor(true);
|
|
169
|
+
if (handled) {
|
|
148
170
|
return;
|
|
149
171
|
}
|
|
150
|
-
smoothingBufferRef.current = [];
|
|
151
|
-
anchorQuadRef.current = null;
|
|
152
|
-
lastQuadRef.current = null;
|
|
153
|
-
setQuad(null);
|
|
154
172
|
return;
|
|
155
173
|
}
|
|
156
174
|
const lastMeasurement = lastMeasurementRef.current;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
const history = smoothingBufferRef.current;
|
|
166
|
-
const hasHistory = history.length >= 2;
|
|
167
|
-
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;
|
|
168
182
|
const anchor = anchorQuadRef.current;
|
|
169
183
|
if (anchor && (0, quad_1.isValidQuad)(anchor)) {
|
|
170
184
|
const delta = (0, quad_1.quadDistance)(candidate, anchor);
|
|
@@ -174,22 +188,45 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
174
188
|
const candidateArea = (0, quad_1.quadArea)(candidate);
|
|
175
189
|
const centerDelta = Math.hypot(candidateCenter.x - anchorCenter.x, candidateCenter.y - anchorCenter.y);
|
|
176
190
|
const areaShift = anchorArea > 0 ? Math.abs(anchorArea - candidateArea) / anchorArea : 0;
|
|
191
|
+
if (centerDelta >= REJECT_CENTER_DELTA || areaShift > 1.2) {
|
|
192
|
+
const handled = fallbackToAnchor(true);
|
|
193
|
+
if (handled) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
177
198
|
if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
|
|
178
199
|
candidate = anchor;
|
|
200
|
+
smoothingBufferRef.current = nextHistory;
|
|
201
|
+
lastMeasurementRef.current = sanitized;
|
|
202
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
179
203
|
}
|
|
180
204
|
else if (delta <= BLEND_DISTANCE && centerDelta <= MAX_CENTER_DELTA && areaShift <= MAX_AREA_SHIFT) {
|
|
181
205
|
const normalizedDelta = Math.min(1, delta / BLEND_DISTANCE);
|
|
182
206
|
const adaptiveAlpha = 0.25 + normalizedDelta * 0.45; // 0.25..0.7 range
|
|
183
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);
|
|
184
211
|
}
|
|
185
212
|
else {
|
|
186
|
-
|
|
213
|
+
const handled = fallbackToAnchor(true);
|
|
214
|
+
if (handled) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
187
218
|
}
|
|
188
219
|
}
|
|
220
|
+
else {
|
|
221
|
+
smoothingBufferRef.current = nextHistory;
|
|
222
|
+
lastMeasurementRef.current = sanitized;
|
|
223
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
224
|
+
}
|
|
189
225
|
candidate = (0, quad_1.orderQuadPoints)(candidate);
|
|
190
226
|
anchorQuadRef.current = candidate;
|
|
191
227
|
lastQuadRef.current = candidate;
|
|
192
228
|
setQuad(candidate);
|
|
229
|
+
anchorMissesRef.current = 0;
|
|
193
230
|
}, []);
|
|
194
231
|
const reportError = (0, react_native_worklets_core_1.useRunOnJS)((step, error) => {
|
|
195
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
|
|
|
@@ -119,33 +120,58 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
119
120
|
const SNAP_CENTER_DISTANCE = 12;
|
|
120
121
|
const BLEND_DISTANCE = 65; // pixels; softly ease between similar shapes
|
|
121
122
|
const MAX_CENTER_DELTA = 85;
|
|
123
|
+
const REJECT_CENTER_DELTA = 145;
|
|
122
124
|
const MAX_AREA_SHIFT = 0.45;
|
|
123
125
|
const HISTORY_RESET_DISTANCE = 70;
|
|
124
126
|
const MIN_AREA_RATIO = 0.0035;
|
|
125
127
|
const MAX_AREA_RATIO = 0.9;
|
|
126
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;
|
|
127
132
|
|
|
128
133
|
const updateQuad = useRunOnJS((value: Point[] | null) => {
|
|
129
134
|
if (__DEV__) {
|
|
130
135
|
console.log('[DocScanner] quad', value);
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
const fallbackToAnchor = (resetHistory: boolean) => {
|
|
139
|
+
if (resetHistory) {
|
|
140
|
+
smoothingBufferRef.current = [];
|
|
141
|
+
lastMeasurementRef.current = null;
|
|
142
|
+
}
|
|
135
143
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
}
|
|
139
156
|
}
|
|
140
157
|
|
|
141
|
-
|
|
158
|
+
anchorMissesRef.current = 0;
|
|
159
|
+
anchorConfidenceRef.current = 0;
|
|
142
160
|
anchorQuadRef.current = null;
|
|
143
161
|
lastQuadRef.current = null;
|
|
144
162
|
setQuad(null);
|
|
163
|
+
return false;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (!isValidQuad(value)) {
|
|
167
|
+
const handled = fallbackToAnchor(false);
|
|
168
|
+
if (handled) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
145
171
|
return;
|
|
146
172
|
}
|
|
147
173
|
|
|
148
|
-
|
|
174
|
+
anchorMissesRef.current = 0;
|
|
149
175
|
|
|
150
176
|
const ordered = orderQuadPoints(value);
|
|
151
177
|
const sanitized = sanitizeQuad(ordered);
|
|
@@ -168,34 +194,24 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
168
194
|
const aspectTooExtreme = aspectRatio > 7;
|
|
169
195
|
|
|
170
196
|
if (areaTooSmall || areaTooLarge || edgesTooShort || aspectTooExtreme) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
|
|
174
|
-
setQuad(lastQuadRef.current);
|
|
197
|
+
const handled = fallbackToAnchor(true);
|
|
198
|
+
if (handled) {
|
|
175
199
|
return;
|
|
176
200
|
}
|
|
177
|
-
|
|
178
|
-
smoothingBufferRef.current = [];
|
|
179
|
-
anchorQuadRef.current = null;
|
|
180
|
-
lastQuadRef.current = null;
|
|
181
|
-
setQuad(null);
|
|
182
201
|
return;
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
const lastMeasurement = lastMeasurementRef.current;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
lastMeasurementRef.current = sanitized;
|
|
205
|
+
const shouldResetHistory =
|
|
206
|
+
lastMeasurement && quadDistance(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE;
|
|
190
207
|
|
|
191
|
-
smoothingBufferRef.current
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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];
|
|
195
212
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
let candidate = hasHistory ? weightedAverageQuad(history) : sanitized;
|
|
213
|
+
const hasHistory = nextHistory.length >= 2;
|
|
214
|
+
let candidate = hasHistory ? weightedAverageQuad(nextHistory) : sanitized;
|
|
199
215
|
|
|
200
216
|
const anchor = anchorQuadRef.current;
|
|
201
217
|
if (anchor && isValidQuad(anchor)) {
|
|
@@ -207,21 +223,44 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
207
223
|
const centerDelta = Math.hypot(candidateCenter.x - anchorCenter.x, candidateCenter.y - anchorCenter.y);
|
|
208
224
|
const areaShift = anchorArea > 0 ? Math.abs(anchorArea - candidateArea) / anchorArea : 0;
|
|
209
225
|
|
|
226
|
+
if (centerDelta >= REJECT_CENTER_DELTA || areaShift > 1.2) {
|
|
227
|
+
const handled = fallbackToAnchor(true);
|
|
228
|
+
if (handled) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
210
234
|
if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
|
|
211
235
|
candidate = anchor;
|
|
236
|
+
smoothingBufferRef.current = nextHistory;
|
|
237
|
+
lastMeasurementRef.current = sanitized;
|
|
238
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
212
239
|
} else if (delta <= BLEND_DISTANCE && centerDelta <= MAX_CENTER_DELTA && areaShift <= MAX_AREA_SHIFT) {
|
|
213
240
|
const normalizedDelta = Math.min(1, delta / BLEND_DISTANCE);
|
|
214
241
|
const adaptiveAlpha = 0.25 + normalizedDelta * 0.45; // 0.25..0.7 range
|
|
215
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);
|
|
216
246
|
} else {
|
|
217
|
-
|
|
247
|
+
const handled = fallbackToAnchor(true);
|
|
248
|
+
if (handled) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
218
252
|
}
|
|
253
|
+
} else {
|
|
254
|
+
smoothingBufferRef.current = nextHistory;
|
|
255
|
+
lastMeasurementRef.current = sanitized;
|
|
256
|
+
anchorConfidenceRef.current = Math.min(anchorConfidenceRef.current + 1, MAX_ANCHOR_CONFIDENCE);
|
|
219
257
|
}
|
|
220
258
|
|
|
221
259
|
candidate = orderQuadPoints(candidate);
|
|
222
260
|
anchorQuadRef.current = candidate;
|
|
223
261
|
lastQuadRef.current = candidate;
|
|
224
262
|
setQuad(candidate);
|
|
263
|
+
anchorMissesRef.current = 0;
|
|
225
264
|
}, []);
|
|
226
265
|
|
|
227
266
|
const reportError = useRunOnJS((step: string, error: unknown) => {
|