react-native-rectangle-doc-scanner 3.100.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
|
@@ -42,6 +42,7 @@ 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
44
|
const react_native_fs_1 = __importDefault(require("react-native-fs"));
|
|
45
|
+
const react_native_fast_opencv_1 = require("react-native-fast-opencv");
|
|
45
46
|
const DocScanner_1 = require("./DocScanner");
|
|
46
47
|
const stripFileUri = (value) => value.replace(/^file:\/\//, '');
|
|
47
48
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
@@ -343,61 +344,75 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
343
344
|
const handleFlashToggle = (0, react_1.useCallback)(() => {
|
|
344
345
|
setFlashEnabled(prev => !prev);
|
|
345
346
|
}, []);
|
|
346
|
-
const handleRotateImage = (0, react_1.useCallback)(
|
|
347
|
+
const handleRotateImage = (0, react_1.useCallback)((degrees) => {
|
|
348
|
+
// UI만 회전 (실제 파일 회전은 confirm 시 처리)
|
|
349
|
+
setRotationDegrees(prev => {
|
|
350
|
+
const newRotation = prev + degrees;
|
|
351
|
+
// -360 ~ 360 범위로 정규화
|
|
352
|
+
if (newRotation <= -360)
|
|
353
|
+
return newRotation + 360;
|
|
354
|
+
if (newRotation >= 360)
|
|
355
|
+
return newRotation - 360;
|
|
356
|
+
return newRotation;
|
|
357
|
+
});
|
|
358
|
+
}, []);
|
|
359
|
+
const handleConfirm = (0, react_1.useCallback)(async () => {
|
|
347
360
|
if (!croppedImageData)
|
|
348
361
|
return;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
base64Length: croppedImageData.base64?.length,
|
|
353
|
-
});
|
|
354
|
-
try {
|
|
355
|
-
// 원본 파일을 ImageCropPicker로 열어서 회전 적용
|
|
356
|
-
// 사용자가 수동으로 회전 버튼을 클릭하고 완료를 눌러야 함
|
|
357
|
-
const rotatedImage = await react_native_image_crop_picker_1.default.openCropper({
|
|
362
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
363
|
+
if (rotationDegrees === 0) {
|
|
364
|
+
onResult({
|
|
358
365
|
path: croppedImageData.path,
|
|
359
|
-
|
|
360
|
-
cropping: true,
|
|
361
|
-
freeStyleCropEnabled: true,
|
|
362
|
-
includeBase64: true,
|
|
363
|
-
compressImageQuality: 0.9,
|
|
364
|
-
cropperToolbarTitle: degrees === 90 ? '우측 90° 회전 → 회전 버튼 클릭 → 완료' : '좌측 90° 회전 → 회전 버튼 클릭 → 완료',
|
|
365
|
-
cropperChooseText: '완료',
|
|
366
|
-
cropperCancelText: '취소',
|
|
367
|
-
cropperRotateButtonsHidden: false,
|
|
368
|
-
enableRotationGesture: true,
|
|
366
|
+
base64: croppedImageData.base64,
|
|
369
367
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// 회전이 있으면 실제 파일 회전 처리
|
|
371
|
+
try {
|
|
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,
|
|
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,
|
|
379
407
|
});
|
|
380
|
-
// rotation degrees는 0으로 리셋
|
|
381
|
-
setRotationDegrees(0);
|
|
382
408
|
}
|
|
383
409
|
catch (error) {
|
|
384
|
-
console.error('[FullDocScanner] Image rotation error:', error);
|
|
385
|
-
|
|
410
|
+
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
411
|
+
setProcessing(false);
|
|
386
412
|
const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : '';
|
|
387
|
-
|
|
388
|
-
// 에러 메시지 표시
|
|
389
|
-
react_native_1.Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다.');
|
|
390
|
-
}
|
|
413
|
+
react_native_1.Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
|
|
391
414
|
}
|
|
392
|
-
}, [croppedImageData]);
|
|
393
|
-
const handleConfirm = (0, react_1.useCallback)(() => {
|
|
394
|
-
if (!croppedImageData)
|
|
395
|
-
return;
|
|
396
|
-
onResult({
|
|
397
|
-
path: croppedImageData.path,
|
|
398
|
-
base64: croppedImageData.base64,
|
|
399
|
-
});
|
|
400
|
-
}, [croppedImageData, onResult]);
|
|
415
|
+
}, [croppedImageData, rotationDegrees, onResult]);
|
|
401
416
|
const handleRetake = (0, react_1.useCallback)(() => {
|
|
402
417
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
403
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,6 +33,7 @@
|
|
|
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": "*",
|
package/src/FullDocScanner.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { launchImageLibrary } from 'react-native-image-picker';
|
|
13
13
|
import ImageCropPicker from 'react-native-image-crop-picker';
|
|
14
14
|
import RNFS from 'react-native-fs';
|
|
15
|
+
import { Cv2 } from 'react-native-fast-opencv';
|
|
15
16
|
import { DocScanner } from './DocScanner';
|
|
16
17
|
import type { CapturedDocument } from './types';
|
|
17
18
|
import type {
|
|
@@ -455,66 +456,78 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
455
456
|
setFlashEnabled(prev => !prev);
|
|
456
457
|
}, []);
|
|
457
458
|
|
|
458
|
-
const handleRotateImage = useCallback(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
459
|
+
const handleRotateImage = useCallback((degrees: -90 | 90) => {
|
|
460
|
+
// UI만 회전 (실제 파일 회전은 confirm 시 처리)
|
|
461
|
+
setRotationDegrees(prev => {
|
|
462
|
+
const newRotation = prev + degrees;
|
|
463
|
+
// -360 ~ 360 범위로 정규화
|
|
464
|
+
if (newRotation <= -360) return newRotation + 360;
|
|
465
|
+
if (newRotation >= 360) return newRotation - 360;
|
|
466
|
+
return newRotation;
|
|
465
467
|
});
|
|
468
|
+
}, []);
|
|
466
469
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
470
|
+
const handleConfirm = useCallback(async () => {
|
|
471
|
+
if (!croppedImageData) return;
|
|
472
|
+
|
|
473
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
474
|
+
if (rotationDegrees === 0) {
|
|
475
|
+
onResult({
|
|
471
476
|
path: croppedImageData.path,
|
|
472
|
-
|
|
473
|
-
cropping: true,
|
|
474
|
-
freeStyleCropEnabled: true,
|
|
475
|
-
includeBase64: true,
|
|
476
|
-
compressImageQuality: 0.9,
|
|
477
|
-
cropperToolbarTitle: degrees === 90 ? '우측 90° 회전 → 회전 버튼 클릭 → 완료' : '좌측 90° 회전 → 회전 버튼 클릭 → 완료',
|
|
478
|
-
cropperChooseText: '완료',
|
|
479
|
-
cropperCancelText: '취소',
|
|
480
|
-
cropperRotateButtonsHidden: false,
|
|
481
|
-
enableRotationGesture: true,
|
|
477
|
+
base64: croppedImageData.base64,
|
|
482
478
|
});
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
483
481
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
+
}
|
|
489
509
|
|
|
490
|
-
//
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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);
|
|
495
517
|
|
|
496
|
-
//
|
|
497
|
-
|
|
518
|
+
// 회전된 이미지로 결과 전달
|
|
519
|
+
onResult({
|
|
520
|
+
path: rotatedPath,
|
|
521
|
+
base64: base64Data,
|
|
522
|
+
});
|
|
498
523
|
} catch (error) {
|
|
499
|
-
console.error('[FullDocScanner] Image rotation error:', error);
|
|
524
|
+
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
525
|
+
setProcessing(false);
|
|
500
526
|
|
|
501
|
-
// 사용자가 취소했으면 아무것도 안함
|
|
502
527
|
const errorMessage = error && typeof error === 'object' && 'message' in error ? (error as Error).message : '';
|
|
503
|
-
|
|
504
|
-
// 에러 메시지 표시
|
|
505
|
-
Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다.');
|
|
506
|
-
}
|
|
528
|
+
Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
|
|
507
529
|
}
|
|
508
|
-
}, [croppedImageData]);
|
|
509
|
-
|
|
510
|
-
const handleConfirm = useCallback(() => {
|
|
511
|
-
if (!croppedImageData) return;
|
|
512
|
-
|
|
513
|
-
onResult({
|
|
514
|
-
path: croppedImageData.path,
|
|
515
|
-
base64: croppedImageData.base64,
|
|
516
|
-
});
|
|
517
|
-
}, [croppedImageData, onResult]);
|
|
530
|
+
}, [croppedImageData, rotationDegrees, onResult]);
|
|
518
531
|
|
|
519
532
|
const handleRetake = useCallback(() => {
|
|
520
533
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|