palette-shader 0.13.0 → 0.15.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 +63 -30
- package/dist/palette-shader.d.ts +10 -10
- package/dist/palette-shader.js +832 -598
- package/dist/palette-shader.js.map +1 -1
- package/dist/palette-shader.umd.cjs +222 -48
- package/dist/palette-shader.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/PaletteViz.ts +61 -31
- package/src/PaletteViz3D.ts +63 -31
- package/src/mesh.ts +4 -4
- package/src/shaderSrc.ts +228 -46
- package/src/shaders/closestColor.frag.glsl +9 -1
- package/src/shaders/hwb2rgb.frag.glsl +1 -1
- package/src/types.ts +14 -4
- package/src/webgl.ts +4 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# palette-shader
|
|
2
2
|
|
|
3
|
-
A dependency-free WebGL2 shader that maps any color palette across a 3-D perceptual color space and snaps each pixel to the nearest palette color. Visualize how a palette distributes across
|
|
3
|
+
A dependency-free WebGL2 shader that maps any color palette across a 3-D perceptual color space and snaps each pixel to the nearest palette color. Visualize how a palette distributes across 30+ color models and ten distance metrics, all on the GPU. Includes 2-D cross-section views (`PaletteViz`) and an interactive 3-D cube/cylinder view (`PaletteViz3D`) with trackball rotation.
|
|
4
4
|
|
|
5
5
|
[**Live demo →**](https://meodai.github.io/color-palette-shader/)
|
|
6
6
|
|
|
@@ -16,6 +16,19 @@ So if one of your palette colors only claims a tiny sliver, it lives very close
|
|
|
16
16
|
- **How balanced** the palette is overall — even regions mean even coverage
|
|
17
17
|
- **Whether a new color is worth adding** — if it doesn't carve out its own space, it's probably not pulling its weight
|
|
18
18
|
|
|
19
|
+
### Common questions → recommended settings
|
|
20
|
+
|
|
21
|
+
| I want to know… | Color model | Distance metric |
|
|
22
|
+
| ----------------------------------------- | ------------------------------- | --------------- |
|
|
23
|
+
| How my hue distribution looks | `okhslPolar` | `oklab` |
|
|
24
|
+
| Which two colors are most similar | `okhsl` or `oklab` | `deltaE2000` |
|
|
25
|
+
| Whether a new color is worth adding | `okhslPolar` | `oklab` |
|
|
26
|
+
| How my palette reads on print | `cielabD50` or `cielchD50Polar` | `cielabD50` |
|
|
27
|
+
| How close the colors look to a human eye | `oklchPolar` | `deltaE2000` |
|
|
28
|
+
| What the palette looks like to a computer | `hslPolar` or `rgb` | `rgb` |
|
|
29
|
+
|
|
30
|
+
For a deeper breakdown of every color model and metric, see [docs/use-cases.md](docs/use-cases.md).
|
|
31
|
+
|
|
19
32
|
---
|
|
20
33
|
|
|
21
34
|
## Install
|
|
@@ -65,21 +78,21 @@ new PaletteViz(options?: PaletteVizOptions)
|
|
|
65
78
|
|
|
66
79
|
All options are optional. The palette defaults to a random 20-color set.
|
|
67
80
|
|
|
68
|
-
| Option | Type | Default | Description
|
|
69
|
-
| ---------------- | ---------------------------- | ------------------ |
|
|
70
|
-
| `palette` | `[number, number, number][]` | random | sRGB colors as `[r, g, b]` arrays, each component in the `0–1` range
|
|
71
|
-
| `container` | `HTMLElement` | `undefined` | Element the canvas is appended to. Omit and use `viz.canvas` to place it yourself
|
|
72
|
-
| `width` | `number` | `512` | Canvas width in CSS pixels
|
|
73
|
-
| `height` | `number` | `512` | Canvas height in CSS pixels
|
|
74
|
-
| `pixelRatio` | `number` | `devicePixelRatio` | Renderer pixel ratio
|
|
75
|
-
| `colorModel` | `string` | `'okhsv'` | Color space for the visualization (see [Color models](#color-models))
|
|
76
|
-
| `distanceMetric` | `string` | `'oklab'` | Distance function for nearest-color matching (see [Distance metrics](#distance-metrics))
|
|
77
|
-
| `axis` | `'x' \| 'y' \| 'z'` | `'y'` | Which axis the `position` value controls
|
|
78
|
-
| `position` | `number` | `0` | 0–1 position along the chosen axis
|
|
79
|
-
| `
|
|
80
|
-
| `showRaw` | `boolean` | `false` | Bypass nearest-color matching (shows the raw color space)
|
|
81
|
-
| `outlineWidth` | `number` | `0` | Draw a transparent outline where palette regions meet. Width in physical pixels. `0` disables (no overhead).
|
|
82
|
-
| `gamutClip` | `boolean` | `false` | Discard out-of-sRGB-gamut pixels instead of clamping. Reveals the true gamut boundary of the color model.
|
|
81
|
+
| Option | Type | Default | Description |
|
|
82
|
+
| ---------------- | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| `palette` | `[number, number, number][]` | random | sRGB colors as `[r, g, b]` arrays, each component in the `0–1` range |
|
|
84
|
+
| `container` | `HTMLElement` | `undefined` | Element the canvas is appended to. Omit and use `viz.canvas` to place it yourself |
|
|
85
|
+
| `width` | `number` | `512` | Canvas width in CSS pixels |
|
|
86
|
+
| `height` | `number` | `512` | Canvas height in CSS pixels |
|
|
87
|
+
| `pixelRatio` | `number` | `devicePixelRatio` | Renderer pixel ratio |
|
|
88
|
+
| `colorModel` | `string` | `'okhsv'` | Color space for the visualization (see [Color models](#color-models)) |
|
|
89
|
+
| `distanceMetric` | `string` | `'oklab'` | Distance function for nearest-color matching (see [Distance metrics](#distance-metrics)) |
|
|
90
|
+
| `axis` | `'x' \| 'y' \| 'z'` | `'y'` | Which axis the `position` value controls |
|
|
91
|
+
| `position` | `number` | `0` | 0–1 position along the chosen axis |
|
|
92
|
+
| `invertAxes` | `('x' \| 'y' \| 'z')[]` | `[]` | Invert one or more axes, for example `['z']` or `['x', 'z']`. In 2-D polar views, `y` inversion is resolved as a vertical view flip to avoid the mirrored center seam. |
|
|
93
|
+
| `showRaw` | `boolean` | `false` | Bypass nearest-color matching (shows the raw color space) |
|
|
94
|
+
| `outlineWidth` | `number` | `0` | Draw a transparent outline where palette regions meet. Width in physical pixels. `0` disables (no overhead). |
|
|
95
|
+
| `gamutClip` | `boolean` | `false` | Discard out-of-sRGB-gamut pixels instead of clamping. Reveals the true gamut boundary of the color model. |
|
|
83
96
|
|
|
84
97
|
---
|
|
85
98
|
|
|
@@ -96,7 +109,7 @@ viz.palette = [
|
|
|
96
109
|
viz.position = 0.5;
|
|
97
110
|
viz.colorModel = 'okhslPolar';
|
|
98
111
|
viz.distanceMetric = 'deltaE2000';
|
|
99
|
-
viz.
|
|
112
|
+
viz.invertAxes = ['z'];
|
|
100
113
|
viz.showRaw = true;
|
|
101
114
|
viz.outlineWidth = 2; // transparent border between regions, in physical pixels
|
|
102
115
|
viz.gamutClip = true; // discard out-of-gamut pixels
|
|
@@ -178,14 +191,16 @@ Controls the 3-D color space the visualization is rendered in. Polar variants (`
|
|
|
178
191
|
|
|
179
192
|
**OK — Lab / LCH**
|
|
180
193
|
|
|
181
|
-
| Value
|
|
182
|
-
|
|
|
183
|
-
| `'oklab'`
|
|
184
|
-
| `'oklch'`
|
|
185
|
-
| `'oklchPolar'`
|
|
186
|
-
| `'
|
|
187
|
-
| `'
|
|
188
|
-
| `'
|
|
194
|
+
| Value | Shape | Description |
|
|
195
|
+
| ---------------- | -------- | ------------------------------------------------------------------------------------ |
|
|
196
|
+
| `'oklab'` | cube | Raw OKLab: x→a, y→b, z→L. |
|
|
197
|
+
| `'oklch'` | cube | OKLab in cylindrical LCH coordinates. Ideal for chroma or lightness slices. |
|
|
198
|
+
| `'oklchPolar'` | wheel | Polar form of OKLch. |
|
|
199
|
+
| `'oklchDiag'` | diagonal | Complementary hue plane: lightness on the diagonal, signed chroma on the anti-diagonal. Two opposite hues share the same view — no gray band in the middle. |
|
|
200
|
+
| `'oklrab'` | cube | OKLab with toe-corrected lightness (Lr). Better perceptual uniformity in dark tones. |
|
|
201
|
+
| `'oklrch'` | cube | OKLrab in cylindrical LCH coordinates. |
|
|
202
|
+
| `'oklrchPolar'` | wheel | Polar form of OKLrch. |
|
|
203
|
+
| `'oklrchDiag'` | diagonal | Complementary hue plane in OKLrch. Same diagonal layout as `oklchDiag` with toe-corrected lightness. |
|
|
189
204
|
|
|
190
205
|
**CIE Lab / LCH — D65**
|
|
191
206
|
|
|
@@ -215,6 +230,22 @@ Controls the 3-D color space the visualization is rendered in. Polar variants (`
|
|
|
215
230
|
| `'hwbPolar'` | wheel | Polar form of HWB. |
|
|
216
231
|
| `'rgb'` | cube | Raw sRGB cube. Useful as a baseline. |
|
|
217
232
|
|
|
233
|
+
**Quantized RGB**
|
|
234
|
+
|
|
235
|
+
| Value | Shape | Description |
|
|
236
|
+
| ------------ | ----- | ---------------------------------------------------------------- |
|
|
237
|
+
| `'rgb6bit'` | cube | 2-bit per channel (64 colors). Game Boy–era palettes. |
|
|
238
|
+
| `'rgb8bit'` | cube | 3-3-2 bit (256 colors). CGA-style quantization. |
|
|
239
|
+
| `'rgb12bit'` | cube | 4-bit per channel (4096 colors). Amiga / NTSC. |
|
|
240
|
+
| `'rgb15bit'` | cube | 5-bit per channel (32768 colors). SVGA HiColor. |
|
|
241
|
+
| `'rgb18bit'` | cube | 6-bit per channel (262144 colors). VGA. |
|
|
242
|
+
|
|
243
|
+
**Spectral**
|
|
244
|
+
|
|
245
|
+
| Value | Shape | Description |
|
|
246
|
+
| ------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
247
|
+
| `'spectrum'` | cube | Visible light wavelengths (410–665 nm) plus purple line, modulated in OKLab. Inspired by censor's SpectroBoxWidget. X→wavelength, Y→lightness, Z→chroma scale. |
|
|
248
|
+
|
|
218
249
|
The OK-variants rely on Björn Ottosson's gamut-aware implementation and produce significantly more even hue distributions than the classic variants at the same GPU cost.
|
|
219
250
|
|
|
220
251
|
### Cube vs. polar — which to use?
|
|
@@ -235,10 +266,12 @@ Controls how "nearest palette color" is determined per pixel.
|
|
|
235
266
|
|
|
236
267
|
**OK**
|
|
237
268
|
|
|
238
|
-
| Value
|
|
239
|
-
|
|
|
240
|
-
| `'oklab'`
|
|
241
|
-
| `'oklrab'`
|
|
269
|
+
| Value | Description | Cost |
|
|
270
|
+
| --------------- | ------------------------------------------------------------------------------------------------------- | ---- |
|
|
271
|
+
| `'oklab'` | **Default.** Euclidean distance in OKLab. Fast, perceptually uniform, excellent general-purpose choice. | low |
|
|
272
|
+
| `'oklrab'` | Euclidean in OKLab with toe-corrected lightness. Slightly better uniformity in dark tones than OKLab. | low |
|
|
273
|
+
| `'okLightness'` | Absolute lightness difference in OKLab (`|ΔL|`). Ignores hue and chroma — groups by brightness only. | low |
|
|
274
|
+
| `'liMatch'` | Spatially varying blend: full OKLab distance at the left edge, pure lightness match at the right. Inspired by censor's li-match. Useful for visualising tonal structure. | low |
|
|
242
275
|
|
|
243
276
|
**CIE — D65**
|
|
244
277
|
|
|
@@ -373,7 +406,7 @@ new PaletteViz3D(options?: PaletteViz3DOptions)
|
|
|
373
406
|
| `colorModel` | `string` | `'okhsv'` | Color model (see [Color models](#color-models)). Polar → cylinder mesh |
|
|
374
407
|
| `distanceMetric` | `string` | `'oklab'` | Distance metric (see [Distance metrics](#distance-metrics)) |
|
|
375
408
|
| `position` | `number` | `1` | 0–1 slice position. `1` shows the full volume; `0` slices it completely away |
|
|
376
|
-
| `
|
|
409
|
+
| `invertAxes` | `('x' \| 'y' \| 'z')[]` | `[]` | Invert one or more axes, for example `['z']` or `['x', 'z']` |
|
|
377
410
|
| `showRaw` | `boolean` | `false` | Bypass nearest-color matching |
|
|
378
411
|
| `outlineWidth` | `number` | `0` | Transparent outline width (physical px). `0` disables |
|
|
379
412
|
| `gamutClip` | `boolean` | `false` | Discard out-of-sRGB-gamut pixels instead of clamping |
|
package/dist/palette-shader.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare type ColorList = ColorRGB[];
|
|
|
4
4
|
|
|
5
5
|
export declare type ColorRGB = [number, number, number];
|
|
6
6
|
|
|
7
|
-
export declare type DistanceMetric = 'rgb' | 'oklab' | 'deltaE76' | 'deltaE94' | 'deltaE2000' | 'kotsarenkoRamos' | 'oklrab' | 'cielabD50';
|
|
7
|
+
export declare type DistanceMetric = 'rgb' | 'oklab' | 'deltaE76' | 'deltaE94' | 'deltaE2000' | 'kotsarenkoRamos' | 'oklrab' | 'cielabD50' | 'okLightness' | 'liMatch';
|
|
8
8
|
|
|
9
9
|
export declare const fragmentShader: string;
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ export declare const paletteToTexture: (palette: ColorList) => Uint8Array;
|
|
|
24
24
|
|
|
25
25
|
export declare class PaletteViz {
|
|
26
26
|
#private;
|
|
27
|
-
constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, axis, position,
|
|
27
|
+
constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, axis, position, invertAxes, showRaw, outlineWidth, gamutClip, }?: PaletteVizOptions);
|
|
28
28
|
get canvas(): HTMLCanvasElement;
|
|
29
29
|
get width(): number;
|
|
30
30
|
get height(): number;
|
|
@@ -45,8 +45,8 @@ export declare class PaletteViz {
|
|
|
45
45
|
get colorModel(): SupportedColorModels;
|
|
46
46
|
set distanceMetric(metric: DistanceMetric);
|
|
47
47
|
get distanceMetric(): DistanceMetric;
|
|
48
|
-
set
|
|
49
|
-
get
|
|
48
|
+
set invertAxes(value: Axis[]);
|
|
49
|
+
get invertAxes(): Axis[];
|
|
50
50
|
set showRaw(value: boolean);
|
|
51
51
|
get showRaw(): boolean;
|
|
52
52
|
set pixelRatio(value: number);
|
|
@@ -62,7 +62,7 @@ export declare class PaletteViz {
|
|
|
62
62
|
|
|
63
63
|
export declare class PaletteViz3D {
|
|
64
64
|
#private;
|
|
65
|
-
constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric,
|
|
65
|
+
constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, invertAxes, showRaw, outlineWidth, gamutClip, position, modelMatrix, }?: PaletteViz3DOptions);
|
|
66
66
|
/**
|
|
67
67
|
* Read the rendered colour at normalised screen coordinates (0–1, y=0 is top).
|
|
68
68
|
* Returns [r, g, b] in [0, 1], or null if the pixel is transparent (no geometry).
|
|
@@ -80,8 +80,8 @@ export declare class PaletteViz3D {
|
|
|
80
80
|
get colorModel(): SupportedColorModels;
|
|
81
81
|
set distanceMetric(metric: DistanceMetric);
|
|
82
82
|
get distanceMetric(): DistanceMetric;
|
|
83
|
-
set
|
|
84
|
-
get
|
|
83
|
+
set invertAxes(value: Axis[]);
|
|
84
|
+
get invertAxes(): Axis[];
|
|
85
85
|
set showRaw(value: boolean);
|
|
86
86
|
get showRaw(): boolean;
|
|
87
87
|
set position(value: number);
|
|
@@ -106,7 +106,7 @@ export declare type PaletteViz3DOptions = {
|
|
|
106
106
|
container?: HTMLElement;
|
|
107
107
|
colorModel?: SupportedColorModels;
|
|
108
108
|
distanceMetric?: DistanceMetric;
|
|
109
|
-
|
|
109
|
+
invertAxes?: Axis[];
|
|
110
110
|
showRaw?: boolean;
|
|
111
111
|
outlineWidth?: number;
|
|
112
112
|
gamutClip?: boolean;
|
|
@@ -124,7 +124,7 @@ export declare type PaletteVizOptions = {
|
|
|
124
124
|
distanceMetric?: DistanceMetric;
|
|
125
125
|
axis?: Axis;
|
|
126
126
|
position?: number;
|
|
127
|
-
|
|
127
|
+
invertAxes?: Axis[];
|
|
128
128
|
showRaw?: boolean;
|
|
129
129
|
outlineWidth?: number;
|
|
130
130
|
gamutClip?: boolean;
|
|
@@ -132,6 +132,6 @@ export declare type PaletteVizOptions = {
|
|
|
132
132
|
|
|
133
133
|
export declare const randomPalette: (size?: number) => ColorList;
|
|
134
134
|
|
|
135
|
-
export declare type SupportedColorModels = 'rgb' | 'oklab' | 'okhsv' | 'okhsvPolar' | 'okhsl' | 'okhslPolar' | 'oklch' | 'oklchPolar' | 'hsv' | 'hsvPolar' | 'hsl' | 'hslPolar' | 'hwb' | 'hwbPolar' | 'oklrab' | 'oklrch' | 'oklrchPolar' | 'cielab' | 'cielch' | 'cielchPolar' | 'cielabD50' | 'cielchD50' | 'cielchD50Polar';
|
|
135
|
+
export declare type SupportedColorModels = 'rgb' | 'rgb12bit' | 'rgb8bit' | 'rgb18bit' | 'rgb6bit' | 'rgb15bit' | 'oklab' | 'okhsv' | 'okhsvPolar' | 'okhsl' | 'okhslPolar' | 'oklch' | 'oklchPolar' | 'hsv' | 'hsvPolar' | 'hsl' | 'hslPolar' | 'hwb' | 'hwbPolar' | 'oklrab' | 'oklrch' | 'oklrchPolar' | 'cielab' | 'cielch' | 'cielchPolar' | 'cielabD50' | 'cielchD50' | 'cielchD50Polar' | 'spectrum' | 'oklchDiag' | 'oklrchDiag';
|
|
136
136
|
|
|
137
137
|
export { }
|