react-native-rectangle-doc-scanner 0.4.0 → 0.6.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.
@@ -38,7 +38,7 @@ const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const react_native_vision_camera_1 = require("react-native-vision-camera");
40
40
  const vision_camera_resize_plugin_1 = require("vision-camera-resize-plugin");
41
- const react_native_reanimated_1 = require("react-native-reanimated");
41
+ const react_native_worklets_core_1 = require("react-native-worklets-core");
42
42
  const react_native_fast_opencv_1 = require("react-native-fast-opencv");
43
43
  const overlay_1 = require("./utils/overlay");
44
44
  const stability_1 = require("./utils/stability");
@@ -77,57 +77,92 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
77
77
  (0, react_1.useEffect)(() => {
78
78
  requestPermission();
79
79
  }, [requestPermission]);
80
+ const updateQuad = (0, react_native_worklets_core_1.useRunOnJS)((value) => {
81
+ if (__DEV__) {
82
+ console.log('[DocScanner] quad', value);
83
+ }
84
+ setQuad(value);
85
+ }, []);
86
+ const reportError = (0, react_native_worklets_core_1.useRunOnJS)((step, error) => {
87
+ const message = error instanceof Error ? error.message : `${error}`;
88
+ console.warn(`[DocScanner] frame error at ${step}: ${message}`);
89
+ }, []);
80
90
  const frameProcessor = (0, react_native_vision_camera_1.useFrameProcessor)((frame) => {
81
91
  'worklet';
82
- const ratio = 480 / frame.width;
83
- const w = Math.floor(frame.width * ratio);
84
- const h = Math.floor(frame.height * ratio);
85
- const resized = resize(frame, {
86
- dataType: 'uint8',
87
- pixelFormat: 'bgr',
88
- scale: { width: w, height: h },
89
- });
90
- const mat = react_native_fast_opencv_1.OpenCV.frameBufferToMat(h, w, 3, resized);
91
- react_native_fast_opencv_1.OpenCV.invoke('cvtColor', mat, mat, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
92
- const kernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 4, 4);
93
- const element = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, kernel);
94
- react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.MORPH_OPEN, element);
95
- react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, kernel, 0);
96
- react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 75, 100);
97
- const contours = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVectorOfVectors);
98
- react_native_fast_opencv_1.OpenCV.invoke('findContours', mat, contours, react_native_fast_opencv_1.RetrievalModes.RETR_LIST, react_native_fast_opencv_1.ContourApproximationModes.CHAIN_APPROX_SIMPLE);
99
- let best = null;
100
- let maxArea = 0;
101
- const arr = react_native_fast_opencv_1.OpenCV.toJSValue(contours).array;
102
- for (let i = 0; i < arr.length; i++) {
103
- const c = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contours, i);
104
- const { value: area } = react_native_fast_opencv_1.OpenCV.invoke('contourArea', c, false);
105
- if (area < w * h * 0.1) {
106
- continue;
107
- }
108
- const { value: peri } = react_native_fast_opencv_1.OpenCV.invoke('arcLength', c, true);
109
- const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
110
- react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', c, approx, 0.02 * peri, true);
111
- const size = react_native_fast_opencv_1.OpenCV.invokeWithOutParam('size', approx);
112
- if (size !== 4) {
113
- continue;
114
- }
115
- const pts = [];
116
- for (let j = 0; j < 4; j++) {
117
- const p = react_native_fast_opencv_1.OpenCV.invoke('atPoint', approx, j, 0);
118
- pts.push({ x: p.x / ratio, y: p.y / ratio });
119
- }
120
- if (!isConvexQuadrilateral(pts)) {
121
- continue;
122
- }
123
- if (area > maxArea) {
124
- best = pts;
125
- maxArea = area;
92
+ let step = 'start';
93
+ try {
94
+ const ratio = 480 / frame.width;
95
+ const width = Math.floor(frame.width * ratio);
96
+ const height = Math.floor(frame.height * ratio);
97
+ step = 'resize';
98
+ const resized = resize(frame, {
99
+ dataType: 'uint8',
100
+ pixelFormat: 'bgr',
101
+ scale: { width: width, height: height },
102
+ });
103
+ step = 'frameBufferToMat';
104
+ const mat = react_native_fast_opencv_1.OpenCV.frameBufferToMat(height, width, 3, resized);
105
+ step = 'cvtColor';
106
+ react_native_fast_opencv_1.OpenCV.invoke('cvtColor', mat, mat, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
107
+ const morphologyKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
108
+ step = 'getStructuringElement';
109
+ const element = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, morphologyKernel);
110
+ step = 'morphologyEx';
111
+ react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.MORPH_OPEN, element);
112
+ const gaussianKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
113
+ step = 'GaussianBlur';
114
+ react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
115
+ step = 'Canny';
116
+ react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 75, 100);
117
+ step = 'createContours';
118
+ const contours = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVectorOfVectors);
119
+ react_native_fast_opencv_1.OpenCV.invoke('findContours', mat, contours, react_native_fast_opencv_1.RetrievalModes.RETR_LIST, react_native_fast_opencv_1.ContourApproximationModes.CHAIN_APPROX_SIMPLE);
120
+ let best = null;
121
+ let maxArea = 0;
122
+ step = 'toJSValue';
123
+ const contourVector = react_native_fast_opencv_1.OpenCV.toJSValue(contours);
124
+ const contourArray = Array.isArray(contourVector?.array) ? contourVector.array : [];
125
+ for (let i = 0; i < contourArray.length; i += 1) {
126
+ step = `contour_${i}_copy`;
127
+ const contour = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contours, i);
128
+ step = `contour_${i}_area`;
129
+ const { value: area } = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour, false);
130
+ if (area < width * height * 0.1) {
131
+ continue;
132
+ }
133
+ step = `contour_${i}_arcLength`;
134
+ const { value: perimeter } = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
135
+ const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
136
+ step = `contour_${i}_approxPolyDP`;
137
+ react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * perimeter, true);
138
+ step = `contour_${i}_toJS`;
139
+ const approxValue = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
140
+ const approxArray = Array.isArray(approxValue?.array) ? approxValue.array : [];
141
+ if (approxArray.length !== 4) {
142
+ continue;
143
+ }
144
+ step = `contour_${i}_convex`;
145
+ const points = approxArray.map((pt) => ({
146
+ x: pt.x / ratio,
147
+ y: pt.y / ratio,
148
+ }));
149
+ if (!isConvexQuadrilateral(points)) {
150
+ continue;
151
+ }
152
+ if (area > maxArea) {
153
+ best = points;
154
+ maxArea = area;
155
+ }
126
156
  }
