• JavaScript 89.1%
  • CSS 6.9%
  • HTML 4%
Find a file
2026-06-22 10:41:34 -04:00
atkinson Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
bayer2 Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
bayer4 Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
bayer8 Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
blue-noise Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
burkes Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
floyd-steinberg Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
halftone Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
jarvis-judice-ninke Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
pattern Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
random Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
scripts Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
sierra Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
stucki Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
threshold Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
.gitignore Initial commit 2026-06-21 02:27:55 +00:00
index.css dither actual size 2026-06-22 10:41:34 -04:00
index.html dither actual size 2026-06-22 10:41:34 -04:00
index.js dither actual size 2026-06-22 10:41:34 -04:00
library.js comma 2026-06-20 22:39:39 -04:00
LICENSE.md setup package for formatting 2026-06-20 22:38:56 -04:00
package-lock.json setup package for formatting 2026-06-20 22:38:56 -04:00
package.json create demo page 2026-06-21 00:08:54 -04:00
README.md Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00
source-sample.svg Change palette to CMYK overprint 2026-06-21 00:30:38 -04:00

Basic Dither Library

This folder contains a browser-friendly dither plug-in library for general image export.

It is designed to be hosted as static JavaScript files on any public web server, CDN, Git repo raw URL, or local asset pipeline. Nothing in this library requires a build step.

What It Provides

The entry point is library.js. When loaded in a page that already exposes window.TPP.registerDitherLibrary, it registers a catalog of available dithers without loading every dither implementation immediately.

Each dither lives in its own self-contained plugin.js file and is loaded only when needed.

Included dithers:

  • threshold
  • bayer2
  • bayer4
  • bayer8
  • floyd-steinberg
  • jarvis-judice-ninke
  • stucki
  • burkes
  • sierra
  • atkinson
  • halftone
  • blue-noise
  • random
  • pattern

To regenerate the gallery samples after changing any plug-in:

npm run dither

Demo Page

Open index.html in a browser served from this folder to try the library interactively. The page:

  • uploads an image and displays it on a canvas
  • loads library.js and the selected plug-in through window.TPP
  • lets you switch between monochrome and a bundled CMYK overprint palette
  • exposes a threshold slider for mono dithering
  • renders the processed result to a second canvas

The gallery shows the original source image, three monochrome threshold settings, and the same image reduced to a CMYK overprint palette built from paper white plus cyan, magenta, yellow, black, and their layered combinations. That gives you a print-flavored comparison point alongside the mono output and also shows how threshold changes the balance of light and dark detail.

Threshold

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Threshold 25 sample Threshold sample Threshold 75 sample Threshold palette sample

The simplest possible dither: each pixel is pushed to black or white based on a single cutoff. It is fast and predictable, but it tends to produce abrupt contouring because no neighboring pixels influence each other.

Bayer 2x2

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Bayer 2x2 25 sample Bayer 2x2 sample Bayer 2x2 75 sample Bayer 2x2 palette sample

This is ordered dithering with a tiny repeating matrix. It keeps edges crisp and is inexpensive to compute, but the small tile can create a visibly repetitive texture.

Bayer 4x4

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Bayer 4x4 25 sample Bayer 4x4 sample Bayer 4x4 75 sample Bayer 4x4 palette sample

This uses a larger ordered threshold matrix than bayer2, which smooths gradients a bit more while still preserving a deliberate pixel-art feel. It is a good middle ground when you want a stable, structured pattern.

Bayer 8x8

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Bayer 8x8 25 sample Bayer 8x8 sample Bayer 8x8 75 sample Bayer 8x8 palette sample

The larger Bayer matrix reduces coarse banding and gives smoother tonal transitions than the smaller Bayer variants. The tradeoff is that the regular grid becomes part of the look.

Floyd-Steinberg

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Floyd-Steinberg 25 sample Floyd-Steinberg sample Floyd-Steinberg 75 sample Floyd-Steinberg palette sample

This is classic error diffusion: each quantization error is pushed into neighboring pixels. It often gives natural-looking gradients and detail retention, but it can produce worm-like texture in flat regions.

Jarvis, Judice & Ninke

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Jarvis, Judice & Ninke 25 sample Jarvis, Judice & Ninke sample Jarvis, Judice & Ninke 75 sample Jarvis, Judice & Ninke palette sample

