@ditherkit/core
Pure dithering algorithms and image utilities. No DOM, no framework.
@ditherkit/core is the algorithmic heart of the toolkit. It exports pure
functions that operate on pixel buffers — no DOM, no framework, no
runtime dependencies. Use it directly when you want full control, or
reach for it when you're building on a stack that doesn't have a
dedicated wrapper yet (Svelte, Vue, Cloudflare Workers, Node CLIs).
What's in the box
- Seven dithering algorithms — six error-diffusion algorithms (Floyd-Steinberg, Atkinson, Jarvis-Judice-Ninke, Stucki, Burkes), an ordered dither (Bayer), and a simple binary threshold.
- Built-in palettes —
palettes.bw,palettes.gameboy,palettes.cga,palettes.nes,palettes.pico8. - Color utilities —
findClosestColorand a palette type. - Image adjustments —
grayscale,applyBrightness,applyContrast. - Dimension utilities —
snapDimensionsToPixelSize,deriveDimensions,computeCoverCrop— pure helpers for resolving the W×H ×pixelSizemodel that both wrappers use internally.
Every function mutates a Uint8ClampedArray of RGBA pixels in place.
That's the same shape as ImageData.data in the browser and what
Sharp hands you in Node, so the core slots into both worlds without
conversion.
A typical pipeline
The algorithms assume grayscale input and a black/white (or color) palette. A normal call order looks like this:
import {
applyBrightness,
applyContrast,
grayscale,
floydSteinbergDither,
type Color,
} from '@ditherkit/core'
const palette: Color[] = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
]
// `pixels` is a Uint8ClampedArray of RGBA. Get it from a canvas
// (`ctx.getImageData(...).data`) or from Sharp's raw buffer.
applyBrightness(pixels, 10) // optional, [-100..100]
applyContrast(pixels, 20) // optional, [-100..100]
grayscale(pixels) // optimal for 2-colour palettes; omit for colour palettes
floydSteinbergDither(pixels, width, height, palette)
// `pixels` is now a 1-bit (or N-bit) dithered image in place.Each step is in-place and allocates no new buffers, so the whole pipeline is O(pixels) time and O(1) extra memory per call.
When NOT to use @ditherkit/core directly
If you're in a plain React app, reach for
@ditherkit/react. If you're on Next.js and want
server-rendered and cached output, reach for
@ditherkit/next. Those packages use @ditherkit/core
internally and give you a component that handles the pixel plumbing
and the Web Worker or Sharp glue for you.
Use @ditherkit/core directly when:
- You're targeting a framework without a wrapper (Svelte, Vue, Solid).
- You're in a build script and just need a
.jpg→.jpgtransform. - You're at the edge (Cloudflare Workers) where Sharp isn't available.
- You want to compose the algorithms into a larger pipeline of your own.