1type CompressOptions = {2 maxWidth: number;3 maxHeight: number;4 mimeType?: "image/jpeg" | "image/webp" | "image/png";5 quality?: number;6 allowUpscale?: boolean;7 jpegBackground?: string;8 maxMegapixels?: number;9};1011async function compressImage(12 file: File,13 options: CompressOptions,14): Promise<Blob> {15 validateImageFile(file);1617 const {18 maxWidth,19 maxHeight,20 mimeType = "image/webp",21 quality = 0.82,22 allowUpscale = false,23 jpegBackground = "#ffffff",24 maxMegapixels = 40,25 } = options;2627 if (!(quality >= 0 && quality <= 1)) {28 throw new Error("quality must be between 0 and 1");29 }3031 const bitmap = await createImageBitmap(file, {32 imageOrientation: "from-image",33 });3435 try {36 const megapixels = (bitmap.width * bitmap.height) / 1_000_000;37 if (megapixels > maxMegapixels) {38 throw new Error("Decoded image dimensions exceed the safety limit");39 }4041 const target = fitInside(42 bitmap.width,43 bitmap.height,44 maxWidth,45 maxHeight,46 allowUpscale,47 );4849 const background = mimeType === "image/jpeg"50 ? jpegBackground51 : undefined;5253 const canvas = drawResized(54 bitmap,55 target.width,56 target.height,57 background,58 );5960 try {61 const blob = await canvasToBlob(canvas, mimeType, quality);6263 if (blob.type !== mimeType) {64 throw new Error(`Requested encoder is unavailable: ${mimeType}`);65 }6667 return blob;68 } finally {69 // Release the backing store sooner on memory-constrained devices.70 canvas.width = 1;71 canvas.height = 1;72 }73 } finally {74 bitmap.close();75 }76}