eink_dither 2.2.1
eink_dither: ^2.2.1 copied to clipboard
Apply dithering algorithms to quantize full-color images for limited-color displays like E-Ink, Thermal Printers, and Dot-Matrix Screens.
📟 EInk Dither #
A Dart/Flutter package for applying dithering algorithms to images destined for E-Ink (electronic ink) displays. It quantizes a full-color image to the E-Ink palette while minimizing banding and contour artifacts. Typical use cases include E-Ink Displays, Thermal Printers, and LED / Dot-Matrix Screens.
Language: English | 中文
✨ Features #
- 13 dithering kernels — error-diffusion (Floyd–Steinberg, Stucki, Atkinson, Jarvis–Judice–Ninke, Burkes, False Floyd–Steinberg (Heckbert), Sierra-3, Two-Row Sierra, Sierra Lite (Sierra-2-4-A)), ordered (Bayer 2×2/4×4/8×8, Blue Noise), and none (no dithering).
- 4 scan orders for error-diffusion kernels (raster, serpentine, zigzag, Hilbert space-filling curve).
- 8 E-Ink palettes — from pure black/white up to 7-color (Gallery 7) and 16-level grayscale (Carta 16).
- Universal quantizer
EInkPaletteQuantizerthat maps any color to the nearest E-Ink color in a palette using Euclidean RGB distance. - A configurable
EInkImageProcessorwith both synchronous (process) and isolate-based asynchronous (processIsolated) processing.
📦 Installation #
Install via pub.dev → pub.dev/packages/eink_dither/install
🚀 Quick Start #
import 'dart:typed_data';
import 'dart:io';
import 'package:eink_dither/eink_dither.dart';
Future<void> main() async {
final Uint8List bytes = await File('photo.jpg').readAsBytes();
// 1. Configure the processor.
final processor = EInkImageProcessor(
palette: EInkPalette.spectra6,
ditherKernel: DitherKernel.floydSteinberg,
scanOrder: DitherScanOrder.serpentine,
intensity: 1.0,
patternSize: 1,
maxSize: 700,
);
// 2a. Synchronous processing (blocks the current thread).
final image = processor.process(bytes);
// 2b. Asynchronous processing in a compute isolate (recommended for UI).
final image2 = await processor.processIsolated(bytes);
// 3. Encode the result (e.g. to PNG).
if (image2 != null) {
final png = img.encodePng(image2); // `image` package
await File('out.png').writeAsBytes(png);
}
}
📚 API Reference #
🌈 EInkColor #
Enum of the ink colors an E-Ink display can render. Includes the CMY/RGB primaries, an
orange, and a 16-step gray ramp (gray1 … gray14, where step i renders as i * 17
in sRGB).
🎨 EInkPalette #
Enum of preset palettes:
| Palette | Inks | Colors |
|---|---|---|
bw |
2 | Black, White |
spectra3Red |
3 | Black, White, Red |
spectra3Yellow |
3 | Black, White, Yellow |
spectra4 |
4 | Black, White, Red, Yellow |
spectra3100Plus |
5 | Black, White, Red, Yellow, Orange |
spectra6 |
6 | Black, White, Red, Green, Blue, Yellow |
gallery7 |
7 | Black, White, Red, Yellow, Blue, Green, Orange |
carta16 |
16 | Black + 14 grays (i*17) + White |
🔢 EInkPaletteQuantizer #
A Quantizer (from the image package) that maps each pixel to the nearest color of a
palette by Euclidean RGB distance. Construct it directly:
final quantizer = EInkPaletteQuantizer([EInkColor.black, EInkColor.white]);
// or from a preset type:
final q2 = EInkPaletteQuantizer.of(EInkPalette.spectra6);
⚙️ EInkImageProcessor #
process runs on the calling thread; processIsolated runs the
same work inside a compute isolate so the UI never blocks.
| Property | Type | Default | Description |
|---|---|---|---|
palette |
EInkPalette |
EInkPalette.spectra6 |
Target ink palette. |
ditherKernel |
DitherKernel |
DitherKernel.floydSteinberg |
Dithering algorithm. |
scanOrder |
DitherScanOrder |
DitherScanOrder.zigzag |
Pixel-visit order (error-diffusion only). |
intensity |
double |
1.0 |
Dither intensity for ordered kernels; ignored by error-diffusion. |
patternSize |
int |
1 |
Scales ordered-dither cells or error-diffusion blocks (larger = coarser). |
maxSize |
int |
800 |
Longest edge is capped to this (proportional resize). |
img.Image? process(Uint8List bytes);
Future<img.Image?> processIsolated(Uint8List bytes);
🎛️ DitherKernel #
The dithering algorithm. Error-diffusion kernels (none aside) propagate quantization
error to neighbours; ordered kernels (bayer2x2, bayer4x4, bayer8x8, blueNoise)
use a fixed threshold matrix and are position-independent.
enum DitherKernel {
none,
falseFloydSteinberg,
floydSteinberg,
stucki,
atkinson,
jarvisJudiceNinke,
burkes,
sierra3,
sierra2,
sierraLite,
bayer2x2,
bayer4x4,
bayer8x8,
blueNoise,
}
The Sierra family (Frankie Sierra) trades quality for speed as the kernel shrinks:
| Kernel | Also known as | Neighbours | Divisor | Notes |
|---|---|---|---|---|
sierra3 |
Sierra, Sierra-3 | 10 | 32 | Three-row kernel, close to Jarvis quality but noticeably faster. |
sierra2 |
Two-Row Sierra | 7 | 16 | Two-row kernel, a good quality/speed compromise. |
sierraLite |
Sierra-2-4-A | 3 | 4 | Smallest variant, fastest; slightly grainier than Floyd–Steinberg. |
🔀 DitherScanOrder #
The order in which pixels are visited by the error-diffusion kernels. It has no effect on the ordered (Bayer / blue-noise) kernels.
| Scan Order | Description |
|---|---|
raster |
Standard raster scan: every row traversed left to right, top to bottom. |
serpentine |
Boustrophedon (snake) scan: horizontal direction reverses every other row, reducing directional artifacts. |
zigzag |
Diagonal zigzag (JPEG-style) scan: visits pixels along anti-diagonals x + y == d, alternating each diagonal's direction, softening the horizontal worm patterns of raster scanning. |
hilbert |
Hilbert space-filling curve: consecutive pixels are adjacent on the grid, maximizing spatial locality; best reduction of directional artifacts among deterministic orders, approximating random-walk diffusion without losing determinism. |

