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
| Format | Use it for |
|---|---|
| PNG | Photos and diffusion dithers. Pixel-exact at any scale. |
| SVG | Ordered dithers and flat art. Sharp at any size. |
| CSS | Backgrounds in a stylesheet: the whole image, or a tile. |
Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/dither-export.jsonUsage
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;
}
): stringAn 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): stringReturns one CSS rule. The default tile kind repeats one matrix tile; the image kind embeds a whole dither.
| Prop | Type | Default |
|---|---|---|
| level* Tile kind. Share of cells inked, 0 to 1. | Type: number | Default – |
| matrix Tile kind. The threshold pattern. | Type: DitherMatrix | Default "bayer4" |
| cell Tile kind. Cell size in CSS pixels. | Type: number | Default 3 |
| ink Tile kind. Use a literal colour in background mode; any colour in mask mode. | Type: DitherColor | Default "#3b82f6" |
| paper Tile kind, background mode. The colour behind the ink. | Type: DitherColor | Default "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: PixelBuffer | Default – |
| format Image kind. PNG needs a browser; SVG is a pure string. | Type: "png" | "svg" | Default "png" |
| selector The rule's selector. | Type: string | Default ".dither" |
download
download(options: {
data: Blob | string;
filename: string;
type?: string;
}): voidSaves 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
scaleis reduced to fit. - The functions never change their input.