react-native-rectangle-doc-scanner 0.1.0 → 0.3.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/README.md +79 -8
- package/dist/DocScanner.d.ts +5 -1
- package/dist/DocScanner.js +7 -4
- package/package.json +9 -1
- package/src/DocScanner.tsx +14 -3
package/README.md
CHANGED
|
@@ -5,25 +5,56 @@ VisionCamera + Fast-OpenCV powered document scanner template built for React Nat
|
|
|
5
5
|
## Features
|
|
6
6
|
- Real-time quad detection using `react-native-fast-opencv`
|
|
7
7
|
- Frame processor worklet executed on the UI thread via `react-native-vision-camera`
|
|
8
|
-
- Resize plugin
|
|
9
|
-
- Skia overlay
|
|
10
|
-
- Stability tracker
|
|
8
|
+
- Resize plugin keeps frame processing fast on lower-end devices
|
|
9
|
+
- Skia overlay visualises detected document contours
|
|
10
|
+
- Stability tracker enables auto-capture once the document is steady
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
Install the module alongside these peer dependencies (your host app should already include them or install them now):
|
|
14
|
+
|
|
15
|
+
- `react-native-vision-camera` (v3+) with frame processors enabled
|
|
16
|
+
- `vision-camera-resize-plugin`
|
|
17
|
+
- `react-native-fast-opencv`
|
|
18
|
+
- `react-native-reanimated` + `react-native-worklets-core`
|
|
19
|
+
- `@shopify/react-native-skia`
|
|
20
|
+
- `react`, `react-native`
|
|
11
21
|
|
|
12
22
|
## Installation
|
|
13
|
-
Install the template as a package and make sure the peer dependencies already exist in your app:
|
|
14
23
|
|
|
15
24
|
```sh
|
|
16
|
-
yarn add react-native-rectangle-doc-scanner
|
|
25
|
+
yarn add react-native-rectangle-doc-scanner \
|
|
26
|
+
react-native-vision-camera \
|
|
27
|
+
vision-camera-resize-plugin \
|
|
28
|
+
react-native-fast-opencv \
|
|
29
|
+
react-native-reanimated \
|
|
30
|
+
react-native-worklets-core \
|
|
31
|
+
@shopify/react-native-skia
|
|
17
32
|
```
|
|
18
33
|
|
|
34
|
+
Follow each dependency’s native installation guide:
|
|
35
|
+
|
|
36
|
+
- Run `npx pod-install` after adding iOS dependencies.
|
|
37
|
+
- For `react-native-reanimated`, add the Babel plugin, enable the JSI runtime, and ensure Reanimated is the first import in `index.js`.
|
|
38
|
+
- Configure `react-native-fast-opencv` according to its README (adds native OpenCV binaries on both platforms).
|
|
39
|
+
- For `react-native-vision-camera`, enable frame processors by adding the new architecture build and the proxy registration they describe. You must also request camera permissions at runtime.
|
|
40
|
+
- Register the resize plugin once in native code (for example inside your `VisionCameraProxy` setup):
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { VisionCameraProxy } from 'react-native-vision-camera';
|
|
44
|
+
import { ResizePlugin } from 'vision-camera-resize-plugin';
|
|
45
|
+
|
|
46
|
+
VisionCameraProxy.installFrameProcessorPlugin('resize', ResizePlugin);
|
|
47
|
+
```
|
|
48
|
+
|
|
19
49
|
## Usage
|
|
50
|
+
|
|
20
51
|
```tsx
|
|
21
52
|
import React from 'react';
|
|
22
|
-
import { View } from 'react-native';
|
|
53
|
+
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
23
54
|
import { DocScanner } from 'react-native-rectangle-doc-scanner';
|
|
24
55
|
|
|
25
56
|
export const ScanScreen = () => (
|
|
26
|
-
<View style={
|
|
57
|
+
<View style={styles.container}>
|
|
27
58
|
<DocScanner
|
|
28
59
|
onCapture={({ path, quad }) => {
|
|
29
60
|
console.log('Document captured at', path, quad);
|
|
@@ -31,11 +62,51 @@ export const ScanScreen = () => (
|
|
|
31
62
|
overlayColor="#ff8800"
|
|
32
63
|
autoCapture
|
|
33
64
|
minStableFrames={8}
|
|
34
|
-
|
|
65
|
+
cameraProps={{ enableZoomGesture: true }}
|
|
66
|
+
>
|
|
67
|
+
<View style={styles.overlayControls}>
|
|
68
|
+
<TouchableOpacity style={styles.button}>
|
|
69
|
+
<Text style={styles.label}>Manual Capture</Text>
|
|
70
|
+
</TouchableOpacity>
|
|
71
|
+
</View>
|
|
72
|
+
</DocScanner>
|
|
35
73
|
</View>
|
|
36
74
|
);
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
container: { flex: 1 },
|
|
78
|
+
overlayControls: {
|
|
79
|
+
position: 'absolute',
|
|
80
|
+
bottom: 32,
|
|
81
|
+
alignSelf: 'center',
|
|
82
|
+
},
|
|
83
|
+
button: {
|
|
84
|
+
paddingHorizontal: 24,
|
|
85
|
+
paddingVertical: 12,
|
|
86
|
+
borderRadius: 999,
|
|
87
|
+
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
88
|
+
},
|
|
89
|
+
label: { color: '#fff', fontWeight: '600' },
|
|
90
|
+
});
|
|
37
91
|
```
|
|
38
92
|
|
|
93
|
+
Passing `children` lets you render any UI on top of the camera preview, so you can freely add buttons, tutorials, or progress indicators without modifying the package.
|
|
94
|
+
|
|
95
|
+
### Props
|
|
96
|
+
|
|
97
|
+
- `onCapture({ path, quad })` — called when a photo is taken; `quad` contains the detected corner coordinates (or `null` if none were found).
|
|
98
|
+
- `overlayColor` (default `#e7a649`) — stroke colour for the contour overlay.
|
|
99
|
+
- `autoCapture` (default `true`) — when `true`, captures automatically after stability is reached; set to `false` to show the built-in shutter button.
|
|
100
|
+
- `minStableFrames` (default `8`) — number of consecutive stable frames required before auto capture triggers.
|
|
101
|
+
- `cameraProps` — forwarded to the underlying `Camera` (except for `frameProcessor`), enabling features such as zoom gestures, HDR, torch control, device selection, etc.
|
|
102
|
+
- `children` — rendered over the camera/overlay for fully custom controls.
|
|
103
|
+
|
|
104
|
+
### Notes on camera behaviour
|
|
105
|
+
|
|
106
|
+
- If you disable `autoCapture`, the built-in shutter button appears; you can still provide your own UI as `children` to replace or augment it.
|
|
107
|
+
- The internal frame processor handles document detection; do not override `frameProcessor` in `cameraProps`.
|
|
108
|
+
- Adjust `minStableFrames` or tweak lighting conditions if auto capture is too sensitive or too slow.
|
|
109
|
+
|
|
39
110
|
## Build
|
|
40
111
|
```sh
|
|
41
112
|
yarn build
|
package/dist/DocScanner.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { Camera } from 'react-native-vision-camera';
|
|
2
3
|
import type { Point } from './types';
|
|
4
|
+
type CameraOverrides = Omit<React.ComponentProps<typeof Camera>, 'style' | 'ref' | 'frameProcessor'>;
|
|
3
5
|
interface Props {
|
|
4
6
|
onCapture?: (photo: {
|
|
5
7
|
path: string;
|
|
@@ -8,6 +10,8 @@ interface Props {
|
|
|
8
10
|
overlayColor?: string;
|
|
9
11
|
autoCapture?: boolean;
|
|
10
12
|
minStableFrames?: number;
|
|
13
|
+
cameraProps?: CameraOverrides;
|
|
14
|
+
children?: ReactNode;
|
|
11
15
|
}
|
|
12
16
|
export declare const DocScanner: React.FC<Props>;
|
|
13
17
|
export {};
|
package/dist/DocScanner.js
CHANGED
|
@@ -42,7 +42,7 @@ const react_native_reanimated_1 = require("react-native-reanimated");
|
|
|
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");
|
|
45
|
-
const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, minStableFrames = 8, }) => {
|
|
45
|
+
const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, minStableFrames = 8, cameraProps, children, }) => {
|
|
46
46
|
const device = (0, react_native_vision_camera_1.useCameraDevice)('back');
|
|
47
47
|
const { hasPermission, requestPermission } = (0, react_native_vision_camera_1.useCameraPermission)();
|
|
48
48
|
const { resize } = (0, vision_camera_resize_plugin_1.useResizePlugin)();
|
|
@@ -115,11 +115,13 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
115
115
|
};
|
|
116
116
|
capture();
|
|
117
117
|
}, [autoCapture, minStableFrames, onCapture, quad, stable]);
|
|
118
|
-
|
|
118
|
+
const { device: overrideDevice, ...cameraRestProps } = cameraProps ?? {};
|
|
119
|
+
const resolvedDevice = overrideDevice ?? device;
|
|
120
|
+
if (!resolvedDevice || !hasPermission) {
|
|
119
121
|
return null;
|
|
120
122
|
}
|
|
121
123
|
return (react_1.default.createElement(react_native_1.View, { style: { flex: 1 } },
|
|
122
|
-
react_1.default.createElement(react_native_vision_camera_1.Camera, { ref: handleCameraRef, style: react_native_1.StyleSheet.absoluteFillObject, device:
|
|
124
|
+
react_1.default.createElement(react_native_vision_camera_1.Camera, { ref: handleCameraRef, style: react_native_1.StyleSheet.absoluteFillObject, device: resolvedDevice, isActive: true, photo: true, frameProcessor: frameProcessor, frameProcessorFps: 15, ...cameraRestProps }),
|
|
123
125
|
react_1.default.createElement(overlay_1.Overlay, { quad: quad, color: overlayColor }),
|
|
124
126
|
!autoCapture && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: async () => {
|
|
125
127
|
if (!camera.current) {
|
|
@@ -127,7 +129,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
127
129
|
}
|
|
128
130
|
const photo = await camera.current.takePhoto({ qualityPrioritization: 'quality' });
|
|
129
131
|
onCapture?.({ path: photo.path, quad });
|
|
130
|
-
} }))
|
|
132
|
+
} })),
|
|
133
|
+
children));
|
|
131
134
|
};
|
|
132
135
|
exports.DocScanner = DocScanner;
|
|
133
136
|
const styles = react_native_1.StyleSheet.create({
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-rectangle-doc-scanner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/danchew90/react-native-rectangle-doc-scanner.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/danchew90/react-native-rectangle-doc-scanner",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/danchew90/react-native-rectangle-doc-scanner/issues"
|
|
13
|
+
},
|
|
6
14
|
"scripts": {
|
|
7
15
|
"build": "tsc",
|
|
8
16
|
"prepare": "yarn build"
|
package/src/DocScanner.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
|
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';
|
|
@@ -22,11 +22,15 @@ type CameraRef = {
|
|
|
22
22
|
}>;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
type CameraOverrides = Omit<React.ComponentProps<typeof Camera>, 'style' | 'ref' | 'frameProcessor'>;
|
|
26
|
+
|
|
25
27
|
interface Props {
|
|
26
28
|
onCapture?: (photo: { path: string; quad: Point[] | null }) => void;
|
|
27
29
|
overlayColor?: string;
|
|
28
30
|
autoCapture?: boolean;
|
|
29
31
|
minStableFrames?: number;
|
|
32
|
+
cameraProps?: CameraOverrides;
|
|
33
|
+
children?: ReactNode;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export const DocScanner: React.FC<Props> = ({
|
|
@@ -34,6 +38,8 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
34
38
|
overlayColor = '#e7a649',
|
|
35
39
|
autoCapture = true,
|
|
36
40
|
minStableFrames = 8,
|
|
41
|
+
cameraProps,
|
|
42
|
+
children,
|
|
37
43
|
}) => {
|
|
38
44
|
const device = useCameraDevice('back');
|
|
39
45
|
const { hasPermission, requestPermission } = useCameraPermission();
|
|
@@ -126,7 +132,10 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
126
132
|
capture();
|
|
127
133
|
}, [autoCapture, minStableFrames, onCapture, quad, stable]);
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
const { device: overrideDevice, ...cameraRestProps } = cameraProps ?? {};
|
|
136
|
+
const resolvedDevice = overrideDevice ?? device;
|
|
137
|
+
|
|
138
|
+
if (!resolvedDevice || !hasPermission) {
|
|
130
139
|
return null;
|
|
131
140
|
}
|
|
132
141
|
|
|
@@ -135,11 +144,12 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
135
144
|
<Camera
|
|
136
145
|
ref={handleCameraRef}
|
|
137
146
|
style={StyleSheet.absoluteFillObject}
|
|
138
|
-
device={
|
|
147
|
+
device={resolvedDevice}
|
|
139
148
|
isActive
|
|
140
149
|
photo
|
|
141
150
|
frameProcessor={frameProcessor}
|
|
142
151
|
frameProcessorFps={15}
|
|
152
|
+
{...cameraRestProps}
|
|
143
153
|
/>
|
|
144
154
|
<Overlay quad={quad} color={overlayColor} />
|
|
145
155
|
{!autoCapture && (
|
|
@@ -155,6 +165,7 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
155
165
|
}}
|
|
156
166
|
/>
|
|
157
167
|
)}
|
|
168
|
+
{children}
|
|
158
169
|
</View>
|
|
159
170
|
);
|
|
160
171
|
};
|