@weasel-js/dither 0.0.0-stage → 1.16.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 orochi235
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,51 @@
1
- # Temporary Holding Version
1
+ # @weasel-js/dither
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Turns pixels into 1-bit ink: on the CPU for the pixels an export produces,
4
+ and as a renderer effect that shows the dither live on the canvas.
5
+
6
+ Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
7
+ scene-graph canvas kit for React. See the
8
+ [API reference](https://orochi235.github.io/weasel/#pkg/dither).
9
+
10
+ ```sh
11
+ npm install @weasel-js/dither
12
+ ```
13
+
14
+ ## Export
15
+
16
+ `ditherPixels(image, { method, threshold, background, protect })` takes
17
+ straight RGBA (what `renderSceneToPixels` returns) and answers one byte per
18
+ pixel, 1 for ink. `method` is `'threshold'`, `'bayer'`, `'floyd-steinberg'`
19
+ or `'atkinson'`. Transparency composites over `background` (white by default)
20
+ before gray is taken. `maskToRgba` paints the result in two colors.
21
+
22
+ ## Live
23
+
24
+ `dither({ method, grid, ink, blank })` is an `Effect[]` for a group or a
25
+ layer group: threshold or Bayer, since error diffusion cannot run in a
26
+ shader. On the screen grid each device pixel is a dot. On an output grid, a
27
+ printer's or a 1-bit panel's, each dot is one dot of the target, so the
28
+ canvas shows what the export will print at any zoom:
29
+
30
+ ```ts
31
+ const grid = { pitch: 72 / 180, origin: { x: 0, y: 0 } }; // 180 dpi, in points
32
+ const layerGroup = {
33
+ id: 'print',
34
+ layers: ['scene'],
35
+ effects: (view, dims) => dither({ method: 'bayer', grid: gridOnScreen(grid, view, dims.dpr) }),
36
+ };
37
+ ```
38
+
39
+ The live result matches the export exactly when a dot is a whole number of
40
+ device pixels, up to 4, and the grid's origin lands on a whole pixel. A dot
41
+ smaller than a device pixel cannot be shown, so each pixel quantizes alone.
42
+
43
+ ## Keeping things out of the dither
44
+
45
+ Put the dither on the groups that have tone (a photo) and end the page with a
46
+ `threshold` pass, and a barcode or a line of type stays crisp with nothing to
47
+ protect. Where tone must be dithered over such artwork, protect it:
48
+ `protectRects` maps items to rects on the grid, `protectMask` turns rects
49
+ into the mask `ditherPixels` takes, and `protectTexture` uploads that mask
50
+ for `dither`, which needs an output grid to read it. Inside it pixels quantize
51
+ at plain threshold, and error diffusion neither enters nor leaves it.
@@ -0,0 +1,102 @@
1
+ import { Rect, OutputGrid, View } from '@weasel-js/geom';
2
+ import { TextureHandle, Effect } from '@weasel-js/renderer';
3
+
4
+ /** How gray becomes ink: a plain cutoff, an ordered 4×4 Bayer matrix, or one
5
+ * of two error-diffusion kernels (CPU only). */
6
+ type DitherMethod = 'threshold' | 'bayer' | 'floyd-steinberg' | 'atkinson';
7
+ /** 0–255 per channel. */
8
+ type Rgb = readonly [number, number, number];
9
+ /** 0–255 per channel, straight alpha. */
10
+ type Rgba = readonly [number, number, number, number];
11
+ /** Top-down, straight (non-premultiplied) RGBA, 4 bytes per pixel: what
12
+ * `renderSceneToPixels` returns, and what `ImageData` holds. */
13
+ interface RgbaImage {
14
+ width: number;
15
+ height: number;
16
+ data: Uint8ClampedArray | Uint8Array;
17
+ }
18
+ /** How `ditherPixels` quantizes an image. */
19
+ interface DitherOptions {
20
+ /** Default `'threshold'`. */
21
+ method?: DitherMethod;
22
+ /** Gray (0–255) below it is ink. Default 128. */
23
+ threshold?: number;
24
+ /** What transparency composites over before gray is taken. Default white. */
25
+ background?: Rgb;
26
+ /** One byte per pixel, nonzero = protected: quantized at plain `threshold`,
27
+ * and error diffusion neither enters nor leaves it. */
28
+ protect?: Uint8Array;
29
+ }
30
+
31
+ /** `image` quantized to ink: one byte per pixel, 1 = ink. */
32
+ declare function ditherPixels(image: RgbaImage, options?: DitherOptions): Uint8Array;
33
+
34
+ /** An ink mask as straight RGBA: `ink` where it is set, `blank` elsewhere. */
35
+ declare function maskToRgba(mask: Uint8Array, width: number, height: number, colors?: {
36
+ ink?: Rgba;
37
+ blank?: Rgba;
38
+ }): Uint8ClampedArray<ArrayBuffer>;
39
+
40
+ /** A per-pixel protect mask (1 = protected) from rects in pixel space. Rects
41
+ * round outward to whole pixels and clip to the image. */
42
+ declare function protectMask(rects: Iterable<Rect>, width: number, height: number): Uint8Array;
43
+
44
+ /**
45
+ * Rects to protect, in dot space (one unit per dot, from the grid's origin),
46
+ * the pixel space an export rendered at the grid's resolution has.
47
+ * `protectedRect` answers an item's rect in document units, which may be
48
+ * larger than the item (a barcode's quiet zone), or false or null to leave
49
+ * it to the dither.
50
+ */
51
+ declare function protectRects<T>(items: Iterable<T>, protectedRect: (item: T) => Rect | false | null, grid: OutputGrid): Rect[];
52
+
53
+ /** A dot grid in device pixels of the drawing buffer, from its top left. */
54
+ interface DeviceGrid {
55
+ pitch: readonly [number, number];
56
+ origin: readonly [number, number];
57
+ }
58
+ /** `grid` where `view` at `dpr` puts it. In a layer group's effect thunk,
59
+ * `dpr` is `dims.dpr`; in a node's draw, `ctx.pixelScale / view.scale.x`.
60
+ * A flipped axis gives a negative pitch, which keeps dots numbered in
61
+ * document order: dot 0 still sits at the grid's document origin. */
62
+ declare function gridOnScreen(grid: OutputGrid, view: View, dpr: number): DeviceGrid;
63
+
64
+ /** How the `dither` effect quantizes what its group drew. */
65
+ interface GpuDitherOptions {
66
+ /** Default `'threshold'`. Error diffusion runs on the CPU only. */
67
+ method?: 'threshold' | 'bayer';
68
+ /** Gray (0–255) below it is ink. Default 128. */
69
+ threshold?: number;
70
+ /** What transparency composites over before gray is taken. Default white. */
71
+ background?: Rgb;
72
+ /** Default opaque black. */
73
+ ink?: Rgba;
74
+ /** Default transparent. */
75
+ blank?: Rgba;
76
+ /** `'screen'` (one dot per device pixel, the default), or an output grid
77
+ * placed with `gridOnScreen`. */
78
+ grid?: 'screen' | DeviceGrid;
79
+ /** A protect mask in dot space, from `protectTexture`. Needs an output grid. */
80
+ protect?: {
81
+ texture: TextureHandle;
82
+ width: number;
83
+ height: number;
84
+ };
85
+ }
86
+ /** Threshold or Bayer dithering as one effect pass. */
87
+ declare function dither(options?: GpuDitherOptions): Effect[];
88
+
89
+ /** A protect mask uploaded for `dither`, one texel per dot, with its size. */
90
+ interface ProtectTexture {
91
+ texture: TextureHandle;
92
+ width: number;
93
+ height: number;
94
+ /** Unregister the texture and free its bitmap. */
95
+ dispose(): void;
96
+ }
97
+ /** A protect mask (from `protectMask`, in dot space) as a texture `dither`
98
+ * can read. The caller disposes it when the mask changes or the view goes. */
99
+ declare function protectTexture(mask: Uint8Array, width: number, height: number): Promise<ProtectTexture>;
100
+
101
+ export { dither, ditherPixels, gridOnScreen, maskToRgba, protectMask, protectRects, protectTexture };
102
+ export type { DeviceGrid, DitherMethod, DitherOptions, GpuDitherOptions, ProtectTexture, Rgb, Rgba, RgbaImage };
package/dist/index.js ADDED
@@ -0,0 +1,279 @@
1
+ import { registerEffect, registerTexture, unregisterTexture } from '@weasel-js/renderer';
2
+
3
+ // src/diffuse.ts
4
+ var FLOYD_STEINBERG = [[1, 0, 7], [-1, 1, 3], [0, 1, 5], [1, 1, 1]];
5
+ var ATKINSON = [[1, 0, 1], [2, 0, 1], [-1, 1, 1], [0, 1, 1], [1, 1, 1], [0, 2, 1]];
6
+ function diffuse(gray, width, height, threshold, kernel, denominator, protect) {
7
+ const mask = new Uint8Array(width * height);
8
+ for (let y = 0; y < height; y++) {
9
+ for (let x = 0; x < width; x++) {
10
+ const i = y * width + x;
11
+ const old = gray[i];
12
+ const ink = old < threshold;
13
+ mask[i] = ink ? 1 : 0;
14
+ if (protect?.[i]) continue;
15
+ const err = old - (ink ? 0 : 255);
16
+ for (const [dx, dy, w] of kernel) {
17
+ const nx = x + dx;
18
+ const ny = y + dy;
19
+ if (nx < 0 || nx >= width || ny >= height) continue;
20
+ const ni = ny * width + nx;
21
+ if (protect?.[ni]) continue;
22
+ gray[ni] = gray[ni] + err * w / denominator;
23
+ }
24
+ }
25
+ }
26
+ return mask;
27
+ }
28
+
29
+ // src/shared.ts
30
+ var BAYER_4X4 = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];
31
+ var LUMA = [0.299, 0.587, 0.114];
32
+ var DEFAULT_THRESHOLD = 128;
33
+ var WHITE = [255, 255, 255];
34
+ function bayerCutoff(x, y, threshold) {
35
+ const m = BAYER_4X4[(y % 4 + 4) % 4 * 4 + (x % 4 + 4) % 4];
36
+ return (m + 0.5) * 16 + (threshold - DEFAULT_THRESHOLD);
37
+ }
38
+
39
+ // src/gray.ts
40
+ function toGray(image, background = WHITE) {
41
+ const n = image.width * image.height;
42
+ const gray = new Float32Array(n);
43
+ const d = image.data;
44
+ for (let i = 0; i < n; i++) {
45
+ const a = d[i * 4 + 3] / 255;
46
+ const r = d[i * 4] * a + background[0] * (1 - a);
47
+ const g = d[i * 4 + 1] * a + background[1] * (1 - a);
48
+ const b = d[i * 4 + 2] * a + background[2] * (1 - a);
49
+ gray[i] = LUMA[0] * r + LUMA[1] * g + LUMA[2] * b;
50
+ }
51
+ return gray;
52
+ }
53
+
54
+ // src/ditherPixels.ts
55
+ function ditherPixels(image, options = {}) {
56
+ const { method = "threshold", threshold = DEFAULT_THRESHOLD, background = WHITE, protect } = options;
57
+ const { width, height } = image;
58
+ if (image.data.length !== width * height * 4) {
59
+ throw new RangeError(`ditherPixels: data holds ${image.data.length} bytes, a ${width}\xD7${height} image needs ${width * height * 4}`);
60
+ }
61
+ if (protect && protect.length !== width * height) {
62
+ throw new RangeError(`ditherPixels: protect holds ${protect.length} pixels, the image ${width * height}`);
63
+ }
64
+ const gray = toGray(image, background);
65
+ switch (method) {
66
+ case "threshold": {
67
+ const mask = new Uint8Array(width * height);
68
+ for (let i = 0; i < mask.length; i++) mask[i] = gray[i] < threshold ? 1 : 0;
69
+ return mask;
70
+ }
71
+ case "bayer": {
72
+ const mask = new Uint8Array(width * height);
73
+ for (let y = 0; y < height; y++) {
74
+ for (let x = 0; x < width; x++) {
75
+ const i = y * width + x;
76
+ const cutoff = protect?.[i] ? threshold : bayerCutoff(x, y, threshold);
77
+ mask[i] = gray[i] < cutoff ? 1 : 0;
78
+ }
79
+ }
80
+ return mask;
81
+ }
82
+ case "floyd-steinberg":
83
+ return diffuse(gray, width, height, threshold, FLOYD_STEINBERG, 16, protect ?? null);
84
+ case "atkinson":
85
+ return diffuse(gray, width, height, threshold, ATKINSON, 8, protect ?? null);
86
+ }
87
+ }
88
+
89
+ // src/maskToRgba.ts
90
+ var INK = [0, 0, 0, 255];
91
+ var BLANK = [0, 0, 0, 0];
92
+ function maskToRgba(mask, width, height, colors = {}) {
93
+ if (mask.length !== width * height) {
94
+ throw new RangeError(`maskToRgba: mask holds ${mask.length} pixels, ${width}\xD7${height} needs ${width * height}`);
95
+ }
96
+ const ink = colors.ink ?? INK;
97
+ const blank = colors.blank ?? BLANK;
98
+ const out = new Uint8ClampedArray(width * height * 4);
99
+ for (let i = 0; i < width * height; i++) out.set(mask[i] ? ink : blank, i * 4);
100
+ return out;
101
+ }
102
+
103
+ // src/protectMask.ts
104
+ function protectMask(rects, width, height) {
105
+ const mask = new Uint8Array(width * height);
106
+ for (const r of rects) {
107
+ const x0 = Math.max(0, Math.floor(r.x));
108
+ const y0 = Math.max(0, Math.floor(r.y));
109
+ const x1 = Math.min(width, Math.ceil(r.x + r.width));
110
+ const y1 = Math.min(height, Math.ceil(r.y + r.height));
111
+ for (let y = y0; y < y1; y++) mask.fill(1, y * width + x0, y * width + Math.max(x0, x1));
112
+ }
113
+ return mask;
114
+ }
115
+
116
+ // src/protectRects.ts
117
+ function protectRects(items, protectedRect, grid) {
118
+ const out = [];
119
+ for (const item of items) {
120
+ const r = protectedRect(item);
121
+ if (!r) continue;
122
+ out.push({
123
+ x: (r.x - grid.origin.x) / grid.pitch,
124
+ y: (r.y - grid.origin.y) / grid.pitch,
125
+ width: r.width / grid.pitch,
126
+ height: r.height / grid.pitch
127
+ });
128
+ }
129
+ return out;
130
+ }
131
+
132
+ // src/gpu/frag.ts
133
+ var list = (xs) => xs.map((x) => x.toFixed(4)).join(", ");
134
+ var DITHER_FRAG = (
135
+ /* glsl */
136
+ `#version 300 es
137
+ precision highp float;
138
+ in vec2 v_uv;
139
+ uniform sampler2D u_source;
140
+ uniform vec2 u_resolution;
141
+ uniform vec2 u_windowOrigin;
142
+ uniform float u_method; // 0 threshold, 1 bayer
143
+ uniform float u_threshold; // 0-255
144
+ uniform vec3 u_background; // 0-1
145
+ uniform vec4 u_ink; // premultiplied 0-1
146
+ uniform vec4 u_blank; // premultiplied 0-1
147
+ uniform vec2 u_pitch; // device px per dot
148
+ uniform vec2 u_gridOrigin; // device px, top-down
149
+ uniform sampler2D u_protect; // one texel per dot, red > 0.5 = protected
150
+ uniform vec2 u_protectSize; // dots; 0 = none
151
+ out vec4 outColor;
152
+
153
+ const float BAYER[16] = float[16](${list(BAYER_4X4)});
154
+ const vec3 LUMA = vec3(${list(LUMA)});
155
+
156
+ float grayOf(vec4 p) {
157
+ return dot(p.rgb + u_background * (1.0 - p.a), LUMA) * 255.0;
158
+ }
159
+
160
+ vec2 uvOf(vec2 devicePx) {
161
+ vec2 local = (devicePx - u_windowOrigin) / u_resolution;
162
+ return vec2(local.x, 1.0 - local.y);
163
+ }
164
+
165
+ void main() {
166
+ vec2 px = u_windowOrigin + vec2(v_uv.x, 1.0 - v_uv.y) * u_resolution;
167
+ vec2 dotIndex = floor((px - u_gridOrigin) / u_pitch);
168
+ vec2 size = abs(u_pitch);
169
+ bool fine = size.x < 1.0 || size.y < 1.0;
170
+
171
+ float gray;
172
+ if (fine) {
173
+ gray = grayOf(texture(u_source, v_uv));
174
+ } else {
175
+ // A negative pitch (a flipped axis) puts start on the dot's far edge and
176
+ // steps the taps back across it.
177
+ vec2 start = u_gridOrigin + dotIndex * u_pitch;
178
+ vec2 taps = clamp(ceil(size), vec2(1.0), vec2(4.0));
179
+ vec4 sum = vec4(0.0);
180
+ for (int j = 0; j < 4; j++) {
181
+ if (float(j) >= taps.y) break;
182
+ for (int i = 0; i < 4; i++) {
183
+ if (float(i) >= taps.x) break;
184
+ vec2 uv = uvOf(start + (vec2(float(i), float(j)) + 0.5) * u_pitch / taps);
185
+ // Past the buffer nothing was drawn: the tap is transparent.
186
+ if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) continue;
187
+ sum += texture(u_source, uv);
188
+ }
189
+ }
190
+ gray = grayOf(sum / (taps.x * taps.y));
191
+ }
192
+
193
+ bool protectedDot = u_protectSize.x > 0.0
194
+ && all(greaterThanEqual(dotIndex, vec2(0.0))) && all(lessThan(dotIndex, u_protectSize))
195
+ && texelFetch(u_protect, ivec2(dotIndex), 0).r > 0.5;
196
+
197
+ float cutoff = u_threshold;
198
+ if (u_method > 0.5 && !protectedDot) {
199
+ vec2 cell = mod(fine ? floor(px) : dotIndex, 4.0);
200
+ cutoff = (BAYER[int(cell.y) * 4 + int(cell.x)] + 0.5) * 16.0 + (u_threshold - 128.0);
201
+ }
202
+ outColor = gray < cutoff ? u_ink : u_blank;
203
+ }
204
+ `
205
+ );
206
+
207
+ // src/gpu/dither.ts
208
+ var PROGRAM = registerEffect("weasel:dither", DITHER_FRAG);
209
+ var INK2 = [0, 0, 0, 255];
210
+ var BLANK2 = [0, 0, 0, 0];
211
+ var SCREEN = { pitch: [1, 1], origin: [0, 0] };
212
+ var premultiplied = ([r, g, b, a]) => {
213
+ const k = a / 255;
214
+ return [r / 255 * k, g / 255 * k, b / 255 * k, k];
215
+ };
216
+ function dither(options = {}) {
217
+ const method = options.method ?? "threshold";
218
+ if (method !== "threshold" && method !== "bayer") {
219
+ throw new Error(`dither: '${String(method)}' is error diffusion, which runs on the CPU only (ditherPixels)`);
220
+ }
221
+ const grid = options.grid === void 0 || options.grid === "screen" ? SCREEN : options.grid;
222
+ if (options.protect && grid === SCREEN) {
223
+ throw new Error("dither: a protect mask is in dots, so it needs an output grid, not the screen");
224
+ }
225
+ const background = options.background ?? WHITE;
226
+ const reach = grid === SCREEN ? 0 : Math.ceil(Math.max(Math.abs(grid.pitch[0]), Math.abs(grid.pitch[1])));
227
+ return [{
228
+ program: PROGRAM,
229
+ reach,
230
+ uniforms: {
231
+ u_method: method === "bayer" ? 1 : 0,
232
+ u_threshold: options.threshold ?? DEFAULT_THRESHOLD,
233
+ u_background: [background[0] / 255, background[1] / 255, background[2] / 255],
234
+ u_ink: premultiplied(options.ink ?? INK2),
235
+ u_blank: premultiplied(options.blank ?? BLANK2),
236
+ u_pitch: [grid.pitch[0], grid.pitch[1]],
237
+ u_gridOrigin: [grid.origin[0], grid.origin[1]],
238
+ ...options.protect ? { u_protect: options.protect.texture, u_protectSize: [options.protect.width, options.protect.height] } : { u_protectSize: [0, 0] }
239
+ }
240
+ }];
241
+ }
242
+
243
+ // src/gpu/gridOnScreen.ts
244
+ function gridOnScreen(grid, view, dpr) {
245
+ const sx = view.scale.x * dpr;
246
+ const sy = view.scale.y * dpr;
247
+ return {
248
+ pitch: [grid.pitch * sx, grid.pitch * sy],
249
+ origin: [(grid.origin.x - view.x) * sx, (grid.origin.y - view.y) * sy]
250
+ };
251
+ }
252
+ async function protectTexture(mask, width, height) {
253
+ if (mask.length !== width * height) {
254
+ throw new RangeError(`protectTexture: mask holds ${mask.length} dots, ${width}\xD7${height} needs ${width * height}`);
255
+ }
256
+ const data = new Uint8ClampedArray(width * height * 4);
257
+ for (let i = 0; i < width * height; i++) {
258
+ const v = mask[i] ? 255 : 0;
259
+ data[i * 4] = v;
260
+ data[i * 4 + 1] = v;
261
+ data[i * 4 + 2] = v;
262
+ data[i * 4 + 3] = 255;
263
+ }
264
+ const bitmap = await createImageBitmap(new ImageData(data, width, height));
265
+ const texture = registerTexture(bitmap);
266
+ return {
267
+ texture,
268
+ width,
269
+ height,
270
+ dispose() {
271
+ unregisterTexture(texture);
272
+ bitmap.close();
273
+ }
274
+ };
275
+ }
276
+
277
+ export { dither, ditherPixels, gridOnScreen, maskToRgba, protectMask, protectRects, protectTexture };
278
+ //# sourceMappingURL=index.js.map
279
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/diffuse.ts","../src/shared.ts","../src/gray.ts","../src/ditherPixels.ts","../src/maskToRgba.ts","../src/protectMask.ts","../src/protectRects.ts","../src/gpu/frag.ts","../src/gpu/dither.ts","../src/gpu/gridOnScreen.ts","../src/gpu/protectTexture.ts"],"names":["INK","BLANK"],"mappings":"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","file":"index.js","sourcesContent":["/** Where a pixel's quantization error goes: offset and weight numerator. */\nexport type Kernel = readonly (readonly [dx: number, dy: number, weight: number])[];\n\nexport const FLOYD_STEINBERG: Kernel = [[1, 0, 7], [-1, 1, 3], [0, 1, 5], [1, 1, 1]];\n\n/** Spreads 6/8 of the error, which keeps highlights and shadows clean. */\nexport const ATKINSON: Kernel = [[1, 0, 1], [2, 0, 1], [-1, 1, 1], [0, 1, 1], [1, 1, 1], [0, 2, 1]];\n\n/** Error diffusion over `gray`, which it consumes. A protected pixel quantizes\n * at `threshold` against a value nothing diffused into, and sends no error. */\nexport function diffuse(\n gray: Float32Array,\n width: number,\n height: number,\n threshold: number,\n kernel: Kernel,\n denominator: number,\n protect: Uint8Array | null,\n): Uint8Array {\n const mask = new Uint8Array(width * height);\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const i = y * width + x;\n const old = gray[i]!;\n const ink = old < threshold;\n mask[i] = ink ? 1 : 0;\n if (protect?.[i]) continue;\n const err = old - (ink ? 0 : 255);\n for (const [dx, dy, w] of kernel) {\n const nx = x + dx;\n const ny = y + dy;\n if (nx < 0 || nx >= width || ny >= height) continue;\n const ni = ny * width + nx;\n if (protect?.[ni]) continue;\n gray[ni] = gray[ni]! + (err * w) / denominator;\n }\n }\n }\n return mask;\n}\n","import type { Rgb } from './types';\n\n/** The classic 4×4 Bayer index matrix, row-major. The GPU shader embeds this\n * same array, so the two paths cannot drift. */\nexport const BAYER_4X4: readonly number[] = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];\n\n/** Rec. 601 luma weights. */\nexport const LUMA: readonly [number, number, number] = [0.299, 0.587, 0.114];\n\nexport const DEFAULT_THRESHOLD = 128;\n\nexport const WHITE: Rgb = [255, 255, 255];\n\n/** The gray an ordered dither compares against at dot (x, y): below it is ink. */\nexport function bayerCutoff(x: number, y: number, threshold: number): number {\n const m = BAYER_4X4[(((y % 4) + 4) % 4) * 4 + (((x % 4) + 4) % 4)]!;\n return (m + 0.5) * 16 + (threshold - DEFAULT_THRESHOLD);\n}\n","import { LUMA, WHITE } from './shared';\nimport type { Rgb, RgbaImage } from './types';\n\n/** Each pixel's gray, 0–255, after compositing it over `background`. */\nexport function toGray(image: RgbaImage, background: Rgb = WHITE): Float32Array {\n const n = image.width * image.height;\n const gray = new Float32Array(n);\n const d = image.data;\n for (let i = 0; i < n; i++) {\n const a = d[i * 4 + 3]! / 255;\n const r = d[i * 4]! * a + background[0] * (1 - a);\n const g = d[i * 4 + 1]! * a + background[1] * (1 - a);\n const b = d[i * 4 + 2]! * a + background[2] * (1 - a);\n gray[i] = LUMA[0] * r + LUMA[1] * g + LUMA[2] * b;\n }\n return gray;\n}\n","import { ATKINSON, FLOYD_STEINBERG, diffuse } from './diffuse';\nimport { toGray } from './gray';\nimport { DEFAULT_THRESHOLD, WHITE, bayerCutoff } from './shared';\nimport type { DitherOptions, RgbaImage } from './types';\n\n/** `image` quantized to ink: one byte per pixel, 1 = ink. */\nexport function ditherPixels(image: RgbaImage, options: DitherOptions = {}): Uint8Array {\n const { method = 'threshold', threshold = DEFAULT_THRESHOLD, background = WHITE, protect } = options;\n const { width, height } = image;\n if (image.data.length !== width * height * 4) {\n throw new RangeError(`ditherPixels: data holds ${image.data.length} bytes, a ${width}×${height} image needs ${width * height * 4}`);\n }\n if (protect && protect.length !== width * height) {\n throw new RangeError(`ditherPixels: protect holds ${protect.length} pixels, the image ${width * height}`);\n }\n const gray = toGray(image, background);\n switch (method) {\n case 'threshold': {\n const mask = new Uint8Array(width * height);\n for (let i = 0; i < mask.length; i++) mask[i] = gray[i]! < threshold ? 1 : 0;\n return mask;\n }\n case 'bayer': {\n const mask = new Uint8Array(width * height);\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const i = y * width + x;\n const cutoff = protect?.[i] ? threshold : bayerCutoff(x, y, threshold);\n mask[i] = gray[i]! < cutoff ? 1 : 0;\n }\n }\n return mask;\n }\n case 'floyd-steinberg':\n return diffuse(gray, width, height, threshold, FLOYD_STEINBERG, 16, protect ?? null);\n case 'atkinson':\n return diffuse(gray, width, height, threshold, ATKINSON, 8, protect ?? null);\n }\n}\n","import type { Rgba } from './types';\n\nconst INK: Rgba = [0, 0, 0, 255];\nconst BLANK: Rgba = [0, 0, 0, 0];\n\n/** An ink mask as straight RGBA: `ink` where it is set, `blank` elsewhere. */\nexport function maskToRgba(\n mask: Uint8Array,\n width: number,\n height: number,\n colors: { ink?: Rgba; blank?: Rgba } = {},\n): Uint8ClampedArray<ArrayBuffer> {\n if (mask.length !== width * height) {\n throw new RangeError(`maskToRgba: mask holds ${mask.length} pixels, ${width}×${height} needs ${width * height}`);\n }\n const ink = colors.ink ?? INK;\n const blank = colors.blank ?? BLANK;\n const out = new Uint8ClampedArray(width * height * 4);\n for (let i = 0; i < width * height; i++) out.set(mask[i] ? ink : blank, i * 4);\n return out;\n}\n","import type { Rect } from '@weasel-js/geom';\n\n/** A per-pixel protect mask (1 = protected) from rects in pixel space. Rects\n * round outward to whole pixels and clip to the image. */\nexport function protectMask(rects: Iterable<Rect>, width: number, height: number): Uint8Array {\n const mask = new Uint8Array(width * height);\n for (const r of rects) {\n const x0 = Math.max(0, Math.floor(r.x));\n const y0 = Math.max(0, Math.floor(r.y));\n const x1 = Math.min(width, Math.ceil(r.x + r.width));\n const y1 = Math.min(height, Math.ceil(r.y + r.height));\n for (let y = y0; y < y1; y++) mask.fill(1, y * width + x0, y * width + Math.max(x0, x1));\n }\n return mask;\n}\n","import type { OutputGrid, Rect } from '@weasel-js/geom';\n\n/**\n * Rects to protect, in dot space (one unit per dot, from the grid's origin),\n * the pixel space an export rendered at the grid's resolution has.\n * `protectedRect` answers an item's rect in document units, which may be\n * larger than the item (a barcode's quiet zone), or false or null to leave\n * it to the dither.\n */\nexport function protectRects<T>(\n items: Iterable<T>,\n protectedRect: (item: T) => Rect | false | null,\n grid: OutputGrid,\n): Rect[] {\n const out: Rect[] = [];\n for (const item of items) {\n const r = protectedRect(item);\n if (!r) continue;\n out.push({\n x: (r.x - grid.origin.x) / grid.pitch,\n y: (r.y - grid.origin.y) / grid.pitch,\n width: r.width / grid.pitch,\n height: r.height / grid.pitch,\n });\n }\n return out;\n}\n","import { BAYER_4X4, LUMA } from '../shared';\n\nconst list = (xs: readonly number[]) => xs.map((x) => x.toFixed(4)).join(', ');\n\n/**\n * One fragment per device pixel. On an output grid, a dot wider than a pixel\n * takes the mean of its footprint on at most 4×4 taps and quantizes once, so\n * every pixel of the dot agrees; a dot narrower than a pixel cannot be shown,\n * and the pixel quantizes alone, on the Bayer cell of its own device pixel.\n */\nexport const DITHER_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 v_uv;\nuniform sampler2D u_source;\nuniform vec2 u_resolution;\nuniform vec2 u_windowOrigin;\nuniform float u_method; // 0 threshold, 1 bayer\nuniform float u_threshold; // 0-255\nuniform vec3 u_background; // 0-1\nuniform vec4 u_ink; // premultiplied 0-1\nuniform vec4 u_blank; // premultiplied 0-1\nuniform vec2 u_pitch; // device px per dot\nuniform vec2 u_gridOrigin; // device px, top-down\nuniform sampler2D u_protect; // one texel per dot, red > 0.5 = protected\nuniform vec2 u_protectSize; // dots; 0 = none\nout vec4 outColor;\n\nconst float BAYER[16] = float[16](${list(BAYER_4X4)});\nconst vec3 LUMA = vec3(${list(LUMA)});\n\nfloat grayOf(vec4 p) {\n return dot(p.rgb + u_background * (1.0 - p.a), LUMA) * 255.0;\n}\n\nvec2 uvOf(vec2 devicePx) {\n vec2 local = (devicePx - u_windowOrigin) / u_resolution;\n return vec2(local.x, 1.0 - local.y);\n}\n\nvoid main() {\n vec2 px = u_windowOrigin + vec2(v_uv.x, 1.0 - v_uv.y) * u_resolution;\n vec2 dotIndex = floor((px - u_gridOrigin) / u_pitch);\n vec2 size = abs(u_pitch);\n bool fine = size.x < 1.0 || size.y < 1.0;\n\n float gray;\n if (fine) {\n gray = grayOf(texture(u_source, v_uv));\n } else {\n // A negative pitch (a flipped axis) puts start on the dot's far edge and\n // steps the taps back across it.\n vec2 start = u_gridOrigin + dotIndex * u_pitch;\n vec2 taps = clamp(ceil(size), vec2(1.0), vec2(4.0));\n vec4 sum = vec4(0.0);\n for (int j = 0; j < 4; j++) {\n if (float(j) >= taps.y) break;\n for (int i = 0; i < 4; i++) {\n if (float(i) >= taps.x) break;\n vec2 uv = uvOf(start + (vec2(float(i), float(j)) + 0.5) * u_pitch / taps);\n // Past the buffer nothing was drawn: the tap is transparent.\n if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) continue;\n sum += texture(u_source, uv);\n }\n }\n gray = grayOf(sum / (taps.x * taps.y));\n }\n\n bool protectedDot = u_protectSize.x > 0.0\n && all(greaterThanEqual(dotIndex, vec2(0.0))) && all(lessThan(dotIndex, u_protectSize))\n && texelFetch(u_protect, ivec2(dotIndex), 0).r > 0.5;\n\n float cutoff = u_threshold;\n if (u_method > 0.5 && !protectedDot) {\n vec2 cell = mod(fine ? floor(px) : dotIndex, 4.0);\n cutoff = (BAYER[int(cell.y) * 4 + int(cell.x)] + 0.5) * 16.0 + (u_threshold - 128.0);\n }\n outColor = gray < cutoff ? u_ink : u_blank;\n}\n`;\n","import { registerEffect, type Effect, type TextureHandle } from '@weasel-js/renderer';\nimport { DEFAULT_THRESHOLD, WHITE } from '../shared';\nimport type { Rgb, Rgba } from '../types';\nimport { DITHER_FRAG } from './frag';\nimport type { DeviceGrid } from './gridOnScreen';\n\nconst PROGRAM = registerEffect('weasel:dither', DITHER_FRAG);\n\nconst INK: Rgba = [0, 0, 0, 255];\nconst BLANK: Rgba = [0, 0, 0, 0];\nconst SCREEN: DeviceGrid = { pitch: [1, 1], origin: [0, 0] };\n\n/** How the `dither` effect quantizes what its group drew. */\nexport interface GpuDitherOptions {\n /** Default `'threshold'`. Error diffusion runs on the CPU only. */\n method?: 'threshold' | 'bayer';\n /** Gray (0–255) below it is ink. Default 128. */\n threshold?: number;\n /** What transparency composites over before gray is taken. Default white. */\n background?: Rgb;\n /** Default opaque black. */\n ink?: Rgba;\n /** Default transparent. */\n blank?: Rgba;\n /** `'screen'` (one dot per device pixel, the default), or an output grid\n * placed with `gridOnScreen`. */\n grid?: 'screen' | DeviceGrid;\n /** A protect mask in dot space, from `protectTexture`. Needs an output grid. */\n protect?: { texture: TextureHandle; width: number; height: number };\n}\n\nconst premultiplied = ([r, g, b, a]: Rgba): number[] => {\n const k = a / 255;\n return [(r / 255) * k, (g / 255) * k, (b / 255) * k, k];\n};\n\n/** Threshold or Bayer dithering as one effect pass. */\nexport function dither(options: GpuDitherOptions = {}): Effect[] {\n const method = options.method ?? 'threshold';\n if (method !== 'threshold' && method !== 'bayer') {\n throw new Error(`dither: '${String(method)}' is error diffusion, which runs on the CPU only (ditherPixels)`);\n }\n const grid = options.grid === undefined || options.grid === 'screen' ? SCREEN : options.grid;\n if (options.protect && grid === SCREEN) {\n throw new Error('dither: a protect mask is in dots, so it needs an output grid, not the screen');\n }\n const background = options.background ?? WHITE;\n const reach = grid === SCREEN ? 0 : Math.ceil(Math.max(Math.abs(grid.pitch[0]), Math.abs(grid.pitch[1])));\n return [{\n program: PROGRAM,\n reach,\n uniforms: {\n u_method: method === 'bayer' ? 1 : 0,\n u_threshold: options.threshold ?? DEFAULT_THRESHOLD,\n u_background: [background[0] / 255, background[1] / 255, background[2] / 255],\n u_ink: premultiplied(options.ink ?? INK),\n u_blank: premultiplied(options.blank ?? BLANK),\n u_pitch: [grid.pitch[0], grid.pitch[1]],\n u_gridOrigin: [grid.origin[0], grid.origin[1]],\n ...(options.protect\n ? { u_protect: options.protect.texture, u_protectSize: [options.protect.width, options.protect.height] }\n : { u_protectSize: [0, 0] }),\n },\n }];\n}\n","import type { OutputGrid, View } from '@weasel-js/geom';\n\n/** A dot grid in device pixels of the drawing buffer, from its top left. */\nexport interface DeviceGrid {\n pitch: readonly [number, number];\n origin: readonly [number, number];\n}\n\n/** `grid` where `view` at `dpr` puts it. In a layer group's effect thunk,\n * `dpr` is `dims.dpr`; in a node's draw, `ctx.pixelScale / view.scale.x`.\n * A flipped axis gives a negative pitch, which keeps dots numbered in\n * document order: dot 0 still sits at the grid's document origin. */\nexport function gridOnScreen(grid: OutputGrid, view: View, dpr: number): DeviceGrid {\n const sx = view.scale.x * dpr;\n const sy = view.scale.y * dpr;\n return {\n pitch: [grid.pitch * sx, grid.pitch * sy],\n origin: [(grid.origin.x - view.x) * sx, (grid.origin.y - view.y) * sy],\n };\n}\n","import { registerTexture, unregisterTexture, type TextureHandle } from '@weasel-js/renderer';\n\n/** A protect mask uploaded for `dither`, one texel per dot, with its size. */\nexport interface ProtectTexture {\n texture: TextureHandle;\n width: number;\n height: number;\n /** Unregister the texture and free its bitmap. */\n dispose(): void;\n}\n\n/** A protect mask (from `protectMask`, in dot space) as a texture `dither`\n * can read. The caller disposes it when the mask changes or the view goes. */\nexport async function protectTexture(mask: Uint8Array, width: number, height: number): Promise<ProtectTexture> {\n if (mask.length !== width * height) {\n throw new RangeError(`protectTexture: mask holds ${mask.length} dots, ${width}×${height} needs ${width * height}`);\n }\n const data = new Uint8ClampedArray(width * height * 4);\n for (let i = 0; i < width * height; i++) {\n const v = mask[i] ? 255 : 0;\n data[i * 4] = v;\n data[i * 4 + 1] = v;\n data[i * 4 + 2] = v;\n data[i * 4 + 3] = 255;\n }\n const bitmap = await createImageBitmap(new ImageData(data, width, height));\n const texture = registerTexture(bitmap);\n return {\n texture, width, height,\n dispose() {\n unregisterTexture(texture);\n bitmap.close();\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@weasel-js/dither",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.16.1",
4
+ "description": "Dithering to 1-bit ink: four methods on the CPU for exported pixels, and threshold and Bayer as a live renderer effect on the screen or an output target's dot grid, with regions kept at plain threshold.",
5
+ "weasel": {
6
+ "tier": "plugin",
7
+ "needs": ["webgl2"]
8
+ },
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "dependencies": {
23
+ "@weasel-js/geom": "1.16.1",
24
+ "@weasel-js/renderer": "1.16.1"
25
+ },
26
+ "author": "orochi235",
27
+ "homepage": "https://orochi235.github.io/weasel/",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/orochi235/weasel.git",
31
+ "directory": "packages/dither"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/orochi235/weasel/issues"
35
+ },
36
+ "engines": {
37
+ "node": ">=24"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "scripts": {
45
+ "build": "tsup"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ }
51
+ }