@opendisplay/epaper-dithering 2.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,18 +2,19 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@opendisplay/epaper-dithering?style=flat-square)](https://www.npmjs.com/package/@opendisplay/epaper-dithering)
4
4
 
5
- High-quality dithering algorithms for e-paper/e-ink displays, implemented in TypeScript. Works in both browser and Node.js environments.
5
+ High-quality dithering algorithms for e-paper/e-ink displays, powered by a Rust/WASM core. Works in both browser and Node.js environments.
6
6
 
7
7
  ## Features
8
8
 
9
+ - **Rust/WASM Core**: Compiled Rust logic bundled inline — no async init, no external files, works everywhere
9
10
  - **9 Dithering Algorithms**: From fast ordered dithering to high-quality error diffusion
10
11
  - **8 Color Schemes**: MONO, BWR, BWY, BWRY, BWGBRY (Spectra 6), GRAYSCALE\_4/8/16
11
- - **Measured Palettes**: Use real display-calibrated colors for accurate dithering (SPECTRA\_7\_3\_6COLOR, BWRY\_3\_97, and more)
12
- - **LCH Color Matching**: Perceptual LAB color space with hue-weighted distance hue errors can't be recovered by error diffusion, so they're prioritized
12
+ - **Measured Palettes**: Use real display-calibrated colors for accurate dithering (SPECTRA\_7\_3\_6COLOR\_V2, BWRY\_3\_97, and more)
13
+ - **OKLab Color Matching**: Weighted Cartesian OKLab preserves hue without the achromatic-attractor bug that plagues LCH-weighted approaches
14
+ - **Pre-dither Adjustments**: Per-image exposure, saturation, shadows, highlights, dynamic-range compression, and gamut compression — all orthogonal knobs
13
15
  - **Serpentine Scanning**: Alternates row direction to eliminate directional artifacts
14
- - **Universal**: Works in browser (Canvas API) and Node.js (with sharp/jimp)
15
- - **Zero Dependencies**: Pure TypeScript, no image library dependencies
16
- - **Fast**: 256-entry sRGB LUT, pre-computed palette LAB arrays, typed array pixel buffers
16
+ - **Universal**: Works in browser (Canvas API) and Node.js (≥18)
17
+ - **Zero Dependencies**: WASM binary bundled inline, no image library required
17
18
 
18
19
  ## Installation
19
20
 
@@ -44,7 +45,7 @@ const imageData = ctx.getImageData(0, 0, img.width, img.height);
44
45
  const dithered = ditherImage(
45
46
  { width: imageData.width, height: imageData.height, data: imageData.data },
46
47
  ColorScheme.BWR,
47
- DitherMode.FLOYD_STEINBERG,
48
+ { mode: DitherMode.FLOYD_STEINBERG },
48
49
  );
49
50
 
50
51
  // Render result
@@ -65,10 +66,10 @@ Standard `ColorScheme` values use ideal sRGB colors (e.g. white = 255,255,255).
65
66
  import { ditherImage, SPECTRA_7_3_6COLOR, BWRY_3_97 } from '@opendisplay/epaper-dithering';
66
67
 
67
68
  // Automatically applies tone compression to fit the display's actual dynamic range
68
- const dithered = ditherImage(imageBuffer, SPECTRA_7_3_6COLOR, DitherMode.BURKES);
69
+ const dithered = ditherImage(imageBuffer, SPECTRA_7_3_6COLOR, { mode: DitherMode.BURKES });
69
70
  ```
70
71
 
71
- Available measured palettes: `SPECTRA_7_3_6COLOR`, `BWRY_3_97`, `MONO_4_26`, `BWRY_4_2`, `SOLUM_BWR`, `HANSHOW_BWR`, `HANSHOW_BWY`.
72
+ Available measured palettes: `SPECTRA_7_3_6COLOR_V2`, `SPECTRA_7_3_6COLOR`, `BWRY_3_97`, `MONO_4_26`, `BWRY_4_2`, `SOLUM_BWR`, `HANSHOW_BWR`, `HANSHOW_BWY`.
72
73
 
73
74
  ### Node.js (with sharp)
74
75
 
@@ -84,7 +85,7 @@ const { data, info } = await sharp('photo.jpg')
84
85
  const dithered = ditherImage(
85
86
  { width: info.width, height: info.height, data: new Uint8ClampedArray(data) },
86
87
  ColorScheme.BWR,
87
- DitherMode.BURKES,
88
+ { mode: DitherMode.BURKES },
88
89
  );
89
90
 
90
91
  const rgbaBuffer = Buffer.alloc(dithered.width * dithered.height * 4);
@@ -101,14 +102,24 @@ await sharp(rgbaBuffer, { raw: { width: dithered.width, height: dithered.height,
101
102
 
102
103
  ## API Reference
103
104
 
104
- ### `ditherImage(image, colorScheme, mode?, serpentine?)`
105
+ ### `ditherImage(image, colorScheme, options?)`
105
106
 
106
- | Parameter | Type | Default | Description |
107
+ ```typescript
108
+ ditherImage(image: ImageBuffer, palette: ColorScheme | ColorPalette, options?: DitherOptions): PaletteImageBuffer
109
+ ```
110
+
111
+ | `options` field | Type | Default | Description |
107
112
  |---|---|---|---|
108
- | `image` | `ImageBuffer` | — | RGBA input image |
109
- | `colorScheme` | `ColorScheme \| ColorPalette` | — | Target palette (enum or measured) |
110
113
  | `mode` | `DitherMode` | `BURKES` | Dithering algorithm |
111
114
  | `serpentine` | `boolean` | `true` | Alternate row direction to reduce artifacts |
115
+ | `exposure` | `number` | `1.0` | Linear-RGB exposure multiplier. `2.0` = +1 stop, `0.5` = −1 stop |
116
+ | `saturation` | `number` | `1.0` | OKLab saturation multiplier. `0.0` = grayscale, `>1` = boost. Hue-preserving |
117
+ | `shadows` | `number` | `0.0` | Shadow lift strength (S-curve lower half). `0.0` = off, `1.0` = strong |
118
+ | `highlights` | `number` | `0.0` | Highlight compression strength (S-curve upper half). `0.0` = off, `1.0` = strong |
119
+ | `tone` | `number \| 'auto' \| 'off'` | `'auto'` | Dynamic range compression. `'auto'` = histogram-based; numeric = fixed strength. Ignored for `ColorScheme` |
120
+ | `gamut` | `number \| 'auto' \| 'off'` | `'auto'` | Pre-dither gamut compression. `'auto'` = activate when image exceeds palette gamut; numeric = fixed. Ignored for `ColorScheme` |
121
+
122
+ Pre-processing pipeline: `exposure → saturation → shadows/highlights → tone → gamut → dither`. Each step is a no-op at its identity value.
112
123
 
113
124
  Returns `PaletteImageBuffer`.
114
125
 
@@ -163,23 +174,35 @@ interface ColorPalette {
163
174
  }
164
175
  ```
165
176
 
166
- ## Local Development / Preview
177
+ ## Preview Tool
178
+
179
+ An interactive browser tool for comparing dithering modes and palettes:
167
180
 
168
- A browser-based preview tool is included at [`dev.html`](./dev.html).
181
+ **Hosted** (always latest release): https://opendisplay.github.io/epaper-dithering/
169
182
 
183
+ **Local** (against your working branch):
170
184
  ```bash
171
185
  cd packages/javascript
172
186
  bun run dev
173
- # opens http://localhost:3456/dev.html
187
+ # http://localhost:3456/dev.html
174
188
  ```
175
189
 
176
- Features:
177
- - drag & drop or paste from clipboard
178
- - live re-render on setting change
179
- - timing display
180
- - palette swatch preview.
190
+ Features: drag & drop or paste from clipboard, live re-render on every setting change, timing display, palette swatch preview.
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ bun install
196
+
197
+ # When Rust source changes, rebuild the WASM (from repo root):
198
+ wasm-pack build packages/rust/wasm --target bundler --out-dir ../../javascript/src/wasm-core
199
+
200
+ bun run test # vitest
201
+ bun run build # tsup → dist/
202
+ bun run type-check
203
+ ```
181
204
 
182
205
  ## Related Projects
183
206
 
184
- - **Python**: [`epaper-dithering`](https://pypi.org/project/epaper-dithering/) — Python implementation (feature superset)
207
+ - **Python**: [`epaper-dithering`](https://pypi.org/project/epaper-dithering/) — Python package, shares the same Rust core
185
208
  - **OpenDisplay**: [`py-opendisplay`](https://github.com/OpenDisplay-org/py-opendisplay) — Python library for OpenDisplay BLE devices