| |
| |
| |
| |
| |
| |
|
|
| 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; |
|
|
| |
| |
| |
| |
| 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) { |
| |
| 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 }); |
| } |
|
|
| |
| 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, |
| }; |
| } |
|
|
| |
| |
| |
| export function supportsWebpOptimization(): boolean { |
| try { |
| return typeof OffscreenCanvas !== "undefined"; |
| } catch { |
| return false; |
| } |
| } |
|
|