react-native-rectangle-doc-scanner 3.37.0 → 3.39.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/dist/CropEditor.js +57 -18
- package/dist/FullDocScanner.js +10 -2
- package/package.json +1 -1
- package/src/CropEditor.tsx +95 -22
- package/src/FullDocScanner.tsx +15 -5
package/dist/CropEditor.js
CHANGED
|
@@ -32,14 +32,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.CropEditor = void 0;
|
|
40
37
|
const react_1 = __importStar(require("react"));
|
|
41
38
|
const react_native_1 = require("react-native");
|
|
42
|
-
const react_native_perspective_image_cropper_1 = __importDefault(require("react-native-perspective-image-cropper"));
|
|
43
39
|
const coordinate_1 = require("./utils/coordinate");
|
|
44
40
|
/**
|
|
45
41
|
* CropEditor Component
|
|
@@ -62,17 +58,25 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
|
|
|
62
58
|
});
|
|
63
59
|
const [isImageLoading, setIsImageLoading] = (0, react_1.useState)(true);
|
|
64
60
|
const [loadError, setLoadError] = (0, react_1.useState)(null);
|
|
61
|
+
const [croppedImageUri, setCroppedImageUri] = (0, react_1.useState)(null);
|
|
65
62
|
(0, react_1.useEffect)(() => {
|
|
66
63
|
console.log('[CropEditor] Document path:', document.path);
|
|
67
64
|
console.log('[CropEditor] Document dimensions:', document.width, 'x', document.height);
|
|
68
65
|
console.log('[CropEditor] Document quad:', document.quad);
|
|
66
|
+
console.log('[CropEditor] Document rectangle:', document.rectangle);
|
|
69
67
|
// Load image size using Image.getSize
|
|
70
|
-
const imageUri = `file://${document.path}`;
|
|
68
|
+
const imageUri = document.path.startsWith('file://') ? document.path : `file://${document.path}`;
|
|
71
69
|
react_native_1.Image.getSize(imageUri, (width, height) => {
|
|
72
70
|
console.log('[CropEditor] Image.getSize success:', { width, height });
|
|
73
71
|
setImageSize({ width, height });
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
// If we have a rectangle (from auto-capture), crop the image
|
|
73
|
+
if (document.rectangle || document.quad) {
|
|
74
|
+
cropImageToRectangle(imageUri, width, height);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
setIsImageLoading(false);
|
|
78
|
+
setLoadError(null);
|
|
79
|
+
}
|
|
76
80
|
}, (error) => {
|
|
77
81
|
console.error('[CropEditor] Image.getSize error:', error);
|
|
78
82
|
// Fallback to document dimensions
|
|
@@ -81,6 +85,48 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
|
|
|
81
85
|
setIsImageLoading(false);
|
|
82
86
|
});
|
|
83
87
|
}, [document]);
|
|
88
|
+
const cropImageToRectangle = (0, react_1.useCallback)((imageUri, width, height) => {
|
|
89
|
+
const cropManager = react_native_1.NativeModules.CustomCropManager;
|
|
90
|
+
if (!cropManager?.crop) {
|
|
91
|
+
console.warn('[CropEditor] CustomCropManager not available, showing original image');
|
|
92
|
+
setIsImageLoading(false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const baseWidth = document.width > 0 ? document.width : width;
|
|
96
|
+
const baseHeight = document.height > 0 ? document.height : height;
|
|
97
|
+
let rectangle = document.rectangle ?? null;
|
|
98
|
+
if (!rectangle && document.quad && document.quad.length === 4) {
|
|
99
|
+
rectangle = (0, coordinate_1.quadToRectangle)(document.quad);
|
|
100
|
+
}
|
|
101
|
+
if (!rectangle) {
|
|
102
|
+
console.warn('[CropEditor] No rectangle found, showing original image');
|
|
103
|
+
setIsImageLoading(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Scale rectangle to actual image size
|
|
107
|
+
const scaledRect = (0, coordinate_1.scaleRectangle)(rectangle, baseWidth, baseHeight, width, height);
|
|
108
|
+
console.log('[CropEditor] Cropping image with rectangle:', scaledRect);
|
|
109
|
+
cropManager.crop({
|
|
110
|
+
topLeft: scaledRect.topLeft,
|
|
111
|
+
topRight: scaledRect.topRight,
|
|
112
|
+
bottomRight: scaledRect.bottomRight,
|
|
113
|
+
bottomLeft: scaledRect.bottomLeft,
|
|
114
|
+
width,
|
|
115
|
+
height,
|
|
116
|
+
}, imageUri, (error, result) => {
|
|
117
|
+
if (error) {
|
|
118
|
+
console.error('[CropEditor] Crop error:', error);
|
|
119
|
+
setIsImageLoading(false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log('[CropEditor] Crop success, base64 length:', result.image?.length);
|
|
123
|
+
// Convert base64 to data URI
|
|
124
|
+
const croppedUri = `data:image/jpeg;base64,${result.image}`;
|
|
125
|
+
setCroppedImageUri(croppedUri);
|
|
126
|
+
setIsImageLoading(false);
|
|
127
|
+
setLoadError(null);
|
|
128
|
+
});
|
|
129
|
+
}, [document]);
|
|
84
130
|
// Get initial rectangle from detected quad or use default
|
|
85
131
|
const getInitialRectangle = (0, react_1.useCallback)(() => {
|
|
86
132
|
if (!imageSize) {
|
|
@@ -135,8 +181,7 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
|
|
|
135
181
|
react_1.default.createElement(react_native_1.Text, { style: styles.errorPath }, imageUri))) : !imageSize || isImageLoading ? (react_1.default.createElement(react_native_1.View, { style: styles.loadingContainer },
|
|
136
182
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: handlerColor }),
|
|
137
183
|
react_1.default.createElement(react_native_1.Text, { style: styles.loadingText }, "Loading image..."))) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
138
|
-
react_1.default.createElement(
|
|
139
|
-
react_1.default.createElement(react_native_1.Image, { source: { uri: imageUri }, style: styles.debugImage, onLoad: () => console.log('[CropEditor] Debug image loaded'), onError: (e) => console.error('[CropEditor] Debug image error:', e.nativeEvent.error) })))));
|
|
184
|
+
react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageUri || imageUri }, style: styles.fullImage, resizeMode: "contain", onLoad: () => console.log('[CropEditor] Image loaded successfully', croppedImageUri ? 'cropped' : 'original'), onError: (e) => console.error('[CropEditor] Image load error:', e.nativeEvent.error) })))));
|
|
140
185
|
};
|
|
141
186
|
exports.CropEditor = CropEditor;
|
|
142
187
|
const styles = react_native_1.StyleSheet.create({
|
|
@@ -171,14 +216,8 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
171
216
|
fontSize: 12,
|
|
172
217
|
textAlign: 'center',
|
|
173
218
|
},
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
height: 100,
|
|
178
|
-
top: 10,
|
|
179
|
-
right: 10,
|
|
180
|
-
opacity: 0.5,
|
|
181
|
-
borderWidth: 2,
|
|
182
|
-
borderColor: 'red',
|
|
219
|
+
fullImage: {
|
|
220
|
+
width: '100%',
|
|
221
|
+
height: '100%',
|
|
183
222
|
},
|
|
184
223
|
});
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -224,12 +224,20 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
224
224
|
console.log('[FullDocScanner] Already processing, skipping manual capture');
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
227
|
+
if (manualCapturePending.current) {
|
|
228
|
+
console.log('[FullDocScanner] Manual capture already pending, skipping');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
227
231
|
console.log('[FullDocScanner] Setting manualCapturePending to true');
|
|
228
232
|
manualCapturePending.current = true;
|
|
229
233
|
const capturePromise = docScannerRef.current?.capture();
|
|
230
234
|
console.log('[FullDocScanner] capturePromise:', !!capturePromise);
|
|
231
|
-
if (capturePromise && typeof capturePromise.
|
|
232
|
-
capturePromise
|
|
235
|
+
if (capturePromise && typeof capturePromise.then === 'function') {
|
|
236
|
+
capturePromise
|
|
237
|
+
.then(() => {
|
|
238
|
+
console.log('[FullDocScanner] Capture success');
|
|
239
|
+
})
|
|
240
|
+
.catch((error) => {
|
|
233
241
|
manualCapturePending.current = false;
|
|
234
242
|
console.warn('[FullDocScanner] manual capture failed', error);
|
|
235
243
|
});
|
package/package.json
CHANGED
package/src/CropEditor.tsx
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
-
import { View, StyleSheet, Image, Dimensions, ActivityIndicator, Text } from 'react-native';
|
|
2
|
+
import { View, StyleSheet, Image, Dimensions, ActivityIndicator, Text, NativeModules } from 'react-native';
|
|
3
3
|
import CustomImageCropper from 'react-native-perspective-image-cropper';
|
|
4
4
|
import type { Rectangle as CropperRectangle } from 'react-native-perspective-image-cropper';
|
|
5
5
|
import type { Point, Rectangle, CapturedDocument } from './types';
|
|
6
6
|
import { createFullImageRectangle, quadToRectangle, scaleRectangle } from './utils/coordinate';
|
|
7
7
|
|
|
8
|
+
type CustomCropManagerType = {
|
|
9
|
+
crop: (
|
|
10
|
+
points: {
|
|
11
|
+
topLeft: Point;
|
|
12
|
+
topRight: Point;
|
|
13
|
+
bottomRight: Point;
|
|
14
|
+
bottomLeft: Point;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
},
|
|
18
|
+
imageUri: string,
|
|
19
|
+
callback: (error: unknown, result: { image: string }) => void,
|
|
20
|
+
) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
8
23
|
interface CropEditorProps {
|
|
9
24
|
document: CapturedDocument;
|
|
10
25
|
overlayColor?: string;
|
|
@@ -42,21 +57,29 @@ export const CropEditor: React.FC<CropEditorProps> = ({
|
|
|
42
57
|
});
|
|
43
58
|
const [isImageLoading, setIsImageLoading] = useState(true);
|
|
44
59
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
60
|
+
const [croppedImageUri, setCroppedImageUri] = useState<string | null>(null);
|
|
45
61
|
|
|
46
62
|
useEffect(() => {
|
|
47
63
|
console.log('[CropEditor] Document path:', document.path);
|
|
48
64
|
console.log('[CropEditor] Document dimensions:', document.width, 'x', document.height);
|
|
49
65
|
console.log('[CropEditor] Document quad:', document.quad);
|
|
66
|
+
console.log('[CropEditor] Document rectangle:', document.rectangle);
|
|
50
67
|
|
|
51
68
|
// Load image size using Image.getSize
|
|
52
|
-
const imageUri = `file://${document.path}`;
|
|
69
|
+
const imageUri = document.path.startsWith('file://') ? document.path : `file://${document.path}`;
|
|
53
70
|
Image.getSize(
|
|
54
71
|
imageUri,
|
|
55
72
|
(width, height) => {
|
|
56
73
|
console.log('[CropEditor] Image.getSize success:', { width, height });
|
|
57
74
|
setImageSize({ width, height });
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
|
|
76
|
+
// If we have a rectangle (from auto-capture), crop the image
|
|
77
|
+
if (document.rectangle || document.quad) {
|
|
78
|
+
cropImageToRectangle(imageUri, width, height);
|
|
79
|
+
} else {
|
|
80
|
+
setIsImageLoading(false);
|
|
81
|
+
setLoadError(null);
|
|
82
|
+
}
|
|
60
83
|
},
|
|
61
84
|
(error) => {
|
|
62
85
|
console.error('[CropEditor] Image.getSize error:', error);
|
|
@@ -68,6 +91,60 @@ export const CropEditor: React.FC<CropEditorProps> = ({
|
|
|
68
91
|
);
|
|
69
92
|
}, [document]);
|
|
70
93
|
|
|
94
|
+
const cropImageToRectangle = useCallback((imageUri: string, width: number, height: number) => {
|
|
95
|
+
const cropManager = NativeModules.CustomCropManager as CustomCropManagerType | undefined;
|
|
96
|
+
|
|
97
|
+
if (!cropManager?.crop) {
|
|
98
|
+
console.warn('[CropEditor] CustomCropManager not available, showing original image');
|
|
99
|
+
setIsImageLoading(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const baseWidth = document.width > 0 ? document.width : width;
|
|
104
|
+
const baseHeight = document.height > 0 ? document.height : height;
|
|
105
|
+
|
|
106
|
+
let rectangle: Rectangle | null = document.rectangle ?? null;
|
|
107
|
+
if (!rectangle && document.quad && document.quad.length === 4) {
|
|
108
|
+
rectangle = quadToRectangle(document.quad);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!rectangle) {
|
|
112
|
+
console.warn('[CropEditor] No rectangle found, showing original image');
|
|
113
|
+
setIsImageLoading(false);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Scale rectangle to actual image size
|
|
118
|
+
const scaledRect = scaleRectangle(rectangle, baseWidth, baseHeight, width, height);
|
|
119
|
+
|
|
120
|
+
console.log('[CropEditor] Cropping image with rectangle:', scaledRect);
|
|
121
|
+
|
|
122
|
+
cropManager.crop(
|
|
123
|
+
{
|
|
124
|
+
topLeft: scaledRect.topLeft,
|
|
125
|
+
topRight: scaledRect.topRight,
|
|
126
|
+
bottomRight: scaledRect.bottomRight,
|
|
127
|
+
bottomLeft: scaledRect.bottomLeft,
|
|
128
|
+
width,
|
|
129
|
+
height,
|
|
130
|
+
},
|
|
131
|
+
imageUri,
|
|
132
|
+
(error: unknown, result: { image: string }) => {
|
|
133
|
+
if (error) {
|
|
134
|
+
console.error('[CropEditor] Crop error:', error);
|
|
135
|
+
setIsImageLoading(false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
console.log('[CropEditor] Crop success, base64 length:', result.image?.length);
|
|
139
|
+
// Convert base64 to data URI
|
|
140
|
+
const croppedUri = `data:image/jpeg;base64,${result.image}`;
|
|
141
|
+
setCroppedImageUri(croppedUri);
|
|
142
|
+
setIsImageLoading(false);
|
|
143
|
+
setLoadError(null);
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}, [document]);
|
|
147
|
+
|
|
71
148
|
// Get initial rectangle from detected quad or use default
|
|
72
149
|
const getInitialRectangle = useCallback((): CropperRectangle | undefined => {
|
|
73
150
|
if (!imageSize) {
|
|
@@ -151,7 +228,16 @@ export const CropEditor: React.FC<CropEditorProps> = ({
|
|
|
151
228
|
</View>
|
|
152
229
|
) : (
|
|
153
230
|
<>
|
|
154
|
-
|
|
231
|
+
{/* Show cropped image if available, otherwise show original */}
|
|
232
|
+
<Image
|
|
233
|
+
source={{ uri: croppedImageUri || imageUri }}
|
|
234
|
+
style={styles.fullImage}
|
|
235
|
+
resizeMode="contain"
|
|
236
|
+
onLoad={() => console.log('[CropEditor] Image loaded successfully', croppedImageUri ? 'cropped' : 'original')}
|
|
237
|
+
onError={(e) => console.error('[CropEditor] Image load error:', e.nativeEvent.error)}
|
|
238
|
+
/>
|
|
239
|
+
{/* Temporarily disabled CustomImageCropper - showing image only */}
|
|
240
|
+
{/* <CustomImageCropper
|
|
155
241
|
height={displaySize.height}
|
|
156
242
|
width={displaySize.width}
|
|
157
243
|
image={imageUri}
|
|
@@ -161,14 +247,7 @@ export const CropEditor: React.FC<CropEditorProps> = ({
|
|
|
161
247
|
handlerColor={handlerColor}
|
|
162
248
|
enablePanStrict={enablePanStrict}
|
|
163
249
|
onDragEnd={handleDragEnd}
|
|
164
|
-
/>
|
|
165
|
-
{/* Debug: Show image in background to verify it loads */}
|
|
166
|
-
<Image
|
|
167
|
-
source={{ uri: imageUri }}
|
|
168
|
-
style={styles.debugImage}
|
|
169
|
-
onLoad={() => console.log('[CropEditor] Debug image loaded')}
|
|
170
|
-
onError={(e) => console.error('[CropEditor] Debug image error:', e.nativeEvent.error)}
|
|
171
|
-
/>
|
|
250
|
+
/> */}
|
|
172
251
|
</>
|
|
173
252
|
)}
|
|
174
253
|
</View>
|
|
@@ -207,14 +286,8 @@ const styles = StyleSheet.create({
|
|
|
207
286
|
fontSize: 12,
|
|
208
287
|
textAlign: 'center',
|
|
209
288
|
},
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
height: 100,
|
|
214
|
-
top: 10,
|
|
215
|
-
right: 10,
|
|
216
|
-
opacity: 0.5,
|
|
217
|
-
borderWidth: 2,
|
|
218
|
-
borderColor: 'red',
|
|
289
|
+
fullImage: {
|
|
290
|
+
width: '100%',
|
|
291
|
+
height: '100%',
|
|
219
292
|
},
|
|
220
293
|
});
|
package/src/FullDocScanner.tsx
CHANGED
|
@@ -347,15 +347,25 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
347
347
|
console.log('[FullDocScanner] Already processing, skipping manual capture');
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
|
+
if (manualCapturePending.current) {
|
|
351
|
+
console.log('[FullDocScanner] Manual capture already pending, skipping');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
350
355
|
console.log('[FullDocScanner] Setting manualCapturePending to true');
|
|
351
356
|
manualCapturePending.current = true;
|
|
357
|
+
|
|
352
358
|
const capturePromise = docScannerRef.current?.capture();
|
|
353
359
|
console.log('[FullDocScanner] capturePromise:', !!capturePromise);
|
|
354
|
-
if (capturePromise && typeof capturePromise.
|
|
355
|
-
capturePromise
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
360
|
+
if (capturePromise && typeof capturePromise.then === 'function') {
|
|
361
|
+
capturePromise
|
|
362
|
+
.then(() => {
|
|
363
|
+
console.log('[FullDocScanner] Capture success');
|
|
364
|
+
})
|
|
365
|
+
.catch((error: unknown) => {
|
|
366
|
+
manualCapturePending.current = false;
|
|
367
|
+
console.warn('[FullDocScanner] manual capture failed', error);
|
|
368
|
+
});
|
|
359
369
|
} else if (!capturePromise) {
|
|
360
370
|
console.warn('[FullDocScanner] No capture promise returned');
|
|
361
371
|
manualCapturePending.current = false;
|