import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" import heic2any from "heic2any" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } export const compressImageBase64 = (base64Str: string, maxWidth = 800, quality = 0.7): Promise => { return new Promise((resolve) => { const img = new Image(); img.src = base64Str; img.onload = () => { let { width, height } = img; if (width > maxWidth) { height = Math.round((height * maxWidth) / width); width = maxWidth; } if (height > maxWidth) { width = Math.round((width * maxWidth) / height); height = maxWidth; } const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); if (!ctx) { resolve(base64Str); return; } ctx.drawImage(img, 0, 0, width, height); resolve(canvas.toDataURL("image/jpeg", quality)); }; img.onerror = () => resolve(base64Str); }); }; export const convertImageToWebpFile = async (file: File): Promise => { return new Promise(async (resolve) => { let processableFile: File | Blob = file; // Convert HEIC/HEIF to JPEG first so the browser can read it const isHeic = file.name.toLowerCase().endsWith('.heic') || file.name.toLowerCase().endsWith('.heif'); if (isHeic) { try { const converted = await heic2any({ blob: file, toType: "image/jpeg", quality: 0.8 // high quality intermediate step }); // heic2any can return an array of blobs if it's an image sequence, just take the first processableFile = Array.isArray(converted) ? converted[0] : converted; } catch (err) { console.error("Failed to convert HEIC image:", err); // If conversion fails, just fall back to returning the original file return resolve(file); } } const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { let { width, height } = img; const maxWidth = 2560; // 2K resolution max for task images if (width > maxWidth) { height = Math.round((height * maxWidth) / width); width = maxWidth; } if (height > maxWidth) { width = Math.round((width * maxWidth) / height); height = maxWidth; } const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); if (!ctx) { resolve(file); // Fallback to original return; } ctx.drawImage(img, 0, 0, width, height); canvas.toBlob( (blob) => { if (!blob) { resolve(file); // Fallback to original return; } // Replace original extension with .webp const originalName = file.name; const finalName = originalName.replace(/\.[^/.]+$/, "") + ".webp"; const newFile = new File([blob], finalName, { type: "image/webp", lastModified: Date.now(), }); resolve(newFile); }, "image/webp", 0.7 // 70% Quality ); }; img.onerror = () => resolve(file); // Fallback img.src = e.target?.result as string; }; reader.onerror = () => resolve(file); // Fallback reader.readAsDataURL(processableFile); }); };