summaryrefslogtreecommitdiff
path: root/app/api/ocr/utils/imageRotation.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/ocr/utils/imageRotation.ts')
-rw-r--r--app/api/ocr/utils/imageRotation.ts244
1 files changed, 244 insertions, 0 deletions
diff --git a/app/api/ocr/utils/imageRotation.ts b/app/api/ocr/utils/imageRotation.ts
new file mode 100644
index 00000000..fe9cf840
--- /dev/null
+++ b/app/api/ocr/utils/imageRotation.ts
@@ -0,0 +1,244 @@
+// app/api/ocr/utils/imageRotation.ts
+// Sharp을 사용한 서버 사이드 이미지 회전
+
+import sharp from 'sharp';
+
+/**
+ * 서버 사이드에서 이미지를 회전시킵니다
+ * @param base64 - base64 인코딩된 이미지 데이터
+ * @param degrees - 회전 각도 (0, 90, 180, 270)
+ * @returns Promise<string> - 회전된 이미지의 base64 데이터
+ */
+export async function rotateImageBase64(base64: string, degrees: number): Promise<string> {
+ try {
+ console.log(`🔄 Rotating image by ${degrees} degrees...`);
+
+ // base64를 Buffer로 변환
+ const inputBuffer = Buffer.from(base64, 'base64');
+
+ // 회전 각도에 따른 처리
+ let rotatedBuffer: Buffer;
+
+ switch (degrees) {
+ case 0:
+ // 회전 없음
+ rotatedBuffer = inputBuffer;
+ break;
+
+ case 90:
+ rotatedBuffer = await sharp(inputBuffer)
+ .rotate(90)
+ .jpeg({
+ quality: 90,
+ progressive: true
+ })
+ .toBuffer();
+ break;
+
+ case 180:
+ rotatedBuffer = await sharp(inputBuffer)
+ .rotate(180)
+ .jpeg({
+ quality: 90,
+ progressive: true
+ })
+ .toBuffer();
+ break;
+
+ case 270:
+ rotatedBuffer = await sharp(inputBuffer)
+ .rotate(270)
+ .jpeg({
+ quality: 90,
+ progressive: true
+ })
+ .toBuffer();
+ break;
+
+ default:
+ console.warn(`⚠️ Unsupported rotation angle: ${degrees}°. Using original image.`);
+ rotatedBuffer = inputBuffer;
+ }
+
+ // Buffer를 다시 base64로 변환
+ const rotatedBase64 = rotatedBuffer.toString('base64');
+
+ console.log(`✅ Image rotated successfully (${degrees}°)`);
+ return rotatedBase64;
+
+ } catch (error) {
+ console.error(`❌ Error rotating image by ${degrees}°:`, error);
+ console.warn('Using original image due to rotation error');
+ return base64; // 실패시 원본 반환
+ }
+}
+
+/**
+ * 이미지 품질을 개선합니다
+ * @param base64 - base64 인코딩된 이미지 데이터
+ * @returns Promise<string> - 개선된 이미지의 base64 데이터
+ */
+export async function enhanceImageQuality(base64: string): Promise<string> {
+ try {
+ console.log('🎨 Enhancing image quality...');
+
+ const inputBuffer = Buffer.from(base64, 'base64');
+
+ const enhancedBuffer = await sharp(inputBuffer)
+ .resize(2000, 2000, {
+ fit: 'inside',
+ withoutEnlargement: true
+ })
+ // 개별 매개변수 방식으로 수정
+ .sharpen(1, 1, 2) // sigma, m1(flat), m2(jagged)
+ .normalize() // 히스토그램 정규화
+ .gamma(1.1) // 약간의 감마 보정
+ .jpeg({
+ quality: 95,
+ progressive: true,
+ mozjpeg: true
+ })
+ .toBuffer();
+
+ const enhancedBase64 = enhancedBuffer.toString('base64');
+
+ console.log('✅ Image quality enhanced');
+ return enhancedBase64;
+
+ } catch (error) {
+ console.error('❌ Error enhancing image:', error);
+ return base64;
+ }
+}
+
+/**
+ * PDF를 고품질 이미지로 변환합니다
+ * @param pdfBuffer - PDF Buffer 데이터
+ * @param pageIndex - 변환할 페이지 인덱스 (0부터 시작)
+ * @returns Promise<string> - 변환된 이미지의 base64 데이터
+ */
+export async function convertPDFToImage(pdfBuffer: Buffer, pageIndex: number = 0): Promise<string> {
+ try {
+ console.log(`📄 Converting PDF page ${pageIndex + 1} to image...`);
+
+ // pdf2pic 라이브러리 사용
+ const pdf2pic = require('pdf2pic');
+
+ const convert = pdf2pic.fromBuffer(pdfBuffer, {
+ density: 300, // 300 DPI for high quality
+ saveFilename: "page",
+ savePath: "/tmp", // 임시 경로
+ format: "jpeg",
+ width: 2480, // A4 크기 @ 300 DPI
+ height: 3508,
+ quality: 100
+ });
+
+ const result = await convert(pageIndex + 1, { responseType: "buffer" });
+ const base64 = result.buffer.toString('base64');
+
+ console.log('✅ PDF converted to image successfully');
+ return base64;
+
+ } catch (error) {
+ console.error('❌ Error converting PDF to image:', error);
+ throw new Error('Failed to convert PDF to image');
+ }
+}
+
+/**
+ * 이미지에서 텍스트 방향을 감지합니다
+ * @param base64 - base64 인코딩된 이미지 데이터
+ * @returns Promise<number> - 감지된 올바른 회전 각도
+ */
+export async function detectTextOrientation(base64: string): Promise<number> {
+ // 이 함수는 간단한 방향 감지를 시뮬레이션합니다
+ // 실제로는 더 정교한 알고리즘이 필요할 수 있습니다
+
+ console.log('🧭 Detecting text orientation...');
+
+ const rotations = [0, 90, 180, 270];
+ const scores: { rotation: number; score: number }[] = [];
+
+ for (const rotation of rotations) {
+ try {
+ const rotatedBase64 = await rotateImageBase64(base64, rotation);
+
+ // 간단한 품질 측정 (실제로는 OCR API 호출이나 다른 방법 사용)
+ const score = await estimateTextQuality(rotatedBase64);
+ scores.push({ rotation, score });
+
+ console.log(` ${rotation}°: quality score = ${score.toFixed(3)}`);
+
+ } catch (error) {
+ console.warn(` ${rotation}°: Failed to test orientation`);
+ scores.push({ rotation, score: 0 });
+ }
+ }
+
+ const bestOrientation = scores.reduce((best, current) =>
+ current.score > best.score ? current : best
+ );
+
+ console.log(`🎯 Best orientation detected: ${bestOrientation.rotation}°`);
+ return bestOrientation.rotation;
+}
+
+/**
+ * 이미지의 텍스트 품질을 추정합니다 (간단한 버전)
+ */
+async function estimateTextQuality(base64: string): Promise<number> {
+ try {
+ const buffer = Buffer.from(base64, 'base64');
+
+ // Sharp을 사용해 이미지 통계 분석
+ const stats = await sharp(buffer)
+ .greyscale()
+ .stats();
+
+ // 간단한 품질 지표 계산
+ // 실제로는 더 복잡한 알고리즘이 필요합니다
+ const contrast = stats.channels[0].max - stats.channels[0].min;
+ const sharpness = stats.channels[0].stdev;
+
+ return (contrast + sharpness) / 510; // 0-1 범위로 정규화
+
+ } catch (error) {
+ return 0;
+ }
+}
+
+/**
+ * 이미지가 회전이 필요한지 빠르게 체크합니다
+ * @param base64 - base64 인코딩된 이미지 데이터
+ * @returns Promise<boolean> - 회전이 필요하면 true
+ */
+export async function needsRotation(base64: string): Promise<boolean> {
+ try {
+ const buffer = Buffer.from(base64, 'base64');
+
+ // 이미지 메타데이터 확인
+ const metadata = await sharp(buffer).metadata();
+
+ // EXIF 방향 정보가 있으면 회전 필요
+ if (metadata.orientation && metadata.orientation > 1) {
+ console.log(`📐 EXIF orientation detected: ${metadata.orientation}`);
+ return true;
+ }
+
+ // 가로/세로 비율 체크 (일반적으로 문서는 세로가 더 긺)
+ if (metadata.width && metadata.height) {
+ const aspectRatio = metadata.width / metadata.height;
+ if (aspectRatio > 1.5) {
+ console.log(`📐 Wide aspect ratio detected: ${aspectRatio.toFixed(2)}`);
+ return true; // 너무 가로로 긴 이미지는 회전 필요할 가능성
+ }
+ }
+
+ return false;
+
+ } catch (error) {
+ console.warn('Error checking if rotation needed:', error);
+ return false;
+ }
+} \ No newline at end of file