This spreads error over a wider neighborhood than Floyd-Steinberg, which usually softens harsh artifacts and produces smoother transitions. It is a solid choice when quality matters more than speed.

Stucki

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Stucki 25 sample Stucki sample Stucki 75 sample Stucki palette sample

Stucki is another wide-kernel error diffusion method with a balanced distribution of error. It tends to preserve detail well while staying a bit cleaner than some harsher diffusion patterns.

Burkes

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Burkes 25 sample Burkes sample Burkes 75 sample Burkes palette sample

Burkes keeps the spirit of larger diffusion kernels while trimming the neighborhood to stay lighter-weight. It can be a nice compromise between performance and smooth tonal reproduction.

Sierra

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Sierra 25 sample Sierra sample Sierra 75 sample Sierra palette sample

Sierra diffuses error across multiple rows like other higher-quality diffusion methods, but with its own weighting pattern. It often produces pleasing gradients with less obvious structure than ordered dithers.

Atkinson

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Atkinson 25 sample Atkinson sample Atkinson 75 sample Atkinson palette sample

Atkinson diffusion intentionally discards some error instead of spreading all of it. That gives it a punchy, airy look that works well for retro graphics and high-contrast imagery.

Halftone

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Halftone 25 sample Halftone sample Halftone 75 sample Halftone palette sample

This uses a clustered-dot threshold pattern inspired by print halftoning. It is useful when you want the image to feel printed or mechanical rather than purely digital.

Blue-noise

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Blue-noise 25 sample Blue-noise sample Blue-noise 75 sample Blue-noise palette sample

This variant uses a generated blue-noise-style mask instead of a regular Bayer grid. The goal is to break up visible repetition and hide patterning in smoother, more organic-looking noise.

Random

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Random 25 sample Random sample Random 75 sample Random palette sample

Random dithering adds noise before thresholding instead of using a fixed matrix or diffusion kernel. It avoids regular tiling, but the grain can feel rough and less controlled than blue-noise or error diffusion.

Pattern

Original Mono 25% Mono 50% Mono 75% CMYK overprint palette
Original sample Pattern 25 sample Pattern sample Pattern 75 sample Pattern palette sample

This is another ordered dither built around a repeating threshold pattern. It is useful when you want a stylized, clearly synthetic texture instead of photorealistic shading.

How To Host It

Host this folder so the files keep their relative paths intact. For example:

https://git.lewismoten.com/tiny-pockets-press/dither-lib/library.js
https://git.lewismoten.com/tiny-pockets-press/dither-lib/threshold/plugin.js
https://git.lewismoten.com/tiny-pockets-press/dither-lib/bayer4/plugin.js

The relative URLs inside library.js are resolved from the URL used to load the library.

How To Use It

Your application should provide:

  • window.TPP.registerDitherLibrary(library)
  • window.TPP.registerDither(dither)

Then load the library:

<script src="https://git.lewismoten.com/tiny-pockets-press/dither-lib/library.js"></script>

When library.js runs, it calls window.TPP.registerDitherLibrary(...) with metadata such as:

  • id
  • name
  • kind
  • url

Your app can then lazy-load the individual plugin.js files later.

Using Outside TPP

The plug-ins are not tied to Tiny Pockets Press image export. The TPP hooks are just a lightweight registration mechanism. Any application can load a plug-in, capture the registered object, and call its handlers directly.

Minimal browser example:

<script>
  let loadedDither = null;
  window.TPP = {
    registerDither(dither) {
      loadedDither = dither;
    },
  };
</script>
<script src="./floyd-steinberg/plugin.js"></script>
<script>
  const canvas = document.querySelector("canvas");
  const context = canvas.getContext("2d");
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);

  loadedDither.applyMono(imageData.data, canvas.width, canvas.height, {
    threshold: 128,
  });

  context.putImageData(imageData, 0, 0);
</script>

For palette dithering, pass a palette like:

const palette = [
  [0, 0, 0],
  [255, 255, 255],
  [214, 116, 62],
  [53, 92, 125],
];

loadedDither.applyPalette(
  imageData.data,
  canvas.width,
  canvas.height,
  palette,
  {},
);

Expected Plug-in Shape

Each plug-in registers itself like this:

window.TPP.registerDither({
  id: "threshold",
  name: "Threshold",
  kind: "both",
  applyMono: function (data, width, height, options) {
    // ...
  },
  applyPalette: function (data, width, height, palette, options) {
    // ...
  },
});

