/** * 이미지 압축 및 최적화 유틸리티 */ const ImageUtils = { /** * 이미지를 압축하고 리사이즈 * @param {File|Blob|String} source - 이미지 파일, Blob 또는 base64 문자열 * @param {Object} options - 압축 옵션 * @returns {Promise} - 압축된 base64 이미지 */ async compressImage(source, options = {}) { const { maxWidth = 1024, // 최대 너비 maxHeight = 1024, // 최대 높이 quality = 0.7, // JPEG 품질 (0-1) format = 'jpeg' // 출력 형식 } = options; return new Promise((resolve, reject) => { let img = new Image(); // 이미지 로드 완료 시 img.onload = () => { // Canvas 생성 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 리사이즈 계산 let { width, height } = this.calculateDimensions( img.width, img.height, maxWidth, maxHeight ); // Canvas 크기 설정 canvas.width = width; canvas.height = height; // 이미지 그리기 ctx.drawImage(img, 0, 0, width, height); // 압축된 이미지를 base64로 변환 canvas.toBlob((blob) => { if (!blob) { reject(new Error('이미지 압축 실패')); return; } const reader = new FileReader(); reader.onloadend = () => { resolve(reader.result); }; reader.onerror = reject; reader.readAsDataURL(blob); }, `image/${format}`, quality); }; img.onerror = () => reject(new Error('이미지 로드 실패')); // 소스 타입에 따라 처리 if (typeof source === 'string') { // Base64 문자열인 경우 img.src = source; } else if (source instanceof File || source instanceof Blob) { // File 또는 Blob인 경우 const reader = new FileReader(); reader.onloadend = () => { img.src = reader.result; }; reader.onerror = reject; reader.readAsDataURL(source); } else { reject(new Error('지원하지 않는 이미지 형식')); } }); }, /** * 이미지 크기 계산 (비율 유지) */ calculateDimensions(originalWidth, originalHeight, maxWidth, maxHeight) { // 원본 크기가 제한 내에 있으면 그대로 반환 if (originalWidth <= maxWidth && originalHeight <= maxHeight) { return { width: originalWidth, height: originalHeight }; } // 비율 계산 const widthRatio = maxWidth / originalWidth; const heightRatio = maxHeight / originalHeight; const ratio = Math.min(widthRatio, heightRatio); return { width: Math.round(originalWidth * ratio), height: Math.round(originalHeight * ratio) }; }, /** * 파일 크기를 사람이 읽을 수 있는 형식으로 변환 */ formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; }, /** * Base64 문자열의 크기 계산 */ getBase64Size(base64String) { const base64Length = base64String.length - (base64String.indexOf(',') + 1); const padding = (base64String.charAt(base64String.length - 2) === '=') ? 2 : ((base64String.charAt(base64String.length - 1) === '=') ? 1 : 0); return (base64Length * 0.75) - padding; }, /** * 이미지 미리보기 생성 (썸네일) */ async createThumbnail(source, size = 150) { return this.compressImage(source, { maxWidth: size, maxHeight: size, quality: 0.8 }); } }; // 전역으로 사용 가능하도록 export window.ImageUtils = ImageUtils;