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.
@@ -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)(async (degrees) => {
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
- console.log('[FullDocScanner] Starting image rotation...', {
350
- path: croppedImageData.path,
351
- hasBase64: !!croppedImageData.base64,
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
- mediaType: 'photo',
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
- console.log('[FullDocScanner] Rotated image saved:', {
371
- path: rotatedImage.path,
372
- hasBase64: !!rotatedImage.data,
373
- base64Length: rotatedImage.data?.length,
374
- });
375
- // 회전된 이미지로 교체
376
- setCroppedImageData({
377
- path: rotatedImage.path,
378
- base64: rotatedImage.data ?? undefined,
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
- if (!errorMessage.includes('cancel') && !errorMessage.includes('User cancelled')) {
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.100.0",
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": "*",
@@ -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(async (degrees: -90 | 90) => {
459
- if (!croppedImageData) return;
460
-
461
- console.log('[FullDocScanner] Starting image rotation...', {
462
- path: croppedImageData.path,
463
- hasBase64: !!croppedImageData.base64,
464
- base64Length: croppedImageData.base64?.length,
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
- try {
468
- // 원본 파일을 ImageCropPicker로 열어서 회전 적용
469
- // 사용자가 수동으로 회전 버튼을 클릭하고 완료를 눌러야 함
470
- const rotatedImage = await ImageCropPicker.openCropper({
470
+ const handleConfirm = useCallback(async () => {
471
+ if (!croppedImageData) return;
472
+
473
+ // 회전이 없으면 기존 데이터 그대로 전달
474
+ if (rotationDegrees === 0) {
475
+ onResult({
471
476
  path: croppedImageData.path,
472
- mediaType: 'photo',
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
- console.log('[FullDocScanner] Rotated image saved:', {
485
- path: rotatedImage.path,
486
- hasBase64: !!rotatedImage.data,
487
- base64Length: rotatedImage.data?.length,
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
- setCroppedImageData({
492
- path: rotatedImage.path,
493
- base64: rotatedImage.data ?? undefined,
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
- // rotation degrees는 0으로 리셋
497
- setRotationDegrees(0);
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
- if (!errorMessage.includes('cancel') && !errorMessage.includes('User cancelled')) {
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');
@@ -0,0 +1,5 @@
1
+ declare module 'react-native-fast-opencv' {
2
+ export class Cv2 {
3
+ static rotate(imagePath: string, rotateCode: number): Promise<string>;
4
+ }
5
+ }