API Reference

Library Metadata

  • id: Stable machine-readable identifier such as floyd-steinberg.
  • name: Human-readable label for UI display.
  • url: Relative path to the plug-in file from library.js.
  • kind: Declares which output modes the plug-in supports.

Current and recommended kind values:

  • mono: The plug-in implements applyMono(...) only.
  • palette: The plug-in implements applyPalette(...) and/or applyPaletteAsync(...) only.
  • both: The plug-in supports monochrome and palette workflows.

All plug-ins in this repository currently use both.

Handler Parameters

  • data: A flat RGBA pixel buffer, usually a Uint8ClampedArray from ImageData.data. The handlers mutate this buffer in place.
  • width: Image width in pixels.
  • height: Image height in pixels.
  • options: Extra algorithm-specific settings. In this library, the common option is threshold, a number from 0 to 255. For bundled palette dithers, 128 is neutral, lower values bias the result lighter, and higher values bias it darker before palette reduction.
  • palette: An array of RGB swatches such as [[0, 0, 0], [255, 255, 255]]. Each swatch is [r, g, b], with channel values from 0 to 255.

Supported handlers:

  • applyMono(data, width, height, options)
  • applyPalette(data, width, height, palette, options)
  • applyPaletteAsync(data, width, height, palette, options)

Handler Behavior

  • applyMono(...): Converts the image to a black-and-white dither in place.
  • applyPalette(...): Reduces the image to the supplied palette in place.
  • applyPaletteAsync(...): Async version of palette reduction for implementations that need to yield, stream, or offload work. If present, it should produce the same kind of in-place result as applyPalette(...).

Practical Notes

  • The alpha channel is passed through in the same buffer. These plug-ins operate on RGB values and generally leave alpha untouched.
  • The library assumes row-major RGBA ordering, which matches browser ImageData.
  • If your app works with another pixel layout, convert into RGBA first, run the dither, then convert back.
  • options is intentionally open-ended so custom plug-ins can add their own knobs without changing the registration format.

Authoring Your Own Plug-in

The easiest way to add a new dither is to copy one of the existing plugin.js files, rename the id and name, and then replace the algorithm inside applyMono(...) and applyPalette(...).

Recommended guidelines:

  • Prefer supporting options.threshold. For mono dithers it acts as the cutoff point; for palette dithers in this library it is also used as a simple brightness bias around 128, which gives host applications a shared control that works in both output modes.
  • Use kind: "mono" if you only support applyMono(...), kind: "palette" if you only support palette reduction, or kind: "both" if you implement both paths.
  • Mutate the provided data buffer in place instead of allocating a brand new output buffer unless your algorithm genuinely requires working storage.
  • Keep palette swatches in [r, g, b] form with 0 to 255 channel values.
  • For long-running palette or diffusion passes, lean toward applyPaletteAsync(...) so the host application can keep the UI responsive while work is in progress.
  • If your async implementation yields control, it should still produce the same final image shape as the synchronous version and should document any progress or chunking behavior expected by the host app.
  • If the algorithm is standard or historical, add a short comment explaining whether the important part is a known kernel, threshold matrix, or sampling pattern. That makes future maintenance easier and helps with provenance.

Minimal template:

window.TPP.registerDither({
  id: "my-dither",
  name: "My Dither",
  kind: "both",
  applyMono: function (data, width, height, options) {
    const threshold = Math.max(
      0,
      Math.min(255, Number((options || {}).threshold) || 128),
    );

    // mutate data in place
  },
  applyPalette: function (data, width, height, palette, options) {
    if (!Array.isArray(palette) || !palette.length) return;

    // mutate data in place
  },
});

Notes

  • The library is intentionally quiet for end users.
  • If registration fails, errors are written to console.error.
  • Because each dither is self-contained, you can also load a single plug-in directly without loading library.js first, as long as window.TPP.registerDither already exists.

Provenance And Licensing

  • The named dithers in this package are standard image-processing methods implemented locally in this repository.
  • The underlying algorithms, kernels, and threshold matrices are historical method definitions, not proprietary to this package.
  • Copyright in this repo applies to this specific code and documentation, not to the underlying dithering methods themselves.
  • If any future plug-in is adapted from another project, keep that source's attribution and license notice with the file.