import { clsx } from "clsx"; import { twMerge } from "tailwind-merge" export function cn(...inputs) { return twMerge(clsx(inputs)); } /** * Compresses and resizes an image file on the client side. * If the file is not an image (e.g. PDF), it resolves with the original file. * * @param {File} file - The file to compress. * @param {number} maxWidth - Max width of compressed image. * @param {number} maxHeight - Max height of compressed image. * @param {number} quality - Compression quality between 0 and 1. * @returns {Promise} - A promise that resolves to the compressed File object. */ export function compressImage(file, maxWidth = 1200, maxHeight = 1200, quality = 0.8) { return new Promise((resolve) => { if (!file || !file.type.startsWith('image/')) { resolve(file); return; } const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { let width = img.width; let height = img.height; // Calculate aspect ratio if (width > height) { if (width > maxWidth) { height = Math.round((height * maxWidth) / width); width = maxWidth; } } else { if (height > maxHeight) { width = Math.round((width * maxHeight) / height); height = maxHeight; } } const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (!ctx) { resolve(file); return; } // Draw image ctx.drawImage(img, 0, 0, width, height); // Convert to blob if (canvas.toBlob) { canvas.toBlob( (blob) => { if (blob) { // Keep original extension or use jpeg, but File constructor needs a name const compressedFile = new File([blob], file.name.substring(0, file.name.lastIndexOf('.')) + '.jpg', { type: 'image/jpeg', lastModified: Date.now() }); resolve(compressedFile); } else { resolve(file); } }, 'image/jpeg', quality ); } else { resolve(file); } }; img.onerror = () => resolve(file); img.src = event.target.result; }; reader.onerror = () => resolve(file); reader.readAsDataURL(file); }); }