# Export

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

> For the complete documentation index, see [llms.txt](/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](/.well-known/agent-skills/dithercn/SKILL.md).

`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 [#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 [#installation]

<InstallBlock name="dither-export" />

## Usage [#usage]

```tsx
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 [#examples]

### A fade in pure CSS [#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.

<ComponentPreview src="examples/export-tile.tsx">
  <ExportTile />
</ComponentPreview>

```ts
toCSS({ level: 0.25, cell: 4, ink: "--dither-ink", mode: "mask" });
```

```css
.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 [#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.

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

## API [#api]

### toPNG [#topng]

```ts
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]

```ts
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]

```ts
toCSS(options: CSSOptions): string
```

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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `level` (required) | `number` |  | Tile kind. Share of cells inked, 0 to 1. |
| `matrix` | `DitherMatrix` | `"bayer4"` | Tile kind. The threshold pattern. |
| `cell` | `number` | `3` | Tile kind. Cell size in CSS pixels. |
| `ink` | `DitherColor` | `"#3b82f6"` | Tile kind. Use a literal colour in background mode; any colour in mask mode. |
| `paper` | `DitherColor` | `"transparent"` | Tile kind, background mode. The colour behind the ink. |
| `mode` | `"background" \| "mask"` | `"background"` | Tile kind. Bake the ink into the tile, or mask the element. |
| `kind` | `"tile" \| "image"` | `"tile"` | "image" embeds a whole dither as a covering background. |
| `image` (required) | `PixelBuffer` |  | Image kind. The dither, one pixel per cell. |
| `format` | `"png" \| "svg"` | `"png"` | Image kind. PNG needs a browser; SVG is a pure string. |
| `selector` | `string` | `".dither"` | The rule's selector. |

### download [#download]

```ts
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 [#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.