File size: 2,359 Bytes
7bafae7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | /**
* Client-side image optimization.
*
* Resizes images and converts them to WebP before upload,
* reducing bandwidth and storage costs significantly.
*/
interface OptimizedVariant {
blob: Blob;
width: number;
height: number;
suffix: string;
}
export interface OptimizedImage {
original: { width: number; height: number };
variants: OptimizedVariant[];
}
const VARIANTS = [
{ suffix: "thumb", maxWidth: 400, quality: 0.7 },
{ suffix: "medium", maxWidth: 1000, quality: 0.8 },
{ suffix: "full", maxWidth: 2000, quality: 0.85 },
] as const;
/**
* Optimize an image file: resize to 3 WebP variants + extract dimensions.
* Returns the variants sorted small to large.
*/
export async function optimizeImage(file: File): Promise<OptimizedImage> {
const bitmap = await createImageBitmap(file);
const { width: origW, height: origH } = bitmap;
const variants: OptimizedVariant[] = [];
for (const { suffix, maxWidth, quality } of VARIANTS) {
// Skip variant if original is smaller than this tier
if (origW <= maxWidth && suffix !== "full") continue;
const scale = Math.min(1, maxWidth / origW);
const w = Math.round(origW * scale);
const h = Math.round(origH * scale);
const canvas = new OffscreenCanvas(w, h);
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("Canvas 2D context unavailable");
ctx.drawImage(bitmap, 0, 0, w, h);
const blob = await canvas.convertToBlob({
type: "image/webp",
quality,
});
variants.push({ blob, width: w, height: h, suffix });
}
// Always ensure at least one variant (the full size)
if (variants.length === 0) {
const canvas = new OffscreenCanvas(origW, origH);
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("Canvas 2D context unavailable");
ctx.drawImage(bitmap, 0, 0);
const blob = await canvas.convertToBlob({
type: "image/webp",
quality: 0.85,
});
variants.push({ blob, width: origW, height: origH, suffix: "full" });
}
bitmap.close();
return {
original: { width: origW, height: origH },
variants,
};
}
/**
* Check if the browser supports WebP encoding via OffscreenCanvas.
*/
export function supportsWebpOptimization(): boolean {
try {
return typeof OffscreenCanvas !== "undefined";
} catch {
return false;
}
}
|