Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 604467e480f8468389092a3b
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
src/lib/engine.ts
// The page's side of the image work: hands jobs to the two workers and opens HEIC photos the browser
// cannot. Nothing here touches the network except fetching the app's own code and model files.
import type { Made, MakeJob, Probe } from "./codec.worker";
import { sniff, type Kind } from "./formats";
type Reply = { id?: number; ok?: boolean; value?: unknown; error?: string; progress?: { loaded: number; total: number } };
function connect(make: () => Worker, onProgress?: (p: { loaded: number; total: number }) => void) {
let worker: Worker | null = null;
let seq = 0;
const waiting = new Map<number, { done: (v: never) => void; fail: (e: Error) => void }>();
return <T>(msg: object, transfer: Transferable[] = []): Promise<T> => {
if (!worker) {
worker = make();
worker.onmessage = (e: MessageEvent<Reply>) => {
const r = e.data;
if (r.progress) return onProgress?.(r.progress);
const w = waiting.get(r.id!);
waiting.delete(r.id!);
if (r.ok) w?.done(r.value as never);
else w?.fail(new Error(r.error));
};
worker.onerror = (e) => {
for (const w of waiting.values()) w.fail(new Error(e.message || "The image worker stopped."));
waiting.clear();
worker = null;
};
}
const id = ++seq;
return new Promise<T>((done, fail) => {
waiting.set(id, { done: done as (v: never) => void, fail });
worker!.postMessage({ id, ...msg }, transfer);
});
};
}
const codec = connect(() => new Worker(new URL("./codec.worker.ts", import.meta.url), { type: "module" }));
let progressListener: ((p: { loaded: number; total: number }) => void) | null = null;
export function onModelProgress(fn: typeof progressListener) { progressListener = fn; }
const cutoutWorker = connect(
() => new Worker(new URL("./cutout.worker.ts", import.meta.url), { type: "module" }),
(p) => progressListener?.(p),
);
/** A problem with one file, worded for the person who added it. */
export class FileProblem extends Error {}
export type Opened = Probe & { kind: Kind; src: Blob };
/** Reads an image: its kind, a copy this browser can decode, its size and a small preview. */
export async function open(file: File): Promise<Opened> {
const kind = sniff(new Uint8Array(await file.slice(0, 64).arrayBuffer()));
try {
return { kind, src: file, ...(await codec<Probe>({ op: "probe", src: file })) };
} catch {
if (kind !== "heic") throw new FileProblem(`${file.name} could not be opened as an image.`);
}
// Most browsers other than Safari cannot open iPhone HEIC photos, so decode those here.
let src: Blob;
try {
const { heicTo } = await import("heic-to/csp");
src = await heicTo({ blob: file, type: "image/png" });
} catch {
throw new FileProblem(`${file.name} looks like a HEIC photo, but it could not be opened.`);
}
return { kind, src, ...(await codec<Probe>({ op: "probe", src })) };
}
// The encoders and the background model load on first use, so a lost connection shows up as a
// failed fetch or import; a size the device cannot hold shows up as a memory error. Each browser
// words them differently, so they are put plainly here.
const plain = (offline: string) => (err: Error): never => {
const m = err.message;
throw new Error(/fetch|import|network|load failed|loading/i.test(m) ? offline
: /memory|allocat|too large|array length/i.test(m) ? "This is too large for this device to make. Pick a smaller size." : m);
};
export function make(job: MakeJob): Promise<Made> {
return codec<Made>({ op: "make", job }).catch(plain("Images could not load its encoder. Check your connection and try again."));
}
export function removeBackground(src: Blob): Promise<Uint8ClampedArray> {
return cutoutWorker<Uint8ClampedArray>({ src }).catch(plain("The background model could not be downloaded. Check your connection and try again."));
}
/** Whether this browser can save AVIF files (most cannot yet). */
export const canSaveAvif = (() => {
const c = document.createElement("canvas");
c.width = c.height = 1;
return c.toDataURL("image/avif").startsWith("data:image/avif");
})();
Change history
Quality pass: background removal, blur background, paste images
Bounded source sync
Bounded source sync
Bounded source sync