@opendisplay/epaper-dithering 2.2.1 → 4.1.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 +58 -24
- package/dist/index.cjs +167 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -16
- package/dist/index.d.ts +33 -16
- package/dist/index.js +167 -381
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@opendisplay/epaper-dithering)
|
|
4
4
|
|
|
5
|
-
High-quality dithering algorithms for e-paper/e-ink displays,
|
|
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
|
-
- **
|
|
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 (
|
|
15
|
-
- **Zero 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
|
|
@@ -64,11 +65,17 @@ Standard `ColorScheme` values use ideal sRGB colors (e.g. white = 255,255,255).
|
|
|
64
65
|
```typescript
|
|
65
66
|
import { ditherImage, SPECTRA_7_3_6COLOR, BWRY_3_97 } from '@opendisplay/epaper-dithering';
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const dithered = ditherImage(imageBuffer, SPECTRA_7_3_6COLOR, { mode: DitherMode.BURKES });
|
|
69
|
+
|
|
70
|
+
// Opt in when you want automatic tone/gamut compression for photos
|
|
71
|
+
const compressed = ditherImage(imageBuffer, SPECTRA_7_3_6COLOR, {
|
|
72
|
+
mode: DitherMode.BURKES,
|
|
73
|
+
tone: 'auto',
|
|
74
|
+
gamut: 'auto',
|
|
75
|
+
});
|
|
69
76
|
```
|
|
70
77
|
|
|
71
|
-
Available measured palettes: `SPECTRA_7_3_6COLOR`, `BWRY_3_97`, `MONO_4_26`, `BWRY_4_2`, `SOLUM_BWR`, `HANSHOW_BWR`, `HANSHOW_BWY`.
|
|
78
|
+
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
79
|
|
|
73
80
|
### Node.js (with sharp)
|
|
74
81
|
|
|
@@ -84,7 +91,7 @@ const { data, info } = await sharp('photo.jpg')
|
|
|
84
91
|
const dithered = ditherImage(
|
|
85
92
|
{ width: info.width, height: info.height, data: new Uint8ClampedArray(data) },
|
|
86
93
|
ColorScheme.BWR,
|
|
87
|
-
DitherMode.BURKES,
|
|
94
|
+
{ mode: DitherMode.BURKES },
|
|
88
95
|
);
|
|
89
96
|
|
|
90
97
|
const rgbaBuffer = Buffer.alloc(dithered.width * dithered.height * 4);
|
|
@@ -101,14 +108,28 @@ await sharp(rgbaBuffer, { raw: { width: dithered.width, height: dithered.height,
|
|
|
101
108
|
|
|
102
109
|
## API Reference
|
|
103
110
|
|
|
104
|
-
### `ditherImage(image, colorScheme,
|
|
111
|
+
### `ditherImage(image, colorScheme, options?)`
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
ditherImage(image: ImageBuffer, palette: ColorScheme | ColorPalette, options?: DitherOptions): PaletteImageBuffer
|
|
115
|
+
```
|
|
105
116
|
|
|
106
|
-
|
|
|
117
|
+
| `options` field | Type | Default | Description |
|
|
107
118
|
|---|---|---|---|
|
|
108
|
-
| `image` | `ImageBuffer` | — | RGBA input image |
|
|
109
|
-
| `colorScheme` | `ColorScheme \| ColorPalette` | — | Target palette (enum or measured) |
|
|
110
119
|
| `mode` | `DitherMode` | `BURKES` | Dithering algorithm |
|
|
111
120
|
| `serpentine` | `boolean` | `true` | Alternate row direction to reduce artifacts |
|
|
121
|
+
| `exposure` | `number` | `1.0` | Linear-RGB exposure multiplier. `2.0` = +1 stop, `0.5` = −1 stop |
|
|
122
|
+
| `saturation` | `number` | `1.0` | OKLab saturation multiplier. `0.0` = grayscale, `>1` = boost. Hue-preserving |
|
|
123
|
+
| `shadows` | `number` | `0.0` | Shadow lift strength (S-curve lower half). `0.0` = off, `1.0` = strong |
|
|
124
|
+
| `highlights` | `number` | `0.0` | Highlight compression strength (S-curve upper half). `0.0` = off, `1.0` = strong |
|
|
125
|
+
| `tone` | `number \| 'auto' \| 'off'` | `0.0` | Dynamic range compression. `0.0`/`'off'` = disabled; `'auto'` = histogram-based; numeric = fixed strength. Ignored for `ColorScheme` |
|
|
126
|
+
| `gamut` | `number \| 'auto' \| 'off'` | `0.0` | Pre-dither gamut compression. `0.0`/`'off'` = disabled; `'auto'` = activate when image exceeds palette gamut; numeric = fixed. Ignored for `ColorScheme` |
|
|
127
|
+
|
|
128
|
+
Pre-processing pipeline: `exposure → saturation → shadows/highlights → tone → gamut → dither`. Each step is a no-op at its identity value.
|
|
129
|
+
|
|
130
|
+
`DitherMode.NONE` performs direct nearest-color mapping without error diffusion or ordered dithering. Built-in measured palettes carry their canonical firmware `scheme`, so pure display colors map to the corresponding firmware palette index even when measured RGB values are used for matching.
|
|
131
|
+
|
|
132
|
+
For built-in measured palettes, exact canonical display colors are also protected in ordered and error-diffusion modes when pre-processing is off: an image made entirely of display colors is returned as a direct palette-index map, and exact display-color pixels inside a mixed image keep their canonical index instead of being rematched to the measured RGB palette. Pre-processing runs before that exact-pixel check, so explicit `tone: 'auto'`, `gamut: 'auto'`, or other adjustments may intentionally alter those pixels first.
|
|
112
133
|
|
|
113
134
|
Returns `PaletteImageBuffer`.
|
|
114
135
|
|
|
@@ -160,26 +181,39 @@ interface PaletteImageBuffer {
|
|
|
160
181
|
interface ColorPalette {
|
|
161
182
|
readonly colors: Record<string, RGB>;
|
|
162
183
|
readonly accent: string;
|
|
184
|
+
readonly scheme?: number;
|
|
163
185
|
}
|
|
164
186
|
```
|
|
165
187
|
|
|
166
|
-
##
|
|
188
|
+
## Preview Tool
|
|
167
189
|
|
|
168
|
-
|
|
190
|
+
An interactive browser tool for comparing dithering modes and palettes:
|
|
169
191
|
|
|
192
|
+
**Hosted** (always latest release): https://opendisplay.github.io/epaper-dithering/
|
|
193
|
+
|
|
194
|
+
**Local** (against your working branch):
|
|
170
195
|
```bash
|
|
171
196
|
cd packages/javascript
|
|
172
197
|
bun run dev
|
|
173
|
-
#
|
|
198
|
+
# → http://localhost:3456/dev.html
|
|
174
199
|
```
|
|
175
200
|
|
|
176
|
-
Features:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
201
|
+
Features: drag & drop or paste from clipboard, live re-render on every setting change, timing display, palette swatch preview.
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
bun install
|
|
207
|
+
|
|
208
|
+
# When Rust source changes, rebuild the WASM (from repo root):
|
|
209
|
+
wasm-pack build packages/rust/wasm --target bundler --out-dir ../../javascript/src/wasm-core
|
|
210
|
+
|
|
211
|
+
bun run test # vitest
|
|
212
|
+
bun run build # tsup → dist/
|
|
213
|
+
bun run type-check
|
|
214
|
+
```
|
|
181
215
|
|
|
182
216
|
## Related Projects
|
|
183
217
|
|
|
184
|
-
- **Python**: [`epaper-dithering`](https://pypi.org/project/epaper-dithering/) — Python
|
|
218
|
+
- **Python**: [`epaper-dithering`](https://pypi.org/project/epaper-dithering/) — Python package, shares the same Rust core
|
|
185
219
|
- **OpenDisplay**: [`py-opendisplay`](https://github.com/OpenDisplay-org/py-opendisplay) — Python library for OpenDisplay BLE devices
|