palette-shader 0.16.0 → 0.17.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 +39 -23
- package/dist/palette-shader.d.ts +71 -26
- package/dist/palette-shader.js +956 -730
- package/dist/palette-shader.js.map +1 -1
- package/dist/palette-shader.umd.cjs +269 -46
- package/dist/palette-shader.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/PaletteViz.ts +84 -233
- package/src/PaletteViz3D.ts +129 -297
- package/src/mesh.ts +1 -1
- package/src/rendererShared.ts +260 -0
- package/src/shaderSrc.ts +62 -10
- package/src/shaders/cam16ucs.frag.glsl +180 -0
- package/src/shaders/closestColor.frag.glsl +4 -2
- package/src/types.ts +6 -1
- package/src/webgl.ts +72 -0
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 30+ color models and
|
|
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 eleven 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
|
|
|
@@ -59,6 +59,7 @@ const viz = new PaletteViz({
|
|
|
59
59
|
container: document.querySelector('#app'),
|
|
60
60
|
width: 512,
|
|
61
61
|
height: 512,
|
|
62
|
+
observeResize: false,
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
// option B — no container, place the canvas yourself
|
|
@@ -78,21 +79,22 @@ new PaletteViz(options?: PaletteVizOptions)
|
|
|
78
79
|
|
|
79
80
|
All options are optional. The palette defaults to a random 20-color set.
|
|
80
81
|
|
|
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
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
82
|
+
| Option | Type | Default | Description |
|
|
83
|
+
| ---------------- | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `palette` | `[number, number, number][]` | random | sRGB colors as `[r, g, b]` arrays, each component in the `0–1` range |
|
|
85
|
+
| `container` | `HTMLElement` | `undefined` | Element the canvas is appended to. Omit and use `viz.canvas` to place it yourself |
|
|
86
|
+
| `width` | `number` | `512` | Canvas width in CSS pixels |
|
|
87
|
+
| `height` | `number` | `512` | Canvas height in CSS pixels |
|
|
88
|
+
| `pixelRatio` | `number` | `devicePixelRatio` | Renderer pixel ratio |
|
|
89
|
+
| `observeResize` | `boolean` | `false` | When `true`, a `ResizeObserver` tracks the laid-out canvas size and updates the backing resolution to match CSS layout. When `false`, `width` and `height` are treated as the explicit display size. |
|
|
90
|
+
| `colorModel` | `string` | `'okhsv'` | Color space for the visualization (see [Color models](#color-models)) |
|
|
91
|
+
| `distanceMetric` | `string` | `'oklab'` | Distance function for nearest-color matching (see [Distance metrics](#distance-metrics)) |
|
|
92
|
+
| `axis` | `'x' \| 'y' \| 'z'` | `'y'` | Which axis the `position` value controls |
|
|
93
|
+
| `position` | `number` | `0` | 0–1 position along the chosen axis |
|
|
94
|
+
| `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. |
|
|
95
|
+
| `showRaw` | `boolean` | `false` | Bypass nearest-color matching (shows the raw color space) |
|
|
96
|
+
| `outlineWidth` | `number` | `0` | Draw a transparent outline where palette regions meet. Width in physical pixels. `0` disables (no overhead). |
|
|
97
|
+
| `gamutClip` | `boolean` | `false` | Discard out-of-sRGB-gamut pixels instead of clamping. Reveals the true gamut boundary of the color model. |
|
|
96
98
|
|
|
97
99
|
---
|
|
98
100
|
|
|
@@ -116,6 +118,12 @@ viz.gamutClip = true; // discard out-of-gamut pixels
|
|
|
116
118
|
viz.pixelRatio = window.devicePixelRatio; // update after display changes
|
|
117
119
|
```
|
|
118
120
|
|
|
121
|
+
Sizing model:
|
|
122
|
+
|
|
123
|
+
- By default, `width` and `height` are the explicit display size and backing resolution source.
|
|
124
|
+
- If you want normal CSS layout to control the canvas size, set `observeResize: true` and style the canvas or its container in CSS.
|
|
125
|
+
- `pixelRatio` only affects backing resolution, not layout size.
|
|
126
|
+
|
|
119
127
|
Additional read-only properties:
|
|
120
128
|
|
|
121
129
|
| Property | Type | Description |
|
|
@@ -218,6 +226,13 @@ Controls the 3-D color space the visualization is rendered in. Polar variants (`
|
|
|
218
226
|
| `'cielchD50'` | cube | CIELab D50 in cylindrical LCH coordinates. |
|
|
219
227
|
| `'cielchD50Polar'` | wheel | Polar form of CIELch D50. |
|
|
220
228
|
|
|
229
|
+
**CAM16-UCS — D65**
|
|
230
|
+
|
|
231
|
+
| Value | Shape | Description |
|
|
232
|
+
| -------------------- | ----- | -------------------------------------------------------------------------------------- |
|
|
233
|
+
| `'cam16ucsD65'` | cube | CAM16-UCS under fixed D65 CAT16 viewing conditions, rendered with an analytic inverse. |
|
|
234
|
+
| `'cam16ucsD65Polar'` | wheel | Polar CAM16-UCS view under the same fixed D65 conditions. |
|
|
235
|
+
|
|
221
236
|
**Classic**
|
|
222
237
|
|
|
223
238
|
| Value | Shape | Description |
|
|
@@ -264,14 +279,15 @@ A practical starting point: use a **polar** model to get an intuitive read on hu
|
|
|
264
279
|
|
|
265
280
|
Controls how "nearest palette color" is determined per pixel.
|
|
266
281
|
|
|
267
|
-
**OK**
|
|
282
|
+
**OK / appearance-inspired**
|
|
268
283
|
|
|
269
|
-
| Value | Description
|
|
270
|
-
| --------------- |
|
|
271
|
-
| `'oklab'` | **Default.** Euclidean distance in OKLab. Fast, perceptually uniform, excellent general-purpose choice.
|
|
272
|
-
| `'oklrab'` | Euclidean in OKLab with toe-corrected lightness. Slightly better uniformity in dark tones than OKLab.
|
|
273
|
-
| `'okLightness'` | Absolute lightness difference in OKLab (
|
|
274
|
-
| `'liMatch'` | Spatially varying blend: full OKLab distance at the left edge, pure lightness match at the right. Inspired by censor's li-match.
|
|
284
|
+
| Value | Description | Cost |
|
|
285
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
|
|
286
|
+
| `'oklab'` | **Default.** Euclidean distance in OKLab. Fast, perceptually uniform, excellent general-purpose choice. | low |
|
|
287
|
+
| `'oklrab'` | Euclidean in OKLab with toe-corrected lightness. Slightly better uniformity in dark tones than OKLab. | low |
|
|
288
|
+
| `'okLightness'` | Absolute lightness difference in OKLab (`ΔL`). Ignores hue and chroma, so regions are grouped by brightness only. | low |
|
|
289
|
+
| `'liMatch'` | Spatially varying blend: full OKLab distance at the left edge, pure lightness match at the right. Inspired by censor's li-match. | low |
|
|
290
|
+
| `'cam16ucsD65'` | Euclidean distance in CAM16-UCS under fixed D65 CAT16 viewing conditions. Useful when you want a full appearance-model metric like censor. | high |
|
|
275
291
|
|
|
276
292
|
**CIE — D65**
|
|
277
293
|
|
package/dist/palette-shader.d.ts
CHANGED
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
export declare type Axis = 'x' | 'y' | 'z';
|
|
2
2
|
|
|
3
|
+
declare abstract class BasePaletteRenderer {
|
|
4
|
+
protected paletteState: ColorList;
|
|
5
|
+
protected cssWidth: number;
|
|
6
|
+
protected cssHeight: number;
|
|
7
|
+
protected pixelRatioState: number;
|
|
8
|
+
protected readonly canvasElement: HTMLCanvasElement;
|
|
9
|
+
protected readonly glContext: WebGL2RenderingContext;
|
|
10
|
+
protected readonly paletteTexture: WebGLTexture;
|
|
11
|
+
protected readonly metricTexture: WebGLTexture;
|
|
12
|
+
protected metricPaletteDirty: boolean;
|
|
13
|
+
protected animationFrameId: number | null;
|
|
14
|
+
protected destroyed: boolean;
|
|
15
|
+
protected readonly containerElement?: HTMLElement;
|
|
16
|
+
protected readonly observeResize: boolean;
|
|
17
|
+
protected resizeObserver: ResizeObserver | null;
|
|
18
|
+
protected constructor({ palette, width, height, pixelRatio, observeResize, container, canvasClassName, }: BaseRendererOptions);
|
|
19
|
+
protected normalizeInvertAxes(axes: Axis[]): Axis[];
|
|
20
|
+
protected syncCanvasSize(width: number, height: number): {
|
|
21
|
+
pw: number;
|
|
22
|
+
ph: number;
|
|
23
|
+
};
|
|
24
|
+
protected syncCanvasSizeFromLayout(): {
|
|
25
|
+
pw: number;
|
|
26
|
+
ph: number;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
30
|
+
protected schedulePaint(): void;
|
|
31
|
+
protected flushScheduledPaint(): void;
|
|
32
|
+
protected uploadMetricPalette(paletteSizeUniform: WebGLUniformLocation | null): void;
|
|
33
|
+
protected attachCanvas(): void;
|
|
34
|
+
protected beginDestroy(): boolean;
|
|
35
|
+
protected destroyBaseResources(): void;
|
|
36
|
+
protected onSurfaceResized(_pw: number, _ph: number): void;
|
|
37
|
+
protected abstract currentMetricCode(): number;
|
|
38
|
+
protected abstract renderFrame(): void;
|
|
39
|
+
get canvas(): HTMLCanvasElement;
|
|
40
|
+
get width(): number;
|
|
41
|
+
get height(): number;
|
|
42
|
+
resize(width: number, height?: number | null): void;
|
|
43
|
+
set palette(palette: ColorList);
|
|
44
|
+
get palette(): ColorList;
|
|
45
|
+
set pixelRatio(value: number);
|
|
46
|
+
get pixelRatio(): number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare type BaseRendererOptions = {
|
|
50
|
+
palette: ColorList;
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
pixelRatio: number;
|
|
54
|
+
observeResize: boolean;
|
|
55
|
+
container?: HTMLElement;
|
|
56
|
+
canvasClassName: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
3
59
|
export declare type ColorList = ColorRGB[];
|
|
4
60
|
|
|
5
61
|
export declare type ColorRGB = [number, number, number];
|
|
6
62
|
|
|
7
|
-
export declare type DistanceMetric = 'rgb' | 'oklab' | 'deltaE76' | 'deltaE94' | 'deltaE2000' | 'kotsarenkoRamos' | 'oklrab' | 'cielabD50' | 'okLightness' | 'liMatch';
|
|
63
|
+
export declare type DistanceMetric = 'rgb' | 'oklab' | 'deltaE76' | 'deltaE94' | 'deltaE2000' | 'kotsarenkoRamos' | 'oklrab' | 'cielabD50' | 'okLightness' | 'liMatch' | 'cam16ucsD65';
|
|
8
64
|
|
|
9
65
|
export declare const fragmentShader: string;
|
|
10
66
|
|
|
@@ -22,16 +78,13 @@ export declare const paletteToRGBA: (palette: ColorList) => Uint8Array;
|
|
|
22
78
|
|
|
23
79
|
export declare const paletteToTexture: (palette: ColorList) => Uint8Array;
|
|
24
80
|
|
|
25
|
-
export declare class PaletteViz {
|
|
81
|
+
export declare class PaletteViz extends BasePaletteRenderer {
|
|
26
82
|
#private;
|
|
27
|
-
constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, axis, position, invertAxes, showRaw, outlineWidth, gamutClip, }?: PaletteVizOptions);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
resize(width: number, height?: number | null): void;
|
|
83
|
+
constructor({ palette, width, height, pixelRatio, observeResize, container, colorModel, distanceMetric, axis, position, invertAxes, showRaw, outlineWidth, gamutClip, }?: PaletteVizOptions);
|
|
84
|
+
protected currentMetricCode(): number;
|
|
85
|
+
protected onSurfaceResized(pw: number, ph: number): void;
|
|
86
|
+
protected renderFrame(): void;
|
|
32
87
|
destroy(): void;
|
|
33
|
-
set palette(palette: ColorList);
|
|
34
|
-
get palette(): ColorList;
|
|
35
88
|
setColor(color: ColorRGB, index: number): void;
|
|
36
89
|
addColor(color: ColorRGB, index?: number): void;
|
|
37
90
|
removeColor(index: number): void;
|
|
@@ -49,8 +102,6 @@ export declare class PaletteViz {
|
|
|
49
102
|
get invertAxes(): Axis[];
|
|
50
103
|
set showRaw(value: boolean);
|
|
51
104
|
get showRaw(): boolean;
|
|
52
|
-
set pixelRatio(value: number);
|
|
53
|
-
get pixelRatio(): number;
|
|
54
105
|
set gamutClip(value: boolean);
|
|
55
106
|
get gamutClip(): boolean;
|
|
56
107
|
set outlineWidth(value: number);
|
|
@@ -60,22 +111,15 @@ export declare class PaletteViz {
|
|
|
60
111
|
static paletteToTexture: (palette: ColorList) => Uint8Array;
|
|
61
112
|
}
|
|
62
113
|
|
|
63
|
-
export declare class PaletteViz3D {
|
|
114
|
+
export declare class PaletteViz3D extends BasePaletteRenderer {
|
|
64
115
|
#private;
|
|
65
|
-
constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, invertAxes, showRaw, outlineWidth, gamutClip, position, modelMatrix, }?: PaletteViz3DOptions);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* Flushes any pending rAF frame to ensure the reading is up to date.
|
|
70
|
-
*/
|
|
116
|
+
constructor({ palette, width, height, pixelRatio, observeResize, container, colorModel, distanceMetric, invertAxes, showRaw, outlineWidth, gamutClip, position, modelMatrix, }?: PaletteViz3DOptions);
|
|
117
|
+
protected currentMetricCode(): number;
|
|
118
|
+
protected onSurfaceResized(pw: number, ph: number): void;
|
|
119
|
+
protected renderFrame(): void;
|
|
71
120
|
getColorAtUV(x: number, y: number): [number, number, number] | null;
|
|
72
|
-
get canvas(): HTMLCanvasElement;
|
|
73
|
-
get width(): number;
|
|
74
|
-
get height(): number;
|
|
75
121
|
resize(width: number, height?: number | null): void;
|
|
76
122
|
destroy(): void;
|
|
77
|
-
set palette(palette: ColorList);
|
|
78
|
-
get palette(): ColorList;
|
|
79
123
|
set colorModel(model: SupportedColorModels);
|
|
80
124
|
get colorModel(): SupportedColorModels;
|
|
81
125
|
set distanceMetric(metric: DistanceMetric);
|
|
@@ -86,9 +130,8 @@ export declare class PaletteViz3D {
|
|
|
86
130
|
get showRaw(): boolean;
|
|
87
131
|
set position(value: number);
|
|
88
132
|
get position(): number;
|
|
89
|
-
/** Apply an incremental spherical rotation (screen-space dx/dy in radians). */
|
|
90
133
|
rotate(dx: number, dy: number): void;
|
|
91
|
-
set modelMatrix(
|
|
134
|
+
set modelMatrix(matrix: Float32Array);
|
|
92
135
|
get modelMatrix(): Float32Array;
|
|
93
136
|
set gamutClip(value: boolean);
|
|
94
137
|
get gamutClip(): boolean;
|
|
@@ -103,6 +146,7 @@ export declare type PaletteViz3DOptions = {
|
|
|
103
146
|
width?: number;
|
|
104
147
|
height?: number;
|
|
105
148
|
pixelRatio?: number;
|
|
149
|
+
observeResize?: boolean;
|
|
106
150
|
container?: HTMLElement;
|
|
107
151
|
colorModel?: SupportedColorModels;
|
|
108
152
|
distanceMetric?: DistanceMetric;
|
|
@@ -119,6 +163,7 @@ export declare type PaletteVizOptions = {
|
|
|
119
163
|
width?: number;
|
|
120
164
|
height?: number;
|
|
121
165
|
pixelRatio?: number;
|
|
166
|
+
observeResize?: boolean;
|
|
122
167
|
container?: HTMLElement;
|
|
123
168
|
colorModel?: SupportedColorModels;
|
|
124
169
|
distanceMetric?: DistanceMetric;
|
|
@@ -132,6 +177,6 @@ export declare type PaletteVizOptions = {
|
|
|
132
177
|
|
|
133
178
|
export declare const randomPalette: (size?: number) => ColorList;
|
|
134
179
|
|
|
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';
|
|
180
|
+
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' | 'cam16ucsD65' | 'cam16ucsD65Polar' | 'spectrum' | 'oklchDiag' | 'oklrchDiag';
|
|
136
181
|
|
|
137
182
|
export { }
|