Palettes
Built-in palette presets — B&W, Game Boy, CGA, NES, PICO-8 — and how to build your own.
A dithering algorithm is only as good as the palette you give it. The
palette decides which colours the algorithm can choose from; the
algorithm decides how to arrange them. @ditherkit/core ships five
named presets covering the most common retro and print targets, plus
the Color type for hand-rolling your own.
import { palettes } from '@ditherkit/core'
palettes.bw // [black, white]
palettes.gameboy // the four greens of the DMG LCD
palettes.cga // CGA mode 4, high intensity
palettes.nes // a curated NES subset
palettes.pico8 // the PICO-8 fantasy console's 16 coloursEvery preset is exported as a Color[], ready to drop into any of the
dithering algorithms:
import { grayscale, floydSteinbergDither, palettes } from '@ditherkit/core'
grayscale(pixels)
floydSteinbergDither(pixels, width, height, palettes.gameboy)There's no runtime cost if you don't import them — the module is tree-shakeable.
The presets
Black & white

palettes.bwThe classic 1-bit target. Two entries — the default you probably want for "dithered photograph" output.
The two-entry palette every 1-bit image uses. Pair it with any algorithm for the classic "dithered photograph" look. This is the palette the algorithm docs use in their examples.
Game Boy

palettes.gameboyFour shades of the original Game Boy DMG LCD, ordered dark → light.
Four greens, exactly as they looked on the original Game Boy LCD. Great for photographs, character portraits, and anything that wants that specific handheld-console vibe. Four entries is enough to carry a photograph recognisably.
CGA

palettes.cgaCGA mode 4, palette 1, high intensity — 1980s PC games in a bottle.
The eye-searing blue/magenta/cyan/white from CGA mode 4, palette 1, high intensity. Most 1980s PC games used it whether they wanted to or not. Four entries.
NES (curated)

palettes.nesA perceptually-spaced curated subset of the full 54-colour NES hardware palette.
The full NES hardware palette has 54 entries — more than you want as a dither target, since most of them are imperceptibly close together. This preset is a curated subset picked for perceptual spacing: enough colours to carry a photograph, few enough that the dither pattern stays legible.
PICO-8

palettes.pico8The PICO-8 fantasy console's 16-colour palette — bright, balanced, friendly to photos.
The sixteen colours from the PICO-8 fantasy console. Deliberately chosen to be friendly — balanced saturation, well-spaced hues — which makes it a surprisingly good general-purpose photo dither target.
Building your own
A palette is just a Color[]. Use hex codes, a picker, or parse
them from a file — the algorithm doesn't care where they came from.
import { type Color } from '@ditherkit/core'
const sepia: Color[] = [
{ r: 34, g: 25, b: 21 },
{ r: 92, g: 68, b: 55 },
{ r: 170, g: 132, b: 98 },
{ r: 250, g: 230, b: 200 },
]A few rules of thumb for hand-built palettes:
- Order doesn't matter for the error-diffusion algorithms (they always pick the nearest). It does matter for Threshold, which uses the first two entries only.
- Perceptual spacing beats dense clusters. Three well-spaced colours beat ten colours bunched in the midtones — dithering can fake intermediate values, but it can't invent tonal range that isn't there.
- Watch the extremes. Most photographs need something close to pure black and pure white, even in a colour palette. Without them shadows and highlights flatten.
Using palettes with the components
Both <DitheredImage /> and <DitheredImageSSR /> accept the
palette as a string of comma-separated hex values, not a
Color[], because the string form is URL-safe for the ISR cache
key. A tiny helper converts:
import { palettes, type Color } from '@ditherkit/core'
function paletteToString(colors: Color[]): string {
return colors
.map(({ r, g, b }) =>
`#${r.toString(16).padStart(2, '0')}${g
.toString(16)
.padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
)
.join(',')
}
// in a component:
<DitheredImageSSR
src={image}
alt="…"
palette={paletteToString(palettes.gameboy)}
/>If you're calling the core algorithms directly (no component), you
can pass the Color[] in as-is — no string conversion needed.