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/formats.ts
// Image kinds, the settings a person picks, and what each image becomes under them.

export type Kind = "jpeg" | "png" | "webp" | "avif" | "heic" | "gif" | "bmp" | "other";
export type OutType = "jpeg" | "png" | "webp" | "avif";
export type Fit = "none" | "width" | "height" | "percent";

export type Settings = {
  format: "same" | OutType;
  /** 1 to 100, for JPG, WebP and AVIF. */
  quality: number;
  /** PNG: reduce to 256 colors (much smaller) or keep every color. */
  pngColors: "fewer" | "all";
  fit: Fit;
  /** Pixels for width or height, a percentage for percent. */
  size: number;
  cutout: boolean;
  /** Behind a cut-out: null keeps it transparent, "blur" puts it on a blurred copy of the photo, else a color. */
  background: string | null;
  /** Remove photo details (location, camera, date). */
  strip: boolean;
};

export const DEFAULTS: Settings = {
  format: "same",
  quality: 75,
  pngColors: "fewer",
  fit: "none",
  size: 1600,
  cutout: false,
  background: null,
  strip: true,
};

export const LABEL: Record<Kind, string> = {
  jpeg: "JPG", png: "PNG", webp: "WebP", avif: "AVIF", heic: "HEIC", gif: "GIF", bmp: "BMP", other: "Image",
};
export const EXT: Record<OutType, string> = { jpeg: "jpg", png: "png", webp: "webp", avif: "avif" };
export const MIME: Record<OutType, string> = { jpeg: "image/jpeg", png: "image/png", webp: "image/webp", avif: "image/avif" };

/** Reads what an image really is from its first bytes, whatever its name says. */
export function sniff(b: Uint8Array): Kind {
  const ascii = (from: number, to: number) => String.fromCharCode(...b.subarray(from, to));
  if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) return "jpeg";
  if (b[0] === 0x89 && ascii(1, 4) === "PNG") return "png";
  if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") return "webp";
  if (ascii(0, 4) === "GIF8") return "gif";
  if (ascii(0, 2) === "BM") return "bmp";
  if (ascii(4, 8) === "ftyp") {
    // The major brand and the compatible brands that follow it, up to the end of the ftyp box.
    const end = Math.min(b.length, (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
    const brands: string[] = [];
    for (let i = 8; i + 4 <= end; i += 4) if (i !== 12) brands.push(ascii(i, i + 4));
    if (brands.some((x) => x === "avif" || x === "avis")) return "avif";
    if (brands.some((x) => /^(heic|heix|hevc|hevx|heim|heis|mif1|msf1)$/.test(x))) return "heic";
  }
  return "other";
}

/** The file type an image is saved as. `avif` says whether this browser can save AVIF. */
export function outType(kind: Kind, s: Settings, avif: boolean): OutType {
  if (s.format !== "same") return s.format;
  if (s.cutout && !s.background) return kind === "webp" ? "webp" : "png";
  if (kind === "jpeg" || kind === "png" || kind === "webp") return kind;
  if (kind === "avif") return avif ? "avif" : "webp";
  if (kind === "heic") return "jpeg";
  return "png";
}

export const MAX_PX = 20000;

/** A size setting is usable when it is a whole number in range; otherwise images keep their size. */
export function sizeProblem(s: Settings): string | null {
  if (s.fit === "none") return null;
  const max = s.fit === "percent" ? 1000 : MAX_PX;
  if (!Number.isInteger(s.size) || s.size < 1 || s.size > max) {
    return s.fit === "percent" ? "Use a whole number from 1 to 1000." : `Use a whole number of pixels from 1 to ${MAX_PX}.`;
  }
  return null;
}

/** The pixel size an image comes out at. The other side follows the image's shape. */
export function target(w: number, h: number, s: Settings): [number, number] {
  if (s.fit === "none" || sizeProblem(s)) return [w, h];
  const r = (n: number) => Math.max(1, Math.round(n));
  if (s.fit === "width") return [s.size, r((h * s.size) / w)];
  if (s.fit === "height") return [r((w * s.size) / h), s.size];
  return [r((w * s.size) / 100), r((h * s.size) / 100)];
}

export const WHITE = "#ffffff";
export const BLUR = "blur";

const TO: Record<string, OutType> = { jpg: "jpeg", jpeg: "jpeg", png: "png", webp: "webp", avif: "avif" };
const whole = (v: string | null, min: number, max: number) => {
  const n = Number(v);
  return v && Number.isInteger(n) && n >= min && n <= max ? n : null;
};

/** Settings from a link such as /?bg=remove or /?to=jpg&width=1600, so an assistant can send a ready preset. */
export function fromQuery(search: string): Settings {
  const q = new URLSearchParams(search);
  const s = { ...DEFAULTS };
  const to = TO[(q.get("to") || "").toLowerCase()];
  if (to) s.format = to;
  s.quality = whole(q.get("quality"), 1, 100) ?? s.quality;
  if (q.get("colors") === "all") s.pngColors = "all";
  for (const fit of ["width", "height", "percent"] as const) {
    const n = whole(q.get(fit), 1, fit === "percent" ? 1000 : MAX_PX);
    if (n) { s.fit = fit; s.size = n; }
  }
  const bg = (q.get("bg") || "").toLowerCase();
  if (bg) {
    if (bg === "remove" || bg === "clear") s.cutout = true;
    else if (bg === "white") { s.cutout = true; s.background = WHITE; }
    else if (bg === BLUR) { s.cutout = true; s.background = BLUR; }
    else if (/^#?[0-9a-f]{6}$/.test(bg)) { s.cutout = true; s.background = `#${bg.replace("#", "")}`; }
  }
  if (q.get("details") === "keep") s.strip = false;
  return s;
}

/** The link parameters for settings, leaving out defaults. */
export function toQuery(s: Settings): string {
  const q = new URLSearchParams();
  if (s.format !== "same") q.set("to", EXT[s.format]);
  if (s.quality !== DEFAULTS.quality) q.set("quality", String(s.quality));
  if (s.pngColors === "all") q.set("colors", "all");
  if (s.fit !== "none" && !sizeProblem(s)) q.set(s.fit, String(s.size));
  if (s.cutout) q.set("bg", !s.background ? "remove" : s.background === WHITE ? "white" : s.background === BLUR ? BLUR : s.background.slice(1));
  if (!s.strip) q.set("details", "keep");
  const text = q.toString();
  return text ? `?${text}` : "";
}

export const stemOf = (name: string) => name.replace(/\.[^.]+$/, "").replace(/[\\/:*?"<>|]+/g, "-").trim() || "image";

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