1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
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;
}
}
|