157
+ step = 'clearBuffers';
158
+ react_native_fast_opencv_1.OpenCV.clearBuffers();
159
+ step = 'updateQuad';
160
+ updateQuad(best);
161
+ }
162
+ catch (error) {
163
+ reportError(step, error);
127
164
  }
128
- react_native_fast_opencv_1.OpenCV.clearBuffers();
129
- (0, react_native_reanimated_1.runOnJS)(setQuad)(best);
130
- }, [resize]);
165
+ }, [resize, reportError, updateQuad]);
131
166
  (0, react_1.useEffect)(() => {
132
167
  const s = (0, stability_1.checkStability)(quad);
133
168
  setStable(s);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -29,5 +29,8 @@
29
29
  "@types/react": "^18.2.41",
30
30
  "@types/react-native": "0.73.0",
31
31
  "typescript": "^5.3.3"
32
+ },
33
+ "dependencies": {
34
+ "react-native-worklets-core": "^1.6.2"
32
35
  }
33
36
  }
@@ -2,7 +2,7 @@ import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'reac
2
2
  import { View, TouchableOpacity, StyleSheet } from 'react-native';
3
3
  import { Camera, useCameraDevice, useCameraPermission, useFrameProcessor } from 'react-native-vision-camera';
4
4
  import { useResizePlugin } from 'vision-camera-resize-plugin';
