react-native-rectangle-doc-scanner 3.38.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 +54 -4
- package/dist/FullDocScanner.js +19 -16
- package/package.json +1 -1
- package/src/CropEditor.tsx +84 -7
- package/src/FullDocScanner.tsx +16 -14
package/dist/CropEditor.js
CHANGED
|
@@ -58,17 +58,25 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
|
|
|
58
58
|
});
|
|
59
59
|
const [isImageLoading, setIsImageLoading] = (0, react_1.useState)(true);
|
|
60
60
|
const [loadError, setLoadError] = (0, react_1.useState)(null);
|
|
61
|
+
const [croppedImageUri, setCroppedImageUri] = (0, react_1.useState)(null);
|
|
61
62
|
(0, react_1.useEffect)(() => {
|
|
62
63
|
console.log('[CropEditor] Document path:', document.path);
|
|
63
64
|
console.log('[CropEditor] Document dimensions:', document.width, 'x', document.height);
|
|
64
65
|
console.log('[CropEditor] Document quad:', document.quad);
|
|
66
|
+
console.log('[CropEditor] Document rectangle:', document.rectangle);
|
|
65
67
|
// Load image size using Image.getSize
|
|
66
|
-
const imageUri = `file://${document.path}`;
|
|
68
|
+
const imageUri = document.path.startsWith('file://') ? document.path : `file://${document.path}`;
|
|
67
69
|
react_native_1.Image.getSize(imageUri, (width, height) => {
|
|
68
70
|
console.log('[CropEditor] Image.getSize success:', { width, height });
|
|
69
71
|
setImageSize({ width, height });
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
80
|
}, (error) => {
|
|
73
81
|
console.error('[CropEditor] Image.getSize error:', error);
|
|
74
82
|
// Fallback to document dimensions
|
|
@@ -77,6 +85,48 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
|
|
|
77
85
|
setIsImageLoading(false);
|
|
78
86
|
});
|
|
79
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]);
|
|
80
130
|
// Get initial rectangle from detected quad or use default
|
|
81
131
|
const getInitialRectangle = (0, react_1.useCallback)(() => {
|
|
82
132
|
if (!imageSize) {
|
|
@@ -131,7 +181,7 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
|
|
|
131
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 },
|
|
132
182
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: handlerColor }),
|
|
133
183
|
react_1.default.createElement(react_native_1.Text, { style: styles.loadingText }, "Loading image..."))) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
134
|
-
react_1.default.createElement(react_native_1.Image, { source: { uri: imageUri }, style: styles.fullImage, resizeMode: "contain", onLoad: () => console.log('[CropEditor] Image loaded successfully'), onError: (e) => console.error('[CropEditor] Image load 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) })))));
|
|
135
185
|
};
|
|
136
186
|
exports.CropEditor = CropEditor;
|
|
137
187
|
const styles = react_native_1.StyleSheet.create({
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -224,25 +224,28 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
224
224
|
console.log('[FullDocScanner] Already processing, skipping manual capture');
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
if (manualCapturePending.current) {
|
|
228
|
+
console.log('[FullDocScanner] Manual capture already pending, skipping');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
229
231
|
console.log('[FullDocScanner] Setting manualCapturePending to true');
|
|
230
232
|
manualCapturePending.current = true;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
else if (!capturePromise) {
|
|
242
|
-
console.warn('[FullDocScanner] No capture promise returned');
|
|
233
|
+
const capturePromise = docScannerRef.current?.capture();
|
|
234
|
+
console.log('[FullDocScanner] capturePromise:', !!capturePromise);
|
|
235
|
+
if (capturePromise && typeof capturePromise.then === 'function') {
|
|
236
|
+
capturePromise
|
|
237
|
+
.then(() => {
|
|
238
|
+
console.log('[FullDocScanner] Capture success');
|
|
239
|
+
})
|
|
240
|
+
.catch((error) => {
|
|
243
241
|
manualCapturePending.current = false;
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
console.warn('[FullDocScanner] manual capture failed', error);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else if (!capturePromise) {
|
|
246
|
+
console.warn('[FullDocScanner] No capture promise returned');
|
|
247
|
+
manualCapturePending.current = false;
|
|
248
|
+
}
|
|
246
249
|
}, []);
|
|
247
250
|
const performCrop = (0, react_1.useCallback)(async () => {
|
|
248
251
|
if (!capturedDoc) {
|
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,12 +228,12 @@ export const CropEditor: React.FC<CropEditorProps> = ({
|
|
|
151
228
|
</View>
|
|
152
229
|
) : (
|
|
153
230
|
<>
|
|
154
|
-
{/*
|
|
231
|
+
{/* Show cropped image if available, otherwise show original */}
|
|
155
232
|
<Image
|
|
156
|
-
source={{ uri: imageUri }}
|
|
233
|
+
source={{ uri: croppedImageUri || imageUri }}
|
|
157
234
|
style={styles.fullImage}
|
|
158
235
|
resizeMode="contain"
|
|
159
|
-
onLoad={() => console.log('[CropEditor] Image loaded successfully')}
|
|
236
|
+
onLoad={() => console.log('[CropEditor] Image loaded successfully', croppedImageUri ? 'cropped' : 'original')}
|
|
160
237
|
onError={(e) => console.error('[CropEditor] Image load error:', e.nativeEvent.error)}
|
|
161
238
|
/>
|
|
162
239
|
{/* Temporarily disabled CustomImageCropper - showing image only */}
|
package/src/FullDocScanner.tsx
CHANGED
|
@@ -347,27 +347,29 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
347
347
|
console.log('[FullDocScanner] Already processing, skipping manual capture');
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
350
|
+
if (manualCapturePending.current) {
|
|
351
|
+
console.log('[FullDocScanner] Manual capture already pending, skipping');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
353
354
|
|
|
354
355
|
console.log('[FullDocScanner] Setting manualCapturePending to true');
|
|
355
356
|
manualCapturePending.current = true;
|
|
356
357
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
358
|
+
const capturePromise = docScannerRef.current?.capture();
|
|
359
|
+
console.log('[FullDocScanner] capturePromise:', !!capturePromise);
|
|
360
|
+
if (capturePromise && typeof capturePromise.then === 'function') {
|
|
361
|
+
capturePromise
|
|
362
|
+
.then(() => {
|
|
363
|
+
console.log('[FullDocScanner] Capture success');
|
|
364
|
+
})
|
|
365
|
+
.catch((error: unknown) => {
|
|
363
366
|
manualCapturePending.current = false;
|
|
364
367
|
console.warn('[FullDocScanner] manual capture failed', error);
|
|
365
368
|
});
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}, 100);
|
|
369
|
+
} else if (!capturePromise) {
|
|
370
|
+
console.warn('[FullDocScanner] No capture promise returned');
|
|
371
|
+
manualCapturePending.current = false;
|
|
372
|
+
}
|
|
371
373
|
}, []);
|
|
372
374
|
|
|
373
375
|
const performCrop = useCallback(async (): Promise<{ base64: string; rectangle: Rectangle }> => {
|