| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const MAX_SIDE = 1024; |
| const QUALITY = 0.85; |
|
|
| export class UnreadableImageError extends Error { |
| constructor(name: string) { |
| super(`Couldn't read "${name}"`); |
| this.name = "UnreadableImageError"; |
| } |
| } |
|
|
| |
| async function decode(file: File): Promise<ImageBitmap | HTMLImageElement> { |
| if (typeof createImageBitmap === "function") { |
| try { |
| return await createImageBitmap(file, { imageOrientation: "from-image" }); |
| } catch { |
| |
| } |
| } |
| const url = URL.createObjectURL(file); |
| try { |
| return await new Promise<HTMLImageElement>((resolve, reject) => { |
| const img = new Image(); |
| img.onload = () => resolve(img); |
| img.onerror = () => reject(new UnreadableImageError(file.name)); |
| img.src = url; |
| }); |
| } finally { |
| URL.revokeObjectURL(url); |
| } |
| } |
|
|
| |
| |
| |
| |
| export async function prepareImage(file: File): Promise<File> { |
| let source: ImageBitmap | HTMLImageElement; |
| try { |
| source = await decode(file); |
| } catch { |
| throw new UnreadableImageError(file.name); |
| } |
|
|
| const w = "width" in source ? source.width : 0; |
| const h = "height" in source ? source.height : 0; |
| if (!w || !h) throw new UnreadableImageError(file.name); |
|
|
| const scale = Math.min(1, MAX_SIDE / Math.max(w, h)); |
| const canvas = document.createElement("canvas"); |
| canvas.width = Math.round(w * scale); |
| canvas.height = Math.round(h * scale); |
| const ctx = canvas.getContext("2d"); |
| if (!ctx) throw new UnreadableImageError(file.name); |
| ctx.drawImage(source as CanvasImageSource, 0, 0, canvas.width, canvas.height); |
| if ("close" in source) source.close(); |
|
|
| const blob = await new Promise<Blob | null>((resolve) => |
| canvas.toBlob(resolve, "image/jpeg", QUALITY) |
| ); |
| if (!blob || blob.size === 0) throw new UnreadableImageError(file.name); |
|
|
| const name = file.name.replace(/\.[^.]+$/, "") || "photo"; |
| return new File([blob], `${name}.jpg`, { type: "image/jpeg" }); |
| } |
|
|