For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. The dithercn agent skill, for building any dither on the engine, is at /.well-known/agent-skills/dithercn/SKILL.md.
dithercn

Export

Turn any dither into a PNG, a compact SVG or a CSS rule from your own code, with dither-export.

dither-export turns a dither into files. It takes the ImageData from ditherImage, one pixel per cell, or a grid of palette indices you worked out yourself, and returns a PNG, an SVG or a CSS rule. Use it to bake a dither into a static asset, to ship a background with no JavaScript, or to give your users a download button.

Which format

FormatUse it for
PNGPhotos and diffusion dithers. Pixel-exact at any scale.
SVGOrdered dithers and flat art. Sharp at any size.
CSSBackgrounds in a stylesheet: the whole image, or a tile.

Installation

pnpm dlx shadcn@latest add https://dithercn.dev/r/dither-export.json

Usage

import { ditherImage } from "@/lib/dither";
import { download, toCSS, toPNG, toSVG } from "@/lib/dither/export";
 
const dither = ditherImage(pixels, {
  method: "atkinson",
  palette: [
    [0.04, 0.04, 0.05],
    [0.95, 0.94, 0.91],
  ],
});
 
// A full-size PNG: every cell becomes a crisp 4 × 4 block.
download({ data: await toPNG(dither, { scale: 4 }), filename: "dither.png" });
 
// An SVG drawn in cells, 4px each.
const svg = toSVG(dither, { cell: 4 });
 
// A CSS rule with the dither as a covering background.
const css = toCSS({ kind: "image", image: dither, selector: ".hero" });

toSVG and toCSS with a tile return strings and run anywhere, including on the server. toPNG, download and the PNG form of toCSS need a browser, because they draw to a canvas.

Examples

A fade in pure CSS

The default kind of toCSS repeats one matrix tile at a coverage level from 0 to 1. In mask mode it paints the element with ink and masks it to the dither, so the ink can be a CSS variable that follows your theme. Four bands at falling levels make the stepped fade at the bottom of this card. The rules are built on the server; the page runs no script for them.

toCSS({ level: 0.25, cell: 4, ink: "--dither-ink", mode: "mask" });
.dither {
  background-color: var(--dither-ink);
  -webkit-mask: url("data:image/svg+xml,…") 0 0 / 16px 16px repeat;
  mask: url("data:image/svg+xml,…") 0 0 / 16px 16px repeat;
}

In the default background mode the ink is baked into the tile and paper fills the gaps. An SVG inside a data URI can't read CSS variables, so pass a literal colour for ink in that mode.

Indexed bitmaps

If you work out the dither yourself, pass toSVG one palette index per cell instead of pixels. Any index without a colour is left transparent.

const svg = toSVG(
  { width: 64, height: 32, indices },
  { cell: 3, palette: ["#0a0a0b", "#3d6bff", "#dfe7ff"] }
);

API

toPNG

toPNG(
  source: HTMLCanvasElement | PixelBuffer,
  options?: { scale?: number }
): Promise<Blob>

Encodes a canvas or pixels as a PNG. scale is a whole-number upscale with no smoothing, so pass the cell size to turn one pixel per cell into a full-size image.

toSVG

toSVG(
  source: PixelBuffer | IndexedBitmap,
  options?: {
    cell?: number;
    palette?: DitherColor[];
    background?: boolean;
  }
): string

An SVG in cell units, scaled to cell pixels per cell (default 1). The most used colour becomes one background rectangle unless background is false. Rows of same-coloured cells become short relative strokes and taller blocks become rectangles, at most two <path>s per colour, so a 400 × 225 ordered dither is about 150 KB before gzip. palette colours an IndexedBitmap by index.

toCSS

toCSS(options: CSSOptions): string

Returns one CSS rule. The default tile kind repeats one matrix tile; the image kind embeds a whole dither.

PropTypeDefault
level*

Tile kind. Share of cells inked, 0 to 1.

Type: numberDefault –
matrix

Tile kind. The threshold pattern.

Type: DitherMatrixDefault "bayer4"
cell

Tile kind. Cell size in CSS pixels.

Type: numberDefault 3
ink

Tile kind. Use a literal colour in background mode; any colour in mask mode.

Type: DitherColorDefault "#3b82f6"
paper

Tile kind, background mode. The colour behind the ink.

Type: DitherColorDefault "transparent"
mode

Tile kind. Bake the ink into the tile, or mask the element.

Type: "background" | "mask"Default "background"
kind

"image" embeds a whole dither as a covering background.

Type: "tile" | "image"Default "tile"
image*

Image kind. The dither, one pixel per cell.

Type: PixelBufferDefault –
format

Image kind. PNG needs a browser; SVG is a pure string.

Type: "png" | "svg"Default "png"
selector

The rule's selector.

Type: stringDefault ".dither"

download

download(options: {
  data: Blob | string;
  filename: string;
  type?: string;
}): void

Saves a Blob or a string through the browser's download flow. Strings are saved with type, default text/plain. svgDataUrl(svg) is exported too: it wraps an SVG string in a CSS url().

Notes

  • PNG and SVG exports match pixel for pixel at their own size. Shown smaller than one pixel per cell, an SVG drops cells in stripes, as any cell-based vector does; use PNG for thumbnails.
  • PNG exports are capped at 16,384 pixels on the longest side, the most every current browser can draw. A larger scale is reduced to fit.
  • The functions never change their input.