palette-shader 0.12.0 → 0.14.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 +93 -38
- package/dist/palette-shader.d.ts +15 -9
- package/dist/palette-shader.js +823 -731
- package/dist/palette-shader.js.map +1 -1
- package/dist/palette-shader.umd.cjs +214 -201
- package/dist/palette-shader.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/PaletteViz.ts +74 -37
- package/src/PaletteViz3D.ts +367 -78
- package/src/index.ts +1 -7
- package/src/math.ts +20 -5
- package/src/mesh.ts +39 -89
- package/src/shaderSrc.ts +264 -99
- package/src/types.ts +7 -2
- package/src/webgl.ts +43 -26
package/README.md
CHANGED
|
@@ -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
|
|
@@ -35,7 +48,10 @@ import { PaletteViz } from 'palette-shader';
|
|
|
35
48
|
import { converter } from 'culori';
|
|
36
49
|
|
|
37
50
|
const toSRGB = converter('srgb');
|
|
38
|
-
const toRGB = (hex) => {
|
|
51
|
+
const toRGB = (hex) => {
|
|
52
|
+
const c = toSRGB(hex);
|
|
53
|
+
return [c.r, c.g, c.b];
|
|
54
|
+
};
|
|
39
55
|
|
|
40
56
|
// option A — pass a container, canvas is appended automatically
|
|
41
57
|
const viz = new PaletteViz({
|
|
@@ -50,6 +66,8 @@ const viz = new PaletteViz({ palette: ['#264653', '#2a9d8f', '#e9c46a'].map(toRG
|
|
|
50
66
|
document.querySelector('#app').appendChild(viz.canvas);
|
|
51
67
|
```
|
|
52
68
|
|
|
69
|
+
If you only use the 2D renderer, import `PaletteViz` and let your bundler tree-shake the rest. `PaletteViz3D` pulls in substantially more code for mesh generation, extra shaders, and interaction handling, so it is worth avoiding in apps that never render the 3-D view.
|
|
70
|
+
|
|
53
71
|
---
|
|
54
72
|
|
|
55
73
|
## Constructor
|
|
@@ -60,20 +78,21 @@ new PaletteViz(options?: PaletteVizOptions)
|
|
|
60
78
|
|
|
61
79
|
All options are optional. The palette defaults to a random 20-color set.
|
|
62
80
|
|
|
63
|
-
| Option | Type
|
|
64
|
-
| ---------------- |
|
|
65
|
-
| `palette` | `[number, number, number][]` | random
|
|
66
|
-
| `container` | `HTMLElement`
|
|
67
|
-
| `width` | `number`
|
|
68
|
-
| `height` | `number`
|
|
69
|
-
| `pixelRatio` | `number`
|
|
70
|
-
| `colorModel` | `string`
|
|
71
|
-
| `distanceMetric` | `string`
|
|
72
|
-
| `axis` | `'x' \| 'y' \| 'z'`
|
|
73
|
-
| `position` | `number`
|
|
74
|
-
| `
|
|
75
|
-
| `showRaw` | `boolean`
|
|
76
|
-
| `outlineWidth` | `number`
|
|
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. |
|
|
77
96
|
|
|
78
97
|
---
|
|
79
98
|
|
|
@@ -82,13 +101,18 @@ All options are optional. The palette defaults to a random 20-color set.
|
|
|
82
101
|
Every constructor option is also a live setter/getter. Assigning any of them re-renders immediately via `requestAnimationFrame`.
|
|
83
102
|
|
|
84
103
|
```js
|
|
85
|
-
viz.palette = [
|
|
104
|
+
viz.palette = [
|
|
105
|
+
[1, 0, 0],
|
|
106
|
+
[0, 1, 0],
|
|
107
|
+
[0, 0, 1],
|
|
108
|
+
];
|
|
86
109
|
viz.position = 0.5;
|
|
87
110
|
viz.colorModel = 'okhslPolar';
|
|
88
111
|
viz.distanceMetric = 'deltaE2000';
|
|
89
|
-
viz.
|
|
112
|
+
viz.invertAxes = ['z'];
|
|
90
113
|
viz.showRaw = true;
|
|
91
114
|
viz.outlineWidth = 2; // transparent border between regions, in physical pixels
|
|
115
|
+
viz.gamutClip = true; // discard out-of-gamut pixels
|
|
92
116
|
viz.pixelRatio = window.devicePixelRatio; // update after display changes
|
|
93
117
|
```
|
|
94
118
|
|
|
@@ -178,11 +202,11 @@ Controls the 3-D color space the visualization is rendered in. Polar variants (`
|
|
|
178
202
|
|
|
179
203
|
**CIE Lab / LCH — D65**
|
|
180
204
|
|
|
181
|
-
| Value | Shape | Description
|
|
182
|
-
| --------------- | ----- |
|
|
183
|
-
| `'cielab'` | cube | CIELab D65: x→a, y→b, z→L. The classic perceptual color space.
|
|
184
|
-
| `'cielch'` | cube | CIELab D65 in cylindrical LCH coordinates.
|
|
185
|
-
| `'cielchPolar'` | wheel | Polar form of CIELch D65.
|
|
205
|
+
| Value | Shape | Description |
|
|
206
|
+
| --------------- | ----- | -------------------------------------------------------------- |
|
|
207
|
+
| `'cielab'` | cube | CIELab D65: x→a, y→b, z→L. The classic perceptual color space. |
|
|
208
|
+
| `'cielch'` | cube | CIELab D65 in cylindrical LCH coordinates. |
|
|
209
|
+
| `'cielchPolar'` | wheel | Polar form of CIELch D65. |
|
|
186
210
|
|
|
187
211
|
**CIE Lab / LCH — D50**
|
|
188
212
|
|
|
@@ -313,7 +337,11 @@ import { paletteToRGBA, randomPalette, fragmentShader } from 'palette-shader';
|
|
|
313
337
|
|
|
314
338
|
// Get raw RGBA bytes (Uint8Array, sRGB, 4 bytes per color)
|
|
315
339
|
// Useful for building your own WebGL texture or processing palette data
|
|
316
|
-
const rgba = paletteToRGBA([
|
|
340
|
+
const rgba = paletteToRGBA([
|
|
341
|
+
[1, 0, 0],
|
|
342
|
+
[0, 1, 0],
|
|
343
|
+
[0, 0, 1],
|
|
344
|
+
]);
|
|
317
345
|
|
|
318
346
|
// Quick random palette for prototyping
|
|
319
347
|
const palette = randomPalette(16);
|
|
@@ -337,7 +365,7 @@ const viz3d = new PaletteViz3D({
|
|
|
337
365
|
palette: ['#264653', '#2a9d8f', '#e9c46a', '#f4a261', '#e76f51'].map(toRGB),
|
|
338
366
|
container: document.querySelector('#app'),
|
|
339
367
|
colorModel: 'okhsv',
|
|
340
|
-
position: 1.0,
|
|
368
|
+
position: 1.0, // 1 = full volume, 0 = fully sliced
|
|
341
369
|
outlineWidth: 2,
|
|
342
370
|
});
|
|
343
371
|
```
|
|
@@ -350,7 +378,7 @@ new PaletteViz3D(options?: PaletteViz3DOptions)
|
|
|
350
378
|
|
|
351
379
|
| Option | Type | Default | Description |
|
|
352
380
|
| ---------------- | ---------------------------- | --------------------- | ---------------------------------------------------------------------------- |
|
|
353
|
-
| `palette` | `[number, number, number][]` | random | sRGB colors as `[r, g, b]`, each in `0–1`
|
|
381
|
+
| `palette` | `[number, number, number][]` | random | sRGB colors as `[r, g, b]`, each in `0–1` |
|
|
354
382
|
| `container` | `HTMLElement` | `undefined` | Element the canvas is appended to |
|
|
355
383
|
| `width` | `number` | `512` | Canvas width in CSS pixels |
|
|
356
384
|
| `height` | `number` | `512` | Canvas height in CSS pixels |
|
|
@@ -358,10 +386,11 @@ new PaletteViz3D(options?: PaletteViz3DOptions)
|
|
|
358
386
|
| `colorModel` | `string` | `'okhsv'` | Color model (see [Color models](#color-models)). Polar → cylinder mesh |
|
|
359
387
|
| `distanceMetric` | `string` | `'oklab'` | Distance metric (see [Distance metrics](#distance-metrics)) |
|
|
360
388
|
| `position` | `number` | `1` | 0–1 slice position. `1` shows the full volume; `0` slices it completely away |
|
|
361
|
-
| `
|
|
389
|
+
| `invertAxes` | `('x' \| 'y' \| 'z')[]` | `[]` | Invert one or more axes, for example `['z']` or `['x', 'z']` |
|
|
362
390
|
| `showRaw` | `boolean` | `false` | Bypass nearest-color matching |
|
|
363
391
|
| `outlineWidth` | `number` | `0` | Transparent outline width (physical px). `0` disables |
|
|
364
|
-
| `
|
|
392
|
+
| `gamutClip` | `boolean` | `false` | Discard out-of-sRGB-gamut pixels instead of clamping |
|
|
393
|
+
| `modelMatrix` | `Float32Array` | slight tilt (default) | Initial 4×4 column-major model rotation matrix |
|
|
365
394
|
|
|
366
395
|
### Properties (3D)
|
|
367
396
|
|
|
@@ -369,13 +398,39 @@ All constructor options except `modelMatrix` are live setter/getters (re-render
|
|
|
369
398
|
|
|
370
399
|
Additional properties:
|
|
371
400
|
|
|
372
|
-
| Property | Type
|
|
373
|
-
| ------------- |
|
|
374
|
-
| `canvas` | `HTMLCanvasElement` | The canvas (read-only)
|
|
375
|
-
| `modelMatrix` | `Float32Array`
|
|
401
|
+
| Property | Type | Description |
|
|
402
|
+
| ------------- | ------------------- | -------------------------------------------------------------- |
|
|
403
|
+
| `canvas` | `HTMLCanvasElement` | The canvas (read-only) |
|
|
404
|
+
| `modelMatrix` | `Float32Array` | Get/set the 4×4 model rotation matrix (copies on read & write) |
|
|
376
405
|
|
|
377
406
|
### Methods (3D)
|
|
378
407
|
|
|
408
|
+
#### `getColorAtUV(x, y)`
|
|
409
|
+
|
|
410
|
+
Returns the rendered color at normalised screen coordinates (`0–1` on both axes, y=0 is top) as `[r, g, b]` in `0–1` sRGB, or `null` if the cursor is over a transparent pixel (i.e. outside the 3D geometry). Flushes any pending rAF frame so the reading is always current.
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
414
|
+
const rect = canvas.getBoundingClientRect();
|
|
415
|
+
const color = viz3d.getColorAtUV(
|
|
416
|
+
(e.clientX - rect.left) / rect.width,
|
|
417
|
+
(e.clientY - rect.top) / rect.height,
|
|
418
|
+
);
|
|
419
|
+
if (color) {
|
|
420
|
+
const hex =
|
|
421
|
+
'#' +
|
|
422
|
+
color
|
|
423
|
+
.map((c) =>
|
|
424
|
+
Math.round(c * 255)
|
|
425
|
+
.toString(16)
|
|
426
|
+
.padStart(2, '0'),
|
|
427
|
+
)
|
|
428
|
+
.join('');
|
|
429
|
+
console.log(hex);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
379
434
|
#### `rotate(dx, dy)`
|
|
380
435
|
|
|
381
436
|
Apply an incremental trackball rotation. `dx` and `dy` are in radians (screen-space). Left-multiplies incremental X/Y rotations onto the accumulated model matrix.
|
|
@@ -413,13 +468,13 @@ const model = mat4Multiply(mat4RotateX(0.4), mat4RotateY(0.6));
|
|
|
413
468
|
viz3d.modelMatrix = model;
|
|
414
469
|
```
|
|
415
470
|
|
|
416
|
-
| Function
|
|
417
|
-
|
|
|
418
|
-
| `mat4Perspective
|
|
419
|
-
| `mat4Multiply`
|
|
420
|
-
| `mat4RotateX`
|
|
421
|
-
| `mat4RotateY`
|
|
422
|
-
| `mat4Translate`
|
|
471
|
+
| Function | Signature | Description |
|
|
472
|
+
| ----------------- | ----------------------------------------- | -------------------------------- |
|
|
473
|
+
| `mat4Perspective` | `(fov, aspect, near, far) → Float32Array` | Perspective projection matrix |
|
|
474
|
+
| `mat4Multiply` | `(a, b) → Float32Array` | Matrix multiplication `a × b` |
|
|
475
|
+
| `mat4RotateX` | `(angle) → Float32Array` | Rotation around X axis (radians) |
|
|
476
|
+
| `mat4RotateY` | `(angle) → Float32Array` | Rotation around Y axis (radians) |
|
|
477
|
+
| `mat4Translate` | `(x, y, z) → Float32Array` | Translation matrix |
|
|
423
478
|
|
|
424
479
|
---
|
|
425
480
|
|
package/dist/palette-shader.d.ts
CHANGED
|
@@ -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,13 @@ 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
|
+
/**
|
|
67
|
+
* Read the rendered colour at normalised screen coordinates (0–1, y=0 is top).
|
|
68
|
+
* Returns [r, g, b] in [0, 1], or null if the pixel is transparent (no geometry).
|
|
69
|
+
* Flushes any pending rAF frame to ensure the reading is up to date.
|
|
70
|
+
*/
|
|
71
|
+
getColorAtUV(x: number, y: number): [number, number, number] | null;
|
|
66
72
|
get canvas(): HTMLCanvasElement;
|
|
67
73
|
get width(): number;
|
|
68
74
|
get height(): number;
|
|
@@ -74,8 +80,8 @@ export declare class PaletteViz3D {
|
|
|
74
80
|
get colorModel(): SupportedColorModels;
|
|
75
81
|
set distanceMetric(metric: DistanceMetric);
|
|
76
82
|
get distanceMetric(): DistanceMetric;
|
|
77
|
-
set
|
|
78
|
-
get
|
|
83
|
+
set invertAxes(value: Axis[]);
|
|
84
|
+
get invertAxes(): Axis[];
|
|
79
85
|
set showRaw(value: boolean);
|
|
80
86
|
get showRaw(): boolean;
|
|
81
87
|
set position(value: number);
|
|
@@ -100,7 +106,7 @@ export declare type PaletteViz3DOptions = {
|
|
|
100
106
|
container?: HTMLElement;
|
|
101
107
|
colorModel?: SupportedColorModels;
|
|
102
108
|
distanceMetric?: DistanceMetric;
|
|
103
|
-
|
|
109
|
+
invertAxes?: Axis[];
|
|
104
110
|
showRaw?: boolean;
|
|
105
111
|
outlineWidth?: number;
|
|
106
112
|
gamutClip?: boolean;
|
|
@@ -118,7 +124,7 @@ export declare type PaletteVizOptions = {
|
|
|
118
124
|
distanceMetric?: DistanceMetric;
|
|
119
125
|
axis?: Axis;
|
|
120
126
|
position?: number;
|
|
121
|
-
|
|
127
|
+
invertAxes?: Axis[];
|
|
122
128
|
showRaw?: boolean;
|
|
123
129
|
outlineWidth?: number;
|
|
124
130
|
gamutClip?: boolean;
|
|
@@ -126,6 +132,6 @@ export declare type PaletteVizOptions = {
|
|
|
126
132
|
|
|
127
133
|
export declare const randomPalette: (size?: number) => ColorList;
|
|
128
134
|
|
|
129
|
-
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';
|
|
130
136
|
|
|
131
137
|
export { }
|