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.
@@ -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;
@@ -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
- 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;
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
- smoothingBufferRef.current = [];
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,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
- missingFrameCountRef.current += 1;
180
- if (missingFrameCountRef.current <= 2) {
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
- smoothingBufferRef.current = [sanitized];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.42.0",
3
+ "version": "0.43.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
 
@@ -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
- 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,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
- missingFrameCountRef.current += 1;
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
- smoothingBufferRef.current = [sanitized];
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) => {