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.
@@ -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;
@@ -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
- if (!(0, quad_1.isValidQuad)(value)) {
116
- missingFrameCountRef.current += 1;
117
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
118
- setQuad(lastQuadRef.current);
119
- 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
+ }
120
135
  }
121
- smoothingBufferRef.current = [];
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
- missingFrameCountRef.current = 0;
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
- missingFrameCountRef.current += 1;
146
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
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
- if (lastMeasurement && (0, quad_1.quadDistance)(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE) {
158
- smoothingBufferRef.current = [];
159
- }
160
- lastMeasurementRef.current = sanitized;
161
- smoothingBufferRef.current.push(sanitized);
162
- if (smoothingBufferRef.current.length > MAX_HISTORY) {
163
- smoothingBufferRef.current.shift();
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
- smoothingBufferRef.current = [sanitized];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.41.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
 
@@ -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
- if (!isValidQuad(value)) {
134
- missingFrameCountRef.current += 1;
138
+ const fallbackToAnchor = (resetHistory: boolean) => {
139
+ if (resetHistory) {
140
+ smoothingBufferRef.current = [];
141
+ lastMeasurementRef.current = null;
142
+ }
135
143
 
136
- if (lastQuadRef.current && missingFrameCountRef.current <= 2) {
137
- setQuad(lastQuadRef.current);
138
- 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
+ }
139
156
  }
140
157
 
141
- smoothingBufferRef.current = [];
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
- missingFrameCountRef.current = 0;
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
- missingFrameCountRef.current += 1;
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
- if (lastMeasurement && quadDistance(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE) {
187
- smoothingBufferRef.current = [];
188
- }
189
- lastMeasurementRef.current = sanitized;
205
+ const shouldResetHistory =
206
+ lastMeasurement && quadDistance(lastMeasurement, sanitized) > HISTORY_RESET_DISTANCE;
190
207
 
191
- smoothingBufferRef.current.push(sanitized);
192
- if (smoothingBufferRef.current.length > MAX_HISTORY) {
193
- smoothingBufferRef.current.shift();
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 history = smoothingBufferRef.current;
197
- const hasHistory = history.length >= 2;
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
- smoothingBufferRef.current = [sanitized];
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) => {