import sharp from "sharp" export interface ImageOptions { width?: number height?: number quality?: number format?: string // "webp", "jpeg", "png", "avif" } export async function optimizeImage(buffer: Buffer, options: ImageOptions): Promise<{ buffer: Buffer; contentType: string; filename: string }> { let pipeline = sharp(buffer) // Resize if width or height is provided and greater than 0 if ((options.width && options.width > 0) || (options.height && options.height > 0)) { pipeline = pipeline.resize({ width: options.width && options.width > 0 ? options.width : undefined, height: options.height && options.height > 0 ? options.height : undefined, fit: "cover", // Or 'contain', 'fill' based on requirement. Cover is usually good for heroes. withoutEnlargement: true, }) } // Default format is AVIF if not specified const format = options.format?.toLowerCase() || "avif" const quality = options.quality && options.quality > 0 ? options.quality : 80 switch (format) { case "jpeg": case "jpg": pipeline = pipeline.jpeg({ quality }) break case "png": pipeline = pipeline.png({ quality, compressionLevel: 9 }) // PNG quality is different, usually compression level break case "webp": pipeline = pipeline.webp({ quality }) break case "avif": pipeline = pipeline.avif({ quality }) break default: pipeline = pipeline.avif({ quality }) break } const processedBuffer = await pipeline.toBuffer() const extension = format === "jpg" ? "jpeg" : format return { buffer: processedBuffer, contentType: `image/${extension}`, filename: `image.${extension}`, // Generic filename, caller can prepend/append if needed } }