5
- import { runOnJS } from 'react-native-reanimated';
5
+ import { useRunOnJS } from 'react-native-worklets-core';
6
6
  import {
7
7
  OpenCV,
8
8
  ColorConversionCodes,
@@ -82,72 +82,113 @@ export const DocScanner: React.FC<Props> = ({
82
82
  requestPermission();
83
83
  }, [requestPermission]);
84
84
 
85
- const frameProcessor = useFrameProcessor((frame) => {
86
- 'worklet';
87
-
88
- const ratio = 480 / frame.width;
89
- const w = Math.floor(frame.width * ratio);
90
- const h = Math.floor(frame.height * ratio);
91
- const resized = resize(frame, {
92
- dataType: 'uint8',
93
- pixelFormat: 'bgr',
94
- scale: { width: w, height: h },
95
- });
96
-
97
- const mat = OpenCV.frameBufferToMat(h, w, 3, resized);
98
-
99
- OpenCV.invoke('cvtColor', mat, mat, ColorConversionCodes.COLOR_BGR2GRAY);
100
-
101
- const kernel = OpenCV.createObject(ObjectType.Size, 4, 4);
102
- const element = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, kernel);
103
- OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.MORPH_OPEN, element);
104
-
105
- OpenCV.invoke('GaussianBlur', mat, mat, kernel, 0);
106
- OpenCV.invoke('Canny', mat, mat, 75, 100);
107
-
108
- const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
109
- OpenCV.invoke('findContours', mat, contours, RetrievalModes.RETR_LIST, ContourApproximationModes.CHAIN_APPROX_SIMPLE);
110
-
111
- let best: Point[] | null = null;
112
- let maxArea = 0;
113
-
114
- const arr = OpenCV.toJSValue(contours).array;
115
-
116
- for (let i = 0; i < arr.length; i++) {
117
- const c = OpenCV.copyObjectFromVector(contours, i);
118
- const { value: area } = OpenCV.invoke('contourArea', c, false);
119
-
120
- if (area < w * h * 0.1) {
121
- continue;
122
- }
85
+ const updateQuad = useRunOnJS((value: Point[] | null) => {
86
+ if (__DEV__) {
87
+ console.log('[DocScanner] quad', value);
88
+ }
89
+ setQuad(value);
90
+ }, []);
123
91
 
124
- const { value: peri } = OpenCV.invoke('arcLength', c, true);
125
- const approx = OpenCV.createObject(ObjectType.PointVector);
126
- OpenCV.invoke('approxPolyDP', c, approx, 0.02 * peri, true);
127
- const size = OpenCV.invokeWithOutParam('size', approx);
128
- if (size !== 4) {
129
- continue;
130
- }
92
+ const reportError = useRunOnJS((step: string, error: unknown) => {
93
+ const message = error instanceof Error ? error.message : `${error}`;
94
+ console.warn(`[DocScanner] frame error at ${step}: ${message}`);
95
+ }, []);
131
96
 
132
- const pts: Point[] = [];
133
- for (let j = 0; j < 4; j++) {
134
- const p = OpenCV.invoke('atPoint', approx, j, 0);
135
- pts.push({ x: p.x / ratio, y: p.y / ratio });
136
- }
97
+ const frameProcessor = useFrameProcessor((frame) => {
98
+ 'worklet';
137
99
 
138
- if (!isConvexQuadrilateral(pts)) {
139
- continue;
100
+ let step = 'start';
101
+
102
+ try {
103
+ const ratio = 480 / frame.width;
104
+ const width = Math.floor(frame.width * ratio);
105
+ const height = Math.floor(frame.height * ratio);
106
+ step = 'resize';
107
+ const resized = resize(frame, {
108
+ dataType: 'uint8',
109
+ pixelFormat: 'bgr',
110
+ scale: { width: width, height: height },
111
+ });
112
+
113
+ step = 'frameBufferToMat';
114
+ const mat = OpenCV.frameBufferToMat(height, width, 3, resized);
115
+
116
+ step = 'cvtColor';
117
+ OpenCV.invoke('cvtColor', mat, mat, ColorConversionCodes.COLOR_BGR2GRAY);
118
+
119
+ const morphologyKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
120
+ step = 'getStructuringElement';
121
+ const element = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, morphologyKernel);
122
+ step = 'morphologyEx';
123
+ OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.MORPH_OPEN, element);
124
+
125
+ const gaussianKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
126
+ step = 'GaussianBlur';
127
+ OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
128
+ step = 'Canny';
129
+ OpenCV.invoke('Canny', mat, mat, 75, 100);
130
+
131
+ step = 'createContours';
132
+ const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
133
+ OpenCV.invoke('findContours', mat, contours, RetrievalModes.RETR_LIST, ContourApproximationModes.CHAIN_APPROX_SIMPLE);
134
+
135
+ let best: Point[] | null = null;
136
+ let maxArea = 0;
137
+
138
+ step = 'toJSValue';
139
+ const contourVector = OpenCV.toJSValue(contours);
140
+ const contourArray = Array.isArray(contourVector?.array) ? contourVector.array : [];
141
+
142
+ for (let i = 0; i < contourArray.length; i += 1) {
143
+ step = `contour_${i}_copy`;
144
+ const contour = OpenCV.copyObjectFromVector(contours, i);
145
+
146
+ step = `contour_${i}_area`;
147
+ const { value: area } = OpenCV.invoke('contourArea', contour, false);
148
+
149
+ if (area < width * height * 0.1) {
150
+ continue;
151
+ }
152
+
153
+ step = `contour_${i}_arcLength`;
154
+ const { value: perimeter } = OpenCV.invoke('arcLength', contour, true);
155
+ const approx = OpenCV.createObject(ObjectType.PointVector);
156
+
157
+ step = `contour_${i}_approxPolyDP`;
158
+ OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * perimeter, true);
159
+
160
+ step = `contour_${i}_toJS`;
161
+ const approxValue = OpenCV.toJSValue(approx);
162
+ const approxArray = Array.isArray(approxValue?.array) ? approxValue.array : [];
163
+
164
+ if (approxArray.length !== 4) {
165
+ continue;
166
+ }
167
+
168
+ step = `contour_${i}_convex`;
169
+ const points: Point[] = approxArray.map((pt: { x: number; y: number }) => ({
170
+ x: pt.x / ratio,
171
+ y: pt.y / ratio,
172
+ }));
173
+
174
+ if (!isConvexQuadrilateral(points)) {
175
+ continue;
176
+ }
177
+
178
+ if (area > maxArea) {
179
+ best = points;
180
+ maxArea = area;
181
+ }
140
182
  }
141
183
 
142
- if (area > maxArea) {
143
- best = pts;
144
- maxArea = area;
145
- }
184
+ step = 'clearBuffers';
185
+ OpenCV.clearBuffers();
186
+ step = 'updateQuad';
187
+ updateQuad(best);
188
+ } catch (error) {
189
+ reportError(step, error);
146
190
  }
147
-
148
- OpenCV.clearBuffers();
149
- runOnJS(setQuad)(best);
150
- }, [resize]);
191
+ }, [resize, reportError, updateQuad]);
151
192
 
152
193
  useEffect(() => {
153
194
  const s = checkStability(quad);
@@ -0,0 +1,8 @@
1
+ import type { DependencyList } from 'react';
2
+
3
+ declare module 'react-native-worklets-core' {
4
+ export function useRunOnJS<T extends (...args: any[]) => any>(
5
+ callback: T,
6
+ deps: DependencyList
7
+ ): (...args: Parameters<T>) => void;
8
+ }