images-free.openapps.xyz

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.
Owned app · rev b674d6e71b51
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.
← All files
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
b674d6e71b · 5 hours ago
Bounded source sync
c6622bcbd1 · 1 day ago
Bounded source sync
92ecc89502 · 1 day ago
Bounded source sync
ae2f8b8be0 · 1 day ago