55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
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
|
|
}
|
|
}
|