/** * Image compression utility for thumbnail uploads. * Loads a File into an Image, draws onto a canvas at max 640×360 * (maintaining aspect ratio), and exports as JPEG base64. */ const MAX_WIDTH = 640; const MAX_HEIGHT = 360; const INITIAL_QUALITY = 0.7; const FALLBACK_QUALITY = 0.5; const MAX_SIZE_BYTES = 100_000; // 100 KB export async function compressImage(file: File): Promise { const bitmap = await createImageBitmap(file); // Calculate output dimensions maintaining aspect ratio let width = bitmap.width; let height = bitmap.height; if (width > MAX_WIDTH || height > MAX_HEIGHT) { const scale = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height); width = Math.round(width * scale); height = Math.round(height * scale); } const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('Failed to get canvas context'); ctx.drawImage(bitmap, 0, 0, width, height); bitmap.close(); // First attempt at normal quality let dataUrl = canvas.toDataURL('image/jpeg', INITIAL_QUALITY); // If too large, retry at lower quality if (dataUrl.length > MAX_SIZE_BYTES * 1.37) { // base64 is ~37% larger than raw bytes dataUrl = canvas.toDataURL('image/jpeg', FALLBACK_QUALITY); } return dataUrl; }