export type CompressionResult = { blob: Blob; originalSize: number; compressedSize: number; width: number; height: number; }; const COMPRESSIBLE_TYPES = ['image/jpeg', 'image/png']; /** Returns true if this file type can be compressed (JPEG or PNG, any size). */ export function isCompressible(file: File): boolean { return COMPRESSIBLE_TYPES.includes(file.type); } /** * Compress an image file via canvas.toBlob. * Returns null if the file type is not compressible (GIF, SVG, WebP, video, audio, etc.). */ export async function compressImage(file: File, quality = 0.82): Promise { if (!COMPRESSIBLE_TYPES.includes(file.type)) return null; const img = await loadImage(file); const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d')!; ctx.drawImage(img, 0, 0); return new Promise((resolve) => { canvas.toBlob( (blob) => { if (!blob) { resolve(null); return; } resolve({ blob, originalSize: file.size, compressedSize: blob.size, width: img.naturalWidth, height: img.naturalHeight, }); }, 'image/jpeg', quality, ); }); } function loadImage(file: File): Promise { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); const img = new Image(); img.onload = () => { URL.revokeObjectURL(url); resolve(img); }; img.onerror = reject; img.src = url; }); } export function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }