react-native-rectangle-doc-scanner 1.0.0 → 1.1.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.
|
@@ -74,6 +74,8 @@ class RNRDocScannerView @JvmOverloads constructor(
|
|
|
74
74
|
private var currentStableCounter: Int = 0
|
|
75
75
|
private var lastQuad: QuadPoints? = null
|
|
76
76
|
private var lastFrameSize: AndroidSize? = null
|
|
77
|
+
private var missedDetections: Int = 0
|
|
78
|
+
private val maxMissedDetections = 4
|
|
77
79
|
private var capturePromise: Promise? = null
|
|
78
80
|
private var captureInFlight: Boolean = false
|
|
79
81
|
|
|
@@ -199,25 +201,43 @@ class RNRDocScannerView @JvmOverloads constructor(
|
|
|
199
201
|
|
|
200
202
|
private fun emitDetectionResult(quad: QuadPoints?, frameSize: AndroidSize) {
|
|
201
203
|
val reactContext = context as? ReactContext ?: return
|
|
204
|
+
|
|
205
|
+
val effectiveQuad: QuadPoints? = when {
|
|
206
|
+
quad != null -> {
|
|
207
|
+
missedDetections = 0
|
|
208
|
+
lastQuad = quad
|
|
209
|
+
quad
|
|
210
|
+
}
|
|
211
|
+
lastQuad != null && missedDetections < maxMissedDetections -> {
|
|
212
|
+
missedDetections += 1
|
|
213
|
+
lastQuad
|
|
214
|
+
}
|
|
215
|
+
else -> {
|
|
216
|
+
missedDetections = 0
|
|
217
|
+
lastQuad = null
|
|
218
|
+
null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
val sizeForEvent = if (effectiveQuad === quad) frameSize else lastFrameSize ?: frameSize
|
|
223
|
+
|
|
202
224
|
val event: WritableMap = Arguments.createMap().apply {
|
|
203
|
-
if (
|
|
225
|
+
if (effectiveQuad != null) {
|
|
204
226
|
val quadMap = Arguments.createMap().apply {
|
|
205
|
-
putMap("topLeft",
|
|
206
|
-
putMap("topRight",
|
|
207
|
-
putMap("bottomRight",
|
|
208
|
-
putMap("bottomLeft",
|
|
227
|
+
putMap("topLeft", effectiveQuad.topLeft.toWritable())
|
|
228
|
+
putMap("topRight", effectiveQuad.topRight.toWritable())
|
|
229
|
+
putMap("bottomRight", effectiveQuad.bottomRight.toWritable())
|
|
230
|
+
putMap("bottomLeft", effectiveQuad.bottomLeft.toWritable())
|
|
209
231
|
}
|
|
210
232
|
putMap("rectangleCoordinates", quadMap)
|
|
211
233
|
currentStableCounter = (currentStableCounter + 1).coerceAtMost(detectionCountBeforeCapture)
|
|
212
|
-
lastQuad = quad
|
|
213
234
|
} else {
|
|
214
235
|
putNull("rectangleCoordinates")
|
|
215
236
|
currentStableCounter = 0
|
|
216
|
-
lastQuad = null
|
|
217
237
|
}
|
|
218
238
|
putInt("stableCounter", currentStableCounter)
|
|
219
|
-
putDouble("frameWidth",
|
|
220
|
-
putDouble("frameHeight",
|
|
239
|
+
putDouble("frameWidth", sizeForEvent.width.toDouble())
|
|
240
|
+
putDouble("frameHeight", sizeForEvent.height.toDouble())
|
|
221
241
|
}
|
|
222
242
|
|
|
223
243
|
reactContext
|
package/dist/utils/overlay.js
CHANGED
|
@@ -62,6 +62,15 @@ const buildPath = (points) => {
|
|
|
62
62
|
path.close();
|
|
63
63
|
return path;
|
|
64
64
|
};
|
|
65
|
+
const orderQuad = (points) => {
|
|
66
|
+
if (points.length !== 4) {
|
|
67
|
+
return points;
|
|
68
|
+
}
|
|
69
|
+
const sorted = [...points].sort((a, b) => (a.y === b.y ? a.x - b.x : a.y - b.y));
|
|
70
|
+
const top = sorted.slice(0, 2).sort((a, b) => a.x - b.x);
|
|
71
|
+
const bottom = sorted.slice(2, 4).sort((a, b) => a.x - b.x);
|
|
72
|
+
return [top[0], top[1], bottom[1], bottom[0]];
|
|
73
|
+
};
|
|
65
74
|
const Overlay = ({ quad, color = '#e7a649', frameSize, showGrid = true, gridColor = 'rgba(231, 166, 73, 0.35)', gridLineWidth = 2, }) => {
|
|
66
75
|
const { width: screenWidth, height: screenHeight } = (0, react_native_1.useWindowDimensions)();
|
|
67
76
|
const fillColor = (0, react_1.useMemo)(() => withAlpha(color, 0.2), [color]);
|
|
@@ -106,10 +115,11 @@ const Overlay = ({ quad, color = '#e7a649', frameSize, showGrid = true, gridColo
|
|
|
106
115
|
if (!transformedQuad) {
|
|
107
116
|
return { outlinePath: null, gridPaths: [] };
|
|
108
117
|
}
|
|
109
|
-
const
|
|
118
|
+
const normalizedQuad = orderQuad(transformedQuad);
|
|
119
|
+
const skPath = buildPath(normalizedQuad);
|
|
110
120
|
const grid = [];
|
|
111
121
|
if (showGrid) {
|
|
112
|
-
const [topLeft, topRight, bottomRight, bottomLeft] =
|
|
122
|
+
const [topLeft, topRight, bottomRight, bottomLeft] = normalizedQuad;
|
|
113
123
|
const steps = [1 / 3, 2 / 3];
|
|
114
124
|
steps.forEach((t) => {
|
|
115
125
|
const start = lerp(topLeft, topRight, t);
|
|
@@ -33,6 +33,8 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
|
|
|
33
33
|
private var isProcessingFrame = false
|
|
34
34
|
private var isCaptureInFlight = false
|
|
35
35
|
private var lastObservation: VNRectangleObservation?
|
|
36
|
+
private var missedDetectionFrames: Int = 0
|
|
37
|
+
private let maxMissedDetections = 4
|
|
36
38
|
private var lastFrameSize: CGSize = .zero
|
|
37
39
|
private var photoCaptureCompletion: ((Result<RNRDocScannerCaptureResult, Error>) -> Void)?
|
|
38
40
|
|
|
@@ -173,15 +175,18 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
|
|
|
173
175
|
return
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
guard let observations = request.results as? [VNRectangleObservation], let observation = observations.max(by: { lhs, rhs in
|
|
179
|
+
lhs.boundingBox.width * lhs.boundingBox.height < rhs.boundingBox.width * rhs.boundingBox.height
|
|
180
|
+
}) else {
|
|
181
|
+
self.lastObservation = nil
|
|
182
|
+
self.handleDetectedRectangle(nil, frameSize: frameSize)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
181
185
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
self.lastObservation = observation
|
|
187
|
+
self.missedDetectionFrames = 0
|
|
188
|
+
self.handleDetectedRectangle(observation, frameSize: frameSize)
|
|
189
|
+
}
|
|
185
190
|
|
|
186
191
|
request.maximumObservations = 1
|
|
187
192
|
request.minimumConfidence = 0.5
|
|
@@ -205,13 +210,27 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
|
|
|
205
210
|
func handleDetectedRectangle(_ rectangle: VNRectangleObservation?, frameSize: CGSize) {
|
|
206
211
|
guard let onRectangleDetect else { return }
|
|
207
212
|
|
|
213
|
+
let effectiveObservation: VNRectangleObservation?
|
|
214
|
+
if let rect = rectangle {
|
|
215
|
+
effectiveObservation = rect
|
|
216
|
+
lastObservation = rect
|
|
217
|
+
missedDetectionFrames = 0
|
|
218
|
+
} else if missedDetectionFrames < maxMissedDetections, let cached = lastObservation {
|
|
219
|
+
missedDetectionFrames += 1
|
|
220
|
+
effectiveObservation = cached
|
|
221
|
+
} else {
|
|
222
|
+
lastObservation = nil
|
|
223
|
+
missedDetectionFrames = 0
|
|
224
|
+
effectiveObservation = nil
|
|
225
|
+
}
|
|
226
|
+
|
|
208
227
|
let payload: [String: Any?]
|
|
209
|
-
if let
|
|
228
|
+
if let observation = effectiveObservation {
|
|
210
229
|
let points = [
|
|
211
|
-
pointForOverlay(from:
|
|
212
|
-
pointForOverlay(from:
|
|
213
|
-
pointForOverlay(from:
|
|
214
|
-
pointForOverlay(from:
|
|
230
|
+
pointForOverlay(from: observation.topLeft, frameSize: frameSize),
|
|
231
|
+
pointForOverlay(from: observation.topRight, frameSize: frameSize),
|
|
232
|
+
pointForOverlay(from: observation.bottomRight, frameSize: frameSize),
|
|
233
|
+
pointForOverlay(from: observation.bottomLeft, frameSize: frameSize),
|
|
215
234
|
]
|
|
216
235
|
|
|
217
236
|
currentStableCounter = min(currentStableCounter + 1, Int(truncating: detectionCountBeforeCapture))
|
package/package.json
CHANGED
package/src/utils/overlay.tsx
CHANGED
|
@@ -47,6 +47,18 @@ const buildPath = (points: Point[]) => {
|
|
|
47
47
|
return path;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
const orderQuad = (points: Point[]): Point[] => {
|
|
51
|
+
if (points.length !== 4) {
|
|
52
|
+
return points;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sorted = [...points].sort((a, b) => (a.y === b.y ? a.x - b.x : a.y - b.y));
|
|
56
|
+
const top = sorted.slice(0, 2).sort((a, b) => a.x - b.x);
|
|
57
|
+
const bottom = sorted.slice(2, 4).sort((a, b) => a.x - b.x);
|
|
58
|
+
|
|
59
|
+
return [top[0], top[1], bottom[1], bottom[0]];
|
|
60
|
+
};
|
|
61
|
+
|
|
50
62
|
export const Overlay: React.FC<OverlayProps> = ({
|
|
51
63
|
quad,
|
|
52
64
|
color = '#e7a649',
|
|
@@ -105,11 +117,12 @@ export const Overlay: React.FC<OverlayProps> = ({
|
|
|
105
117
|
return { outlinePath: null, gridPaths: [] };
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
const
|
|
120
|
+
const normalizedQuad = orderQuad(transformedQuad);
|
|
121
|
+
const skPath = buildPath(normalizedQuad);
|
|
109
122
|
const grid: ReturnType<typeof Skia.Path.Make>[] = [];
|
|
110
123
|
|
|
111
124
|
if (showGrid) {
|
|
112
|
-
const [topLeft, topRight, bottomRight, bottomLeft] =
|
|
125
|
+
const [topLeft, topRight, bottomRight, bottomLeft] = normalizedQuad;
|
|
113
126
|
const steps = [1 / 3, 2 / 3];
|
|
114
127
|
|
|
115
128
|
steps.forEach((t) => {
|