Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687export const MAX_IMAGE_BYTES = 1_000_000;
const KEPT_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"]);const MAX_SIDE = 4096;const VECTOR_SIDE = 1024;const QUALITY = 0.85;const MAX_ATTEMPTS = 8;
export class ImageError extends Error {}
const extensions: Record<string, string> = { "image/jpeg": "jpg", "image/png": "png", "image/webp": "webp"};
const renamed = (name: string, type: string) => `${name.replace(/\.[^.]*$/, "")}.${extensions[type] ?? "png"}`;
const decode = async (file: File, url: string): Promise<HTMLImageElement> => { const image = new Image(); image.src = url; try { await image.decode(); } catch { throw new ImageError(`"${file.name}" isn't an image this browser can read.`); } return image;};
const render = ( image: HTMLImageElement, width: number, height: number, type: string): Promise<Blob> => new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const context = canvas.getContext("2d"); if (!context) return reject(new ImageError("This browser can't resize images.")); if (type === "image/jpeg") { context.fillStyle = "#fff"; context.fillRect(0, 0, width, height); } context.drawImage(image, 0, 0, width, height); canvas.toBlob( (blob) => blob ? resolve(blob) : reject(new ImageError("The image couldn't be encoded.")), type, QUALITY ); });
export const prepareImage = async (file: File): Promise<File> => { if (KEPT_TYPES.has(file.type) && file.size <= MAX_IMAGE_BYTES) return file; if (file.type === "image/gif") throw new ImageError( `"${file.name}" is too large, and a gif can't be shrunk without losing its animation.` ); const url = URL.createObjectURL(file); try { const image = await decode(file, url); const longest = Math.max(image.naturalWidth, image.naturalHeight) || VECTOR_SIDE; const vector = file.type === "image/svg+xml"; const natural = image.naturalWidth || VECTOR_SIDE; const naturalHeight = image.naturalHeight || VECTOR_SIDE; let scale = vector ? VECTOR_SIDE / longest : Math.min(1, MAX_SIDE / longest); const type = file.type === "image/jpeg" ? "image/jpeg" : "image/webp"; for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { const blob = await render( image, Math.max(1, Math.round(natural * scale)), Math.max(1, Math.round(naturalHeight * scale)), type ); if (blob.size <= MAX_IMAGE_BYTES) return new File([blob], renamed(file.name, blob.type), { type: blob.type }); scale *= Math.min(0.9, Math.sqrt(MAX_IMAGE_BYTES / blob.size) * 0.95); } throw new ImageError(`"${file.name}" is too large to shrink under 1 MB.`); } finally { URL.revokeObjectURL(url); }};