🖼️ Algorithm Examples #
All previews below were generated with EInkPalette.spectra6 at maxSize: 700.
🟦 Ordered dithering #
These kernels ignore scanOrder. intensity (threshold intensity) and patternSize
(threshold-cell scale) apply to them only.
none (no dithering) |
bayer2x2 |
bayer4x4 |
bayer8x8 |
blueNoise |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
🌊 Error-diffusion dithering #
The combination kernel × scanOrder produces a distinct texture.
| Kernel ↓ / Scan → | Raster | Serpentine | Zigzag | Hilbert |
|---|---|---|---|---|
| Floyd–Steinberg | ![]() |
![]() |
![]() |
![]() |
| False Floyd–Steinberg (Heckbert) | ![]() |
![]() |
![]() |
![]() |
| Stucki | ![]() |
![]() |
![]() |
![]() |
| Atkinson | ![]() |
![]() |
![]() |
![]() |
| Jarvis–Judice–Ninke | ![]() |
![]() |
![]() |
![]() |
| Burkes | ![]() |
![]() |
![]() |
![]() |
| Sierra (Sierra-3) | ![]() |
![]() |
![]() |
![]() |
| Two-Row Sierra (Sierra-2) | ![]() |
![]() |
![]() |
![]() |
| Sierra Lite (Sierra-2-4-A) | ![]() |
![]() |
![]() |
![]() |
🔧 Low-level API #
If you need finer control, use ditherImage (or the ordered-only helpers
ditherImageBayer / ditherImageBlueNoise) directly with any Quantizer:
import 'package:image/image.dart' as img;
import 'package:eink_dither/eink_dither.dart';
final decoded = img.decodeImage(bytes)!;
final quantizer = EInkPaletteQuantizer.of(EInkPalette.spectra6);
final out = ditherImage(
decoded,
quantizer: quantizer,
kernel: DitherKernel.floydSteinberg,
scanOrder: DitherScanOrder.hilbert,
patternSize: 1,
);
ℹ️ Additional information #
- Repository: github.com/runoob-coder/eink_dither
- Issue tracker: github.com/runoob-coder/eink_dither/issues
- Example app: The
example/directory contains a Flutter demo that lets you pick an image and tweak palette, kernel, scan order, intensity, and pattern size live. - Contributions: Pull requests and issues are welcome!
💛 Support #
If eink_dither helps you build better UIs, please consider supporting it.
It only takes a few seconds and helps other Flutter developers discover the library.
☕️ Buy Me a Coffee #
🙏 Acknowledgments #
This package stands on the shoulders of the researchers who pioneered digital dithering and halftoning. We gratefully acknowledge their foundational contributions:
- Floyd–Steinberg — Robert W. Floyd & Louis Steinberg (1976), the classic error-diffusion kernel.
- False Floyd–Steinberg (Heckbert) — Paul Heckbert, introduced in his 1982 SIGGRAPH course notes Color Image Quantization for Frame Buffer Display.
- Jarvis–Judice–Ninke — J. F. Jarvis, C. N. Judice & W. H. Ninke (1976), Bell Labs.
- Stucki — Peter Stucki (1981), an optimized refinement of the Jarvis kernel at IBM.
- Burkes — Daniel Burkes, a simplified 7-pixel variant of the Jarvis–Judice–Ninke kernel.
- Atkinson — Bill Atkinson, created for MacPaint / HyperCard on early Macintosh systems.
- Sierra (Sierra-3), Two-Row Sierra (Sierra-2) and Sierra Lite (Sierra-2-4-A) — Frankie Sierra (1989–1990), a family of progressively smaller kernels balancing quality against speed.
- Bayer (ordered dithering) — Bryce E. Bayer (1973), best known for the Bayer color filter array.
- Blue noise / void-and-cluster — Robert A. Ulichney (1987, 1993), Digital Halftoning, who formalized blue-noise dithering and the void-and-cluster mask generation method.









































