react-native-rectangle-doc-scanner 3.99.0 → 3.101.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/FullDocScanner.js
CHANGED
|
@@ -41,8 +41,8 @@ const react_1 = __importStar(require("react"));
|
|
|
41
41
|
const react_native_1 = require("react-native");
|
|
42
42
|
const react_native_image_picker_1 = require("react-native-image-picker");
|
|
43
43
|
const react_native_image_crop_picker_1 = __importDefault(require("react-native-image-crop-picker"));
|
|
44
|
-
const react_native_image_rotate_1 = __importDefault(require("react-native-image-rotate"));
|
|
45
44
|
const react_native_fs_1 = __importDefault(require("react-native-fs"));
|
|
45
|
+
const react_native_fast_opencv_1 = require("react-native-fast-opencv");
|
|
46
46
|
const DocScanner_1 = require("./DocScanner");
|
|
47
47
|
const stripFileUri = (value) => value.replace(/^file:\/\//, '');
|
|
48
48
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
@@ -344,75 +344,75 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
344
344
|
const handleFlashToggle = (0, react_1.useCallback)(() => {
|
|
345
345
|
setFlashEnabled(prev => !prev);
|
|
346
346
|
}, []);
|
|
347
|
-
const handleRotateImage = (0, react_1.useCallback)(
|
|
348
|
-
|
|
349
|
-
return;
|
|
350
|
-
// UI 회전 상태 먼저 업데이트 (즉각 반응)
|
|
347
|
+
const handleRotateImage = (0, react_1.useCallback)((degrees) => {
|
|
348
|
+
// UI만 회전 (실제 파일 회전은 confirm 시 처리)
|
|
351
349
|
setRotationDegrees(prev => {
|
|
352
|
-
const newRotation =
|
|
350
|
+
const newRotation = prev + degrees;
|
|
351
|
+
// -360 ~ 360 범위로 정규화
|
|
352
|
+
if (newRotation <= -360)
|
|
353
|
+
return newRotation + 360;
|
|
354
|
+
if (newRotation >= 360)
|
|
355
|
+
return newRotation - 360;
|
|
353
356
|
return newRotation;
|
|
354
357
|
});
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
}, []);
|
|
359
|
+
const handleConfirm = (0, react_1.useCallback)(async () => {
|
|
360
|
+
if (!croppedImageData)
|
|
361
|
+
return;
|
|
362
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
363
|
+
if (rotationDegrees === 0) {
|
|
364
|
+
onResult({
|
|
365
|
+
path: croppedImageData.path,
|
|
366
|
+
base64: croppedImageData.base64,
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// 회전이 있으면 실제 파일 회전 처리
|
|
359
371
|
try {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
setCroppedImageData({
|
|
383
|
-
path: savedImage.path,
|
|
384
|
-
base64: savedImage.data ?? undefined,
|
|
385
|
-
});
|
|
386
|
-
// rotation degrees는 0으로 리셋
|
|
387
|
-
setRotationDegrees(0);
|
|
388
|
-
}
|
|
389
|
-
catch (cropError) {
|
|
390
|
-
console.error('[FullDocScanner] Failed to save rotated image:', cropError);
|
|
391
|
-
// 사용자가 취소한 경우 rotation 원복
|
|
392
|
-
setRotationDegrees(prev => (prev - degrees + 360) % 360);
|
|
393
|
-
}
|
|
394
|
-
}, (error) => {
|
|
395
|
-
console.error('[FullDocScanner] Image rotation error:', error);
|
|
396
|
-
// 에러 발생 시 UI rotation 원복
|
|
397
|
-
setRotationDegrees(prev => {
|
|
398
|
-
const revertRotation = (prev - degrees + 360) % 360;
|
|
399
|
-
return revertRotation;
|
|
372
|
+
setProcessing(true);
|
|
373
|
+
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
374
|
+
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
375
|
+
// OpenCV 회전 코드 매핑
|
|
376
|
+
// ROTATE_90_CLOCKWISE = 0
|
|
377
|
+
// ROTATE_180 = 1
|
|
378
|
+
// ROTATE_90_COUNTERCLOCKWISE = 2
|
|
379
|
+
let rotateCode;
|
|
380
|
+
if (rotationNormalized === 90) {
|
|
381
|
+
rotateCode = 0; // ROTATE_90_CLOCKWISE
|
|
382
|
+
}
|
|
383
|
+
else if (rotationNormalized === 180) {
|
|
384
|
+
rotateCode = 1; // ROTATE_180
|
|
385
|
+
}
|
|
386
|
+
else if (rotationNormalized === 270 || rotationNormalized === -90) {
|
|
387
|
+
rotateCode = 2; // ROTATE_90_COUNTERCLOCKWISE
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
// 회전 없음
|
|
391
|
+
onResult({
|
|
392
|
+
path: croppedImageData.path,
|
|
393
|
+
base64: croppedImageData.base64,
|
|
400
394
|
});
|
|
395
|
+
setProcessing(false);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
// OpenCV로 이미지 회전
|
|
399
|
+
const rotatedPath = await react_native_fast_opencv_1.Cv2.rotate(croppedImageData.path, rotateCode);
|
|
400
|
+
// 회전된 이미지를 base64로 변환
|
|
401
|
+
const base64Data = await react_native_fs_1.default.readFile(rotatedPath, 'base64');
|
|
402
|
+
setProcessing(false);
|
|
403
|
+
// 회전된 이미지로 결과 전달
|
|
404
|
+
onResult({
|
|
405
|
+
path: rotatedPath,
|
|
406
|
+
base64: base64Data,
|
|
401
407
|
});
|
|
402
408
|
}
|
|
403
409
|
catch (error) {
|
|
404
|
-
console.error('[FullDocScanner]
|
|
405
|
-
|
|
410
|
+
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
411
|
+
setProcessing(false);
|
|
412
|
+
const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : '';
|
|
413
|
+
react_native_1.Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
|
|
406
414
|
}
|
|
407
|
-
}, [croppedImageData]);
|
|
408
|
-
const handleConfirm = (0, react_1.useCallback)(() => {
|
|
409
|
-
if (!croppedImageData)
|
|
410
|
-
return;
|
|
411
|
-
onResult({
|
|
412
|
-
path: croppedImageData.path,
|
|
413
|
-
base64: croppedImageData.base64,
|
|
414
|
-
});
|
|
415
|
-
}, [croppedImageData, onResult]);
|
|
415
|
+
}, [croppedImageData, rotationDegrees, onResult]);
|
|
416
416
|
const handleRetake = (0, react_1.useCallback)(() => {
|
|
417
417
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
418
418
|
setCroppedImageData(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-rectangle-doc-scanner",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.101.0",
|
|
4
4
|
"description": "Native-backed document scanner for React Native with customizable overlays.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": "*",
|
|
35
35
|
"react-native": "*",
|
|
36
|
+
"react-native-fast-opencv": "*",
|
|
36
37
|
"react-native-fs": "*",
|
|
37
38
|
"react-native-image-crop-picker": "*",
|
|
38
39
|
"react-native-image-picker": "*",
|
|
39
|
-
"react-native-image-rotate": "*",
|
|
40
40
|
"react-native-svg": "*"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -47,7 +47,5 @@
|
|
|
47
47
|
"react-native-image-picker": "^7.1.2",
|
|
48
48
|
"typescript": "^5.3.3"
|
|
49
49
|
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"react-native-image-rotate": "*"
|
|
52
|
-
}
|
|
50
|
+
"dependencies": {}
|
|
53
51
|
}
|
package/src/FullDocScanner.tsx
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import { launchImageLibrary } from 'react-native-image-picker';
|
|
13
13
|
import ImageCropPicker from 'react-native-image-crop-picker';
|
|
14
|
-
import ImageRotate from 'react-native-image-rotate';
|
|
15
14
|
import RNFS from 'react-native-fs';
|
|
15
|
+
import { Cv2 } from 'react-native-fast-opencv';
|
|
16
16
|
import { DocScanner } from './DocScanner';
|
|
17
17
|
import type { CapturedDocument } from './types';
|
|
18
18
|
import type {
|
|
@@ -456,86 +456,78 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
456
456
|
setFlashEnabled(prev => !prev);
|
|
457
457
|
}, []);
|
|
458
458
|
|
|
459
|
-
const handleRotateImage = useCallback(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
// UI 회전 상태 먼저 업데이트 (즉각 반응)
|
|
459
|
+
const handleRotateImage = useCallback((degrees: -90 | 90) => {
|
|
460
|
+
// UI만 회전 (실제 파일 회전은 confirm 시 처리)
|
|
463
461
|
setRotationDegrees(prev => {
|
|
464
|
-
const newRotation =
|
|
462
|
+
const newRotation = prev + degrees;
|
|
463
|
+
// -360 ~ 360 범위로 정규화
|
|
464
|
+
if (newRotation <= -360) return newRotation + 360;
|
|
465
|
+
if (newRotation >= 360) return newRotation - 360;
|
|
465
466
|
return newRotation;
|
|
466
467
|
});
|
|
468
|
+
}, []);
|
|
467
469
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
hasBase64: !!croppedImageData.base64,
|
|
471
|
-
});
|
|
470
|
+
const handleConfirm = useCallback(async () => {
|
|
471
|
+
if (!croppedImageData) return;
|
|
472
472
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
degrees,
|
|
481
|
-
async (rotatedImageUri: string) => {
|
|
482
|
-
console.log('[FullDocScanner] Image rotated, URI:', rotatedImageUri);
|
|
483
|
-
|
|
484
|
-
try {
|
|
485
|
-
// 회전된 이미지를 실제 파일로 저장하기 위해 ImageCropPicker 사용
|
|
486
|
-
// cropping: true이지만 전체 이미지를 선택하면 됨
|
|
487
|
-
const savedImage = await ImageCropPicker.openCropper({
|
|
488
|
-
path: rotatedImageUri,
|
|
489
|
-
mediaType: 'photo',
|
|
490
|
-
cropping: true,
|
|
491
|
-
freeStyleCropEnabled: true,
|
|
492
|
-
includeBase64: true,
|
|
493
|
-
compressImageQuality: 0.9,
|
|
494
|
-
cropperToolbarTitle: '회전 완료 - 확인을 눌러주세요',
|
|
495
|
-
cropperChooseText: '확인',
|
|
496
|
-
cropperCancelText: '취소',
|
|
497
|
-
cropperRotateButtonsHidden: true,
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
console.log('[FullDocScanner] Rotated image saved with base64');
|
|
501
|
-
|
|
502
|
-
// 회전된 이미지로 교체
|
|
503
|
-
setCroppedImageData({
|
|
504
|
-
path: savedImage.path,
|
|
505
|
-
base64: savedImage.data ?? undefined,
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
// rotation degrees는 0으로 리셋
|
|
509
|
-
setRotationDegrees(0);
|
|
510
|
-
} catch (cropError) {
|
|
511
|
-
console.error('[FullDocScanner] Failed to save rotated image:', cropError);
|
|
512
|
-
// 사용자가 취소한 경우 rotation 원복
|
|
513
|
-
setRotationDegrees(prev => (prev - degrees + 360) % 360);
|
|
514
|
-
}
|
|
515
|
-
},
|
|
516
|
-
(error: Error) => {
|
|
517
|
-
console.error('[FullDocScanner] Image rotation error:', error);
|
|
518
|
-
// 에러 발생 시 UI rotation 원복
|
|
519
|
-
setRotationDegrees(prev => {
|
|
520
|
-
const revertRotation = (prev - degrees + 360) % 360;
|
|
521
|
-
return revertRotation;
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
);
|
|
525
|
-
} catch (error) {
|
|
526
|
-
console.error('[FullDocScanner] Rotation setup error:', error);
|
|
527
|
-
setRotationDegrees(prev => (prev - degrees + 360) % 360);
|
|
473
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
474
|
+
if (rotationDegrees === 0) {
|
|
475
|
+
onResult({
|
|
476
|
+
path: croppedImageData.path,
|
|
477
|
+
base64: croppedImageData.base64,
|
|
478
|
+
});
|
|
479
|
+
return;
|
|
528
480
|
}
|
|
529
|
-
}, [croppedImageData]);
|
|
530
481
|
|
|
531
|
-
|
|
532
|
-
|
|
482
|
+
// 회전이 있으면 실제 파일 회전 처리
|
|
483
|
+
try {
|
|
484
|
+
setProcessing(true);
|
|
485
|
+
|
|
486
|
+
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
487
|
+
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
488
|
+
|
|
489
|
+
// OpenCV 회전 코드 매핑
|
|
490
|
+
// ROTATE_90_CLOCKWISE = 0
|
|
491
|
+
// ROTATE_180 = 1
|
|
492
|
+
// ROTATE_90_COUNTERCLOCKWISE = 2
|
|
493
|
+
let rotateCode: number;
|
|
494
|
+
if (rotationNormalized === 90) {
|
|
495
|
+
rotateCode = 0; // ROTATE_90_CLOCKWISE
|
|
496
|
+
} else if (rotationNormalized === 180) {
|
|
497
|
+
rotateCode = 1; // ROTATE_180
|
|
498
|
+
} else if (rotationNormalized === 270 || rotationNormalized === -90) {
|
|
499
|
+
rotateCode = 2; // ROTATE_90_COUNTERCLOCKWISE
|
|
500
|
+
} else {
|
|
501
|
+
// 회전 없음
|
|
502
|
+
onResult({
|
|
503
|
+
path: croppedImageData.path,
|
|
504
|
+
base64: croppedImageData.base64,
|
|
505
|
+
});
|
|
506
|
+
setProcessing(false);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
533
509
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
510
|
+
// OpenCV로 이미지 회전
|
|
511
|
+
const rotatedPath = await Cv2.rotate(croppedImageData.path, rotateCode);
|
|
512
|
+
|
|
513
|
+
// 회전된 이미지를 base64로 변환
|
|
514
|
+
const base64Data = await RNFS.readFile(rotatedPath, 'base64');
|
|
515
|
+
|
|
516
|
+
setProcessing(false);
|
|
517
|
+
|
|
518
|
+
// 회전된 이미지로 결과 전달
|
|
519
|
+
onResult({
|
|
520
|
+
path: rotatedPath,
|
|
521
|
+
base64: base64Data,
|
|
522
|
+
});
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
525
|
+
setProcessing(false);
|
|
526
|
+
|
|
527
|
+
const errorMessage = error && typeof error === 'object' && 'message' in error ? (error as Error).message : '';
|
|
528
|
+
Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
|
|
529
|
+
}
|
|
530
|
+
}, [croppedImageData, rotationDegrees, onResult]);
|
|
539
531
|
|
|
540
532
|
const handleRetake = useCallback(() => {
|
|
541
533
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|