palette-shader 0.3.0 → 0.5.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 CHANGED
@@ -56,19 +56,20 @@ new PaletteViz(options?: PaletteVizOptions)
56
56
 
57
57
  All options are optional. The palette defaults to a random 20-colour set.
58
58
 
59
- | Option | Type | Default | Description |
60
- | ----------------- | ------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
61
- | `palette` | `string[]` | random | CSS colour strings (`#hex`, `rgb()`, `hsl()`, …) |
62
- | `container` | `HTMLElement` | `undefined` | Element the canvas is appended to. Omit and use `viz.canvas` to place it yourself |
63
- | `width` | `number` | `512` | Canvas width in CSS pixels |
64
- | `height` | `number` | `512` | Canvas height in CSS pixels |
65
- | `pixelRatio` | `number` | `devicePixelRatio` | Renderer pixel ratio |
66
- | `colorModel` | `string` | `'okhsv'` | Colour space for the visualisation (see [Colour models](#colour-models)) |
67
- | `distanceMetric` | `string` | `'oklab'` | Distance function for nearest-colour matching (see [Distance metrics](#distance-metrics)) |
68
- | `axis` | `'x' \| 'y' \| 'z'` | `'y'` | Which axis the `position` value controls |
69
- | `position` | `number` | `0` | 0–1 position along the chosen axis |
70
- | `invertLightness` | `boolean` | `false` | Flip the lightness/value axis |
71
- | `showRaw` | `boolean` | `false` | Bypass nearest-colour matching (shows the raw colour space) |
59
+ | Option | Type | Default | Description |
60
+ | ---------------- | ------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
61
+ | `palette` | `string[]` | random | CSS colour strings (`#hex`, `rgb()`, `hsl()`, …) |
62
+ | `container` | `HTMLElement` | `undefined` | Element the canvas is appended to. Omit and use `viz.canvas` to place it yourself |
63
+ | `width` | `number` | `512` | Canvas width in CSS pixels |
64
+ | `height` | `number` | `512` | Canvas height in CSS pixels |
65
+ | `pixelRatio` | `number` | `devicePixelRatio` | Renderer pixel ratio |
66
+ | `colorModel` | `string` | `'okhsv'` | Colour space for the visualisation (see [Colour models](#colour-models)) |
67
+ | `distanceMetric` | `string` | `'oklab'` | Distance function for nearest-colour matching (see [Distance metrics](#distance-metrics)) |
68
+ | `axis` | `'x' \| 'y' \| 'z'` | `'y'` | Which axis the `position` value controls |
69
+ | `position` | `number` | `0` | 0–1 position along the chosen axis |
70
+ | `invertZ` | `boolean` | `false` | Flip the lightness/value axis |
71
+ | `showRaw` | `boolean` | `false` | Bypass nearest-colour matching (shows the raw colour space) |
72
+ | `outlineWidth` | `number` | `0` | Draw a transparent outline where palette regions meet. Width in physical pixels. `0` disables (no overhead). |
72
73
 
73
74
  ---
74
75
 
@@ -81,8 +82,9 @@ viz.palette = ['#ff0000', '#00ff00', '#0000ff'];
81
82
  viz.position = 0.5;
82
83
  viz.colorModel = 'okhslPolar';
83
84
  viz.distanceMetric = 'deltaE2000';
84
- viz.invertLightness = true;
85
+ viz.invertZ = true;
85
86
  viz.showRaw = true;
87
+ viz.outlineWidth = 2; // transparent border between regions, in physical pixels
86
88
  ```
87
89
 
88
90
  Additional read-only properties:
@@ -133,7 +135,7 @@ viz.removeColor('#a8dadc');
133
135
 
134
136
  ### `destroy()`
135
137
 
136
- Cancel the animation frame, release all WebGL resources (program, texture, buffer, VAO), and remove the canvas from the DOM.
138
+ Cancel the animation frame, release all WebGL resources (programs, textures, framebuffer, buffer, VAO), and remove the canvas from the DOM.
137
139
 
138
140
  ---
139
141
 
@@ -217,6 +219,28 @@ document.querySelector('#slider').addEventListener('input', (e) => {
217
219
  });
218
220
  ```
219
221
 
222
+ ### Transparent outlines between regions
223
+
224
+ `outlineWidth` draws a transparent gap where one palette colour's region meets another, revealing whatever is behind the canvas. Width is in physical pixels (i.e. it already accounts for `pixelRatio`).
225
+
226
+ ```js
227
+ const viz = new PaletteViz({
228
+ palette,
229
+ outlineWidth: 2,
230
+ container: document.querySelector('#app'),
231
+ });
232
+
233
+ // change at runtime — no shader recompile while the value stays > 0
234
+ viz.outlineWidth = 4;
235
+
236
+ // set back to 0 to disable entirely (zero GPU overhead)
237
+ viz.outlineWidth = 0;
238
+ ```
239
+
240
+ Implemented as a two-pass render: pass 1 draws the colour regions into an offscreen framebuffer at the same cost as without outlines; pass 2 runs a tiny edge-detection shader that checks four neighbours via texture reads (no colour-space math). The result is that enabling outlines adds negligible overhead compared to the single-pass approach.
241
+
242
+ When `outlineWidth` is `0` (the default) the framebuffer and outline program are never allocated.
243
+
220
244
  ### Utility exports
221
245
 
222
246
  ```js
@@ -14,7 +14,7 @@ export declare const paletteToTexture: (palette: ColorList) => Uint8Array;
14
14
 
15
15
  export declare class PaletteViz {
16
16
  #private;
17
- constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, axis, position, invertLightness, showRaw, }?: PaletteVizOptions);
17
+ constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, axis, position, invertZ, showRaw, }?: PaletteVizOptions);
18
18
  get canvas(): HTMLCanvasElement;
19
19
  get width(): number;
20
20
  get height(): number;
@@ -34,8 +34,8 @@ export declare class PaletteViz {
34
34
  get colorModel(): SupportedColorModels;
35
35
  set distanceMetric(metric: DistanceMetric);
36
36
  get distanceMetric(): DistanceMetric;
37
- set invertLightness(value: boolean);
38
- get invertLightness(): boolean;
37
+ set invertZ(value: boolean);
38
+ get invertZ(): boolean;
39
39
  set showRaw(value: boolean);
40
40
  get showRaw(): boolean;
41
41
  static paletteToRGBA: (palette: ColorList) => Uint8Array;
@@ -53,7 +53,7 @@ declare type PaletteVizOptions = {
53
53
  distanceMetric?: DistanceMetric;
54
54
  axis?: Axis;
55
55
  position?: number;
56
- invertLightness?: boolean;
56
+ invertZ?: boolean;
57
57
  showRaw?: boolean;
58
58
  };
59
59
 
@@ -1085,7 +1085,7 @@ class O {
1085
1085
  distanceMetric: b = "oklab",
1086
1086
  axis: l = "y",
1087
1087
  position: s = 0,
1088
- invertLightness: h = !1,
1088
+ invertZ: h = !1,
1089
1089
  showRaw: m = !1
1090
1090
  } = {}) {
1091
1091
  this.#t = t, this.#f = e, this.#i = a, this.#g = r, this.#b = i, this.#d = b, this.#c = l, this.#_ = s, this.#h = h, this.#p = m, this.#k = f, this.#e = document.createElement("canvas"), this.#e.classList.add("palette-viz");
@@ -1185,10 +1185,10 @@ class O {
1185
1185
  get distanceMetric() {
1186
1186
  return this.#d;
1187
1187
  }
1188
- set invertLightness(t) {
1188
+ set invertZ(t) {
1189
1189
  this.#h = t, this.#s(), this.#n();
1190
1190
  }
1191
- get invertLightness() {
1191
+ get invertZ() {
1192
1192
  return this.#h;
1193
1193
  }
1194
1194
  set showRaw(t) {
@@ -1 +1 @@
1
- {"version":3,"file":"palette-shader.js","sources":["../src/shaders/srgb2rgb.frag.glsl?raw","../src/shaders/oklab.frag.glsl?raw","../src/shaders/hsl2rgb.frag.glsl?raw","../src/shaders/hsv2rgb.frag.glsl?raw","../src/shaders/lch2rgb.frag.glsl?raw","../src/shaders/deltaE.frag.glsl?raw","../src/shaders/closestColor.frag.glsl?raw","../src/index.ts"],"sourcesContent":["export default \"// https://lygia.xyz/\\nfloat srgb2rgb(const in float v) { return (v < 0.04045) ? v * 0.0773993808 : pow((v + 0.055) * 0.947867298578199, 2.4); }\\nvec3 srgb2rgb(const in vec3 srgb) { return vec3(srgb2rgb(srgb.r), srgb2rgb(srgb.g), srgb2rgb(srgb.b)); }\\nvec4 srgb2rgb(const in vec4 srgb) { return vec4(srgb2rgb(srgb.rgb), srgb.a); }\\n\"","export default \"// Copyright(c) 2021 Björn Ottosson\\n//\\n// Permission is hereby granted, free of charge, to any person obtaining a copy of\\n// this softwareand associated documentation files(the \\\"Software\\\"), to deal in\\n// the Software without restriction, including without limitation the rights to\\n// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies\\n// of the Software, and to permit persons to whom the Software is furnished to do\\n// so, subject to the following conditions :\\n// The above copyright noticeand this permission notice shall be included in all\\n// copies or substantial portions of the Software.\\n// THE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE\\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\n// SOFTWARE.\\n\\n#define M_PI 3.1415926535897932384626433832795\\n\\nfloat cbrt( float x )\\n{\\n return sign(x)*pow(abs(x),1.0f/3.0f);\\n}\\n\\nfloat srgb_transfer_function(float a)\\n{\\n\\treturn .0031308f >= a ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f;\\n}\\n\\nfloat srgb_transfer_function_inv(float a)\\n{\\n\\treturn .04045f < a ? pow((a + .055f) / 1.055f, 2.4f) : a / 12.92f;\\n}\\n\\nvec3 linear_srgb_to_oklab(vec3 c)\\n{\\n\\tfloat l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;\\n\\tfloat m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;\\n\\tfloat s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;\\n\\n\\tfloat l_ = cbrt(l);\\n\\tfloat m_ = cbrt(m);\\n\\tfloat s_ = cbrt(s);\\n\\n\\treturn vec3(\\n\\t\\t0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,\\n\\t\\t1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,\\n\\t\\t0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_\\n\\t);\\n}\\n\\nvec3 oklab_to_linear_srgb(vec3 c)\\n{\\n\\tfloat l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;\\n\\tfloat m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;\\n\\tfloat s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;\\n\\n\\tfloat l = l_ * l_ * l_;\\n\\tfloat m = m_ * m_ * m_;\\n\\tfloat s = s_ * s_ * s_;\\n\\n\\treturn vec3(\\n\\t\\t+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,\\n\\t\\t-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,\\n\\t\\t-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s\\n\\t);\\n}\\n\\n// Finds the maximum saturation possible for a given hue that fits in sRGB\\n// Saturation here is defined as S = C/L\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat compute_max_saturation(float a, float b)\\n{\\n\\t// Max saturation will be when one of r, g or b goes below zero.\\n\\n\\t// Select different coefficients depending on which component goes below zero first\\n\\tfloat k0, k1, k2, k3, k4, wl, wm, ws;\\n\\n\\tif (-1.88170328f * a - 0.80936493f * b > 1.f)\\n\\t{\\n\\t\\t// Red component\\n\\t\\tk0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;\\n\\t\\twl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;\\n\\t}\\n\\telse if (1.81444104f * a - 1.19445276f * b > 1.f)\\n\\t{\\n\\t\\t// Green component\\n\\t\\tk0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;\\n\\t\\twl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Blue component\\n\\t\\tk0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;\\n\\t\\twl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;\\n\\t}\\n\\n\\t// Approximate max saturation using a polynomial:\\n\\tfloat S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;\\n\\n\\t// Do one step Halley's method to get closer\\n\\t// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite\\n\\t// this should be sufficient for most applications, otherwise do two/three steps \\n\\n\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t{\\n\\t\\tfloat l_ = 1.f + S * k_l;\\n\\t\\tfloat m_ = 1.f + S * k_m;\\n\\t\\tfloat s_ = 1.f + S * k_s;\\n\\n\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\tfloat l_dS = 3.f * k_l * l_ * l_;\\n\\t\\tfloat m_dS = 3.f * k_m * m_ * m_;\\n\\t\\tfloat s_dS = 3.f * k_s * s_ * s_;\\n\\n\\t\\tfloat l_dS2 = 6.f * k_l * k_l * l_;\\n\\t\\tfloat m_dS2 = 6.f * k_m * k_m * m_;\\n\\t\\tfloat s_dS2 = 6.f * k_s * k_s * s_;\\n\\n\\t\\tfloat f = wl * l + wm * m + ws * s;\\n\\t\\tfloat f1 = wl * l_dS + wm * m_dS + ws * s_dS;\\n\\t\\tfloat f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;\\n\\n\\t\\tS = S - f * f1 / (f1 * f1 - 0.5f * f * f2);\\n\\t}\\n\\n\\treturn S;\\n}\\n\\n// finds L_cusp and C_cusp for a given hue\\n// a and b must be normalized so a^2 + b^2 == 1\\nvec2 find_cusp(float a, float b)\\n{\\n\\t// First, find the maximum saturation (saturation S = C/L)\\n\\tfloat S_cusp = compute_max_saturation(a, b);\\n\\n\\t// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:\\n\\tvec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b ));\\n\\tfloat L_cusp = cbrt(1.f / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));\\n\\tfloat C_cusp = L_cusp * S_cusp;\\n\\n\\treturn vec2( L_cusp , C_cusp );\\n}\\n\\n// Finds intersection of the line defined by \\n// L = L0 * (1 - t) + t * L1;\\n// C = t * C1;\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0, vec2 cusp)\\n{\\n\\t// Find the intersection for upper and lower half seprately\\n\\tfloat t;\\n\\tif (((L1 - L0) * cusp.y - (cusp.x - L0) * C1) <= 0.f)\\n\\t{\\n\\t\\t// Lower half\\n\\n\\t\\tt = cusp.y * L0 / (C1 * cusp.x + cusp.y * (L0 - L1));\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Upper half\\n\\n\\t\\t// First intersect with triangle\\n\\t\\tt = cusp.y * (L0 - 1.f) / (C1 * (cusp.x - 1.f) + cusp.y * (L0 - L1));\\n\\n\\t\\t// Then one step Halley's method\\n\\t\\t{\\n\\t\\t\\tfloat dL = L1 - L0;\\n\\t\\t\\tfloat dC = C1;\\n\\n\\t\\t\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\t\\t\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\t\\t\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t\\t\\tfloat l_dt = dL + dC * k_l;\\n\\t\\t\\tfloat m_dt = dL + dC * k_m;\\n\\t\\t\\tfloat s_dt = dL + dC * k_s;\\n\\n\\n\\t\\t\\t// If higher accuracy is required, 2 or 3 iterations of the following block can be used:\\n\\t\\t\\t{\\n\\t\\t\\t\\tfloat L = L0 * (1.f - t) + t * L1;\\n\\t\\t\\t\\tfloat C = t * C1;\\n\\n\\t\\t\\t\\tfloat l_ = L + C * k_l;\\n\\t\\t\\t\\tfloat m_ = L + C * k_m;\\n\\t\\t\\t\\tfloat s_ = L + C * k_s;\\n\\n\\t\\t\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\t\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\t\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt = 3.f * l_dt * l_ * l_;\\n\\t\\t\\t\\tfloat mdt = 3.f * m_dt * m_ * m_;\\n\\t\\t\\t\\tfloat sdt = 3.f * s_dt * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt2 = 6.f * l_dt * l_dt * l_;\\n\\t\\t\\t\\tfloat mdt2 = 6.f * m_dt * m_dt * m_;\\n\\t\\t\\t\\tfloat sdt2 = 6.f * s_dt * s_dt * s_;\\n\\n\\t\\t\\t\\tfloat r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1.f;\\n\\t\\t\\t\\tfloat r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;\\n\\t\\t\\t\\tfloat r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_r = r1 / (r1 * r1 - 0.5f * r * r2);\\n\\t\\t\\t\\tfloat t_r = -r * u_r;\\n\\n\\t\\t\\t\\tfloat g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1.f;\\n\\t\\t\\t\\tfloat g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;\\n\\t\\t\\t\\tfloat g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_g = g1 / (g1 * g1 - 0.5f * g * g2);\\n\\t\\t\\t\\tfloat t_g = -g * u_g;\\n\\n\\t\\t\\t\\tfloat b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1.f;\\n\\t\\t\\t\\tfloat b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;\\n\\t\\t\\t\\tfloat b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_b = b1 / (b1 * b1 - 0.5f * b * b2);\\n\\t\\t\\t\\tfloat t_b = -b * u_b;\\n\\n\\t\\t\\t\\tt_r = u_r >= 0.f ? t_r : 10000.f;\\n\\t\\t\\t\\tt_g = u_g >= 0.f ? t_g : 10000.f;\\n\\t\\t\\t\\tt_b = u_b >= 0.f ? t_b : 10000.f;\\n\\n\\t\\t\\t\\tt += min(t_r, min(t_g, t_b));\\n\\t\\t\\t}\\n\\t\\t}\\n\\t}\\n\\n\\treturn t;\\n}\\n\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0)\\n{\\n\\t// Find the cusp of the gamut triangle\\n\\tvec2 cusp = find_cusp(a, b);\\n\\n\\treturn find_gamut_intersection(a, b, L1, C1, L0, cusp);\\n}\\n\\nvec3 gamut_clip_preserve_chroma(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = clamp(L, 0.f, 1.f);\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_0_5(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = 0.5;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_L_cusp(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat L0 = cusp.x;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_0_5(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat Ld = L - 0.5f;\\n\\tfloat e1 = 0.5f + abs(Ld) + alpha * C;\\n\\tfloat L0 = 0.5f * (1.f + sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_L_cusp(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat Ld = L - cusp.x;\\n\\tfloat k = 2.f * (Ld > 0.f ? 1.f - cusp.x : cusp.x);\\n\\n\\tfloat e1 = 0.5f * k + abs(Ld) + alpha * C / k;\\n\\tfloat L0 = cusp.x + 0.5f * (sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * k * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nfloat toe(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn 0.5f * (k_3 * x - k_1 + sqrt((k_3 * x - k_1) * (k_3 * x - k_1) + 4.f * k_2 * k_3 * x));\\n}\\n\\nfloat toe_inv(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn (x * x + k_1 * x) / (k_3 * (x + k_2));\\n}\\n\\nvec2 to_ST(vec2 cusp)\\n{\\n\\tfloat L = cusp.x;\\n\\tfloat C = cusp.y;\\n\\treturn vec2( C / L, C / (1.f - L) );\\n}\\n\\n// Returns a smooth approximation of the location of the cusp\\n// This polynomial was created by an optimization process\\n// It has been designed so that S_mid < S_max and T_mid < T_max\\nvec2 get_ST_mid(float a_, float b_)\\n{\\n\\tfloat S = 0.11516993f + 1.f / (\\n\\t\\t+7.44778970f + 4.15901240f * b_\\n\\t\\t+ a_ * (-2.19557347f + 1.75198401f * b_\\n\\t\\t\\t+ a_ * (-2.13704948f - 10.02301043f * b_\\n\\t\\t\\t\\t+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\tfloat T = 0.11239642f + 1.f / (\\n\\t\\t+1.61320320f - 0.68124379f * b_\\n\\t\\t+ a_ * (+0.40370612f + 0.90148123f * b_\\n\\t\\t\\t+ a_ * (-0.27087943f + 0.61223990f * b_\\n\\t\\t\\t\\t+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\treturn vec2( S, T );\\n}\\n\\nvec3 get_Cs(float L, float a_, float b_)\\n{\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat C_max = find_gamut_intersection(a_, b_, L, 1.f, L, cusp);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\t\\n\\t// Scale factor to compensate for the curved part of gamut shape:\\n\\tfloat k = C_max / min((L * ST_max.x), (1.f - L) * ST_max.y);\\n\\n\\tfloat C_mid;\\n\\t{\\n\\t\\tvec2 ST_mid = get_ST_mid(a_, b_);\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tfloat C_a = L * ST_mid.x;\\n\\t\\tfloat C_b = (1.f - L) * ST_mid.y;\\n\\t\\tC_mid = 0.9f * k * sqrt(sqrt(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));\\n\\t}\\n\\n\\tfloat C_0;\\n\\t{\\n\\t\\t// for C_0, the shape is independent of hue, so vec2 are constant. Values picked to roughly be the average values of vec2.\\n\\t\\tfloat C_a = L * 0.4f;\\n\\t\\tfloat C_b = (1.f - L) * 0.8f;\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tC_0 = sqrt(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));\\n\\t}\\n\\n\\treturn vec3( C_0, C_mid, C_max );\\n}\\n\\nvec3 okhsl_to_srgb(vec3 hsl)\\n{\\n\\tfloat h = hsl.x;\\n\\tfloat s = hsl.y;\\n\\tfloat l = hsl.z;\\n\\n\\tif (l == 1.0f)\\n\\t{\\n\\t\\treturn vec3( 1.f, 1.f, 1.f );\\n\\t}\\n\\n\\telse if (l == 0.f)\\n\\t{\\n\\t\\treturn vec3( 0.f, 0.f, 0.f );\\n\\t}\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\tfloat L = toe_inv(l);\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat C, t, k_0, k_1, k_2;\\n\\n\\tif (s < mid)\\n\\t{\\n\\t\\tt = mid_inv * s;\\n\\n\\t\\tk_1 = mid * C_0;\\n\\t\\tk_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tC = t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tt = (s - mid)/ (1.f - mid);\\n\\n\\t\\tk_0 = C_mid;\\n\\t\\tk_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tk_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tC = k_0 + t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsl(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\t// Inverse of the interpolation in okhsl_to_srgb:\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat s;\\n\\tif (C < C_mid)\\n\\t{\\n\\t\\tfloat k_1 = mid * C_0;\\n\\t\\tfloat k_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tfloat t = C / (k_1 + k_2 * C);\\n\\t\\ts = t * mid;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tfloat k_0 = C_mid;\\n\\t\\tfloat k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tfloat k_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tfloat t = (C - k_0) / (k_1 + k_2 * (C - k_0));\\n\\t\\ts = mid + (1.f - mid) * t;\\n\\t}\\n\\n\\tfloat l = toe(L);\\n\\treturn vec3( h, s, l );\\n}\\n\\n\\nvec3 okhsv_to_srgb(vec3 hsv)\\n{\\n\\tfloat h = hsv.x;\\n\\tfloat s = hsv.y;\\n\\tfloat v = hsv.z;\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\t\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f- S_0 / S_max;\\n\\n\\t// first we compute L and V as if the gamut is a perfect triangle:\\n\\n\\t// L, C when v==1:\\n\\tfloat L_v = 1.f - s * S_0 / (S_0 + T_max - T_max * k * s);\\n\\tfloat C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);\\n\\n\\tfloat L = v * L_v;\\n\\tfloat C = v * C_v;\\n\\n\\t// then we compensate for both toe and the curved top part of the triangle:\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\tfloat L_new = toe_inv(L);\\n\\tC = C * L_new / L;\\n\\tL = L_new;\\n\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L * scale_L;\\n\\tC = C * scale_L;\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsv(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f - S_0 / S_max;\\n\\n\\t// first we find L_v, C_v, L_vt and C_vt\\n\\n\\tfloat t = T_max / (C + L * T_max);\\n\\tfloat L_v = t * L;\\n\\tfloat C_v = t * C;\\n\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\t// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L / scale_L;\\n\\tC = C / scale_L;\\n\\n\\tC = C * toe(L) / L;\\n\\tL = toe(L);\\n\\n\\t// we can now compute v and s:\\n\\n\\tfloat v = L / L_v;\\n\\tfloat s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);\\n\\n\\treturn vec3 (h, s, v );\\n}\"","export default \"vec3 hsl2rgb( in vec3 c ) {\\n vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );\\n return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));\\n}\"","export default \"vec3 hsv2rgb(vec3 c) {\\n vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\\n vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\\n return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\\n}\"","export default \"// slightly rearranged vector components so it matches with LCH\\n// M_PI and srgb_transfer_function are provided by oklab.frag.glsl (included before this file)\\nvec3 lch2rgb(vec3 lch) {\\n lch.y *= 0.34;\\n\\n vec3 lab = vec3(\\n lch.x,\\n lch.y * cos(lch.z * M_PI*2.0),\\n lch.y * sin(lch.z * M_PI*2.0)\\n );\\n\\n vec3 lms = vec3(\\n lab.x + 0.3963377774f * lab.y + 0.2158037573f * lab.z,\\n lab.x - 0.1055613458f * lab.y - 0.0638541728f * lab.z,\\n lab.x - 0.0894841775f * lab.y - 1.2914855480f * lab.z\\n );\\n\\n lms = pow(max(lms, vec3(0.0)), vec3(3.0));\\n\\n vec3 rgb = vec3(\\n +4.0767416621f * lms.x - 3.3077115913f * lms.y + 0.2309699292f * lms.z,\\n -1.2684380046f * lms.x + 2.6097574011f * lms.y - 0.3413193965f * lms.z,\\n -0.0041960863f * lms.x - 0.7034186147f * lms.y + 1.7076147010f * lms.z\\n );\\n\\n return vec3(\\n srgb_transfer_function(rgb.r),\\n srgb_transfer_function(rgb.g),\\n srgb_transfer_function(rgb.b)\\n );\\n}\\n\"","export default \"// Kotsarenko/Ramos weighted RGB distance.\\n// Operates on sRGB values directly (no linearisation needed).\\n// Weights red and blue channels by the mean red value, which improves\\n// perceptual uniformity compared to plain Euclidean RGB at minimal cost.\\nfloat kotsarenkoRamos(vec3 c1, vec3 c2) {\\n float rMean = (c1.r + c2.r) * 0.5;\\n vec3 d = c1 - c2;\\n return sqrt((2.0 + rMean) * d.r*d.r + 4.0 * d.g*d.g + (3.0 - rMean) * d.b*d.b);\\n}\\n\\n// ── CIELab ────────────────────────────────────────────────────────────────────\\n// sRGB -> XYZ (D65) -> CIELab\\n// Depends on: srgb2rgb() from srgb2rgb.frag.glsl, cbrt() from oklab.frag.glsl\\n\\nfloat _lab_f(float t) {\\n float delta = 6.0 / 29.0;\\n return t > delta * delta * delta\\n ? cbrt(t)\\n : t / (3.0 * delta * delta) + 4.0 / 29.0;\\n}\\n\\nvec3 srgb_to_cielab(vec3 srgb) {\\n vec3 lin = srgb2rgb(srgb);\\n\\n // Linear sRGB -> XYZ (D65 illuminant)\\n vec3 xyz = vec3(\\n 0.4124564 * lin.r + 0.3575761 * lin.g + 0.1804375 * lin.b,\\n 0.2126729 * lin.r + 0.7151522 * lin.g + 0.0721750 * lin.b,\\n 0.0193339 * lin.r + 0.1191920 * lin.g + 0.9503041 * lin.b\\n );\\n\\n // XYZ -> Lab (D65 white point: 0.95047, 1.00000, 1.08883)\\n float fx = _lab_f(xyz.x / 0.95047);\\n float fy = _lab_f(xyz.y);\\n float fz = _lab_f(xyz.z / 1.08883);\\n\\n return vec3(\\n 116.0 * fy - 16.0, // L*\\n 500.0 * (fx - fy), // a*\\n 200.0 * (fy - fz) // b*\\n );\\n}\\n\\n// CIE76: plain Euclidean distance in CIELab\\nfloat deltaE76(vec3 lab1, vec3 lab2) {\\n return distance(lab1, lab2);\\n}\\n\\n// CIE94: weighted chroma/hue corrections, cheaper than CIEDE2000\\n// Uses graphics application constants: kL=1, K1=0.045, K2=0.015\\nfloat deltaE94(vec3 lab1, vec3 lab2) {\\n float dL = lab1.x - lab2.x;\\n float da = lab1.y - lab2.y;\\n float db = lab1.z - lab2.z;\\n float C1 = sqrt(lab1.y * lab1.y + lab1.z * lab1.z);\\n float C2 = sqrt(lab2.y * lab2.y + lab2.z * lab2.z);\\n float dC = C1 - C2;\\n float dH = sqrt(max(0.0, da*da + db*db - dC*dC));\\n float SC = 1.0 + 0.045 * C1;\\n float SH = 1.0 + 0.015 * C1;\\n return sqrt(dL*dL + (dC/SC)*(dC/SC) + (dH/SH)*(dH/SH));\\n}\\n\\n// CIEDE2000\\nfloat deltaE2000(vec3 lab1, vec3 lab2) {\\n float L1 = lab1.x, a1 = lab1.y, b1 = lab1.z;\\n float L2 = lab2.x, a2 = lab2.y, b2 = lab2.z;\\n\\n // Chroma\\n float C1 = sqrt(a1*a1 + b1*b1);\\n float C2 = sqrt(a2*a2 + b2*b2);\\n float Cavg = (C1 + C2) * 0.5;\\n float Cavg7 = pow(Cavg, 7.0);\\n\\n // G factor: adjustment to a* axis\\n float G = 0.5 * (1.0 - sqrt(Cavg7 / (Cavg7 + 6103515625.0))); // 25^7\\n\\n float a1p = a1 * (1.0 + G);\\n float a2p = a2 * (1.0 + G);\\n float C1p = sqrt(a1p*a1p + b1*b1);\\n float C2p = sqrt(a2p*a2p + b2*b2);\\n\\n // Guard atan(0,0): GLSL ES leaves that undefined, so skip it for achromatic colors.\\n // When a color has no chroma its hue angle is meaningless — we just need it to\\n // be a well-defined number so it doesn't corrupt the rest of the formula.\\n bool c1Achromatic = C1p < 1e-6;\\n bool c2Achromatic = C2p < 1e-6;\\n\\n float h1p = c1Achromatic ? 0.0 : atan(b1, a1p);\\n if (h1p < 0.0) h1p += TWO_PI;\\n float h2p = c2Achromatic ? 0.0 : atan(b2, a2p);\\n if (h2p < 0.0) h2p += TWO_PI;\\n\\n // Deltas\\n float dLp = L2 - L1;\\n float dCp = C2p - C1p;\\n\\n float dhp = 0.0;\\n if (!c1Achromatic && !c2Achromatic) {\\n dhp = h2p - h1p;\\n if (dhp > M_PI) dhp -= TWO_PI;\\n else if (dhp < -M_PI) dhp += TWO_PI;\\n }\\n float dHp = 2.0 * sqrt(C1p * C2p) * sin(dhp * 0.5);\\n\\n // Averages\\n float Lp = (L1 + L2) * 0.5;\\n float Cp = (C1p + C2p) * 0.5;\\n\\n // When one color is achromatic, its hue is 0 and the average is simply the other's hue\\n float hp;\\n if (c1Achromatic || c2Achromatic) {\\n hp = h1p + h2p;\\n } else if (abs(h1p - h2p) <= M_PI) {\\n hp = (h1p + h2p) * 0.5;\\n } else if (h1p + h2p < TWO_PI) {\\n hp = (h1p + h2p + TWO_PI) * 0.5;\\n } else {\\n hp = (h1p + h2p - TWO_PI) * 0.5;\\n }\\n\\n float T = 1.0\\n - 0.17 * cos(hp - radians(30.0))\\n + 0.24 * cos(2.0 * hp)\\n + 0.32 * cos(3.0 * hp + radians(6.0))\\n - 0.20 * cos(4.0 * hp - radians(63.0));\\n\\n // Weighting functions\\n float Lpm50sq = (Lp - 50.0) * (Lp - 50.0);\\n float SL = 1.0 + 0.015 * Lpm50sq / sqrt(20.0 + Lpm50sq);\\n float SC = 1.0 + 0.045 * Cp;\\n float SH = 1.0 + 0.015 * Cp * T;\\n\\n // Rotation term\\n float Cp7 = pow(Cp, 7.0);\\n float RC = 2.0 * sqrt(Cp7 / (Cp7 + 6103515625.0));\\n float hpDeg = degrees(hp);\\n float dTheta = radians(30.0) * exp(-((hpDeg - 275.0) / 25.0) * ((hpDeg - 275.0) / 25.0));\\n float RT = -sin(2.0 * dTheta) * RC;\\n\\n float dLn = dLp / SL;\\n float dCn = dCp / SC;\\n float dHn = dHp / SH;\\n\\n return sqrt(dLn*dLn + dCn*dCn + dHn*dHn + RT * dCn * dHn);\\n}\\n\"","export default \"// DISTANCE_METRIC define: 0=rgb, 1=oklab, 2=deltaE76, 3=deltaE2000, 4=kotsarenkoRamos, 5=deltaE94\\nvec3 closestColor(vec3 color, sampler2D paletteTexture) {\\n int paletteSize = textureSize(paletteTexture, 0).x;\\n float minDist = 1000000.0;\\n vec3 closest = vec3(0.0);\\n\\n // Pre-convert the input color once — palette entries are converted inside the loop.\\n #if DISTANCE_METRIC == 1\\n vec3 colorConverted = linear_srgb_to_oklab(srgb2rgb(color));\\n #elif DISTANCE_METRIC == 2 || DISTANCE_METRIC == 3 || DISTANCE_METRIC == 5\\n vec3 colorConverted = srgb_to_cielab(color);\\n #else\\n vec3 colorConverted = color;\\n #endif\\n\\n for (int i = 0; i < paletteSize; i++) {\\n vec3 paletteColor = texelFetch(paletteTexture, ivec2(i, 0), 0).rgb;\\n\\n float dist;\\n #if DISTANCE_METRIC == 1\\n dist = distance(colorConverted, linear_srgb_to_oklab(srgb2rgb(paletteColor)));\\n #elif DISTANCE_METRIC == 2\\n dist = deltaE76(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 3\\n dist = deltaE2000(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 4\\n dist = kotsarenkoRamos(color, paletteColor);\\n #elif DISTANCE_METRIC == 5\\n dist = deltaE94(colorConverted, srgb_to_cielab(paletteColor));\\n #else\\n dist = distance(colorConverted, paletteColor);\\n #endif\\n\\n if (dist < minDist) {\\n minDist = dist;\\n closest = paletteColor;\\n }\\n }\\n\\n return closest;\\n}\\n\"","import {\n ColorString,\n ColorList,\n PaletteVizOptions,\n SupportedColorModels,\n Axis,\n DistanceMetric,\n} from './types.ts';\n\n// @ts-ignore\nimport shaderSRGB2RGB from './shaders/srgb2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderOKLab from './shaders/oklab.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSL2RGB from './shaders/hsl2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSV2RGB from './shaders/hsv2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderLCH2RGB from './shaders/lch2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderDeltaE from './shaders/deltaE.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderClosestColor from './shaders/closestColor.frag.glsl?raw' assert { type: 'raw' };\n\n// Include order matters:\n// srgb2rgb – srgb2rgb()\n// oklab – M_PI, cbrt(), srgb_transfer_function(), okhsv/okhsl_to_srgb(), …\n// hsl2rgb, hsv2rgb, lch2rgb – color model conversions (lch2rgb uses M_PI + srgb_transfer_function)\n// deltaE – srgb_to_cielab(), deltaE76/94/2000() (uses srgb2rgb, cbrt, M_PI, TWO_PI)\n// closestColor – branches on DISTANCE_METRIC define; uses everything above\n//\n// Defines (compile-time, prepended to shader source — trigger recompile, no runtime branching):\n// DISTANCE_METRIC int 0=rgb 1=oklab 2=deltaE76 3=deltaE2000 4=kotsarenkoRamos 5=deltaE94\n// COLOR_MODEL int 0=rgb 1=oklab 2=okhsv 3=okhsvPolar 4=okhsl 5=okhslPolar\n// 6=oklch 7=oklchPolar 8=hsv 9=hsvPolar 10=hsl 11=hslPolar\n// PROGRESS_AXIS int 0=x 1=y 2=z\n// INVERT_Z flag (defined = true)\n// SHOW_RAW flag (defined = true)\n\nconst vertexShaderSrc = `\nprecision highp float;\nlayout(location = 0) in vec2 a_position;\nout vec2 vUv;\nvoid main() {\n vUv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`;\n\n// fragmentShader is exported so users can inspect or reuse the GLSL source.\n// Defines are NOT embedded here — they are prepended at compile time via buildProgram().\n// Note: #version 300 es is prepended by buildProgram() (must be first line,\n// before defines). Do not add it here.\nexport const fragmentShader = `\nprecision highp float;\n#define TWO_PI 6.28318530718\nin vec2 vUv;\nout vec4 fragColor;\nuniform float progress;\nuniform sampler2D paletteTexture;\n\n${shaderSRGB2RGB}\n${shaderOKLab}\n${shaderHSL2RGB}\n${shaderHSV2RGB}\n${shaderLCH2RGB}\n${shaderDeltaE}\n${shaderClosestColor}\n\n// COLOR_MODEL: 0=rgb, 1=oklab, 2=okhsv, 3=okhsvPolar, 4=okhsl, 5=okhslPolar,\n// 6=oklch, 7=oklchPolar, 8=hsv, 9=hsvPolar, 10=hsl, 11=hslPolar\nvec3 modelToRGB(vec3 colorCoords) {\n #if COLOR_MODEL == 0\n return colorCoords;\n #elif COLOR_MODEL == 1\n vec3 linear = oklab_to_linear_srgb(vec3(colorCoords.z, colorCoords.x - 0.5, colorCoords.y - 0.5));\n return clamp(vec3(srgb_transfer_function(linear.r), srgb_transfer_function(linear.g), srgb_transfer_function(linear.b)), 0.0, 1.0);\n #elif COLOR_MODEL == 2 || COLOR_MODEL == 3\n return okhsv_to_srgb(colorCoords);\n #elif COLOR_MODEL == 4 || COLOR_MODEL == 5\n return okhsl_to_srgb(colorCoords);\n #elif COLOR_MODEL == 6 || COLOR_MODEL == 7\n return lch2rgb(vec3(colorCoords.z, colorCoords.y, colorCoords.x));\n #elif COLOR_MODEL == 8 || COLOR_MODEL == 9\n return hsv2rgb(colorCoords);\n #else\n return hsl2rgb(colorCoords);\n #endif\n}\n\nvoid main(){\n #if PROGRESS_AXIS == 1\n vec3 colorCoords = vec3(vUv.x, progress, vUv.y);\n #elif PROGRESS_AXIS == 2\n vec3 colorCoords = vec3(vUv.x, vUv.y, 1. - progress);\n #else\n vec3 colorCoords = vec3(progress, vUv.x, vUv.y);\n #endif\n\n #if COLOR_MODEL == 3 || COLOR_MODEL == 5 || COLOR_MODEL == 7 || COLOR_MODEL == 9 || COLOR_MODEL == 11\n vec2 toCenter = vUv - 0.5;\n float angle = atan(toCenter.y, toCenter.x);\n float radius = length(toCenter) * 2.0;\n\n #if PROGRESS_AXIS == 2\n colorCoords = vec3((angle / TWO_PI), radius, 1. - progress);\n #elif PROGRESS_AXIS == 1\n colorCoords = vec3((angle / TWO_PI), 1. - progress, radius);\n if (radius > 1.0) { discard; }\n #else\n float hue = 1.0 - abs(0.5 - progress * .5) * 2.0;\n if (vUv.x > 0.5) { hue += 0.5; }\n colorCoords = vec3(hue, abs(0.5 - vUv.x) * 2.0, vUv.y);\n #endif\n #endif\n\n #ifdef INVERT_Z\n colorCoords.z = 1. - colorCoords.z;\n #endif\n\n vec3 rgb = modelToRGB(colorCoords);\n\n #ifdef SHOW_RAW\n fragColor = vec4(rgb, 1.);\n #else\n fragColor = vec4(closestColor(rgb, paletteTexture), 1.);\n #endif\n}`;\n\n// ── Color parsing ──────────────────────────────────────────────────────────────\n// Use a canvas 2D context as a free CSS color parser — handles hex, rgb(),\n// hsl(), named colors, etc. Lazy-initialised to avoid issues at module load time.\n\nlet _colorCtx: CanvasRenderingContext2D | null = null;\n\nfunction cssToSRGB(color: string): [number, number, number] {\n if (!_colorCtx) {\n const c = document.createElement('canvas');\n c.width = c.height = 1;\n _colorCtx = c.getContext('2d')!;\n }\n _colorCtx.fillStyle = '#000000'; // reset before setting\n _colorCtx.fillStyle = color;\n const v = _colorCtx.fillStyle; // browser normalises to '#rrggbb' or 'rgba(...)'\n if (v[0] === '#') {\n return [\n parseInt(v.slice(1, 3), 16) / 255,\n parseInt(v.slice(3, 5), 16) / 255,\n parseInt(v.slice(5, 7), 16) / 255,\n ];\n }\n // rgba(r, g, b, a) fallback\n const m = v.match(/[\\d.]+/g)!;\n return [+m[0] / 255, +m[1] / 255, +m[2] / 255];\n}\n\n// ── Palette helpers ────────────────────────────────────────────────────────────\n\n// Returns the palette as a flat RGBA Uint8Array (sRGB, 1×N texture row).\n// Useful for building your own WebGL texture or inspecting raw color data.\nexport const paletteToRGBA = (palette: ColorList): Uint8Array => {\n const data = new Uint8Array(palette.length * 4);\n palette.forEach((color, i) => {\n try {\n const [r, g, b] = cssToSRGB(color);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n } catch {\n console.error(`Invalid color: ${color}`);\n }\n });\n return data;\n};\n\n// Backwards-compatible alias (previously returned a Three.js DataTexture)\nexport const paletteToTexture = paletteToRGBA;\n\nexport const randomPalette = (size = 20): ColorList =>\n Array.from(\n { length: size },\n () =>\n `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`,\n );\n\n// ── WebGL helpers ──────────────────────────────────────────────────────────────\n\ntype Defines = Record<string, number | false>;\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`Shader compile error:\\n${log}`);\n }\n return shader;\n}\n\nfunction buildProgram(\n gl: WebGL2RenderingContext,\n defines: Defines,\n fragSrc: string,\n vertSrc: string,\n): WebGLProgram {\n // #version 300 es must be the very first line — prepend it before defines.\n const defineStr =\n Object.entries(defines)\n .filter(([, v]) => v !== false)\n .map(([k, v]) => `#define ${k} ${v}`)\n .join('\\n') + '\\n';\n const prefix = '#version 300 es\\n' + defineStr;\n\n const vert = compileShader(gl, gl.VERTEX_SHADER, prefix + vertSrc);\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, prefix + fragSrc);\n\n const prog = gl.createProgram()!;\n gl.attachShader(prog, vert);\n gl.attachShader(prog, frag);\n gl.linkProgram(prog);\n gl.deleteShader(vert);\n gl.deleteShader(frag);\n\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(prog);\n gl.deleteProgram(prog);\n throw new Error(`Program link error:\\n${log}`);\n }\n return prog;\n}\n\nfunction uploadPaletteTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n palette: ColorList,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // RGBA8: sized internal format required by WebGL2 spec\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA8,\n palette.length,\n 1,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n paletteToRGBA(palette),\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n}\n\n// ── PaletteViz ─────────────────────────────────────────────────────────────────\n\nexport class PaletteViz {\n #palette: ColorList = [];\n #width = 512;\n #height = 512;\n #pixelRatio = 1;\n\n // shader state\n #position = 0.0;\n #axis: Axis = 'y';\n #colorModel: SupportedColorModels = 'okhsv';\n #distanceMetric: DistanceMetric = 'oklab';\n #invertLightness = false;\n #showRaw = false;\n\n // uniform value maps\n readonly #axisMap = { x: 0, y: 1, z: 2 } as const;\n readonly #colorModelMap = {\n rgb: 0,\n oklab: 1,\n okhsv: 2,\n okhsvPolar: 3,\n okhsl: 4,\n okhslPolar: 5,\n oklch: 6,\n oklchPolar: 7,\n hsv: 8,\n hsvPolar: 9,\n hsl: 10,\n hslPolar: 11,\n } as const;\n readonly #distanceMetricMap = {\n rgb: 0,\n oklab: 1,\n deltaE76: 2,\n deltaE2000: 3,\n kotsarenkoRamos: 4,\n deltaE94: 5,\n } as const;\n\n // WebGL\n #canvas: HTMLCanvasElement;\n #gl: WebGL2RenderingContext;\n #program: WebGLProgram | null = null;\n #texture: WebGLTexture | null = null;\n #quadBuffer: WebGLBuffer | null = null;\n #vao: WebGLVertexArrayObject | null = null;\n #animationFrame: number | null = null;\n\n // cached uniform locations (re-queried after each program rebuild)\n #uProgress: WebGLUniformLocation | null = null;\n #uPaletteTexture: WebGLUniformLocation | null = null;\n\n // dom\n #container: HTMLElement | undefined;\n\n constructor({\n palette = randomPalette(),\n width = 512,\n height = 512,\n pixelRatio = window.devicePixelRatio,\n container,\n colorModel = 'okhsv',\n distanceMetric = 'oklab',\n axis = 'y',\n position = 0.0,\n invertLightness = false,\n showRaw = false,\n }: PaletteVizOptions = {}) {\n this.#palette = palette;\n this.#width = width;\n this.#height = height;\n this.#pixelRatio = pixelRatio;\n this.#colorModel = colorModel;\n this.#distanceMetric = distanceMetric;\n this.#axis = axis;\n this.#position = position;\n this.#invertLightness = invertLightness;\n this.#showRaw = showRaw;\n this.#container = container;\n\n this.#canvas = document.createElement('canvas');\n this.#canvas.classList.add('palette-viz');\n const gl = this.#canvas.getContext('webgl2');\n if (!gl) throw new Error('WebGL2 not supported');\n this.#gl = gl;\n\n // Quad buffer + VAO — set up once, reused every frame.\n // layout(location=0) in the vertex shader pins a_position to slot 0,\n // so the VAO remains valid across shader recompiles.\n this.#quadBuffer = gl.createBuffer()!;\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#quadBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n\n this.#vao = gl.createVertexArray()!;\n gl.bindVertexArray(this.#vao);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n\n this.#texture = gl.createTexture()!;\n uploadPaletteTexture(gl, this.#texture, this.#palette);\n\n this.#buildProgram();\n this.#setSize(this.#width, this.#height);\n this.#container?.appendChild(this.#canvas);\n this.#paint();\n }\n\n #defines(): Defines {\n return {\n DISTANCE_METRIC: this.#distanceMetricMap[this.#distanceMetric],\n COLOR_MODEL: this.#colorModelMap[this.#colorModel],\n PROGRESS_AXIS: this.#axisMap[this.#axis],\n INVERT_Z: this.#invertLightness ? 1 : false,\n SHOW_RAW: this.#showRaw ? 1 : false,\n };\n }\n\n #buildProgram(): void {\n const gl = this.#gl;\n if (this.#program) gl.deleteProgram(this.#program);\n this.#program = buildProgram(gl, this.#defines(), fragmentShader, vertexShaderSrc);\n this.#uProgress = gl.getUniformLocation(this.#program, 'progress');\n this.#uPaletteTexture = gl.getUniformLocation(this.#program, 'paletteTexture');\n }\n\n #setSize(w: number, h: number): void {\n const pw = Math.round(w * this.#pixelRatio);\n const ph = Math.round(h * this.#pixelRatio);\n this.#canvas.width = pw;\n this.#canvas.height = ph;\n this.#canvas.style.width = `${w}px`;\n this.#canvas.style.height = `${h}px`;\n this.#gl.viewport(0, 0, pw, ph);\n }\n\n #paint(): void {\n if (this.#animationFrame !== null) cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = requestAnimationFrame(() => {\n const gl = this.#gl;\n gl.useProgram(this.#program);\n\n gl.uniform1f(this.#uProgress, this.#position);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.#texture);\n gl.uniform1i(this.#uPaletteTexture, 0);\n\n gl.bindVertexArray(this.#vao);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n gl.bindVertexArray(null);\n });\n }\n\n // ── Public API ──────────────────────────────────────────────────────────────\n\n get canvas(): HTMLCanvasElement {\n return this.#canvas;\n }\n get width() {\n return this.#width;\n }\n get height() {\n return this.#height;\n }\n\n resize(width: number, height: number | null = null): void {\n this.#width = width;\n this.#height = height ?? width;\n this.#setSize(this.#width, this.#height);\n this.#paint();\n }\n\n destroy(): void {\n if (this.#animationFrame !== null) {\n cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = null;\n }\n const gl = this.#gl;\n gl.deleteProgram(this.#program);\n gl.deleteTexture(this.#texture);\n gl.deleteBuffer(this.#quadBuffer);\n gl.deleteVertexArray(this.#vao);\n this.#canvas.remove();\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n\n // ── Palette ─────────────────────────────────────────────────────────────────\n\n set palette(palette: ColorList) {\n this.#palette = palette;\n uploadPaletteTexture(this.#gl, this.#texture!, palette);\n this.#paint();\n }\n get palette() {\n return this.#palette;\n }\n\n setColor(color: ColorString, index: number): void {\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette[index] = color;\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n addColor(color: ColorString, index?: number): void {\n this.#palette.splice(index ?? this.#palette.length, 0, color);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n removeColor(index: number): void;\n removeColor(color: ColorString): void;\n removeColor(indexOrColor: number | ColorString): void {\n const index =\n typeof indexOrColor === 'number' ? indexOrColor : this.#palette.indexOf(indexOrColor);\n if (index === -1) throw new Error('Color not found in palette');\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette.splice(index, 1);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n // ── Shader properties ────────────────────────────────────────────────────────\n\n set position(value: number) {\n this.#position = value;\n this.#paint();\n }\n get position() {\n return this.#position;\n }\n\n set axis(axis: Axis) {\n if (!(axis in this.#axisMap)) throw new Error(\"axis must be 'x', 'y', or 'z'\");\n this.#axis = axis;\n this.#buildProgram();\n this.#paint();\n }\n get axis() {\n return this.#axis;\n }\n\n set colorModel(model: SupportedColorModels) {\n if (!(model in this.#colorModelMap)) throw new Error(`colorModel '${model}' is not supported`);\n this.#colorModel = model;\n this.#buildProgram();\n this.#paint();\n }\n get colorModel() {\n return this.#colorModel;\n }\n\n set distanceMetric(metric: DistanceMetric) {\n if (!(metric in this.#distanceMetricMap))\n throw new Error(\n \"distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'\",\n );\n this.#distanceMetric = metric;\n this.#buildProgram();\n this.#paint();\n }\n get distanceMetric() {\n return this.#distanceMetric;\n }\n\n set invertLightness(value: boolean) {\n this.#invertLightness = value;\n this.#buildProgram();\n this.#paint();\n }\n get invertLightness() {\n return this.#invertLightness;\n }\n\n set showRaw(value: boolean) {\n this.#showRaw = value;\n this.#buildProgram();\n this.#paint();\n }\n get showRaw() {\n return this.#showRaw;\n }\n\n static paletteToRGBA = paletteToRGBA;\n /** @deprecated use PaletteViz.paletteToRGBA */\n static paletteToTexture = paletteToRGBA;\n}\n"],"names":["shaderSRGB2RGB","shaderOKLab","shaderHSL2RGB","shaderHSV2RGB","shaderLCH2RGB","shaderDeltaE","shaderClosestColor","vertexShaderSrc","fragmentShader","_colorCtx","cssToSRGB","color","c","v","m","paletteToRGBA","palette","data","i","g","b","paletteToTexture","randomPalette","size","compileShader","gl","type","src","shader","log","buildProgram","defines","fragSrc","vertSrc","prefix","k","vert","frag","prog","uploadPaletteTexture","tex","PaletteViz","#palette","#width","#height","#pixelRatio","#position","#axis","#colorModel","#distanceMetric","#invertLightness","#showRaw","#axisMap","#colorModelMap","#distanceMetricMap","#canvas","#gl","#program","#texture","#quadBuffer","#vao","#animationFrame","#uProgress","#uPaletteTexture","#container","width","height","pixelRatio","container","colorModel","distanceMetric","axis","position","invertLightness","showRaw","#buildProgram","#setSize","#paint","#defines","w","h","pw","ph","index","indexOrColor","value","model","metric"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA,GCAfC,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ICAfC,IAAe;AAAA;AAAA;AAAA,ICAfC,IAAe;AAAA;AAAA;AAAA;AAAA,ICAfC,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCAfC,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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"}
1
+ {"version":3,"file":"palette-shader.js","sources":["../src/shaders/srgb2rgb.frag.glsl?raw","../src/shaders/oklab.frag.glsl?raw","../src/shaders/hsl2rgb.frag.glsl?raw","../src/shaders/hsv2rgb.frag.glsl?raw","../src/shaders/lch2rgb.frag.glsl?raw","../src/shaders/deltaE.frag.glsl?raw","../src/shaders/closestColor.frag.glsl?raw","../src/index.ts"],"sourcesContent":["export default \"// https://lygia.xyz/\\nfloat srgb2rgb(const in float v) { return (v < 0.04045) ? v * 0.0773993808 : pow((v + 0.055) * 0.947867298578199, 2.4); }\\nvec3 srgb2rgb(const in vec3 srgb) { return vec3(srgb2rgb(srgb.r), srgb2rgb(srgb.g), srgb2rgb(srgb.b)); }\\nvec4 srgb2rgb(const in vec4 srgb) { return vec4(srgb2rgb(srgb.rgb), srgb.a); }\\n\"","export default \"// Copyright(c) 2021 Björn Ottosson\\n//\\n// Permission is hereby granted, free of charge, to any person obtaining a copy of\\n// this softwareand associated documentation files(the \\\"Software\\\"), to deal in\\n// the Software without restriction, including without limitation the rights to\\n// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies\\n// of the Software, and to permit persons to whom the Software is furnished to do\\n// so, subject to the following conditions :\\n// The above copyright noticeand this permission notice shall be included in all\\n// copies or substantial portions of the Software.\\n// THE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE\\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\n// SOFTWARE.\\n\\n#define M_PI 3.1415926535897932384626433832795\\n\\nfloat cbrt( float x )\\n{\\n return sign(x)*pow(abs(x),1.0f/3.0f);\\n}\\n\\nfloat srgb_transfer_function(float a)\\n{\\n\\treturn .0031308f >= a ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f;\\n}\\n\\nfloat srgb_transfer_function_inv(float a)\\n{\\n\\treturn .04045f < a ? pow((a + .055f) / 1.055f, 2.4f) : a / 12.92f;\\n}\\n\\nvec3 linear_srgb_to_oklab(vec3 c)\\n{\\n\\tfloat l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;\\n\\tfloat m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;\\n\\tfloat s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;\\n\\n\\tfloat l_ = cbrt(l);\\n\\tfloat m_ = cbrt(m);\\n\\tfloat s_ = cbrt(s);\\n\\n\\treturn vec3(\\n\\t\\t0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,\\n\\t\\t1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,\\n\\t\\t0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_\\n\\t);\\n}\\n\\nvec3 oklab_to_linear_srgb(vec3 c)\\n{\\n\\tfloat l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;\\n\\tfloat m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;\\n\\tfloat s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;\\n\\n\\tfloat l = l_ * l_ * l_;\\n\\tfloat m = m_ * m_ * m_;\\n\\tfloat s = s_ * s_ * s_;\\n\\n\\treturn vec3(\\n\\t\\t+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,\\n\\t\\t-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,\\n\\t\\t-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s\\n\\t);\\n}\\n\\n// Finds the maximum saturation possible for a given hue that fits in sRGB\\n// Saturation here is defined as S = C/L\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat compute_max_saturation(float a, float b)\\n{\\n\\t// Max saturation will be when one of r, g or b goes below zero.\\n\\n\\t// Select different coefficients depending on which component goes below zero first\\n\\tfloat k0, k1, k2, k3, k4, wl, wm, ws;\\n\\n\\tif (-1.88170328f * a - 0.80936493f * b > 1.f)\\n\\t{\\n\\t\\t// Red component\\n\\t\\tk0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;\\n\\t\\twl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;\\n\\t}\\n\\telse if (1.81444104f * a - 1.19445276f * b > 1.f)\\n\\t{\\n\\t\\t// Green component\\n\\t\\tk0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;\\n\\t\\twl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Blue component\\n\\t\\tk0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;\\n\\t\\twl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;\\n\\t}\\n\\n\\t// Approximate max saturation using a polynomial:\\n\\tfloat S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;\\n\\n\\t// Do one step Halley's method to get closer\\n\\t// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite\\n\\t// this should be sufficient for most applications, otherwise do two/three steps \\n\\n\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t{\\n\\t\\tfloat l_ = 1.f + S * k_l;\\n\\t\\tfloat m_ = 1.f + S * k_m;\\n\\t\\tfloat s_ = 1.f + S * k_s;\\n\\n\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\tfloat l_dS = 3.f * k_l * l_ * l_;\\n\\t\\tfloat m_dS = 3.f * k_m * m_ * m_;\\n\\t\\tfloat s_dS = 3.f * k_s * s_ * s_;\\n\\n\\t\\tfloat l_dS2 = 6.f * k_l * k_l * l_;\\n\\t\\tfloat m_dS2 = 6.f * k_m * k_m * m_;\\n\\t\\tfloat s_dS2 = 6.f * k_s * k_s * s_;\\n\\n\\t\\tfloat f = wl * l + wm * m + ws * s;\\n\\t\\tfloat f1 = wl * l_dS + wm * m_dS + ws * s_dS;\\n\\t\\tfloat f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;\\n\\n\\t\\tS = S - f * f1 / (f1 * f1 - 0.5f * f * f2);\\n\\t}\\n\\n\\treturn S;\\n}\\n\\n// finds L_cusp and C_cusp for a given hue\\n// a and b must be normalized so a^2 + b^2 == 1\\nvec2 find_cusp(float a, float b)\\n{\\n\\t// First, find the maximum saturation (saturation S = C/L)\\n\\tfloat S_cusp = compute_max_saturation(a, b);\\n\\n\\t// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:\\n\\tvec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b ));\\n\\tfloat L_cusp = cbrt(1.f / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));\\n\\tfloat C_cusp = L_cusp * S_cusp;\\n\\n\\treturn vec2( L_cusp , C_cusp );\\n}\\n\\n// Finds intersection of the line defined by \\n// L = L0 * (1 - t) + t * L1;\\n// C = t * C1;\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0, vec2 cusp)\\n{\\n\\t// Find the intersection for upper and lower half seprately\\n\\tfloat t;\\n\\tif (((L1 - L0) * cusp.y - (cusp.x - L0) * C1) <= 0.f)\\n\\t{\\n\\t\\t// Lower half\\n\\n\\t\\tt = cusp.y * L0 / (C1 * cusp.x + cusp.y * (L0 - L1));\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Upper half\\n\\n\\t\\t// First intersect with triangle\\n\\t\\tt = cusp.y * (L0 - 1.f) / (C1 * (cusp.x - 1.f) + cusp.y * (L0 - L1));\\n\\n\\t\\t// Then one step Halley's method\\n\\t\\t{\\n\\t\\t\\tfloat dL = L1 - L0;\\n\\t\\t\\tfloat dC = C1;\\n\\n\\t\\t\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\t\\t\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\t\\t\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t\\t\\tfloat l_dt = dL + dC * k_l;\\n\\t\\t\\tfloat m_dt = dL + dC * k_m;\\n\\t\\t\\tfloat s_dt = dL + dC * k_s;\\n\\n\\n\\t\\t\\t// If higher accuracy is required, 2 or 3 iterations of the following block can be used:\\n\\t\\t\\t{\\n\\t\\t\\t\\tfloat L = L0 * (1.f - t) + t * L1;\\n\\t\\t\\t\\tfloat C = t * C1;\\n\\n\\t\\t\\t\\tfloat l_ = L + C * k_l;\\n\\t\\t\\t\\tfloat m_ = L + C * k_m;\\n\\t\\t\\t\\tfloat s_ = L + C * k_s;\\n\\n\\t\\t\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\t\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\t\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt = 3.f * l_dt * l_ * l_;\\n\\t\\t\\t\\tfloat mdt = 3.f * m_dt * m_ * m_;\\n\\t\\t\\t\\tfloat sdt = 3.f * s_dt * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt2 = 6.f * l_dt * l_dt * l_;\\n\\t\\t\\t\\tfloat mdt2 = 6.f * m_dt * m_dt * m_;\\n\\t\\t\\t\\tfloat sdt2 = 6.f * s_dt * s_dt * s_;\\n\\n\\t\\t\\t\\tfloat r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1.f;\\n\\t\\t\\t\\tfloat r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;\\n\\t\\t\\t\\tfloat r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_r = r1 / (r1 * r1 - 0.5f * r * r2);\\n\\t\\t\\t\\tfloat t_r = -r * u_r;\\n\\n\\t\\t\\t\\tfloat g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1.f;\\n\\t\\t\\t\\tfloat g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;\\n\\t\\t\\t\\tfloat g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_g = g1 / (g1 * g1 - 0.5f * g * g2);\\n\\t\\t\\t\\tfloat t_g = -g * u_g;\\n\\n\\t\\t\\t\\tfloat b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1.f;\\n\\t\\t\\t\\tfloat b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;\\n\\t\\t\\t\\tfloat b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_b = b1 / (b1 * b1 - 0.5f * b * b2);\\n\\t\\t\\t\\tfloat t_b = -b * u_b;\\n\\n\\t\\t\\t\\tt_r = u_r >= 0.f ? t_r : 10000.f;\\n\\t\\t\\t\\tt_g = u_g >= 0.f ? t_g : 10000.f;\\n\\t\\t\\t\\tt_b = u_b >= 0.f ? t_b : 10000.f;\\n\\n\\t\\t\\t\\tt += min(t_r, min(t_g, t_b));\\n\\t\\t\\t}\\n\\t\\t}\\n\\t}\\n\\n\\treturn t;\\n}\\n\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0)\\n{\\n\\t// Find the cusp of the gamut triangle\\n\\tvec2 cusp = find_cusp(a, b);\\n\\n\\treturn find_gamut_intersection(a, b, L1, C1, L0, cusp);\\n}\\n\\nvec3 gamut_clip_preserve_chroma(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = clamp(L, 0.f, 1.f);\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_0_5(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = 0.5;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_L_cusp(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat L0 = cusp.x;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_0_5(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat Ld = L - 0.5f;\\n\\tfloat e1 = 0.5f + abs(Ld) + alpha * C;\\n\\tfloat L0 = 0.5f * (1.f + sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_L_cusp(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat Ld = L - cusp.x;\\n\\tfloat k = 2.f * (Ld > 0.f ? 1.f - cusp.x : cusp.x);\\n\\n\\tfloat e1 = 0.5f * k + abs(Ld) + alpha * C / k;\\n\\tfloat L0 = cusp.x + 0.5f * (sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * k * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nfloat toe(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn 0.5f * (k_3 * x - k_1 + sqrt((k_3 * x - k_1) * (k_3 * x - k_1) + 4.f * k_2 * k_3 * x));\\n}\\n\\nfloat toe_inv(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn (x * x + k_1 * x) / (k_3 * (x + k_2));\\n}\\n\\nvec2 to_ST(vec2 cusp)\\n{\\n\\tfloat L = cusp.x;\\n\\tfloat C = cusp.y;\\n\\treturn vec2( C / L, C / (1.f - L) );\\n}\\n\\n// Returns a smooth approximation of the location of the cusp\\n// This polynomial was created by an optimization process\\n// It has been designed so that S_mid < S_max and T_mid < T_max\\nvec2 get_ST_mid(float a_, float b_)\\n{\\n\\tfloat S = 0.11516993f + 1.f / (\\n\\t\\t+7.44778970f + 4.15901240f * b_\\n\\t\\t+ a_ * (-2.19557347f + 1.75198401f * b_\\n\\t\\t\\t+ a_ * (-2.13704948f - 10.02301043f * b_\\n\\t\\t\\t\\t+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\tfloat T = 0.11239642f + 1.f / (\\n\\t\\t+1.61320320f - 0.68124379f * b_\\n\\t\\t+ a_ * (+0.40370612f + 0.90148123f * b_\\n\\t\\t\\t+ a_ * (-0.27087943f + 0.61223990f * b_\\n\\t\\t\\t\\t+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\treturn vec2( S, T );\\n}\\n\\nvec3 get_Cs(float L, float a_, float b_)\\n{\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat C_max = find_gamut_intersection(a_, b_, L, 1.f, L, cusp);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\t\\n\\t// Scale factor to compensate for the curved part of gamut shape:\\n\\tfloat k = C_max / min((L * ST_max.x), (1.f - L) * ST_max.y);\\n\\n\\tfloat C_mid;\\n\\t{\\n\\t\\tvec2 ST_mid = get_ST_mid(a_, b_);\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tfloat C_a = L * ST_mid.x;\\n\\t\\tfloat C_b = (1.f - L) * ST_mid.y;\\n\\t\\tC_mid = 0.9f * k * sqrt(sqrt(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));\\n\\t}\\n\\n\\tfloat C_0;\\n\\t{\\n\\t\\t// for C_0, the shape is independent of hue, so vec2 are constant. Values picked to roughly be the average values of vec2.\\n\\t\\tfloat C_a = L * 0.4f;\\n\\t\\tfloat C_b = (1.f - L) * 0.8f;\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tC_0 = sqrt(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));\\n\\t}\\n\\n\\treturn vec3( C_0, C_mid, C_max );\\n}\\n\\nvec3 okhsl_to_srgb(vec3 hsl)\\n{\\n\\tfloat h = hsl.x;\\n\\tfloat s = hsl.y;\\n\\tfloat l = hsl.z;\\n\\n\\tif (l == 1.0f)\\n\\t{\\n\\t\\treturn vec3( 1.f, 1.f, 1.f );\\n\\t}\\n\\n\\telse if (l == 0.f)\\n\\t{\\n\\t\\treturn vec3( 0.f, 0.f, 0.f );\\n\\t}\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\tfloat L = toe_inv(l);\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat C, t, k_0, k_1, k_2;\\n\\n\\tif (s < mid)\\n\\t{\\n\\t\\tt = mid_inv * s;\\n\\n\\t\\tk_1 = mid * C_0;\\n\\t\\tk_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tC = t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tt = (s - mid)/ (1.f - mid);\\n\\n\\t\\tk_0 = C_mid;\\n\\t\\tk_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tk_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tC = k_0 + t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsl(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\t// Inverse of the interpolation in okhsl_to_srgb:\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat s;\\n\\tif (C < C_mid)\\n\\t{\\n\\t\\tfloat k_1 = mid * C_0;\\n\\t\\tfloat k_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tfloat t = C / (k_1 + k_2 * C);\\n\\t\\ts = t * mid;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tfloat k_0 = C_mid;\\n\\t\\tfloat k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tfloat k_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tfloat t = (C - k_0) / (k_1 + k_2 * (C - k_0));\\n\\t\\ts = mid + (1.f - mid) * t;\\n\\t}\\n\\n\\tfloat l = toe(L);\\n\\treturn vec3( h, s, l );\\n}\\n\\n\\nvec3 okhsv_to_srgb(vec3 hsv)\\n{\\n\\tfloat h = hsv.x;\\n\\tfloat s = hsv.y;\\n\\tfloat v = hsv.z;\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\t\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f- S_0 / S_max;\\n\\n\\t// first we compute L and V as if the gamut is a perfect triangle:\\n\\n\\t// L, C when v==1:\\n\\tfloat L_v = 1.f - s * S_0 / (S_0 + T_max - T_max * k * s);\\n\\tfloat C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);\\n\\n\\tfloat L = v * L_v;\\n\\tfloat C = v * C_v;\\n\\n\\t// then we compensate for both toe and the curved top part of the triangle:\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\tfloat L_new = toe_inv(L);\\n\\tC = C * L_new / L;\\n\\tL = L_new;\\n\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L * scale_L;\\n\\tC = C * scale_L;\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsv(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f - S_0 / S_max;\\n\\n\\t// first we find L_v, C_v, L_vt and C_vt\\n\\n\\tfloat t = T_max / (C + L * T_max);\\n\\tfloat L_v = t * L;\\n\\tfloat C_v = t * C;\\n\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\t// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L / scale_L;\\n\\tC = C / scale_L;\\n\\n\\tC = C * toe(L) / L;\\n\\tL = toe(L);\\n\\n\\t// we can now compute v and s:\\n\\n\\tfloat v = L / L_v;\\n\\tfloat s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);\\n\\n\\treturn vec3 (h, s, v );\\n}\"","export default \"vec3 hsl2rgb( in vec3 c ) {\\n vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );\\n return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));\\n}\"","export default \"vec3 hsv2rgb(vec3 c) {\\n vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\\n vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\\n return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\\n}\"","export default \"// slightly rearranged vector components so it matches with LCH\\n// M_PI and srgb_transfer_function are provided by oklab.frag.glsl (included before this file)\\nvec3 lch2rgb(vec3 lch) {\\n lch.y *= 0.34;\\n\\n vec3 lab = vec3(\\n lch.x,\\n lch.y * cos(lch.z * M_PI*2.0),\\n lch.y * sin(lch.z * M_PI*2.0)\\n );\\n\\n vec3 lms = vec3(\\n lab.x + 0.3963377774f * lab.y + 0.2158037573f * lab.z,\\n lab.x - 0.1055613458f * lab.y - 0.0638541728f * lab.z,\\n lab.x - 0.0894841775f * lab.y - 1.2914855480f * lab.z\\n );\\n\\n lms = pow(max(lms, vec3(0.0)), vec3(3.0));\\n\\n vec3 rgb = vec3(\\n +4.0767416621f * lms.x - 3.3077115913f * lms.y + 0.2309699292f * lms.z,\\n -1.2684380046f * lms.x + 2.6097574011f * lms.y - 0.3413193965f * lms.z,\\n -0.0041960863f * lms.x - 0.7034186147f * lms.y + 1.7076147010f * lms.z\\n );\\n\\n return vec3(\\n srgb_transfer_function(rgb.r),\\n srgb_transfer_function(rgb.g),\\n srgb_transfer_function(rgb.b)\\n );\\n}\\n\"","export default \"// Kotsarenko/Ramos weighted RGB distance.\\n// Operates on sRGB values directly (no linearisation needed).\\n// Weights red and blue channels by the mean red value, which improves\\n// perceptual uniformity compared to plain Euclidean RGB at minimal cost.\\nfloat kotsarenkoRamos(vec3 c1, vec3 c2) {\\n float rMean = (c1.r + c2.r) * 0.5;\\n vec3 d = c1 - c2;\\n return sqrt((2.0 + rMean) * d.r*d.r + 4.0 * d.g*d.g + (3.0 - rMean) * d.b*d.b);\\n}\\n\\n// ── CIELab ────────────────────────────────────────────────────────────────────\\n// sRGB -> XYZ (D65) -> CIELab\\n// Depends on: srgb2rgb() from srgb2rgb.frag.glsl, cbrt() from oklab.frag.glsl\\n\\nfloat _lab_f(float t) {\\n float delta = 6.0 / 29.0;\\n return t > delta * delta * delta\\n ? cbrt(t)\\n : t / (3.0 * delta * delta) + 4.0 / 29.0;\\n}\\n\\nvec3 srgb_to_cielab(vec3 srgb) {\\n vec3 lin = srgb2rgb(srgb);\\n\\n // Linear sRGB -> XYZ (D65 illuminant)\\n vec3 xyz = vec3(\\n 0.4124564 * lin.r + 0.3575761 * lin.g + 0.1804375 * lin.b,\\n 0.2126729 * lin.r + 0.7151522 * lin.g + 0.0721750 * lin.b,\\n 0.0193339 * lin.r + 0.1191920 * lin.g + 0.9503041 * lin.b\\n );\\n\\n // XYZ -> Lab (D65 white point: 0.95047, 1.00000, 1.08883)\\n float fx = _lab_f(xyz.x / 0.95047);\\n float fy = _lab_f(xyz.y);\\n float fz = _lab_f(xyz.z / 1.08883);\\n\\n return vec3(\\n 116.0 * fy - 16.0, // L*\\n 500.0 * (fx - fy), // a*\\n 200.0 * (fy - fz) // b*\\n );\\n}\\n\\n// CIE76: plain Euclidean distance in CIELab\\nfloat deltaE76(vec3 lab1, vec3 lab2) {\\n return distance(lab1, lab2);\\n}\\n\\n// CIE94: weighted chroma/hue corrections, cheaper than CIEDE2000\\n// Uses graphics application constants: kL=1, K1=0.045, K2=0.015\\nfloat deltaE94(vec3 lab1, vec3 lab2) {\\n float dL = lab1.x - lab2.x;\\n float da = lab1.y - lab2.y;\\n float db = lab1.z - lab2.z;\\n float C1 = sqrt(lab1.y * lab1.y + lab1.z * lab1.z);\\n float C2 = sqrt(lab2.y * lab2.y + lab2.z * lab2.z);\\n float dC = C1 - C2;\\n float dH = sqrt(max(0.0, da*da + db*db - dC*dC));\\n float SC = 1.0 + 0.045 * C1;\\n float SH = 1.0 + 0.015 * C1;\\n return sqrt(dL*dL + (dC/SC)*(dC/SC) + (dH/SH)*(dH/SH));\\n}\\n\\n// CIEDE2000\\nfloat deltaE2000(vec3 lab1, vec3 lab2) {\\n float L1 = lab1.x, a1 = lab1.y, b1 = lab1.z;\\n float L2 = lab2.x, a2 = lab2.y, b2 = lab2.z;\\n\\n // Chroma\\n float C1 = sqrt(a1*a1 + b1*b1);\\n float C2 = sqrt(a2*a2 + b2*b2);\\n float Cavg = (C1 + C2) * 0.5;\\n float Cavg7 = pow(Cavg, 7.0);\\n\\n // G factor: adjustment to a* axis\\n float G = 0.5 * (1.0 - sqrt(Cavg7 / (Cavg7 + 6103515625.0))); // 25^7\\n\\n float a1p = a1 * (1.0 + G);\\n float a2p = a2 * (1.0 + G);\\n float C1p = sqrt(a1p*a1p + b1*b1);\\n float C2p = sqrt(a2p*a2p + b2*b2);\\n\\n // Guard atan(0,0): GLSL ES leaves that undefined, so skip it for achromatic colors.\\n // When a color has no chroma its hue angle is meaningless — we just need it to\\n // be a well-defined number so it doesn't corrupt the rest of the formula.\\n bool c1Achromatic = C1p < 1e-6;\\n bool c2Achromatic = C2p < 1e-6;\\n\\n float h1p = c1Achromatic ? 0.0 : atan(b1, a1p);\\n if (h1p < 0.0) h1p += TWO_PI;\\n float h2p = c2Achromatic ? 0.0 : atan(b2, a2p);\\n if (h2p < 0.0) h2p += TWO_PI;\\n\\n // Deltas\\n float dLp = L2 - L1;\\n float dCp = C2p - C1p;\\n\\n float dhp = 0.0;\\n if (!c1Achromatic && !c2Achromatic) {\\n dhp = h2p - h1p;\\n if (dhp > M_PI) dhp -= TWO_PI;\\n else if (dhp < -M_PI) dhp += TWO_PI;\\n }\\n float dHp = 2.0 * sqrt(C1p * C2p) * sin(dhp * 0.5);\\n\\n // Averages\\n float Lp = (L1 + L2) * 0.5;\\n float Cp = (C1p + C2p) * 0.5;\\n\\n // When one color is achromatic, its hue is 0 and the average is simply the other's hue\\n float hp;\\n if (c1Achromatic || c2Achromatic) {\\n hp = h1p + h2p;\\n } else if (abs(h1p - h2p) <= M_PI) {\\n hp = (h1p + h2p) * 0.5;\\n } else if (h1p + h2p < TWO_PI) {\\n hp = (h1p + h2p + TWO_PI) * 0.5;\\n } else {\\n hp = (h1p + h2p - TWO_PI) * 0.5;\\n }\\n\\n float T = 1.0\\n - 0.17 * cos(hp - radians(30.0))\\n + 0.24 * cos(2.0 * hp)\\n + 0.32 * cos(3.0 * hp + radians(6.0))\\n - 0.20 * cos(4.0 * hp - radians(63.0));\\n\\n // Weighting functions\\n float Lpm50sq = (Lp - 50.0) * (Lp - 50.0);\\n float SL = 1.0 + 0.015 * Lpm50sq / sqrt(20.0 + Lpm50sq);\\n float SC = 1.0 + 0.045 * Cp;\\n float SH = 1.0 + 0.015 * Cp * T;\\n\\n // Rotation term\\n float Cp7 = pow(Cp, 7.0);\\n float RC = 2.0 * sqrt(Cp7 / (Cp7 + 6103515625.0));\\n float hpDeg = degrees(hp);\\n float dTheta = radians(30.0) * exp(-((hpDeg - 275.0) / 25.0) * ((hpDeg - 275.0) / 25.0));\\n float RT = -sin(2.0 * dTheta) * RC;\\n\\n float dLn = dLp / SL;\\n float dCn = dCp / SC;\\n float dHn = dHp / SH;\\n\\n return sqrt(dLn*dLn + dCn*dCn + dHn*dHn + RT * dCn * dHn);\\n}\\n\"","export default \"// DISTANCE_METRIC define: 0=rgb, 1=oklab, 2=deltaE76, 3=deltaE2000, 4=kotsarenkoRamos, 5=deltaE94\\nvec3 closestColor(vec3 color, sampler2D paletteTexture) {\\n int paletteSize = textureSize(paletteTexture, 0).x;\\n float minDist = 1000000.0;\\n vec3 closest = vec3(0.0);\\n\\n // Pre-convert the input color once — palette entries are converted inside the loop.\\n #if DISTANCE_METRIC == 1\\n vec3 colorConverted = linear_srgb_to_oklab(srgb2rgb(color));\\n #elif DISTANCE_METRIC == 2 || DISTANCE_METRIC == 3 || DISTANCE_METRIC == 5\\n vec3 colorConverted = srgb_to_cielab(color);\\n #else\\n vec3 colorConverted = color;\\n #endif\\n\\n for (int i = 0; i < paletteSize; i++) {\\n vec3 paletteColor = texelFetch(paletteTexture, ivec2(i, 0), 0).rgb;\\n\\n float dist;\\n #if DISTANCE_METRIC == 1\\n dist = distance(colorConverted, linear_srgb_to_oklab(srgb2rgb(paletteColor)));\\n #elif DISTANCE_METRIC == 2\\n dist = deltaE76(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 3\\n dist = deltaE2000(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 4\\n dist = kotsarenkoRamos(color, paletteColor);\\n #elif DISTANCE_METRIC == 5\\n dist = deltaE94(colorConverted, srgb_to_cielab(paletteColor));\\n #else\\n dist = distance(colorConverted, paletteColor);\\n #endif\\n\\n if (dist < minDist) {\\n minDist = dist;\\n closest = paletteColor;\\n }\\n }\\n\\n return closest;\\n}\\n\"","import {\n ColorString,\n ColorList,\n PaletteVizOptions,\n SupportedColorModels,\n Axis,\n DistanceMetric,\n} from './types.ts';\n\n// @ts-ignore\nimport shaderSRGB2RGB from './shaders/srgb2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderOKLab from './shaders/oklab.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSL2RGB from './shaders/hsl2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSV2RGB from './shaders/hsv2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderLCH2RGB from './shaders/lch2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderDeltaE from './shaders/deltaE.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderClosestColor from './shaders/closestColor.frag.glsl?raw' assert { type: 'raw' };\n\n// Include order matters:\n// srgb2rgb – srgb2rgb()\n// oklab – M_PI, cbrt(), srgb_transfer_function(), okhsv/okhsl_to_srgb(), …\n// hsl2rgb, hsv2rgb, lch2rgb – color model conversions (lch2rgb uses M_PI + srgb_transfer_function)\n// deltaE – srgb_to_cielab(), deltaE76/94/2000() (uses srgb2rgb, cbrt, M_PI, TWO_PI)\n// closestColor – branches on DISTANCE_METRIC define; uses everything above\n//\n// Defines (compile-time, prepended to shader source — trigger recompile, no runtime branching):\n// DISTANCE_METRIC int 0=rgb 1=oklab 2=deltaE76 3=deltaE2000 4=kotsarenkoRamos 5=deltaE94\n// COLOR_MODEL int 0=rgb 1=oklab 2=okhsv 3=okhsvPolar 4=okhsl 5=okhslPolar\n// 6=oklch 7=oklchPolar 8=hsv 9=hsvPolar 10=hsl 11=hslPolar\n// PROGRESS_AXIS int 0=x 1=y 2=z\n// INVERT_Z flag (defined = true)\n// SHOW_RAW flag (defined = true)\n\nconst vertexShaderSrc = `\nprecision highp float;\nlayout(location = 0) in vec2 a_position;\nout vec2 vUv;\nvoid main() {\n vUv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`;\n\n// fragmentShader is exported so users can inspect or reuse the GLSL source.\n// Defines are NOT embedded here — they are prepended at compile time via buildProgram().\n// Note: #version 300 es is prepended by buildProgram() (must be first line,\n// before defines). Do not add it here.\nexport const fragmentShader = `\nprecision highp float;\n#define TWO_PI 6.28318530718\nin vec2 vUv;\nout vec4 fragColor;\nuniform float progress;\nuniform sampler2D paletteTexture;\n\n${shaderSRGB2RGB}\n${shaderOKLab}\n${shaderHSL2RGB}\n${shaderHSV2RGB}\n${shaderLCH2RGB}\n${shaderDeltaE}\n${shaderClosestColor}\n\n// COLOR_MODEL: 0=rgb, 1=oklab, 2=okhsv, 3=okhsvPolar, 4=okhsl, 5=okhslPolar,\n// 6=oklch, 7=oklchPolar, 8=hsv, 9=hsvPolar, 10=hsl, 11=hslPolar\nvec3 modelToRGB(vec3 colorCoords) {\n #if COLOR_MODEL == 0\n return colorCoords;\n #elif COLOR_MODEL == 1\n vec3 linear = oklab_to_linear_srgb(vec3(colorCoords.z, colorCoords.x - 0.5, colorCoords.y - 0.5));\n return clamp(vec3(srgb_transfer_function(linear.r), srgb_transfer_function(linear.g), srgb_transfer_function(linear.b)), 0.0, 1.0);\n #elif COLOR_MODEL == 2 || COLOR_MODEL == 3\n return okhsv_to_srgb(colorCoords);\n #elif COLOR_MODEL == 4 || COLOR_MODEL == 5\n return okhsl_to_srgb(colorCoords);\n #elif COLOR_MODEL == 6 || COLOR_MODEL == 7\n return lch2rgb(vec3(colorCoords.z, colorCoords.y, colorCoords.x));\n #elif COLOR_MODEL == 8 || COLOR_MODEL == 9\n return hsv2rgb(colorCoords);\n #else\n return hsl2rgb(colorCoords);\n #endif\n}\n\nvoid main(){\n #if PROGRESS_AXIS == 1\n vec3 colorCoords = vec3(vUv.x, progress, vUv.y);\n #elif PROGRESS_AXIS == 2\n vec3 colorCoords = vec3(vUv.x, vUv.y, 1. - progress);\n #else\n vec3 colorCoords = vec3(progress, vUv.x, vUv.y);\n #endif\n\n #if COLOR_MODEL == 3 || COLOR_MODEL == 5 || COLOR_MODEL == 7 || COLOR_MODEL == 9 || COLOR_MODEL == 11\n vec2 toCenter = vUv - 0.5;\n float angle = atan(toCenter.y, toCenter.x);\n float radius = length(toCenter) * 2.0;\n\n #if PROGRESS_AXIS == 2\n colorCoords = vec3((angle / TWO_PI), radius, 1. - progress);\n #elif PROGRESS_AXIS == 1\n colorCoords = vec3((angle / TWO_PI), 1. - progress, radius);\n if (radius > 1.0) { discard; }\n #else\n float hue = 1.0 - abs(0.5 - progress * .5) * 2.0;\n if (vUv.x > 0.5) { hue += 0.5; }\n colorCoords = vec3(hue, abs(0.5 - vUv.x) * 2.0, vUv.y);\n #endif\n #endif\n\n #ifdef INVERT_Z\n colorCoords.z = 1. - colorCoords.z;\n #endif\n\n vec3 rgb = modelToRGB(colorCoords);\n\n #ifdef SHOW_RAW\n fragColor = vec4(rgb, 1.);\n #else\n fragColor = vec4(closestColor(rgb, paletteTexture), 1.);\n #endif\n}`;\n\n// ── Color parsing ──────────────────────────────────────────────────────────────\n// Use a canvas 2D context as a free CSS color parser — handles hex, rgb(),\n// hsl(), named colors, etc. Lazy-initialised to avoid issues at module load time.\n\nlet _colorCtx: CanvasRenderingContext2D | null = null;\n\nfunction cssToSRGB(color: string): [number, number, number] {\n if (!_colorCtx) {\n const c = document.createElement('canvas');\n c.width = c.height = 1;\n _colorCtx = c.getContext('2d')!;\n }\n _colorCtx.fillStyle = '#000000'; // reset before setting\n _colorCtx.fillStyle = color;\n const v = _colorCtx.fillStyle; // browser normalises to '#rrggbb' or 'rgba(...)'\n if (v[0] === '#') {\n return [\n parseInt(v.slice(1, 3), 16) / 255,\n parseInt(v.slice(3, 5), 16) / 255,\n parseInt(v.slice(5, 7), 16) / 255,\n ];\n }\n // rgba(r, g, b, a) fallback\n const m = v.match(/[\\d.]+/g)!;\n return [+m[0] / 255, +m[1] / 255, +m[2] / 255];\n}\n\n// ── Palette helpers ────────────────────────────────────────────────────────────\n\n// Returns the palette as a flat RGBA Uint8Array (sRGB, 1×N texture row).\n// Useful for building your own WebGL texture or inspecting raw color data.\nexport const paletteToRGBA = (palette: ColorList): Uint8Array => {\n const data = new Uint8Array(palette.length * 4);\n palette.forEach((color, i) => {\n try {\n const [r, g, b] = cssToSRGB(color);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n } catch {\n console.error(`Invalid color: ${color}`);\n }\n });\n return data;\n};\n\n// Backwards-compatible alias (previously returned a Three.js DataTexture)\nexport const paletteToTexture = paletteToRGBA;\n\nexport const randomPalette = (size = 20): ColorList =>\n Array.from(\n { length: size },\n () =>\n `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`,\n );\n\n// ── WebGL helpers ──────────────────────────────────────────────────────────────\n\ntype Defines = Record<string, number | false>;\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`Shader compile error:\\n${log}`);\n }\n return shader;\n}\n\nfunction buildProgram(\n gl: WebGL2RenderingContext,\n defines: Defines,\n fragSrc: string,\n vertSrc: string,\n): WebGLProgram {\n // #version 300 es must be the very first line — prepend it before defines.\n const defineStr =\n Object.entries(defines)\n .filter(([, v]) => v !== false)\n .map(([k, v]) => `#define ${k} ${v}`)\n .join('\\n') + '\\n';\n const prefix = '#version 300 es\\n' + defineStr;\n\n const vert = compileShader(gl, gl.VERTEX_SHADER, prefix + vertSrc);\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, prefix + fragSrc);\n\n const prog = gl.createProgram()!;\n gl.attachShader(prog, vert);\n gl.attachShader(prog, frag);\n gl.linkProgram(prog);\n gl.deleteShader(vert);\n gl.deleteShader(frag);\n\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(prog);\n gl.deleteProgram(prog);\n throw new Error(`Program link error:\\n${log}`);\n }\n return prog;\n}\n\nfunction uploadPaletteTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n palette: ColorList,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // RGBA8: sized internal format required by WebGL2 spec\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA8,\n palette.length,\n 1,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n paletteToRGBA(palette),\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n}\n\n// ── PaletteViz ─────────────────────────────────────────────────────────────────\n\nexport class PaletteViz {\n #palette: ColorList = [];\n #width = 512;\n #height = 512;\n #pixelRatio = 1;\n\n // shader state\n #position = 0.0;\n #axis: Axis = 'y';\n #colorModel: SupportedColorModels = 'okhsv';\n #distanceMetric: DistanceMetric = 'oklab';\n #invertZ = false;\n #showRaw = false;\n\n // uniform value maps\n readonly #axisMap = { x: 0, y: 1, z: 2 } as const;\n readonly #colorModelMap = {\n rgb: 0,\n oklab: 1,\n okhsv: 2,\n okhsvPolar: 3,\n okhsl: 4,\n okhslPolar: 5,\n oklch: 6,\n oklchPolar: 7,\n hsv: 8,\n hsvPolar: 9,\n hsl: 10,\n hslPolar: 11,\n } as const;\n readonly #distanceMetricMap = {\n rgb: 0,\n oklab: 1,\n deltaE76: 2,\n deltaE2000: 3,\n kotsarenkoRamos: 4,\n deltaE94: 5,\n } as const;\n\n // WebGL\n #canvas: HTMLCanvasElement;\n #gl: WebGL2RenderingContext;\n #program: WebGLProgram | null = null;\n #texture: WebGLTexture | null = null;\n #quadBuffer: WebGLBuffer | null = null;\n #vao: WebGLVertexArrayObject | null = null;\n #animationFrame: number | null = null;\n\n // cached uniform locations (re-queried after each program rebuild)\n #uProgress: WebGLUniformLocation | null = null;\n #uPaletteTexture: WebGLUniformLocation | null = null;\n\n // dom\n #container: HTMLElement | undefined;\n\n constructor({\n palette = randomPalette(),\n width = 512,\n height = 512,\n pixelRatio = window.devicePixelRatio,\n container,\n colorModel = 'okhsv',\n distanceMetric = 'oklab',\n axis = 'y',\n position = 0.0,\n invertZ = false,\n showRaw = false,\n }: PaletteVizOptions = {}) {\n this.#palette = palette;\n this.#width = width;\n this.#height = height;\n this.#pixelRatio = pixelRatio;\n this.#colorModel = colorModel;\n this.#distanceMetric = distanceMetric;\n this.#axis = axis;\n this.#position = position;\n this.#invertZ = invertZ;\n this.#showRaw = showRaw;\n this.#container = container;\n\n this.#canvas = document.createElement('canvas');\n this.#canvas.classList.add('palette-viz');\n const gl = this.#canvas.getContext('webgl2');\n if (!gl) throw new Error('WebGL2 not supported');\n this.#gl = gl;\n\n // Quad buffer + VAO — set up once, reused every frame.\n // layout(location=0) in the vertex shader pins a_position to slot 0,\n // so the VAO remains valid across shader recompiles.\n this.#quadBuffer = gl.createBuffer()!;\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#quadBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n\n this.#vao = gl.createVertexArray()!;\n gl.bindVertexArray(this.#vao);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n\n this.#texture = gl.createTexture()!;\n uploadPaletteTexture(gl, this.#texture, this.#palette);\n\n this.#buildProgram();\n this.#setSize(this.#width, this.#height);\n this.#container?.appendChild(this.#canvas);\n this.#paint();\n }\n\n #defines(): Defines {\n return {\n DISTANCE_METRIC: this.#distanceMetricMap[this.#distanceMetric],\n COLOR_MODEL: this.#colorModelMap[this.#colorModel],\n PROGRESS_AXIS: this.#axisMap[this.#axis],\n INVERT_Z: this.#invertZ ? 1 : false,\n SHOW_RAW: this.#showRaw ? 1 : false,\n };\n }\n\n #buildProgram(): void {\n const gl = this.#gl;\n if (this.#program) gl.deleteProgram(this.#program);\n this.#program = buildProgram(gl, this.#defines(), fragmentShader, vertexShaderSrc);\n this.#uProgress = gl.getUniformLocation(this.#program, 'progress');\n this.#uPaletteTexture = gl.getUniformLocation(this.#program, 'paletteTexture');\n }\n\n #setSize(w: number, h: number): void {\n const pw = Math.round(w * this.#pixelRatio);\n const ph = Math.round(h * this.#pixelRatio);\n this.#canvas.width = pw;\n this.#canvas.height = ph;\n this.#canvas.style.width = `${w}px`;\n this.#canvas.style.height = `${h}px`;\n this.#gl.viewport(0, 0, pw, ph);\n }\n\n #paint(): void {\n if (this.#animationFrame !== null) cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = requestAnimationFrame(() => {\n const gl = this.#gl;\n gl.useProgram(this.#program);\n\n gl.uniform1f(this.#uProgress, this.#position);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.#texture);\n gl.uniform1i(this.#uPaletteTexture, 0);\n\n gl.bindVertexArray(this.#vao);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n gl.bindVertexArray(null);\n });\n }\n\n // ── Public API ──────────────────────────────────────────────────────────────\n\n get canvas(): HTMLCanvasElement {\n return this.#canvas;\n }\n get width() {\n return this.#width;\n }\n get height() {\n return this.#height;\n }\n\n resize(width: number, height: number | null = null): void {\n this.#width = width;\n this.#height = height ?? width;\n this.#setSize(this.#width, this.#height);\n this.#paint();\n }\n\n destroy(): void {\n if (this.#animationFrame !== null) {\n cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = null;\n }\n const gl = this.#gl;\n gl.deleteProgram(this.#program);\n gl.deleteTexture(this.#texture);\n gl.deleteBuffer(this.#quadBuffer);\n gl.deleteVertexArray(this.#vao);\n this.#canvas.remove();\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n\n // ── Palette ─────────────────────────────────────────────────────────────────\n\n set palette(palette: ColorList) {\n this.#palette = palette;\n uploadPaletteTexture(this.#gl, this.#texture!, palette);\n this.#paint();\n }\n get palette() {\n return this.#palette;\n }\n\n setColor(color: ColorString, index: number): void {\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette[index] = color;\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n addColor(color: ColorString, index?: number): void {\n this.#palette.splice(index ?? this.#palette.length, 0, color);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n removeColor(index: number): void;\n removeColor(color: ColorString): void;\n removeColor(indexOrColor: number | ColorString): void {\n const index =\n typeof indexOrColor === 'number' ? indexOrColor : this.#palette.indexOf(indexOrColor);\n if (index === -1) throw new Error('Color not found in palette');\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette.splice(index, 1);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n // ── Shader properties ────────────────────────────────────────────────────────\n\n set position(value: number) {\n this.#position = value;\n this.#paint();\n }\n get position() {\n return this.#position;\n }\n\n set axis(axis: Axis) {\n if (!(axis in this.#axisMap)) throw new Error(\"axis must be 'x', 'y', or 'z'\");\n this.#axis = axis;\n this.#buildProgram();\n this.#paint();\n }\n get axis() {\n return this.#axis;\n }\n\n set colorModel(model: SupportedColorModels) {\n if (!(model in this.#colorModelMap)) throw new Error(`colorModel '${model}' is not supported`);\n this.#colorModel = model;\n this.#buildProgram();\n this.#paint();\n }\n get colorModel() {\n return this.#colorModel;\n }\n\n set distanceMetric(metric: DistanceMetric) {\n if (!(metric in this.#distanceMetricMap))\n throw new Error(\n \"distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'\",\n );\n this.#distanceMetric = metric;\n this.#buildProgram();\n this.#paint();\n }\n get distanceMetric() {\n return this.#distanceMetric;\n }\n\n set invertZ(value: boolean) {\n this.#invertZ = value;\n this.#buildProgram();\n this.#paint();\n }\n get invertZ() {\n return this.#invertZ;\n }\n\n set showRaw(value: boolean) {\n this.#showRaw = value;\n this.#buildProgram();\n this.#paint();\n }\n get showRaw() {\n return this.#showRaw;\n }\n\n static paletteToRGBA = paletteToRGBA;\n /** @deprecated use PaletteViz.paletteToRGBA */\n static paletteToTexture = paletteToRGBA;\n}\n"],"names":["shaderSRGB2RGB","shaderOKLab","shaderHSL2RGB","shaderHSV2RGB","shaderLCH2RGB","shaderDeltaE","shaderClosestColor","vertexShaderSrc","fragmentShader","_colorCtx","cssToSRGB","color","c","v","m","paletteToRGBA","palette","data","i","g","b","paletteToTexture","randomPalette","size","compileShader","gl","type","src","shader","log","buildProgram","defines","fragSrc","vertSrc","prefix","k","vert","frag","prog","uploadPaletteTexture","tex","PaletteViz","#palette","#width","#height","#pixelRatio","#position","#axis","#colorModel","#distanceMetric","#invertZ","#showRaw","#axisMap","#colorModelMap","#distanceMetricMap","#canvas","#gl","#program","#texture","#quadBuffer","#vao","#animationFrame","#uProgress","#uPaletteTexture","#container","width","height","pixelRatio","container","colorModel","distanceMetric","axis","position","invertZ","showRaw","#buildProgram","#setSize","#paint","#defines","w","h","pw","ph","index","indexOrColor","value","model","metric"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA,GCAfC,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ICAfC,IAAe;AAAA;AAAA;AAAA,ICAfC,IAAe;AAAA;AAAA;AAAA;AAAA,ICAfC,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCAfC,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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"}
@@ -965,5 +965,5 @@ ${l}`)}return a}function I(n,t,e,a){const i=`#version 300 es
965
965
  `+(Object.entries(t).filter(([,f])=>f!==!1).map(([f,m])=>`#define ${f} ${m}`).join(`
966
966
  `)+`
967
967
  `),_=C(n,n.VERTEX_SHADER,i+a),p=C(n,n.FRAGMENT_SHADER,i+e),s=n.createProgram();if(n.attachShader(s,_),n.attachShader(s,p),n.linkProgram(s),n.deleteShader(_),n.deleteShader(p),!n.getProgramParameter(s,n.LINK_STATUS)){const f=n.getProgramInfoLog(s);throw n.deleteProgram(s),new Error(`Program link error:
968
- ${f}`)}return s}function h(n,t,e){n.bindTexture(n.TEXTURE_2D,t),n.texImage2D(n.TEXTURE_2D,0,n.RGBA8,e.length,1,0,n.RGBA,n.UNSIGNED_BYTE,d(e)),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.NEAREST),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.NEAREST),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE)}class O{#t=[];#f=512;#i=512;#g=1;#_=0;#c="y";#b="okhsv";#d="oklab";#h=!1;#p=!1;#C={x:0,y:1,z:2};#v={rgb:0,oklab:1,okhsv:2,okhsvPolar:3,okhsl:4,okhslPolar:5,oklch:6,oklchPolar:7,hsv:8,hsvPolar:9,hsl:10,hslPolar:11};#L={rgb:0,oklab:1,deltaE76:2,deltaE2000:3,kotsarenkoRamos:4,deltaE94:5};#e;#a;#r=null;#o=null;#u=null;#m=null;#l=null;#x=null;#T=null;#k;constructor({palette:t=u(),width:e=512,height:a=512,pixelRatio:l=window.devicePixelRatio,container:i,colorModel:_="okhsv",distanceMetric:p="oklab",axis:s="y",position:f=0,invertLightness:m=!1,showRaw:w=!1}={}){this.#t=t,this.#f=e,this.#i=a,this.#g=l,this.#b=_,this.#d=p,this.#c=s,this.#_=f,this.#h=m,this.#p=w,this.#k=i,this.#e=document.createElement("canvas"),this.#e.classList.add("palette-viz");const o=this.#e.getContext("webgl2");if(!o)throw new Error("WebGL2 not supported");this.#a=o,this.#u=o.createBuffer(),o.bindBuffer(o.ARRAY_BUFFER,this.#u),o.bufferData(o.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),o.STATIC_DRAW),this.#m=o.createVertexArray(),o.bindVertexArray(this.#m),o.enableVertexAttribArray(0),o.vertexAttribPointer(0,2,o.FLOAT,!1,0,0),o.bindVertexArray(null),this.#o=o.createTexture(),h(o,this.#o,this.#t),this.#s(),this.#S(this.#f,this.#i),this.#k?.appendChild(this.#e),this.#n()}#E(){return{DISTANCE_METRIC:this.#L[this.#d],COLOR_MODEL:this.#v[this.#b],PROGRESS_AXIS:this.#C[this.#c],INVERT_Z:this.#h?1:!1,SHOW_RAW:this.#p?1:!1}}#s(){const t=this.#a;this.#r&&t.deleteProgram(this.#r),this.#r=I(t,this.#E(),g,E),this.#x=t.getUniformLocation(this.#r,"progress"),this.#T=t.getUniformLocation(this.#r,"paletteTexture")}#S(t,e){const a=Math.round(t*this.#g),l=Math.round(e*this.#g);this.#e.width=a,this.#e.height=l,this.#e.style.width=`${t}px`,this.#e.style.height=`${e}px`,this.#a.viewport(0,0,a,l)}#n(){this.#l!==null&&cancelAnimationFrame(this.#l),this.#l=requestAnimationFrame(()=>{const t=this.#a;t.useProgram(this.#r),t.uniform1f(this.#x,this.#_),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.#o),t.uniform1i(this.#T,0),t.bindVertexArray(this.#m),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.bindVertexArray(null)})}get canvas(){return this.#e}get width(){return this.#f}get height(){return this.#i}resize(t,e=null){this.#f=t,this.#i=e??t,this.#S(this.#f,this.#i),this.#n()}destroy(){this.#l!==null&&(cancelAnimationFrame(this.#l),this.#l=null);const t=this.#a;t.deleteProgram(this.#r),t.deleteTexture(this.#o),t.deleteBuffer(this.#u),t.deleteVertexArray(this.#m),this.#e.remove(),t.getExtension("WEBGL_lose_context")?.loseContext()}set palette(t){this.#t=t,h(this.#a,this.#o,t),this.#n()}get palette(){return this.#t}setColor(t,e){if(e<0||e>=this.#t.length)throw new Error(`Index ${e} out of range`);this.#t[e]=t,h(this.#a,this.#o,this.#t),this.#n()}addColor(t,e){this.#t.splice(e??this.#t.length,0,t),h(this.#a,this.#o,this.#t),this.#n()}removeColor(t){const e=typeof t=="number"?t:this.#t.indexOf(t);if(e===-1)throw new Error("Color not found in palette");if(e<0||e>=this.#t.length)throw new Error(`Index ${e} out of range`);this.#t.splice(e,1),h(this.#a,this.#o,this.#t),this.#n()}set position(t){this.#_=t,this.#n()}get position(){return this.#_}set axis(t){if(!(t in this.#C))throw new Error("axis must be 'x', 'y', or 'z'");this.#c=t,this.#s(),this.#n()}get axis(){return this.#c}set colorModel(t){if(!(t in this.#v))throw new Error(`colorModel '${t}' is not supported`);this.#b=t,this.#s(),this.#n()}get colorModel(){return this.#b}set distanceMetric(t){if(!(t in this.#L))throw new Error("distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'");this.#d=t,this.#s(),this.#n()}get distanceMetric(){return this.#d}set invertLightness(t){this.#h=t,this.#s(),this.#n()}get invertLightness(){return this.#h}set showRaw(t){this.#p=t,this.#s(),this.#n()}get showRaw(){return this.#p}static paletteToRGBA=d;static paletteToTexture=d}r.PaletteViz=O,r.fragmentShader=g,r.paletteToRGBA=d,r.paletteToTexture=y,r.randomPalette=u,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
968
+ ${f}`)}return s}function h(n,t,e){n.bindTexture(n.TEXTURE_2D,t),n.texImage2D(n.TEXTURE_2D,0,n.RGBA8,e.length,1,0,n.RGBA,n.UNSIGNED_BYTE,d(e)),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.NEAREST),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.NEAREST),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE)}class O{#t=[];#f=512;#i=512;#g=1;#_=0;#c="y";#b="okhsv";#d="oklab";#h=!1;#p=!1;#C={x:0,y:1,z:2};#v={rgb:0,oklab:1,okhsv:2,okhsvPolar:3,okhsl:4,okhslPolar:5,oklch:6,oklchPolar:7,hsv:8,hsvPolar:9,hsl:10,hslPolar:11};#L={rgb:0,oklab:1,deltaE76:2,deltaE2000:3,kotsarenkoRamos:4,deltaE94:5};#e;#a;#r=null;#o=null;#u=null;#m=null;#l=null;#x=null;#T=null;#k;constructor({palette:t=u(),width:e=512,height:a=512,pixelRatio:l=window.devicePixelRatio,container:i,colorModel:_="okhsv",distanceMetric:p="oklab",axis:s="y",position:f=0,invertZ:m=!1,showRaw:w=!1}={}){this.#t=t,this.#f=e,this.#i=a,this.#g=l,this.#b=_,this.#d=p,this.#c=s,this.#_=f,this.#h=m,this.#p=w,this.#k=i,this.#e=document.createElement("canvas"),this.#e.classList.add("palette-viz");const o=this.#e.getContext("webgl2");if(!o)throw new Error("WebGL2 not supported");this.#a=o,this.#u=o.createBuffer(),o.bindBuffer(o.ARRAY_BUFFER,this.#u),o.bufferData(o.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),o.STATIC_DRAW),this.#m=o.createVertexArray(),o.bindVertexArray(this.#m),o.enableVertexAttribArray(0),o.vertexAttribPointer(0,2,o.FLOAT,!1,0,0),o.bindVertexArray(null),this.#o=o.createTexture(),h(o,this.#o,this.#t),this.#s(),this.#S(this.#f,this.#i),this.#k?.appendChild(this.#e),this.#n()}#E(){return{DISTANCE_METRIC:this.#L[this.#d],COLOR_MODEL:this.#v[this.#b],PROGRESS_AXIS:this.#C[this.#c],INVERT_Z:this.#h?1:!1,SHOW_RAW:this.#p?1:!1}}#s(){const t=this.#a;this.#r&&t.deleteProgram(this.#r),this.#r=I(t,this.#E(),g,E),this.#x=t.getUniformLocation(this.#r,"progress"),this.#T=t.getUniformLocation(this.#r,"paletteTexture")}#S(t,e){const a=Math.round(t*this.#g),l=Math.round(e*this.#g);this.#e.width=a,this.#e.height=l,this.#e.style.width=`${t}px`,this.#e.style.height=`${e}px`,this.#a.viewport(0,0,a,l)}#n(){this.#l!==null&&cancelAnimationFrame(this.#l),this.#l=requestAnimationFrame(()=>{const t=this.#a;t.useProgram(this.#r),t.uniform1f(this.#x,this.#_),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.#o),t.uniform1i(this.#T,0),t.bindVertexArray(this.#m),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.bindVertexArray(null)})}get canvas(){return this.#e}get width(){return this.#f}get height(){return this.#i}resize(t,e=null){this.#f=t,this.#i=e??t,this.#S(this.#f,this.#i),this.#n()}destroy(){this.#l!==null&&(cancelAnimationFrame(this.#l),this.#l=null);const t=this.#a;t.deleteProgram(this.#r),t.deleteTexture(this.#o),t.deleteBuffer(this.#u),t.deleteVertexArray(this.#m),this.#e.remove(),t.getExtension("WEBGL_lose_context")?.loseContext()}set palette(t){this.#t=t,h(this.#a,this.#o,t),this.#n()}get palette(){return this.#t}setColor(t,e){if(e<0||e>=this.#t.length)throw new Error(`Index ${e} out of range`);this.#t[e]=t,h(this.#a,this.#o,this.#t),this.#n()}addColor(t,e){this.#t.splice(e??this.#t.length,0,t),h(this.#a,this.#o,this.#t),this.#n()}removeColor(t){const e=typeof t=="number"?t:this.#t.indexOf(t);if(e===-1)throw new Error("Color not found in palette");if(e<0||e>=this.#t.length)throw new Error(`Index ${e} out of range`);this.#t.splice(e,1),h(this.#a,this.#o,this.#t),this.#n()}set position(t){this.#_=t,this.#n()}get position(){return this.#_}set axis(t){if(!(t in this.#C))throw new Error("axis must be 'x', 'y', or 'z'");this.#c=t,this.#s(),this.#n()}get axis(){return this.#c}set colorModel(t){if(!(t in this.#v))throw new Error(`colorModel '${t}' is not supported`);this.#b=t,this.#s(),this.#n()}get colorModel(){return this.#b}set distanceMetric(t){if(!(t in this.#L))throw new Error("distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'");this.#d=t,this.#s(),this.#n()}get distanceMetric(){return this.#d}set invertZ(t){this.#h=t,this.#s(),this.#n()}get invertZ(){return this.#h}set showRaw(t){this.#p=t,this.#s(),this.#n()}get showRaw(){return this.#p}static paletteToRGBA=d;static paletteToTexture=d}r.PaletteViz=O,r.fragmentShader=g,r.paletteToRGBA=d,r.paletteToTexture=y,r.randomPalette=u,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
969
969
  //# sourceMappingURL=palette-shader.umd.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"palette-shader.umd.cjs","sources":["../src/shaders/srgb2rgb.frag.glsl?raw","../src/shaders/oklab.frag.glsl?raw","../src/shaders/hsl2rgb.frag.glsl?raw","../src/shaders/hsv2rgb.frag.glsl?raw","../src/shaders/lch2rgb.frag.glsl?raw","../src/shaders/deltaE.frag.glsl?raw","../src/shaders/closestColor.frag.glsl?raw","../src/index.ts"],"sourcesContent":["export default \"// https://lygia.xyz/\\nfloat srgb2rgb(const in float v) { return (v < 0.04045) ? v * 0.0773993808 : pow((v + 0.055) * 0.947867298578199, 2.4); }\\nvec3 srgb2rgb(const in vec3 srgb) { return vec3(srgb2rgb(srgb.r), srgb2rgb(srgb.g), srgb2rgb(srgb.b)); }\\nvec4 srgb2rgb(const in vec4 srgb) { return vec4(srgb2rgb(srgb.rgb), srgb.a); }\\n\"","export default \"// Copyright(c) 2021 Björn Ottosson\\n//\\n// Permission is hereby granted, free of charge, to any person obtaining a copy of\\n// this softwareand associated documentation files(the \\\"Software\\\"), to deal in\\n// the Software without restriction, including without limitation the rights to\\n// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies\\n// of the Software, and to permit persons to whom the Software is furnished to do\\n// so, subject to the following conditions :\\n// The above copyright noticeand this permission notice shall be included in all\\n// copies or substantial portions of the Software.\\n// THE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE\\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\n// SOFTWARE.\\n\\n#define M_PI 3.1415926535897932384626433832795\\n\\nfloat cbrt( float x )\\n{\\n return sign(x)*pow(abs(x),1.0f/3.0f);\\n}\\n\\nfloat srgb_transfer_function(float a)\\n{\\n\\treturn .0031308f >= a ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f;\\n}\\n\\nfloat srgb_transfer_function_inv(float a)\\n{\\n\\treturn .04045f < a ? pow((a + .055f) / 1.055f, 2.4f) : a / 12.92f;\\n}\\n\\nvec3 linear_srgb_to_oklab(vec3 c)\\n{\\n\\tfloat l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;\\n\\tfloat m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;\\n\\tfloat s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;\\n\\n\\tfloat l_ = cbrt(l);\\n\\tfloat m_ = cbrt(m);\\n\\tfloat s_ = cbrt(s);\\n\\n\\treturn vec3(\\n\\t\\t0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,\\n\\t\\t1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,\\n\\t\\t0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_\\n\\t);\\n}\\n\\nvec3 oklab_to_linear_srgb(vec3 c)\\n{\\n\\tfloat l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;\\n\\tfloat m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;\\n\\tfloat s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;\\n\\n\\tfloat l = l_ * l_ * l_;\\n\\tfloat m = m_ * m_ * m_;\\n\\tfloat s = s_ * s_ * s_;\\n\\n\\treturn vec3(\\n\\t\\t+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,\\n\\t\\t-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,\\n\\t\\t-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s\\n\\t);\\n}\\n\\n// Finds the maximum saturation possible for a given hue that fits in sRGB\\n// Saturation here is defined as S = C/L\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat compute_max_saturation(float a, float b)\\n{\\n\\t// Max saturation will be when one of r, g or b goes below zero.\\n\\n\\t// Select different coefficients depending on which component goes below zero first\\n\\tfloat k0, k1, k2, k3, k4, wl, wm, ws;\\n\\n\\tif (-1.88170328f * a - 0.80936493f * b > 1.f)\\n\\t{\\n\\t\\t// Red component\\n\\t\\tk0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;\\n\\t\\twl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;\\n\\t}\\n\\telse if (1.81444104f * a - 1.19445276f * b > 1.f)\\n\\t{\\n\\t\\t// Green component\\n\\t\\tk0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;\\n\\t\\twl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Blue component\\n\\t\\tk0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;\\n\\t\\twl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;\\n\\t}\\n\\n\\t// Approximate max saturation using a polynomial:\\n\\tfloat S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;\\n\\n\\t// Do one step Halley's method to get closer\\n\\t// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite\\n\\t// this should be sufficient for most applications, otherwise do two/three steps \\n\\n\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t{\\n\\t\\tfloat l_ = 1.f + S * k_l;\\n\\t\\tfloat m_ = 1.f + S * k_m;\\n\\t\\tfloat s_ = 1.f + S * k_s;\\n\\n\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\tfloat l_dS = 3.f * k_l * l_ * l_;\\n\\t\\tfloat m_dS = 3.f * k_m * m_ * m_;\\n\\t\\tfloat s_dS = 3.f * k_s * s_ * s_;\\n\\n\\t\\tfloat l_dS2 = 6.f * k_l * k_l * l_;\\n\\t\\tfloat m_dS2 = 6.f * k_m * k_m * m_;\\n\\t\\tfloat s_dS2 = 6.f * k_s * k_s * s_;\\n\\n\\t\\tfloat f = wl * l + wm * m + ws * s;\\n\\t\\tfloat f1 = wl * l_dS + wm * m_dS + ws * s_dS;\\n\\t\\tfloat f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;\\n\\n\\t\\tS = S - f * f1 / (f1 * f1 - 0.5f * f * f2);\\n\\t}\\n\\n\\treturn S;\\n}\\n\\n// finds L_cusp and C_cusp for a given hue\\n// a and b must be normalized so a^2 + b^2 == 1\\nvec2 find_cusp(float a, float b)\\n{\\n\\t// First, find the maximum saturation (saturation S = C/L)\\n\\tfloat S_cusp = compute_max_saturation(a, b);\\n\\n\\t// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:\\n\\tvec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b ));\\n\\tfloat L_cusp = cbrt(1.f / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));\\n\\tfloat C_cusp = L_cusp * S_cusp;\\n\\n\\treturn vec2( L_cusp , C_cusp );\\n}\\n\\n// Finds intersection of the line defined by \\n// L = L0 * (1 - t) + t * L1;\\n// C = t * C1;\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0, vec2 cusp)\\n{\\n\\t// Find the intersection for upper and lower half seprately\\n\\tfloat t;\\n\\tif (((L1 - L0) * cusp.y - (cusp.x - L0) * C1) <= 0.f)\\n\\t{\\n\\t\\t// Lower half\\n\\n\\t\\tt = cusp.y * L0 / (C1 * cusp.x + cusp.y * (L0 - L1));\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Upper half\\n\\n\\t\\t// First intersect with triangle\\n\\t\\tt = cusp.y * (L0 - 1.f) / (C1 * (cusp.x - 1.f) + cusp.y * (L0 - L1));\\n\\n\\t\\t// Then one step Halley's method\\n\\t\\t{\\n\\t\\t\\tfloat dL = L1 - L0;\\n\\t\\t\\tfloat dC = C1;\\n\\n\\t\\t\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\t\\t\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\t\\t\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t\\t\\tfloat l_dt = dL + dC * k_l;\\n\\t\\t\\tfloat m_dt = dL + dC * k_m;\\n\\t\\t\\tfloat s_dt = dL + dC * k_s;\\n\\n\\n\\t\\t\\t// If higher accuracy is required, 2 or 3 iterations of the following block can be used:\\n\\t\\t\\t{\\n\\t\\t\\t\\tfloat L = L0 * (1.f - t) + t * L1;\\n\\t\\t\\t\\tfloat C = t * C1;\\n\\n\\t\\t\\t\\tfloat l_ = L + C * k_l;\\n\\t\\t\\t\\tfloat m_ = L + C * k_m;\\n\\t\\t\\t\\tfloat s_ = L + C * k_s;\\n\\n\\t\\t\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\t\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\t\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt = 3.f * l_dt * l_ * l_;\\n\\t\\t\\t\\tfloat mdt = 3.f * m_dt * m_ * m_;\\n\\t\\t\\t\\tfloat sdt = 3.f * s_dt * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt2 = 6.f * l_dt * l_dt * l_;\\n\\t\\t\\t\\tfloat mdt2 = 6.f * m_dt * m_dt * m_;\\n\\t\\t\\t\\tfloat sdt2 = 6.f * s_dt * s_dt * s_;\\n\\n\\t\\t\\t\\tfloat r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1.f;\\n\\t\\t\\t\\tfloat r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;\\n\\t\\t\\t\\tfloat r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_r = r1 / (r1 * r1 - 0.5f * r * r2);\\n\\t\\t\\t\\tfloat t_r = -r * u_r;\\n\\n\\t\\t\\t\\tfloat g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1.f;\\n\\t\\t\\t\\tfloat g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;\\n\\t\\t\\t\\tfloat g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_g = g1 / (g1 * g1 - 0.5f * g * g2);\\n\\t\\t\\t\\tfloat t_g = -g * u_g;\\n\\n\\t\\t\\t\\tfloat b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1.f;\\n\\t\\t\\t\\tfloat b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;\\n\\t\\t\\t\\tfloat b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_b = b1 / (b1 * b1 - 0.5f * b * b2);\\n\\t\\t\\t\\tfloat t_b = -b * u_b;\\n\\n\\t\\t\\t\\tt_r = u_r >= 0.f ? t_r : 10000.f;\\n\\t\\t\\t\\tt_g = u_g >= 0.f ? t_g : 10000.f;\\n\\t\\t\\t\\tt_b = u_b >= 0.f ? t_b : 10000.f;\\n\\n\\t\\t\\t\\tt += min(t_r, min(t_g, t_b));\\n\\t\\t\\t}\\n\\t\\t}\\n\\t}\\n\\n\\treturn t;\\n}\\n\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0)\\n{\\n\\t// Find the cusp of the gamut triangle\\n\\tvec2 cusp = find_cusp(a, b);\\n\\n\\treturn find_gamut_intersection(a, b, L1, C1, L0, cusp);\\n}\\n\\nvec3 gamut_clip_preserve_chroma(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = clamp(L, 0.f, 1.f);\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_0_5(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = 0.5;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_L_cusp(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat L0 = cusp.x;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_0_5(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat Ld = L - 0.5f;\\n\\tfloat e1 = 0.5f + abs(Ld) + alpha * C;\\n\\tfloat L0 = 0.5f * (1.f + sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_L_cusp(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat Ld = L - cusp.x;\\n\\tfloat k = 2.f * (Ld > 0.f ? 1.f - cusp.x : cusp.x);\\n\\n\\tfloat e1 = 0.5f * k + abs(Ld) + alpha * C / k;\\n\\tfloat L0 = cusp.x + 0.5f * (sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * k * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nfloat toe(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn 0.5f * (k_3 * x - k_1 + sqrt((k_3 * x - k_1) * (k_3 * x - k_1) + 4.f * k_2 * k_3 * x));\\n}\\n\\nfloat toe_inv(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn (x * x + k_1 * x) / (k_3 * (x + k_2));\\n}\\n\\nvec2 to_ST(vec2 cusp)\\n{\\n\\tfloat L = cusp.x;\\n\\tfloat C = cusp.y;\\n\\treturn vec2( C / L, C / (1.f - L) );\\n}\\n\\n// Returns a smooth approximation of the location of the cusp\\n// This polynomial was created by an optimization process\\n// It has been designed so that S_mid < S_max and T_mid < T_max\\nvec2 get_ST_mid(float a_, float b_)\\n{\\n\\tfloat S = 0.11516993f + 1.f / (\\n\\t\\t+7.44778970f + 4.15901240f * b_\\n\\t\\t+ a_ * (-2.19557347f + 1.75198401f * b_\\n\\t\\t\\t+ a_ * (-2.13704948f - 10.02301043f * b_\\n\\t\\t\\t\\t+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\tfloat T = 0.11239642f + 1.f / (\\n\\t\\t+1.61320320f - 0.68124379f * b_\\n\\t\\t+ a_ * (+0.40370612f + 0.90148123f * b_\\n\\t\\t\\t+ a_ * (-0.27087943f + 0.61223990f * b_\\n\\t\\t\\t\\t+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\treturn vec2( S, T );\\n}\\n\\nvec3 get_Cs(float L, float a_, float b_)\\n{\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat C_max = find_gamut_intersection(a_, b_, L, 1.f, L, cusp);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\t\\n\\t// Scale factor to compensate for the curved part of gamut shape:\\n\\tfloat k = C_max / min((L * ST_max.x), (1.f - L) * ST_max.y);\\n\\n\\tfloat C_mid;\\n\\t{\\n\\t\\tvec2 ST_mid = get_ST_mid(a_, b_);\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tfloat C_a = L * ST_mid.x;\\n\\t\\tfloat C_b = (1.f - L) * ST_mid.y;\\n\\t\\tC_mid = 0.9f * k * sqrt(sqrt(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));\\n\\t}\\n\\n\\tfloat C_0;\\n\\t{\\n\\t\\t// for C_0, the shape is independent of hue, so vec2 are constant. Values picked to roughly be the average values of vec2.\\n\\t\\tfloat C_a = L * 0.4f;\\n\\t\\tfloat C_b = (1.f - L) * 0.8f;\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tC_0 = sqrt(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));\\n\\t}\\n\\n\\treturn vec3( C_0, C_mid, C_max );\\n}\\n\\nvec3 okhsl_to_srgb(vec3 hsl)\\n{\\n\\tfloat h = hsl.x;\\n\\tfloat s = hsl.y;\\n\\tfloat l = hsl.z;\\n\\n\\tif (l == 1.0f)\\n\\t{\\n\\t\\treturn vec3( 1.f, 1.f, 1.f );\\n\\t}\\n\\n\\telse if (l == 0.f)\\n\\t{\\n\\t\\treturn vec3( 0.f, 0.f, 0.f );\\n\\t}\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\tfloat L = toe_inv(l);\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat C, t, k_0, k_1, k_2;\\n\\n\\tif (s < mid)\\n\\t{\\n\\t\\tt = mid_inv * s;\\n\\n\\t\\tk_1 = mid * C_0;\\n\\t\\tk_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tC = t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tt = (s - mid)/ (1.f - mid);\\n\\n\\t\\tk_0 = C_mid;\\n\\t\\tk_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tk_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tC = k_0 + t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsl(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\t// Inverse of the interpolation in okhsl_to_srgb:\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat s;\\n\\tif (C < C_mid)\\n\\t{\\n\\t\\tfloat k_1 = mid * C_0;\\n\\t\\tfloat k_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tfloat t = C / (k_1 + k_2 * C);\\n\\t\\ts = t * mid;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tfloat k_0 = C_mid;\\n\\t\\tfloat k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tfloat k_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tfloat t = (C - k_0) / (k_1 + k_2 * (C - k_0));\\n\\t\\ts = mid + (1.f - mid) * t;\\n\\t}\\n\\n\\tfloat l = toe(L);\\n\\treturn vec3( h, s, l );\\n}\\n\\n\\nvec3 okhsv_to_srgb(vec3 hsv)\\n{\\n\\tfloat h = hsv.x;\\n\\tfloat s = hsv.y;\\n\\tfloat v = hsv.z;\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\t\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f- S_0 / S_max;\\n\\n\\t// first we compute L and V as if the gamut is a perfect triangle:\\n\\n\\t// L, C when v==1:\\n\\tfloat L_v = 1.f - s * S_0 / (S_0 + T_max - T_max * k * s);\\n\\tfloat C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);\\n\\n\\tfloat L = v * L_v;\\n\\tfloat C = v * C_v;\\n\\n\\t// then we compensate for both toe and the curved top part of the triangle:\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\tfloat L_new = toe_inv(L);\\n\\tC = C * L_new / L;\\n\\tL = L_new;\\n\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L * scale_L;\\n\\tC = C * scale_L;\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsv(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f - S_0 / S_max;\\n\\n\\t// first we find L_v, C_v, L_vt and C_vt\\n\\n\\tfloat t = T_max / (C + L * T_max);\\n\\tfloat L_v = t * L;\\n\\tfloat C_v = t * C;\\n\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\t// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L / scale_L;\\n\\tC = C / scale_L;\\n\\n\\tC = C * toe(L) / L;\\n\\tL = toe(L);\\n\\n\\t// we can now compute v and s:\\n\\n\\tfloat v = L / L_v;\\n\\tfloat s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);\\n\\n\\treturn vec3 (h, s, v );\\n}\"","export default \"vec3 hsl2rgb( in vec3 c ) {\\n vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );\\n return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));\\n}\"","export default \"vec3 hsv2rgb(vec3 c) {\\n vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\\n vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\\n return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\\n}\"","export default \"// slightly rearranged vector components so it matches with LCH\\n// M_PI and srgb_transfer_function are provided by oklab.frag.glsl (included before this file)\\nvec3 lch2rgb(vec3 lch) {\\n lch.y *= 0.34;\\n\\n vec3 lab = vec3(\\n lch.x,\\n lch.y * cos(lch.z * M_PI*2.0),\\n lch.y * sin(lch.z * M_PI*2.0)\\n );\\n\\n vec3 lms = vec3(\\n lab.x + 0.3963377774f * lab.y + 0.2158037573f * lab.z,\\n lab.x - 0.1055613458f * lab.y - 0.0638541728f * lab.z,\\n lab.x - 0.0894841775f * lab.y - 1.2914855480f * lab.z\\n );\\n\\n lms = pow(max(lms, vec3(0.0)), vec3(3.0));\\n\\n vec3 rgb = vec3(\\n +4.0767416621f * lms.x - 3.3077115913f * lms.y + 0.2309699292f * lms.z,\\n -1.2684380046f * lms.x + 2.6097574011f * lms.y - 0.3413193965f * lms.z,\\n -0.0041960863f * lms.x - 0.7034186147f * lms.y + 1.7076147010f * lms.z\\n );\\n\\n return vec3(\\n srgb_transfer_function(rgb.r),\\n srgb_transfer_function(rgb.g),\\n srgb_transfer_function(rgb.b)\\n );\\n}\\n\"","export default \"// Kotsarenko/Ramos weighted RGB distance.\\n// Operates on sRGB values directly (no linearisation needed).\\n// Weights red and blue channels by the mean red value, which improves\\n// perceptual uniformity compared to plain Euclidean RGB at minimal cost.\\nfloat kotsarenkoRamos(vec3 c1, vec3 c2) {\\n float rMean = (c1.r + c2.r) * 0.5;\\n vec3 d = c1 - c2;\\n return sqrt((2.0 + rMean) * d.r*d.r + 4.0 * d.g*d.g + (3.0 - rMean) * d.b*d.b);\\n}\\n\\n// ── CIELab ────────────────────────────────────────────────────────────────────\\n// sRGB -> XYZ (D65) -> CIELab\\n// Depends on: srgb2rgb() from srgb2rgb.frag.glsl, cbrt() from oklab.frag.glsl\\n\\nfloat _lab_f(float t) {\\n float delta = 6.0 / 29.0;\\n return t > delta * delta * delta\\n ? cbrt(t)\\n : t / (3.0 * delta * delta) + 4.0 / 29.0;\\n}\\n\\nvec3 srgb_to_cielab(vec3 srgb) {\\n vec3 lin = srgb2rgb(srgb);\\n\\n // Linear sRGB -> XYZ (D65 illuminant)\\n vec3 xyz = vec3(\\n 0.4124564 * lin.r + 0.3575761 * lin.g + 0.1804375 * lin.b,\\n 0.2126729 * lin.r + 0.7151522 * lin.g + 0.0721750 * lin.b,\\n 0.0193339 * lin.r + 0.1191920 * lin.g + 0.9503041 * lin.b\\n );\\n\\n // XYZ -> Lab (D65 white point: 0.95047, 1.00000, 1.08883)\\n float fx = _lab_f(xyz.x / 0.95047);\\n float fy = _lab_f(xyz.y);\\n float fz = _lab_f(xyz.z / 1.08883);\\n\\n return vec3(\\n 116.0 * fy - 16.0, // L*\\n 500.0 * (fx - fy), // a*\\n 200.0 * (fy - fz) // b*\\n );\\n}\\n\\n// CIE76: plain Euclidean distance in CIELab\\nfloat deltaE76(vec3 lab1, vec3 lab2) {\\n return distance(lab1, lab2);\\n}\\n\\n// CIE94: weighted chroma/hue corrections, cheaper than CIEDE2000\\n// Uses graphics application constants: kL=1, K1=0.045, K2=0.015\\nfloat deltaE94(vec3 lab1, vec3 lab2) {\\n float dL = lab1.x - lab2.x;\\n float da = lab1.y - lab2.y;\\n float db = lab1.z - lab2.z;\\n float C1 = sqrt(lab1.y * lab1.y + lab1.z * lab1.z);\\n float C2 = sqrt(lab2.y * lab2.y + lab2.z * lab2.z);\\n float dC = C1 - C2;\\n float dH = sqrt(max(0.0, da*da + db*db - dC*dC));\\n float SC = 1.0 + 0.045 * C1;\\n float SH = 1.0 + 0.015 * C1;\\n return sqrt(dL*dL + (dC/SC)*(dC/SC) + (dH/SH)*(dH/SH));\\n}\\n\\n// CIEDE2000\\nfloat deltaE2000(vec3 lab1, vec3 lab2) {\\n float L1 = lab1.x, a1 = lab1.y, b1 = lab1.z;\\n float L2 = lab2.x, a2 = lab2.y, b2 = lab2.z;\\n\\n // Chroma\\n float C1 = sqrt(a1*a1 + b1*b1);\\n float C2 = sqrt(a2*a2 + b2*b2);\\n float Cavg = (C1 + C2) * 0.5;\\n float Cavg7 = pow(Cavg, 7.0);\\n\\n // G factor: adjustment to a* axis\\n float G = 0.5 * (1.0 - sqrt(Cavg7 / (Cavg7 + 6103515625.0))); // 25^7\\n\\n float a1p = a1 * (1.0 + G);\\n float a2p = a2 * (1.0 + G);\\n float C1p = sqrt(a1p*a1p + b1*b1);\\n float C2p = sqrt(a2p*a2p + b2*b2);\\n\\n // Guard atan(0,0): GLSL ES leaves that undefined, so skip it for achromatic colors.\\n // When a color has no chroma its hue angle is meaningless — we just need it to\\n // be a well-defined number so it doesn't corrupt the rest of the formula.\\n bool c1Achromatic = C1p < 1e-6;\\n bool c2Achromatic = C2p < 1e-6;\\n\\n float h1p = c1Achromatic ? 0.0 : atan(b1, a1p);\\n if (h1p < 0.0) h1p += TWO_PI;\\n float h2p = c2Achromatic ? 0.0 : atan(b2, a2p);\\n if (h2p < 0.0) h2p += TWO_PI;\\n\\n // Deltas\\n float dLp = L2 - L1;\\n float dCp = C2p - C1p;\\n\\n float dhp = 0.0;\\n if (!c1Achromatic && !c2Achromatic) {\\n dhp = h2p - h1p;\\n if (dhp > M_PI) dhp -= TWO_PI;\\n else if (dhp < -M_PI) dhp += TWO_PI;\\n }\\n float dHp = 2.0 * sqrt(C1p * C2p) * sin(dhp * 0.5);\\n\\n // Averages\\n float Lp = (L1 + L2) * 0.5;\\n float Cp = (C1p + C2p) * 0.5;\\n\\n // When one color is achromatic, its hue is 0 and the average is simply the other's hue\\n float hp;\\n if (c1Achromatic || c2Achromatic) {\\n hp = h1p + h2p;\\n } else if (abs(h1p - h2p) <= M_PI) {\\n hp = (h1p + h2p) * 0.5;\\n } else if (h1p + h2p < TWO_PI) {\\n hp = (h1p + h2p + TWO_PI) * 0.5;\\n } else {\\n hp = (h1p + h2p - TWO_PI) * 0.5;\\n }\\n\\n float T = 1.0\\n - 0.17 * cos(hp - radians(30.0))\\n + 0.24 * cos(2.0 * hp)\\n + 0.32 * cos(3.0 * hp + radians(6.0))\\n - 0.20 * cos(4.0 * hp - radians(63.0));\\n\\n // Weighting functions\\n float Lpm50sq = (Lp - 50.0) * (Lp - 50.0);\\n float SL = 1.0 + 0.015 * Lpm50sq / sqrt(20.0 + Lpm50sq);\\n float SC = 1.0 + 0.045 * Cp;\\n float SH = 1.0 + 0.015 * Cp * T;\\n\\n // Rotation term\\n float Cp7 = pow(Cp, 7.0);\\n float RC = 2.0 * sqrt(Cp7 / (Cp7 + 6103515625.0));\\n float hpDeg = degrees(hp);\\n float dTheta = radians(30.0) * exp(-((hpDeg - 275.0) / 25.0) * ((hpDeg - 275.0) / 25.0));\\n float RT = -sin(2.0 * dTheta) * RC;\\n\\n float dLn = dLp / SL;\\n float dCn = dCp / SC;\\n float dHn = dHp / SH;\\n\\n return sqrt(dLn*dLn + dCn*dCn + dHn*dHn + RT * dCn * dHn);\\n}\\n\"","export default \"// DISTANCE_METRIC define: 0=rgb, 1=oklab, 2=deltaE76, 3=deltaE2000, 4=kotsarenkoRamos, 5=deltaE94\\nvec3 closestColor(vec3 color, sampler2D paletteTexture) {\\n int paletteSize = textureSize(paletteTexture, 0).x;\\n float minDist = 1000000.0;\\n vec3 closest = vec3(0.0);\\n\\n // Pre-convert the input color once — palette entries are converted inside the loop.\\n #if DISTANCE_METRIC == 1\\n vec3 colorConverted = linear_srgb_to_oklab(srgb2rgb(color));\\n #elif DISTANCE_METRIC == 2 || DISTANCE_METRIC == 3 || DISTANCE_METRIC == 5\\n vec3 colorConverted = srgb_to_cielab(color);\\n #else\\n vec3 colorConverted = color;\\n #endif\\n\\n for (int i = 0; i < paletteSize; i++) {\\n vec3 paletteColor = texelFetch(paletteTexture, ivec2(i, 0), 0).rgb;\\n\\n float dist;\\n #if DISTANCE_METRIC == 1\\n dist = distance(colorConverted, linear_srgb_to_oklab(srgb2rgb(paletteColor)));\\n #elif DISTANCE_METRIC == 2\\n dist = deltaE76(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 3\\n dist = deltaE2000(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 4\\n dist = kotsarenkoRamos(color, paletteColor);\\n #elif DISTANCE_METRIC == 5\\n dist = deltaE94(colorConverted, srgb_to_cielab(paletteColor));\\n #else\\n dist = distance(colorConverted, paletteColor);\\n #endif\\n\\n if (dist < minDist) {\\n minDist = dist;\\n closest = paletteColor;\\n }\\n }\\n\\n return closest;\\n}\\n\"","import {\n ColorString,\n ColorList,\n PaletteVizOptions,\n SupportedColorModels,\n Axis,\n DistanceMetric,\n} from './types.ts';\n\n// @ts-ignore\nimport shaderSRGB2RGB from './shaders/srgb2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderOKLab from './shaders/oklab.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSL2RGB from './shaders/hsl2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSV2RGB from './shaders/hsv2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderLCH2RGB from './shaders/lch2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderDeltaE from './shaders/deltaE.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderClosestColor from './shaders/closestColor.frag.glsl?raw' assert { type: 'raw' };\n\n// Include order matters:\n// srgb2rgb – srgb2rgb()\n// oklab – M_PI, cbrt(), srgb_transfer_function(), okhsv/okhsl_to_srgb(), …\n// hsl2rgb, hsv2rgb, lch2rgb – color model conversions (lch2rgb uses M_PI + srgb_transfer_function)\n// deltaE – srgb_to_cielab(), deltaE76/94/2000() (uses srgb2rgb, cbrt, M_PI, TWO_PI)\n// closestColor – branches on DISTANCE_METRIC define; uses everything above\n//\n// Defines (compile-time, prepended to shader source — trigger recompile, no runtime branching):\n// DISTANCE_METRIC int 0=rgb 1=oklab 2=deltaE76 3=deltaE2000 4=kotsarenkoRamos 5=deltaE94\n// COLOR_MODEL int 0=rgb 1=oklab 2=okhsv 3=okhsvPolar 4=okhsl 5=okhslPolar\n// 6=oklch 7=oklchPolar 8=hsv 9=hsvPolar 10=hsl 11=hslPolar\n// PROGRESS_AXIS int 0=x 1=y 2=z\n// INVERT_Z flag (defined = true)\n// SHOW_RAW flag (defined = true)\n\nconst vertexShaderSrc = `\nprecision highp float;\nlayout(location = 0) in vec2 a_position;\nout vec2 vUv;\nvoid main() {\n vUv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`;\n\n// fragmentShader is exported so users can inspect or reuse the GLSL source.\n// Defines are NOT embedded here — they are prepended at compile time via buildProgram().\n// Note: #version 300 es is prepended by buildProgram() (must be first line,\n// before defines). Do not add it here.\nexport const fragmentShader = `\nprecision highp float;\n#define TWO_PI 6.28318530718\nin vec2 vUv;\nout vec4 fragColor;\nuniform float progress;\nuniform sampler2D paletteTexture;\n\n${shaderSRGB2RGB}\n${shaderOKLab}\n${shaderHSL2RGB}\n${shaderHSV2RGB}\n${shaderLCH2RGB}\n${shaderDeltaE}\n${shaderClosestColor}\n\n// COLOR_MODEL: 0=rgb, 1=oklab, 2=okhsv, 3=okhsvPolar, 4=okhsl, 5=okhslPolar,\n// 6=oklch, 7=oklchPolar, 8=hsv, 9=hsvPolar, 10=hsl, 11=hslPolar\nvec3 modelToRGB(vec3 colorCoords) {\n #if COLOR_MODEL == 0\n return colorCoords;\n #elif COLOR_MODEL == 1\n vec3 linear = oklab_to_linear_srgb(vec3(colorCoords.z, colorCoords.x - 0.5, colorCoords.y - 0.5));\n return clamp(vec3(srgb_transfer_function(linear.r), srgb_transfer_function(linear.g), srgb_transfer_function(linear.b)), 0.0, 1.0);\n #elif COLOR_MODEL == 2 || COLOR_MODEL == 3\n return okhsv_to_srgb(colorCoords);\n #elif COLOR_MODEL == 4 || COLOR_MODEL == 5\n return okhsl_to_srgb(colorCoords);\n #elif COLOR_MODEL == 6 || COLOR_MODEL == 7\n return lch2rgb(vec3(colorCoords.z, colorCoords.y, colorCoords.x));\n #elif COLOR_MODEL == 8 || COLOR_MODEL == 9\n return hsv2rgb(colorCoords);\n #else\n return hsl2rgb(colorCoords);\n #endif\n}\n\nvoid main(){\n #if PROGRESS_AXIS == 1\n vec3 colorCoords = vec3(vUv.x, progress, vUv.y);\n #elif PROGRESS_AXIS == 2\n vec3 colorCoords = vec3(vUv.x, vUv.y, 1. - progress);\n #else\n vec3 colorCoords = vec3(progress, vUv.x, vUv.y);\n #endif\n\n #if COLOR_MODEL == 3 || COLOR_MODEL == 5 || COLOR_MODEL == 7 || COLOR_MODEL == 9 || COLOR_MODEL == 11\n vec2 toCenter = vUv - 0.5;\n float angle = atan(toCenter.y, toCenter.x);\n float radius = length(toCenter) * 2.0;\n\n #if PROGRESS_AXIS == 2\n colorCoords = vec3((angle / TWO_PI), radius, 1. - progress);\n #elif PROGRESS_AXIS == 1\n colorCoords = vec3((angle / TWO_PI), 1. - progress, radius);\n if (radius > 1.0) { discard; }\n #else\n float hue = 1.0 - abs(0.5 - progress * .5) * 2.0;\n if (vUv.x > 0.5) { hue += 0.5; }\n colorCoords = vec3(hue, abs(0.5 - vUv.x) * 2.0, vUv.y);\n #endif\n #endif\n\n #ifdef INVERT_Z\n colorCoords.z = 1. - colorCoords.z;\n #endif\n\n vec3 rgb = modelToRGB(colorCoords);\n\n #ifdef SHOW_RAW\n fragColor = vec4(rgb, 1.);\n #else\n fragColor = vec4(closestColor(rgb, paletteTexture), 1.);\n #endif\n}`;\n\n// ── Color parsing ──────────────────────────────────────────────────────────────\n// Use a canvas 2D context as a free CSS color parser — handles hex, rgb(),\n// hsl(), named colors, etc. Lazy-initialised to avoid issues at module load time.\n\nlet _colorCtx: CanvasRenderingContext2D | null = null;\n\nfunction cssToSRGB(color: string): [number, number, number] {\n if (!_colorCtx) {\n const c = document.createElement('canvas');\n c.width = c.height = 1;\n _colorCtx = c.getContext('2d')!;\n }\n _colorCtx.fillStyle = '#000000'; // reset before setting\n _colorCtx.fillStyle = color;\n const v = _colorCtx.fillStyle; // browser normalises to '#rrggbb' or 'rgba(...)'\n if (v[0] === '#') {\n return [\n parseInt(v.slice(1, 3), 16) / 255,\n parseInt(v.slice(3, 5), 16) / 255,\n parseInt(v.slice(5, 7), 16) / 255,\n ];\n }\n // rgba(r, g, b, a) fallback\n const m = v.match(/[\\d.]+/g)!;\n return [+m[0] / 255, +m[1] / 255, +m[2] / 255];\n}\n\n// ── Palette helpers ────────────────────────────────────────────────────────────\n\n// Returns the palette as a flat RGBA Uint8Array (sRGB, 1×N texture row).\n// Useful for building your own WebGL texture or inspecting raw color data.\nexport const paletteToRGBA = (palette: ColorList): Uint8Array => {\n const data = new Uint8Array(palette.length * 4);\n palette.forEach((color, i) => {\n try {\n const [r, g, b] = cssToSRGB(color);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n } catch {\n console.error(`Invalid color: ${color}`);\n }\n });\n return data;\n};\n\n// Backwards-compatible alias (previously returned a Three.js DataTexture)\nexport const paletteToTexture = paletteToRGBA;\n\nexport const randomPalette = (size = 20): ColorList =>\n Array.from(\n { length: size },\n () =>\n `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`,\n );\n\n// ── WebGL helpers ──────────────────────────────────────────────────────────────\n\ntype Defines = Record<string, number | false>;\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`Shader compile error:\\n${log}`);\n }\n return shader;\n}\n\nfunction buildProgram(\n gl: WebGL2RenderingContext,\n defines: Defines,\n fragSrc: string,\n vertSrc: string,\n): WebGLProgram {\n // #version 300 es must be the very first line — prepend it before defines.\n const defineStr =\n Object.entries(defines)\n .filter(([, v]) => v !== false)\n .map(([k, v]) => `#define ${k} ${v}`)\n .join('\\n') + '\\n';\n const prefix = '#version 300 es\\n' + defineStr;\n\n const vert = compileShader(gl, gl.VERTEX_SHADER, prefix + vertSrc);\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, prefix + fragSrc);\n\n const prog = gl.createProgram()!;\n gl.attachShader(prog, vert);\n gl.attachShader(prog, frag);\n gl.linkProgram(prog);\n gl.deleteShader(vert);\n gl.deleteShader(frag);\n\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(prog);\n gl.deleteProgram(prog);\n throw new Error(`Program link error:\\n${log}`);\n }\n return prog;\n}\n\nfunction uploadPaletteTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n palette: ColorList,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // RGBA8: sized internal format required by WebGL2 spec\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA8,\n palette.length,\n 1,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n paletteToRGBA(palette),\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n}\n\n// ── PaletteViz ─────────────────────────────────────────────────────────────────\n\nexport class PaletteViz {\n #palette: ColorList = [];\n #width = 512;\n #height = 512;\n #pixelRatio = 1;\n\n // shader state\n #position = 0.0;\n #axis: Axis = 'y';\n #colorModel: SupportedColorModels = 'okhsv';\n #distanceMetric: DistanceMetric = 'oklab';\n #invertLightness = false;\n #showRaw = false;\n\n // uniform value maps\n readonly #axisMap = { x: 0, y: 1, z: 2 } as const;\n readonly #colorModelMap = {\n rgb: 0,\n oklab: 1,\n okhsv: 2,\n okhsvPolar: 3,\n okhsl: 4,\n okhslPolar: 5,\n oklch: 6,\n oklchPolar: 7,\n hsv: 8,\n hsvPolar: 9,\n hsl: 10,\n hslPolar: 11,\n } as const;\n readonly #distanceMetricMap = {\n rgb: 0,\n oklab: 1,\n deltaE76: 2,\n deltaE2000: 3,\n kotsarenkoRamos: 4,\n deltaE94: 5,\n } as const;\n\n // WebGL\n #canvas: HTMLCanvasElement;\n #gl: WebGL2RenderingContext;\n #program: WebGLProgram | null = null;\n #texture: WebGLTexture | null = null;\n #quadBuffer: WebGLBuffer | null = null;\n #vao: WebGLVertexArrayObject | null = null;\n #animationFrame: number | null = null;\n\n // cached uniform locations (re-queried after each program rebuild)\n #uProgress: WebGLUniformLocation | null = null;\n #uPaletteTexture: WebGLUniformLocation | null = null;\n\n // dom\n #container: HTMLElement | undefined;\n\n constructor({\n palette = randomPalette(),\n width = 512,\n height = 512,\n pixelRatio = window.devicePixelRatio,\n container,\n colorModel = 'okhsv',\n distanceMetric = 'oklab',\n axis = 'y',\n position = 0.0,\n invertLightness = false,\n showRaw = false,\n }: PaletteVizOptions = {}) {\n this.#palette = palette;\n this.#width = width;\n this.#height = height;\n this.#pixelRatio = pixelRatio;\n this.#colorModel = colorModel;\n this.#distanceMetric = distanceMetric;\n this.#axis = axis;\n this.#position = position;\n this.#invertLightness = invertLightness;\n this.#showRaw = showRaw;\n this.#container = container;\n\n this.#canvas = document.createElement('canvas');\n this.#canvas.classList.add('palette-viz');\n const gl = this.#canvas.getContext('webgl2');\n if (!gl) throw new Error('WebGL2 not supported');\n this.#gl = gl;\n\n // Quad buffer + VAO — set up once, reused every frame.\n // layout(location=0) in the vertex shader pins a_position to slot 0,\n // so the VAO remains valid across shader recompiles.\n this.#quadBuffer = gl.createBuffer()!;\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#quadBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n\n this.#vao = gl.createVertexArray()!;\n gl.bindVertexArray(this.#vao);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n\n this.#texture = gl.createTexture()!;\n uploadPaletteTexture(gl, this.#texture, this.#palette);\n\n this.#buildProgram();\n this.#setSize(this.#width, this.#height);\n this.#container?.appendChild(this.#canvas);\n this.#paint();\n }\n\n #defines(): Defines {\n return {\n DISTANCE_METRIC: this.#distanceMetricMap[this.#distanceMetric],\n COLOR_MODEL: this.#colorModelMap[this.#colorModel],\n PROGRESS_AXIS: this.#axisMap[this.#axis],\n INVERT_Z: this.#invertLightness ? 1 : false,\n SHOW_RAW: this.#showRaw ? 1 : false,\n };\n }\n\n #buildProgram(): void {\n const gl = this.#gl;\n if (this.#program) gl.deleteProgram(this.#program);\n this.#program = buildProgram(gl, this.#defines(), fragmentShader, vertexShaderSrc);\n this.#uProgress = gl.getUniformLocation(this.#program, 'progress');\n this.#uPaletteTexture = gl.getUniformLocation(this.#program, 'paletteTexture');\n }\n\n #setSize(w: number, h: number): void {\n const pw = Math.round(w * this.#pixelRatio);\n const ph = Math.round(h * this.#pixelRatio);\n this.#canvas.width = pw;\n this.#canvas.height = ph;\n this.#canvas.style.width = `${w}px`;\n this.#canvas.style.height = `${h}px`;\n this.#gl.viewport(0, 0, pw, ph);\n }\n\n #paint(): void {\n if (this.#animationFrame !== null) cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = requestAnimationFrame(() => {\n const gl = this.#gl;\n gl.useProgram(this.#program);\n\n gl.uniform1f(this.#uProgress, this.#position);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.#texture);\n gl.uniform1i(this.#uPaletteTexture, 0);\n\n gl.bindVertexArray(this.#vao);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n gl.bindVertexArray(null);\n });\n }\n\n // ── Public API ──────────────────────────────────────────────────────────────\n\n get canvas(): HTMLCanvasElement {\n return this.#canvas;\n }\n get width() {\n return this.#width;\n }\n get height() {\n return this.#height;\n }\n\n resize(width: number, height: number | null = null): void {\n this.#width = width;\n this.#height = height ?? width;\n this.#setSize(this.#width, this.#height);\n this.#paint();\n }\n\n destroy(): void {\n if (this.#animationFrame !== null) {\n cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = null;\n }\n const gl = this.#gl;\n gl.deleteProgram(this.#program);\n gl.deleteTexture(this.#texture);\n gl.deleteBuffer(this.#quadBuffer);\n gl.deleteVertexArray(this.#vao);\n this.#canvas.remove();\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n\n // ── Palette ─────────────────────────────────────────────────────────────────\n\n set palette(palette: ColorList) {\n this.#palette = palette;\n uploadPaletteTexture(this.#gl, this.#texture!, palette);\n this.#paint();\n }\n get palette() {\n return this.#palette;\n }\n\n setColor(color: ColorString, index: number): void {\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette[index] = color;\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n addColor(color: ColorString, index?: number): void {\n this.#palette.splice(index ?? this.#palette.length, 0, color);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n removeColor(index: number): void;\n removeColor(color: ColorString): void;\n removeColor(indexOrColor: number | ColorString): void {\n const index =\n typeof indexOrColor === 'number' ? indexOrColor : this.#palette.indexOf(indexOrColor);\n if (index === -1) throw new Error('Color not found in palette');\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette.splice(index, 1);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n // ── Shader properties ────────────────────────────────────────────────────────\n\n set position(value: number) {\n this.#position = value;\n this.#paint();\n }\n get position() {\n return this.#position;\n }\n\n set axis(axis: Axis) {\n if (!(axis in this.#axisMap)) throw new Error(\"axis must be 'x', 'y', or 'z'\");\n this.#axis = axis;\n this.#buildProgram();\n this.#paint();\n }\n get axis() {\n return this.#axis;\n }\n\n set colorModel(model: SupportedColorModels) {\n if (!(model in this.#colorModelMap)) throw new Error(`colorModel '${model}' is not supported`);\n this.#colorModel = model;\n this.#buildProgram();\n this.#paint();\n }\n get colorModel() {\n return this.#colorModel;\n }\n\n set distanceMetric(metric: DistanceMetric) {\n if (!(metric in this.#distanceMetricMap))\n throw new Error(\n \"distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'\",\n );\n this.#distanceMetric = metric;\n this.#buildProgram();\n this.#paint();\n }\n get distanceMetric() {\n return this.#distanceMetric;\n }\n\n set invertLightness(value: boolean) {\n this.#invertLightness = value;\n this.#buildProgram();\n this.#paint();\n }\n get invertLightness() {\n return this.#invertLightness;\n }\n\n set showRaw(value: boolean) {\n this.#showRaw = value;\n this.#buildProgram();\n this.#paint();\n }\n get showRaw() {\n return this.#showRaw;\n }\n\n static paletteToRGBA = paletteToRGBA;\n /** @deprecated use PaletteViz.paletteToRGBA */\n static paletteToTexture = paletteToRGBA;\n}\n"],"names":["shaderSRGB2RGB","shaderOKLab","shaderHSL2RGB","shaderHSV2RGB","shaderLCH2RGB","shaderDeltaE","shaderClosestColor","vertexShaderSrc","fragmentShader","_colorCtx","cssToSRGB","color","c","v","m","paletteToRGBA","palette","data","i","r","g","b","paletteToTexture","randomPalette","size","compileShader","gl","type","src","shader","log","buildProgram","defines","fragSrc","vertSrc","prefix","k","vert","frag","prog","uploadPaletteTexture","tex","PaletteViz","#palette","#width","#height","#pixelRatio","#position","#axis","#colorModel","#distanceMetric","#invertLightness","#showRaw","#axisMap","#colorModelMap","#distanceMetricMap","#canvas","#gl","#program","#texture","#quadBuffer","#vao","#animationFrame","#uProgress","#uPaletteTexture","#container","width","height","pixelRatio","container","colorModel","distanceMetric","axis","position","invertLightness","showRaw","#buildProgram","#setSize","#paint","#defines","w","h","pw","ph","index","indexOrColor","value","model","metric"],"mappings":"sOAAA,MAAAA,EAAe;AAAA;AAAA;AAAA;AAAA,ECAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCAfC,EAAe;AAAA;AAAA;AAAA,GCAfC,EAAe;AAAA;AAAA;AAAA;AAAA,GCAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ECAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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"}
1
+ {"version":3,"file":"palette-shader.umd.cjs","sources":["../src/shaders/srgb2rgb.frag.glsl?raw","../src/shaders/oklab.frag.glsl?raw","../src/shaders/hsl2rgb.frag.glsl?raw","../src/shaders/hsv2rgb.frag.glsl?raw","../src/shaders/lch2rgb.frag.glsl?raw","../src/shaders/deltaE.frag.glsl?raw","../src/shaders/closestColor.frag.glsl?raw","../src/index.ts"],"sourcesContent":["export default \"// https://lygia.xyz/\\nfloat srgb2rgb(const in float v) { return (v < 0.04045) ? v * 0.0773993808 : pow((v + 0.055) * 0.947867298578199, 2.4); }\\nvec3 srgb2rgb(const in vec3 srgb) { return vec3(srgb2rgb(srgb.r), srgb2rgb(srgb.g), srgb2rgb(srgb.b)); }\\nvec4 srgb2rgb(const in vec4 srgb) { return vec4(srgb2rgb(srgb.rgb), srgb.a); }\\n\"","export default \"// Copyright(c) 2021 Björn Ottosson\\n//\\n// Permission is hereby granted, free of charge, to any person obtaining a copy of\\n// this softwareand associated documentation files(the \\\"Software\\\"), to deal in\\n// the Software without restriction, including without limitation the rights to\\n// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies\\n// of the Software, and to permit persons to whom the Software is furnished to do\\n// so, subject to the following conditions :\\n// The above copyright noticeand this permission notice shall be included in all\\n// copies or substantial portions of the Software.\\n// THE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE\\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\n// SOFTWARE.\\n\\n#define M_PI 3.1415926535897932384626433832795\\n\\nfloat cbrt( float x )\\n{\\n return sign(x)*pow(abs(x),1.0f/3.0f);\\n}\\n\\nfloat srgb_transfer_function(float a)\\n{\\n\\treturn .0031308f >= a ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f;\\n}\\n\\nfloat srgb_transfer_function_inv(float a)\\n{\\n\\treturn .04045f < a ? pow((a + .055f) / 1.055f, 2.4f) : a / 12.92f;\\n}\\n\\nvec3 linear_srgb_to_oklab(vec3 c)\\n{\\n\\tfloat l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;\\n\\tfloat m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;\\n\\tfloat s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;\\n\\n\\tfloat l_ = cbrt(l);\\n\\tfloat m_ = cbrt(m);\\n\\tfloat s_ = cbrt(s);\\n\\n\\treturn vec3(\\n\\t\\t0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,\\n\\t\\t1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,\\n\\t\\t0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_\\n\\t);\\n}\\n\\nvec3 oklab_to_linear_srgb(vec3 c)\\n{\\n\\tfloat l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;\\n\\tfloat m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;\\n\\tfloat s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;\\n\\n\\tfloat l = l_ * l_ * l_;\\n\\tfloat m = m_ * m_ * m_;\\n\\tfloat s = s_ * s_ * s_;\\n\\n\\treturn vec3(\\n\\t\\t+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,\\n\\t\\t-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,\\n\\t\\t-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s\\n\\t);\\n}\\n\\n// Finds the maximum saturation possible for a given hue that fits in sRGB\\n// Saturation here is defined as S = C/L\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat compute_max_saturation(float a, float b)\\n{\\n\\t// Max saturation will be when one of r, g or b goes below zero.\\n\\n\\t// Select different coefficients depending on which component goes below zero first\\n\\tfloat k0, k1, k2, k3, k4, wl, wm, ws;\\n\\n\\tif (-1.88170328f * a - 0.80936493f * b > 1.f)\\n\\t{\\n\\t\\t// Red component\\n\\t\\tk0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;\\n\\t\\twl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;\\n\\t}\\n\\telse if (1.81444104f * a - 1.19445276f * b > 1.f)\\n\\t{\\n\\t\\t// Green component\\n\\t\\tk0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;\\n\\t\\twl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Blue component\\n\\t\\tk0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;\\n\\t\\twl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;\\n\\t}\\n\\n\\t// Approximate max saturation using a polynomial:\\n\\tfloat S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;\\n\\n\\t// Do one step Halley's method to get closer\\n\\t// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite\\n\\t// this should be sufficient for most applications, otherwise do two/three steps \\n\\n\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t{\\n\\t\\tfloat l_ = 1.f + S * k_l;\\n\\t\\tfloat m_ = 1.f + S * k_m;\\n\\t\\tfloat s_ = 1.f + S * k_s;\\n\\n\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\tfloat l_dS = 3.f * k_l * l_ * l_;\\n\\t\\tfloat m_dS = 3.f * k_m * m_ * m_;\\n\\t\\tfloat s_dS = 3.f * k_s * s_ * s_;\\n\\n\\t\\tfloat l_dS2 = 6.f * k_l * k_l * l_;\\n\\t\\tfloat m_dS2 = 6.f * k_m * k_m * m_;\\n\\t\\tfloat s_dS2 = 6.f * k_s * k_s * s_;\\n\\n\\t\\tfloat f = wl * l + wm * m + ws * s;\\n\\t\\tfloat f1 = wl * l_dS + wm * m_dS + ws * s_dS;\\n\\t\\tfloat f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;\\n\\n\\t\\tS = S - f * f1 / (f1 * f1 - 0.5f * f * f2);\\n\\t}\\n\\n\\treturn S;\\n}\\n\\n// finds L_cusp and C_cusp for a given hue\\n// a and b must be normalized so a^2 + b^2 == 1\\nvec2 find_cusp(float a, float b)\\n{\\n\\t// First, find the maximum saturation (saturation S = C/L)\\n\\tfloat S_cusp = compute_max_saturation(a, b);\\n\\n\\t// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:\\n\\tvec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b ));\\n\\tfloat L_cusp = cbrt(1.f / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));\\n\\tfloat C_cusp = L_cusp * S_cusp;\\n\\n\\treturn vec2( L_cusp , C_cusp );\\n}\\n\\n// Finds intersection of the line defined by \\n// L = L0 * (1 - t) + t * L1;\\n// C = t * C1;\\n// a and b must be normalized so a^2 + b^2 == 1\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0, vec2 cusp)\\n{\\n\\t// Find the intersection for upper and lower half seprately\\n\\tfloat t;\\n\\tif (((L1 - L0) * cusp.y - (cusp.x - L0) * C1) <= 0.f)\\n\\t{\\n\\t\\t// Lower half\\n\\n\\t\\tt = cusp.y * L0 / (C1 * cusp.x + cusp.y * (L0 - L1));\\n\\t}\\n\\telse\\n\\t{\\n\\t\\t// Upper half\\n\\n\\t\\t// First intersect with triangle\\n\\t\\tt = cusp.y * (L0 - 1.f) / (C1 * (cusp.x - 1.f) + cusp.y * (L0 - L1));\\n\\n\\t\\t// Then one step Halley's method\\n\\t\\t{\\n\\t\\t\\tfloat dL = L1 - L0;\\n\\t\\t\\tfloat dC = C1;\\n\\n\\t\\t\\tfloat k_l = +0.3963377774f * a + 0.2158037573f * b;\\n\\t\\t\\tfloat k_m = -0.1055613458f * a - 0.0638541728f * b;\\n\\t\\t\\tfloat k_s = -0.0894841775f * a - 1.2914855480f * b;\\n\\n\\t\\t\\tfloat l_dt = dL + dC * k_l;\\n\\t\\t\\tfloat m_dt = dL + dC * k_m;\\n\\t\\t\\tfloat s_dt = dL + dC * k_s;\\n\\n\\n\\t\\t\\t// If higher accuracy is required, 2 or 3 iterations of the following block can be used:\\n\\t\\t\\t{\\n\\t\\t\\t\\tfloat L = L0 * (1.f - t) + t * L1;\\n\\t\\t\\t\\tfloat C = t * C1;\\n\\n\\t\\t\\t\\tfloat l_ = L + C * k_l;\\n\\t\\t\\t\\tfloat m_ = L + C * k_m;\\n\\t\\t\\t\\tfloat s_ = L + C * k_s;\\n\\n\\t\\t\\t\\tfloat l = l_ * l_ * l_;\\n\\t\\t\\t\\tfloat m = m_ * m_ * m_;\\n\\t\\t\\t\\tfloat s = s_ * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt = 3.f * l_dt * l_ * l_;\\n\\t\\t\\t\\tfloat mdt = 3.f * m_dt * m_ * m_;\\n\\t\\t\\t\\tfloat sdt = 3.f * s_dt * s_ * s_;\\n\\n\\t\\t\\t\\tfloat ldt2 = 6.f * l_dt * l_dt * l_;\\n\\t\\t\\t\\tfloat mdt2 = 6.f * m_dt * m_dt * m_;\\n\\t\\t\\t\\tfloat sdt2 = 6.f * s_dt * s_dt * s_;\\n\\n\\t\\t\\t\\tfloat r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1.f;\\n\\t\\t\\t\\tfloat r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;\\n\\t\\t\\t\\tfloat r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_r = r1 / (r1 * r1 - 0.5f * r * r2);\\n\\t\\t\\t\\tfloat t_r = -r * u_r;\\n\\n\\t\\t\\t\\tfloat g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1.f;\\n\\t\\t\\t\\tfloat g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;\\n\\t\\t\\t\\tfloat g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_g = g1 / (g1 * g1 - 0.5f * g * g2);\\n\\t\\t\\t\\tfloat t_g = -g * u_g;\\n\\n\\t\\t\\t\\tfloat b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1.f;\\n\\t\\t\\t\\tfloat b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;\\n\\t\\t\\t\\tfloat b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;\\n\\n\\t\\t\\t\\tfloat u_b = b1 / (b1 * b1 - 0.5f * b * b2);\\n\\t\\t\\t\\tfloat t_b = -b * u_b;\\n\\n\\t\\t\\t\\tt_r = u_r >= 0.f ? t_r : 10000.f;\\n\\t\\t\\t\\tt_g = u_g >= 0.f ? t_g : 10000.f;\\n\\t\\t\\t\\tt_b = u_b >= 0.f ? t_b : 10000.f;\\n\\n\\t\\t\\t\\tt += min(t_r, min(t_g, t_b));\\n\\t\\t\\t}\\n\\t\\t}\\n\\t}\\n\\n\\treturn t;\\n}\\n\\nfloat find_gamut_intersection(float a, float b, float L1, float C1, float L0)\\n{\\n\\t// Find the cusp of the gamut triangle\\n\\tvec2 cusp = find_cusp(a, b);\\n\\n\\treturn find_gamut_intersection(a, b, L1, C1, L0, cusp);\\n}\\n\\nvec3 gamut_clip_preserve_chroma(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = clamp(L, 0.f, 1.f);\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_0_5(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L0 = 0.5;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_project_to_L_cusp(vec3 rgb)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat L0 = cusp.x;\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_0_5(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat Ld = L - 0.5f;\\n\\tfloat e1 = 0.5f + abs(Ld) + alpha * C;\\n\\tfloat L0 = 0.5f * (1.f + sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nvec3 gamut_clip_adaptive_L0_L_cusp(vec3 rgb, float alpha)\\n{\\n\\tif (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)\\n\\t\\treturn rgb;\\n\\n\\tvec3 lab = linear_srgb_to_oklab(rgb);\\n\\n\\tfloat L = lab.x;\\n\\tfloat eps = 0.00001f;\\n\\tfloat C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\t// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat Ld = L - cusp.x;\\n\\tfloat k = 2.f * (Ld > 0.f ? 1.f - cusp.x : cusp.x);\\n\\n\\tfloat e1 = 0.5f * k + abs(Ld) + alpha * C / k;\\n\\tfloat L0 = cusp.x + 0.5f * (sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * k * abs(Ld))));\\n\\n\\tfloat t = find_gamut_intersection(a_, b_, L, C, L0);\\n\\tfloat L_clipped = L0 * (1.f - t) + t * L;\\n\\tfloat C_clipped = t * C;\\n\\n\\treturn oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));\\n}\\n\\nfloat toe(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn 0.5f * (k_3 * x - k_1 + sqrt((k_3 * x - k_1) * (k_3 * x - k_1) + 4.f * k_2 * k_3 * x));\\n}\\n\\nfloat toe_inv(float x)\\n{\\n\\tfloat k_1 = 0.206f;\\n\\tfloat k_2 = 0.03f;\\n\\tfloat k_3 = (1.f + k_1) / (1.f + k_2);\\n\\treturn (x * x + k_1 * x) / (k_3 * (x + k_2));\\n}\\n\\nvec2 to_ST(vec2 cusp)\\n{\\n\\tfloat L = cusp.x;\\n\\tfloat C = cusp.y;\\n\\treturn vec2( C / L, C / (1.f - L) );\\n}\\n\\n// Returns a smooth approximation of the location of the cusp\\n// This polynomial was created by an optimization process\\n// It has been designed so that S_mid < S_max and T_mid < T_max\\nvec2 get_ST_mid(float a_, float b_)\\n{\\n\\tfloat S = 0.11516993f + 1.f / (\\n\\t\\t+7.44778970f + 4.15901240f * b_\\n\\t\\t+ a_ * (-2.19557347f + 1.75198401f * b_\\n\\t\\t\\t+ a_ * (-2.13704948f - 10.02301043f * b_\\n\\t\\t\\t\\t+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\tfloat T = 0.11239642f + 1.f / (\\n\\t\\t+1.61320320f - 0.68124379f * b_\\n\\t\\t+ a_ * (+0.40370612f + 0.90148123f * b_\\n\\t\\t\\t+ a_ * (-0.27087943f + 0.61223990f * b_\\n\\t\\t\\t\\t+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_\\n\\t\\t\\t\\t\\t)))\\n\\t\\t);\\n\\n\\treturn vec2( S, T );\\n}\\n\\nvec3 get_Cs(float L, float a_, float b_)\\n{\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\n\\tfloat C_max = find_gamut_intersection(a_, b_, L, 1.f, L, cusp);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\t\\n\\t// Scale factor to compensate for the curved part of gamut shape:\\n\\tfloat k = C_max / min((L * ST_max.x), (1.f - L) * ST_max.y);\\n\\n\\tfloat C_mid;\\n\\t{\\n\\t\\tvec2 ST_mid = get_ST_mid(a_, b_);\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tfloat C_a = L * ST_mid.x;\\n\\t\\tfloat C_b = (1.f - L) * ST_mid.y;\\n\\t\\tC_mid = 0.9f * k * sqrt(sqrt(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));\\n\\t}\\n\\n\\tfloat C_0;\\n\\t{\\n\\t\\t// for C_0, the shape is independent of hue, so vec2 are constant. Values picked to roughly be the average values of vec2.\\n\\t\\tfloat C_a = L * 0.4f;\\n\\t\\tfloat C_b = (1.f - L) * 0.8f;\\n\\n\\t\\t// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.\\n\\t\\tC_0 = sqrt(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));\\n\\t}\\n\\n\\treturn vec3( C_0, C_mid, C_max );\\n}\\n\\nvec3 okhsl_to_srgb(vec3 hsl)\\n{\\n\\tfloat h = hsl.x;\\n\\tfloat s = hsl.y;\\n\\tfloat l = hsl.z;\\n\\n\\tif (l == 1.0f)\\n\\t{\\n\\t\\treturn vec3( 1.f, 1.f, 1.f );\\n\\t}\\n\\n\\telse if (l == 0.f)\\n\\t{\\n\\t\\treturn vec3( 0.f, 0.f, 0.f );\\n\\t}\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\tfloat L = toe_inv(l);\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat C, t, k_0, k_1, k_2;\\n\\n\\tif (s < mid)\\n\\t{\\n\\t\\tt = mid_inv * s;\\n\\n\\t\\tk_1 = mid * C_0;\\n\\t\\tk_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tC = t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tt = (s - mid)/ (1.f - mid);\\n\\n\\t\\tk_0 = C_mid;\\n\\t\\tk_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tk_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tC = k_0 + t * k_1 / (1.f - k_2 * t);\\n\\t}\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsl(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec3 cs = get_Cs(L, a_, b_);\\n\\tfloat C_0 = cs.x;\\n\\tfloat C_mid = cs.y;\\n\\tfloat C_max = cs.z;\\n\\n\\t// Inverse of the interpolation in okhsl_to_srgb:\\n\\n\\tfloat mid = 0.8f;\\n\\tfloat mid_inv = 1.25f;\\n\\n\\tfloat s;\\n\\tif (C < C_mid)\\n\\t{\\n\\t\\tfloat k_1 = mid * C_0;\\n\\t\\tfloat k_2 = (1.f - k_1 / C_mid);\\n\\n\\t\\tfloat t = C / (k_1 + k_2 * C);\\n\\t\\ts = t * mid;\\n\\t}\\n\\telse\\n\\t{\\n\\t\\tfloat k_0 = C_mid;\\n\\t\\tfloat k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;\\n\\t\\tfloat k_2 = (1.f - (k_1) / (C_max - C_mid));\\n\\n\\t\\tfloat t = (C - k_0) / (k_1 + k_2 * (C - k_0));\\n\\t\\ts = mid + (1.f - mid) * t;\\n\\t}\\n\\n\\tfloat l = toe(L);\\n\\treturn vec3( h, s, l );\\n}\\n\\n\\nvec3 okhsv_to_srgb(vec3 hsv)\\n{\\n\\tfloat h = hsv.x;\\n\\tfloat s = hsv.y;\\n\\tfloat v = hsv.z;\\n\\n\\tfloat a_ = cos(2.f * M_PI * h);\\n\\tfloat b_ = sin(2.f * M_PI * h);\\n\\t\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f- S_0 / S_max;\\n\\n\\t// first we compute L and V as if the gamut is a perfect triangle:\\n\\n\\t// L, C when v==1:\\n\\tfloat L_v = 1.f - s * S_0 / (S_0 + T_max - T_max * k * s);\\n\\tfloat C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);\\n\\n\\tfloat L = v * L_v;\\n\\tfloat C = v * C_v;\\n\\n\\t// then we compensate for both toe and the curved top part of the triangle:\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\tfloat L_new = toe_inv(L);\\n\\tC = C * L_new / L;\\n\\tL = L_new;\\n\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L * scale_L;\\n\\tC = C * scale_L;\\n\\n\\tvec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));\\n\\treturn vec3(\\n\\t\\tsrgb_transfer_function(rgb.r),\\n\\t\\tsrgb_transfer_function(rgb.g),\\n\\t\\tsrgb_transfer_function(rgb.b)\\n\\t);\\n}\\n\\nvec3 srgb_to_okhsv(vec3 rgb)\\n{\\n\\tvec3 lab = linear_srgb_to_oklab(vec3(\\n\\t\\tsrgb_transfer_function_inv(rgb.r),\\n\\t\\tsrgb_transfer_function_inv(rgb.g),\\n\\t\\tsrgb_transfer_function_inv(rgb.b)\\n\\t\\t));\\n\\n\\tfloat C = sqrt(lab.y * lab.y + lab.z * lab.z);\\n\\tfloat a_ = lab.y / C;\\n\\tfloat b_ = lab.z / C;\\n\\n\\tfloat L = lab.x;\\n\\tfloat h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;\\n\\n\\tvec2 cusp = find_cusp(a_, b_);\\n\\tvec2 ST_max = to_ST(cusp);\\n\\tfloat S_max = ST_max.x;\\n\\tfloat T_max = ST_max.y;\\n\\tfloat S_0 = 0.5f;\\n\\tfloat k = 1.f - S_0 / S_max;\\n\\n\\t// first we find L_v, C_v, L_vt and C_vt\\n\\n\\tfloat t = T_max / (C + L * T_max);\\n\\tfloat L_v = t * L;\\n\\tfloat C_v = t * C;\\n\\n\\tfloat L_vt = toe_inv(L_v);\\n\\tfloat C_vt = C_v * L_vt / L_v;\\n\\n\\t// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:\\n\\tvec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));\\n\\tfloat scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));\\n\\n\\tL = L / scale_L;\\n\\tC = C / scale_L;\\n\\n\\tC = C * toe(L) / L;\\n\\tL = toe(L);\\n\\n\\t// we can now compute v and s:\\n\\n\\tfloat v = L / L_v;\\n\\tfloat s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);\\n\\n\\treturn vec3 (h, s, v );\\n}\"","export default \"vec3 hsl2rgb( in vec3 c ) {\\n vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );\\n return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));\\n}\"","export default \"vec3 hsv2rgb(vec3 c) {\\n vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\\n vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\\n return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\\n}\"","export default \"// slightly rearranged vector components so it matches with LCH\\n// M_PI and srgb_transfer_function are provided by oklab.frag.glsl (included before this file)\\nvec3 lch2rgb(vec3 lch) {\\n lch.y *= 0.34;\\n\\n vec3 lab = vec3(\\n lch.x,\\n lch.y * cos(lch.z * M_PI*2.0),\\n lch.y * sin(lch.z * M_PI*2.0)\\n );\\n\\n vec3 lms = vec3(\\n lab.x + 0.3963377774f * lab.y + 0.2158037573f * lab.z,\\n lab.x - 0.1055613458f * lab.y - 0.0638541728f * lab.z,\\n lab.x - 0.0894841775f * lab.y - 1.2914855480f * lab.z\\n );\\n\\n lms = pow(max(lms, vec3(0.0)), vec3(3.0));\\n\\n vec3 rgb = vec3(\\n +4.0767416621f * lms.x - 3.3077115913f * lms.y + 0.2309699292f * lms.z,\\n -1.2684380046f * lms.x + 2.6097574011f * lms.y - 0.3413193965f * lms.z,\\n -0.0041960863f * lms.x - 0.7034186147f * lms.y + 1.7076147010f * lms.z\\n );\\n\\n return vec3(\\n srgb_transfer_function(rgb.r),\\n srgb_transfer_function(rgb.g),\\n srgb_transfer_function(rgb.b)\\n );\\n}\\n\"","export default \"// Kotsarenko/Ramos weighted RGB distance.\\n// Operates on sRGB values directly (no linearisation needed).\\n// Weights red and blue channels by the mean red value, which improves\\n// perceptual uniformity compared to plain Euclidean RGB at minimal cost.\\nfloat kotsarenkoRamos(vec3 c1, vec3 c2) {\\n float rMean = (c1.r + c2.r) * 0.5;\\n vec3 d = c1 - c2;\\n return sqrt((2.0 + rMean) * d.r*d.r + 4.0 * d.g*d.g + (3.0 - rMean) * d.b*d.b);\\n}\\n\\n// ── CIELab ────────────────────────────────────────────────────────────────────\\n// sRGB -> XYZ (D65) -> CIELab\\n// Depends on: srgb2rgb() from srgb2rgb.frag.glsl, cbrt() from oklab.frag.glsl\\n\\nfloat _lab_f(float t) {\\n float delta = 6.0 / 29.0;\\n return t > delta * delta * delta\\n ? cbrt(t)\\n : t / (3.0 * delta * delta) + 4.0 / 29.0;\\n}\\n\\nvec3 srgb_to_cielab(vec3 srgb) {\\n vec3 lin = srgb2rgb(srgb);\\n\\n // Linear sRGB -> XYZ (D65 illuminant)\\n vec3 xyz = vec3(\\n 0.4124564 * lin.r + 0.3575761 * lin.g + 0.1804375 * lin.b,\\n 0.2126729 * lin.r + 0.7151522 * lin.g + 0.0721750 * lin.b,\\n 0.0193339 * lin.r + 0.1191920 * lin.g + 0.9503041 * lin.b\\n );\\n\\n // XYZ -> Lab (D65 white point: 0.95047, 1.00000, 1.08883)\\n float fx = _lab_f(xyz.x / 0.95047);\\n float fy = _lab_f(xyz.y);\\n float fz = _lab_f(xyz.z / 1.08883);\\n\\n return vec3(\\n 116.0 * fy - 16.0, // L*\\n 500.0 * (fx - fy), // a*\\n 200.0 * (fy - fz) // b*\\n );\\n}\\n\\n// CIE76: plain Euclidean distance in CIELab\\nfloat deltaE76(vec3 lab1, vec3 lab2) {\\n return distance(lab1, lab2);\\n}\\n\\n// CIE94: weighted chroma/hue corrections, cheaper than CIEDE2000\\n// Uses graphics application constants: kL=1, K1=0.045, K2=0.015\\nfloat deltaE94(vec3 lab1, vec3 lab2) {\\n float dL = lab1.x - lab2.x;\\n float da = lab1.y - lab2.y;\\n float db = lab1.z - lab2.z;\\n float C1 = sqrt(lab1.y * lab1.y + lab1.z * lab1.z);\\n float C2 = sqrt(lab2.y * lab2.y + lab2.z * lab2.z);\\n float dC = C1 - C2;\\n float dH = sqrt(max(0.0, da*da + db*db - dC*dC));\\n float SC = 1.0 + 0.045 * C1;\\n float SH = 1.0 + 0.015 * C1;\\n return sqrt(dL*dL + (dC/SC)*(dC/SC) + (dH/SH)*(dH/SH));\\n}\\n\\n// CIEDE2000\\nfloat deltaE2000(vec3 lab1, vec3 lab2) {\\n float L1 = lab1.x, a1 = lab1.y, b1 = lab1.z;\\n float L2 = lab2.x, a2 = lab2.y, b2 = lab2.z;\\n\\n // Chroma\\n float C1 = sqrt(a1*a1 + b1*b1);\\n float C2 = sqrt(a2*a2 + b2*b2);\\n float Cavg = (C1 + C2) * 0.5;\\n float Cavg7 = pow(Cavg, 7.0);\\n\\n // G factor: adjustment to a* axis\\n float G = 0.5 * (1.0 - sqrt(Cavg7 / (Cavg7 + 6103515625.0))); // 25^7\\n\\n float a1p = a1 * (1.0 + G);\\n float a2p = a2 * (1.0 + G);\\n float C1p = sqrt(a1p*a1p + b1*b1);\\n float C2p = sqrt(a2p*a2p + b2*b2);\\n\\n // Guard atan(0,0): GLSL ES leaves that undefined, so skip it for achromatic colors.\\n // When a color has no chroma its hue angle is meaningless — we just need it to\\n // be a well-defined number so it doesn't corrupt the rest of the formula.\\n bool c1Achromatic = C1p < 1e-6;\\n bool c2Achromatic = C2p < 1e-6;\\n\\n float h1p = c1Achromatic ? 0.0 : atan(b1, a1p);\\n if (h1p < 0.0) h1p += TWO_PI;\\n float h2p = c2Achromatic ? 0.0 : atan(b2, a2p);\\n if (h2p < 0.0) h2p += TWO_PI;\\n\\n // Deltas\\n float dLp = L2 - L1;\\n float dCp = C2p - C1p;\\n\\n float dhp = 0.0;\\n if (!c1Achromatic && !c2Achromatic) {\\n dhp = h2p - h1p;\\n if (dhp > M_PI) dhp -= TWO_PI;\\n else if (dhp < -M_PI) dhp += TWO_PI;\\n }\\n float dHp = 2.0 * sqrt(C1p * C2p) * sin(dhp * 0.5);\\n\\n // Averages\\n float Lp = (L1 + L2) * 0.5;\\n float Cp = (C1p + C2p) * 0.5;\\n\\n // When one color is achromatic, its hue is 0 and the average is simply the other's hue\\n float hp;\\n if (c1Achromatic || c2Achromatic) {\\n hp = h1p + h2p;\\n } else if (abs(h1p - h2p) <= M_PI) {\\n hp = (h1p + h2p) * 0.5;\\n } else if (h1p + h2p < TWO_PI) {\\n hp = (h1p + h2p + TWO_PI) * 0.5;\\n } else {\\n hp = (h1p + h2p - TWO_PI) * 0.5;\\n }\\n\\n float T = 1.0\\n - 0.17 * cos(hp - radians(30.0))\\n + 0.24 * cos(2.0 * hp)\\n + 0.32 * cos(3.0 * hp + radians(6.0))\\n - 0.20 * cos(4.0 * hp - radians(63.0));\\n\\n // Weighting functions\\n float Lpm50sq = (Lp - 50.0) * (Lp - 50.0);\\n float SL = 1.0 + 0.015 * Lpm50sq / sqrt(20.0 + Lpm50sq);\\n float SC = 1.0 + 0.045 * Cp;\\n float SH = 1.0 + 0.015 * Cp * T;\\n\\n // Rotation term\\n float Cp7 = pow(Cp, 7.0);\\n float RC = 2.0 * sqrt(Cp7 / (Cp7 + 6103515625.0));\\n float hpDeg = degrees(hp);\\n float dTheta = radians(30.0) * exp(-((hpDeg - 275.0) / 25.0) * ((hpDeg - 275.0) / 25.0));\\n float RT = -sin(2.0 * dTheta) * RC;\\n\\n float dLn = dLp / SL;\\n float dCn = dCp / SC;\\n float dHn = dHp / SH;\\n\\n return sqrt(dLn*dLn + dCn*dCn + dHn*dHn + RT * dCn * dHn);\\n}\\n\"","export default \"// DISTANCE_METRIC define: 0=rgb, 1=oklab, 2=deltaE76, 3=deltaE2000, 4=kotsarenkoRamos, 5=deltaE94\\nvec3 closestColor(vec3 color, sampler2D paletteTexture) {\\n int paletteSize = textureSize(paletteTexture, 0).x;\\n float minDist = 1000000.0;\\n vec3 closest = vec3(0.0);\\n\\n // Pre-convert the input color once — palette entries are converted inside the loop.\\n #if DISTANCE_METRIC == 1\\n vec3 colorConverted = linear_srgb_to_oklab(srgb2rgb(color));\\n #elif DISTANCE_METRIC == 2 || DISTANCE_METRIC == 3 || DISTANCE_METRIC == 5\\n vec3 colorConverted = srgb_to_cielab(color);\\n #else\\n vec3 colorConverted = color;\\n #endif\\n\\n for (int i = 0; i < paletteSize; i++) {\\n vec3 paletteColor = texelFetch(paletteTexture, ivec2(i, 0), 0).rgb;\\n\\n float dist;\\n #if DISTANCE_METRIC == 1\\n dist = distance(colorConverted, linear_srgb_to_oklab(srgb2rgb(paletteColor)));\\n #elif DISTANCE_METRIC == 2\\n dist = deltaE76(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 3\\n dist = deltaE2000(colorConverted, srgb_to_cielab(paletteColor));\\n #elif DISTANCE_METRIC == 4\\n dist = kotsarenkoRamos(color, paletteColor);\\n #elif DISTANCE_METRIC == 5\\n dist = deltaE94(colorConverted, srgb_to_cielab(paletteColor));\\n #else\\n dist = distance(colorConverted, paletteColor);\\n #endif\\n\\n if (dist < minDist) {\\n minDist = dist;\\n closest = paletteColor;\\n }\\n }\\n\\n return closest;\\n}\\n\"","import {\n ColorString,\n ColorList,\n PaletteVizOptions,\n SupportedColorModels,\n Axis,\n DistanceMetric,\n} from './types.ts';\n\n// @ts-ignore\nimport shaderSRGB2RGB from './shaders/srgb2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderOKLab from './shaders/oklab.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSL2RGB from './shaders/hsl2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderHSV2RGB from './shaders/hsv2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderLCH2RGB from './shaders/lch2rgb.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderDeltaE from './shaders/deltaE.frag.glsl?raw' assert { type: 'raw' };\n// @ts-ignore\nimport shaderClosestColor from './shaders/closestColor.frag.glsl?raw' assert { type: 'raw' };\n\n// Include order matters:\n// srgb2rgb – srgb2rgb()\n// oklab – M_PI, cbrt(), srgb_transfer_function(), okhsv/okhsl_to_srgb(), …\n// hsl2rgb, hsv2rgb, lch2rgb – color model conversions (lch2rgb uses M_PI + srgb_transfer_function)\n// deltaE – srgb_to_cielab(), deltaE76/94/2000() (uses srgb2rgb, cbrt, M_PI, TWO_PI)\n// closestColor – branches on DISTANCE_METRIC define; uses everything above\n//\n// Defines (compile-time, prepended to shader source — trigger recompile, no runtime branching):\n// DISTANCE_METRIC int 0=rgb 1=oklab 2=deltaE76 3=deltaE2000 4=kotsarenkoRamos 5=deltaE94\n// COLOR_MODEL int 0=rgb 1=oklab 2=okhsv 3=okhsvPolar 4=okhsl 5=okhslPolar\n// 6=oklch 7=oklchPolar 8=hsv 9=hsvPolar 10=hsl 11=hslPolar\n// PROGRESS_AXIS int 0=x 1=y 2=z\n// INVERT_Z flag (defined = true)\n// SHOW_RAW flag (defined = true)\n\nconst vertexShaderSrc = `\nprecision highp float;\nlayout(location = 0) in vec2 a_position;\nout vec2 vUv;\nvoid main() {\n vUv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`;\n\n// fragmentShader is exported so users can inspect or reuse the GLSL source.\n// Defines are NOT embedded here — they are prepended at compile time via buildProgram().\n// Note: #version 300 es is prepended by buildProgram() (must be first line,\n// before defines). Do not add it here.\nexport const fragmentShader = `\nprecision highp float;\n#define TWO_PI 6.28318530718\nin vec2 vUv;\nout vec4 fragColor;\nuniform float progress;\nuniform sampler2D paletteTexture;\n\n${shaderSRGB2RGB}\n${shaderOKLab}\n${shaderHSL2RGB}\n${shaderHSV2RGB}\n${shaderLCH2RGB}\n${shaderDeltaE}\n${shaderClosestColor}\n\n// COLOR_MODEL: 0=rgb, 1=oklab, 2=okhsv, 3=okhsvPolar, 4=okhsl, 5=okhslPolar,\n// 6=oklch, 7=oklchPolar, 8=hsv, 9=hsvPolar, 10=hsl, 11=hslPolar\nvec3 modelToRGB(vec3 colorCoords) {\n #if COLOR_MODEL == 0\n return colorCoords;\n #elif COLOR_MODEL == 1\n vec3 linear = oklab_to_linear_srgb(vec3(colorCoords.z, colorCoords.x - 0.5, colorCoords.y - 0.5));\n return clamp(vec3(srgb_transfer_function(linear.r), srgb_transfer_function(linear.g), srgb_transfer_function(linear.b)), 0.0, 1.0);\n #elif COLOR_MODEL == 2 || COLOR_MODEL == 3\n return okhsv_to_srgb(colorCoords);\n #elif COLOR_MODEL == 4 || COLOR_MODEL == 5\n return okhsl_to_srgb(colorCoords);\n #elif COLOR_MODEL == 6 || COLOR_MODEL == 7\n return lch2rgb(vec3(colorCoords.z, colorCoords.y, colorCoords.x));\n #elif COLOR_MODEL == 8 || COLOR_MODEL == 9\n return hsv2rgb(colorCoords);\n #else\n return hsl2rgb(colorCoords);\n #endif\n}\n\nvoid main(){\n #if PROGRESS_AXIS == 1\n vec3 colorCoords = vec3(vUv.x, progress, vUv.y);\n #elif PROGRESS_AXIS == 2\n vec3 colorCoords = vec3(vUv.x, vUv.y, 1. - progress);\n #else\n vec3 colorCoords = vec3(progress, vUv.x, vUv.y);\n #endif\n\n #if COLOR_MODEL == 3 || COLOR_MODEL == 5 || COLOR_MODEL == 7 || COLOR_MODEL == 9 || COLOR_MODEL == 11\n vec2 toCenter = vUv - 0.5;\n float angle = atan(toCenter.y, toCenter.x);\n float radius = length(toCenter) * 2.0;\n\n #if PROGRESS_AXIS == 2\n colorCoords = vec3((angle / TWO_PI), radius, 1. - progress);\n #elif PROGRESS_AXIS == 1\n colorCoords = vec3((angle / TWO_PI), 1. - progress, radius);\n if (radius > 1.0) { discard; }\n #else\n float hue = 1.0 - abs(0.5 - progress * .5) * 2.0;\n if (vUv.x > 0.5) { hue += 0.5; }\n colorCoords = vec3(hue, abs(0.5 - vUv.x) * 2.0, vUv.y);\n #endif\n #endif\n\n #ifdef INVERT_Z\n colorCoords.z = 1. - colorCoords.z;\n #endif\n\n vec3 rgb = modelToRGB(colorCoords);\n\n #ifdef SHOW_RAW\n fragColor = vec4(rgb, 1.);\n #else\n fragColor = vec4(closestColor(rgb, paletteTexture), 1.);\n #endif\n}`;\n\n// ── Color parsing ──────────────────────────────────────────────────────────────\n// Use a canvas 2D context as a free CSS color parser — handles hex, rgb(),\n// hsl(), named colors, etc. Lazy-initialised to avoid issues at module load time.\n\nlet _colorCtx: CanvasRenderingContext2D | null = null;\n\nfunction cssToSRGB(color: string): [number, number, number] {\n if (!_colorCtx) {\n const c = document.createElement('canvas');\n c.width = c.height = 1;\n _colorCtx = c.getContext('2d')!;\n }\n _colorCtx.fillStyle = '#000000'; // reset before setting\n _colorCtx.fillStyle = color;\n const v = _colorCtx.fillStyle; // browser normalises to '#rrggbb' or 'rgba(...)'\n if (v[0] === '#') {\n return [\n parseInt(v.slice(1, 3), 16) / 255,\n parseInt(v.slice(3, 5), 16) / 255,\n parseInt(v.slice(5, 7), 16) / 255,\n ];\n }\n // rgba(r, g, b, a) fallback\n const m = v.match(/[\\d.]+/g)!;\n return [+m[0] / 255, +m[1] / 255, +m[2] / 255];\n}\n\n// ── Palette helpers ────────────────────────────────────────────────────────────\n\n// Returns the palette as a flat RGBA Uint8Array (sRGB, 1×N texture row).\n// Useful for building your own WebGL texture or inspecting raw color data.\nexport const paletteToRGBA = (palette: ColorList): Uint8Array => {\n const data = new Uint8Array(palette.length * 4);\n palette.forEach((color, i) => {\n try {\n const [r, g, b] = cssToSRGB(color);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n } catch {\n console.error(`Invalid color: ${color}`);\n }\n });\n return data;\n};\n\n// Backwards-compatible alias (previously returned a Three.js DataTexture)\nexport const paletteToTexture = paletteToRGBA;\n\nexport const randomPalette = (size = 20): ColorList =>\n Array.from(\n { length: size },\n () =>\n `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`,\n );\n\n// ── WebGL helpers ──────────────────────────────────────────────────────────────\n\ntype Defines = Record<string, number | false>;\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`Shader compile error:\\n${log}`);\n }\n return shader;\n}\n\nfunction buildProgram(\n gl: WebGL2RenderingContext,\n defines: Defines,\n fragSrc: string,\n vertSrc: string,\n): WebGLProgram {\n // #version 300 es must be the very first line — prepend it before defines.\n const defineStr =\n Object.entries(defines)\n .filter(([, v]) => v !== false)\n .map(([k, v]) => `#define ${k} ${v}`)\n .join('\\n') + '\\n';\n const prefix = '#version 300 es\\n' + defineStr;\n\n const vert = compileShader(gl, gl.VERTEX_SHADER, prefix + vertSrc);\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, prefix + fragSrc);\n\n const prog = gl.createProgram()!;\n gl.attachShader(prog, vert);\n gl.attachShader(prog, frag);\n gl.linkProgram(prog);\n gl.deleteShader(vert);\n gl.deleteShader(frag);\n\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(prog);\n gl.deleteProgram(prog);\n throw new Error(`Program link error:\\n${log}`);\n }\n return prog;\n}\n\nfunction uploadPaletteTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n palette: ColorList,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // RGBA8: sized internal format required by WebGL2 spec\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA8,\n palette.length,\n 1,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n paletteToRGBA(palette),\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n}\n\n// ── PaletteViz ─────────────────────────────────────────────────────────────────\n\nexport class PaletteViz {\n #palette: ColorList = [];\n #width = 512;\n #height = 512;\n #pixelRatio = 1;\n\n // shader state\n #position = 0.0;\n #axis: Axis = 'y';\n #colorModel: SupportedColorModels = 'okhsv';\n #distanceMetric: DistanceMetric = 'oklab';\n #invertZ = false;\n #showRaw = false;\n\n // uniform value maps\n readonly #axisMap = { x: 0, y: 1, z: 2 } as const;\n readonly #colorModelMap = {\n rgb: 0,\n oklab: 1,\n okhsv: 2,\n okhsvPolar: 3,\n okhsl: 4,\n okhslPolar: 5,\n oklch: 6,\n oklchPolar: 7,\n hsv: 8,\n hsvPolar: 9,\n hsl: 10,\n hslPolar: 11,\n } as const;\n readonly #distanceMetricMap = {\n rgb: 0,\n oklab: 1,\n deltaE76: 2,\n deltaE2000: 3,\n kotsarenkoRamos: 4,\n deltaE94: 5,\n } as const;\n\n // WebGL\n #canvas: HTMLCanvasElement;\n #gl: WebGL2RenderingContext;\n #program: WebGLProgram | null = null;\n #texture: WebGLTexture | null = null;\n #quadBuffer: WebGLBuffer | null = null;\n #vao: WebGLVertexArrayObject | null = null;\n #animationFrame: number | null = null;\n\n // cached uniform locations (re-queried after each program rebuild)\n #uProgress: WebGLUniformLocation | null = null;\n #uPaletteTexture: WebGLUniformLocation | null = null;\n\n // dom\n #container: HTMLElement | undefined;\n\n constructor({\n palette = randomPalette(),\n width = 512,\n height = 512,\n pixelRatio = window.devicePixelRatio,\n container,\n colorModel = 'okhsv',\n distanceMetric = 'oklab',\n axis = 'y',\n position = 0.0,\n invertZ = false,\n showRaw = false,\n }: PaletteVizOptions = {}) {\n this.#palette = palette;\n this.#width = width;\n this.#height = height;\n this.#pixelRatio = pixelRatio;\n this.#colorModel = colorModel;\n this.#distanceMetric = distanceMetric;\n this.#axis = axis;\n this.#position = position;\n this.#invertZ = invertZ;\n this.#showRaw = showRaw;\n this.#container = container;\n\n this.#canvas = document.createElement('canvas');\n this.#canvas.classList.add('palette-viz');\n const gl = this.#canvas.getContext('webgl2');\n if (!gl) throw new Error('WebGL2 not supported');\n this.#gl = gl;\n\n // Quad buffer + VAO — set up once, reused every frame.\n // layout(location=0) in the vertex shader pins a_position to slot 0,\n // so the VAO remains valid across shader recompiles.\n this.#quadBuffer = gl.createBuffer()!;\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#quadBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n\n this.#vao = gl.createVertexArray()!;\n gl.bindVertexArray(this.#vao);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n\n this.#texture = gl.createTexture()!;\n uploadPaletteTexture(gl, this.#texture, this.#palette);\n\n this.#buildProgram();\n this.#setSize(this.#width, this.#height);\n this.#container?.appendChild(this.#canvas);\n this.#paint();\n }\n\n #defines(): Defines {\n return {\n DISTANCE_METRIC: this.#distanceMetricMap[this.#distanceMetric],\n COLOR_MODEL: this.#colorModelMap[this.#colorModel],\n PROGRESS_AXIS: this.#axisMap[this.#axis],\n INVERT_Z: this.#invertZ ? 1 : false,\n SHOW_RAW: this.#showRaw ? 1 : false,\n };\n }\n\n #buildProgram(): void {\n const gl = this.#gl;\n if (this.#program) gl.deleteProgram(this.#program);\n this.#program = buildProgram(gl, this.#defines(), fragmentShader, vertexShaderSrc);\n this.#uProgress = gl.getUniformLocation(this.#program, 'progress');\n this.#uPaletteTexture = gl.getUniformLocation(this.#program, 'paletteTexture');\n }\n\n #setSize(w: number, h: number): void {\n const pw = Math.round(w * this.#pixelRatio);\n const ph = Math.round(h * this.#pixelRatio);\n this.#canvas.width = pw;\n this.#canvas.height = ph;\n this.#canvas.style.width = `${w}px`;\n this.#canvas.style.height = `${h}px`;\n this.#gl.viewport(0, 0, pw, ph);\n }\n\n #paint(): void {\n if (this.#animationFrame !== null) cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = requestAnimationFrame(() => {\n const gl = this.#gl;\n gl.useProgram(this.#program);\n\n gl.uniform1f(this.#uProgress, this.#position);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.#texture);\n gl.uniform1i(this.#uPaletteTexture, 0);\n\n gl.bindVertexArray(this.#vao);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n gl.bindVertexArray(null);\n });\n }\n\n // ── Public API ──────────────────────────────────────────────────────────────\n\n get canvas(): HTMLCanvasElement {\n return this.#canvas;\n }\n get width() {\n return this.#width;\n }\n get height() {\n return this.#height;\n }\n\n resize(width: number, height: number | null = null): void {\n this.#width = width;\n this.#height = height ?? width;\n this.#setSize(this.#width, this.#height);\n this.#paint();\n }\n\n destroy(): void {\n if (this.#animationFrame !== null) {\n cancelAnimationFrame(this.#animationFrame);\n this.#animationFrame = null;\n }\n const gl = this.#gl;\n gl.deleteProgram(this.#program);\n gl.deleteTexture(this.#texture);\n gl.deleteBuffer(this.#quadBuffer);\n gl.deleteVertexArray(this.#vao);\n this.#canvas.remove();\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n\n // ── Palette ─────────────────────────────────────────────────────────────────\n\n set palette(palette: ColorList) {\n this.#palette = palette;\n uploadPaletteTexture(this.#gl, this.#texture!, palette);\n this.#paint();\n }\n get palette() {\n return this.#palette;\n }\n\n setColor(color: ColorString, index: number): void {\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette[index] = color;\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n addColor(color: ColorString, index?: number): void {\n this.#palette.splice(index ?? this.#palette.length, 0, color);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n removeColor(index: number): void;\n removeColor(color: ColorString): void;\n removeColor(indexOrColor: number | ColorString): void {\n const index =\n typeof indexOrColor === 'number' ? indexOrColor : this.#palette.indexOf(indexOrColor);\n if (index === -1) throw new Error('Color not found in palette');\n if (index < 0 || index >= this.#palette.length) throw new Error(`Index ${index} out of range`);\n this.#palette.splice(index, 1);\n uploadPaletteTexture(this.#gl, this.#texture!, this.#palette);\n this.#paint();\n }\n\n // ── Shader properties ────────────────────────────────────────────────────────\n\n set position(value: number) {\n this.#position = value;\n this.#paint();\n }\n get position() {\n return this.#position;\n }\n\n set axis(axis: Axis) {\n if (!(axis in this.#axisMap)) throw new Error(\"axis must be 'x', 'y', or 'z'\");\n this.#axis = axis;\n this.#buildProgram();\n this.#paint();\n }\n get axis() {\n return this.#axis;\n }\n\n set colorModel(model: SupportedColorModels) {\n if (!(model in this.#colorModelMap)) throw new Error(`colorModel '${model}' is not supported`);\n this.#colorModel = model;\n this.#buildProgram();\n this.#paint();\n }\n get colorModel() {\n return this.#colorModel;\n }\n\n set distanceMetric(metric: DistanceMetric) {\n if (!(metric in this.#distanceMetricMap))\n throw new Error(\n \"distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'\",\n );\n this.#distanceMetric = metric;\n this.#buildProgram();\n this.#paint();\n }\n get distanceMetric() {\n return this.#distanceMetric;\n }\n\n set invertZ(value: boolean) {\n this.#invertZ = value;\n this.#buildProgram();\n this.#paint();\n }\n get invertZ() {\n return this.#invertZ;\n }\n\n set showRaw(value: boolean) {\n this.#showRaw = value;\n this.#buildProgram();\n this.#paint();\n }\n get showRaw() {\n return this.#showRaw;\n }\n\n static paletteToRGBA = paletteToRGBA;\n /** @deprecated use PaletteViz.paletteToRGBA */\n static paletteToTexture = paletteToRGBA;\n}\n"],"names":["shaderSRGB2RGB","shaderOKLab","shaderHSL2RGB","shaderHSV2RGB","shaderLCH2RGB","shaderDeltaE","shaderClosestColor","vertexShaderSrc","fragmentShader","_colorCtx","cssToSRGB","color","c","v","m","paletteToRGBA","palette","data","i","r","g","b","paletteToTexture","randomPalette","size","compileShader","gl","type","src","shader","log","buildProgram","defines","fragSrc","vertSrc","prefix","k","vert","frag","prog","uploadPaletteTexture","tex","PaletteViz","#palette","#width","#height","#pixelRatio","#position","#axis","#colorModel","#distanceMetric","#invertZ","#showRaw","#axisMap","#colorModelMap","#distanceMetricMap","#canvas","#gl","#program","#texture","#quadBuffer","#vao","#animationFrame","#uProgress","#uPaletteTexture","#container","width","height","pixelRatio","container","colorModel","distanceMetric","axis","position","invertZ","showRaw","#buildProgram","#setSize","#paint","#defines","w","h","pw","ph","index","indexOrColor","value","model","metric"],"mappings":"sOAAA,MAAAA,EAAe;AAAA;AAAA;AAAA;AAAA,ECAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCAfC,EAAe;AAAA;AAAA;AAAA,GCAfC,EAAe;AAAA;AAAA;AAAA;AAAA,GCAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ECAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ECAfC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ECuCTC,EAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAaXC,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5BR,CAAc;AAAA,EACdC,CAAW;AAAA,EACXC,CAAa;AAAA,EACbC,CAAa;AAAA,EACbC,CAAa;AAAA,EACbC,CAAY;AAAA,EACZC,CAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAkEpB,IAAIG,EAA6C,KAEjD,SAASC,EAAUC,EAAyC,CAC1D,GAAI,CAACF,EAAW,CACd,MAAMG,EAAI,SAAS,cAAc,QAAQ,EACzCA,EAAE,MAAQA,EAAE,OAAS,EACrBH,EAAYG,EAAE,WAAW,IAAI,CAC/B,CACAH,EAAU,UAAY,UACtBA,EAAU,UAAYE,EACtB,MAAME,EAAIJ,EAAU,UACpB,GAAII,EAAE,CAAC,IAAM,IACX,MAAO,CACL,SAASA,EAAE,MAAM,EAAG,CAAC,EAAG,EAAE,EAAI,IAC9B,SAASA,EAAE,MAAM,EAAG,CAAC,EAAG,EAAE,EAAI,IAC9B,SAASA,EAAE,MAAM,EAAG,CAAC,EAAG,EAAE,EAAI,GAAA,EAIlC,MAAMC,EAAID,EAAE,MAAM,SAAS,EAC3B,MAAO,CAAC,CAACC,EAAE,CAAC,EAAI,IAAK,CAACA,EAAE,CAAC,EAAI,IAAK,CAACA,EAAE,CAAC,EAAI,GAAG,CAC/C,CAMO,MAAMC,EAAiBC,GAAmC,CAC/D,MAAMC,EAAO,IAAI,WAAWD,EAAQ,OAAS,CAAC,EAC9C,OAAAA,EAAQ,QAAQ,CAACL,EAAOO,IAAM,CAC5B,GAAI,CACF,KAAM,CAACC,EAAGC,EAAGC,CAAC,EAAIX,EAAUC,CAAK,EACjCM,EAAKC,EAAI,EAAI,CAAC,EAAI,KAAK,MAAMC,EAAI,GAAG,EACpCF,EAAKC,EAAI,EAAI,CAAC,EAAI,KAAK,MAAME,EAAI,GAAG,EACpCH,EAAKC,EAAI,EAAI,CAAC,EAAI,KAAK,MAAMG,EAAI,GAAG,EACpCJ,EAAKC,EAAI,EAAI,CAAC,EAAI,GACpB,MAAQ,CACN,QAAQ,MAAM,kBAAkBP,CAAK,EAAE,CACzC,CACF,CAAC,EACMM,CACT,EAGaK,EAAmBP,EAEnBQ,EAAgB,CAACC,EAAO,KACnC,MAAM,KACJ,CAAE,OAAQA,CAAA,EACV,IACE,OAAO,KAAK,MAAM,KAAK,SAAW,GAAG,CAAC,KAAK,KAAK,MAAM,KAAK,SAAW,GAAG,CAAC,KAAK,KAAK,MAAM,KAAK,OAAA,EAAW,GAAG,CAAC,GAClH,EAMF,SAASC,EAAcC,EAA4BC,EAAcC,EAA0B,CACzF,MAAMC,EAASH,EAAG,aAAaC,CAAI,EAGnC,GAFAD,EAAG,aAAaG,EAAQD,CAAG,EAC3BF,EAAG,cAAcG,CAAM,EACnB,CAACH,EAAG,mBAAmBG,EAAQH,EAAG,cAAc,EAAG,CACrD,MAAMI,EAAMJ,EAAG,iBAAiBG,CAAM,EACtC,MAAAH,EAAG,aAAaG,CAAM,EAChB,IAAI,MAAM;AAAA,EAA0BC,CAAG,EAAE,CACjD,CACA,OAAOD,CACT,CAEA,SAASE,EACPL,EACAM,EACAC,EACAC,EACc,CAOd,MAAMC,EAAS;AAAA,GAJb,OAAO,QAAQH,CAAO,EACnB,OAAO,CAAC,CAAA,CAAGnB,CAAC,IAAMA,IAAM,EAAK,EAC7B,IAAI,CAAC,CAACuB,EAAGvB,CAAC,IAAM,WAAWuB,CAAC,IAAIvB,CAAC,EAAE,EACnC,KAAK;AAAA,CAAI,EAAI;AAAA,GAGZwB,EAAOZ,EAAcC,EAAIA,EAAG,cAAeS,EAASD,CAAO,EAC3DI,EAAOb,EAAcC,EAAIA,EAAG,gBAAiBS,EAASF,CAAO,EAE7DM,EAAOb,EAAG,cAAA,EAOhB,GANAA,EAAG,aAAaa,EAAMF,CAAI,EAC1BX,EAAG,aAAaa,EAAMD,CAAI,EAC1BZ,EAAG,YAAYa,CAAI,EACnBb,EAAG,aAAaW,CAAI,EACpBX,EAAG,aAAaY,CAAI,EAEhB,CAACZ,EAAG,oBAAoBa,EAAMb,EAAG,WAAW,EAAG,CACjD,MAAMI,EAAMJ,EAAG,kBAAkBa,CAAI,EACrC,MAAAb,EAAG,cAAca,CAAI,EACf,IAAI,MAAM;AAAA,EAAwBT,CAAG,EAAE,CAC/C,CACA,OAAOS,CACT,CAEA,SAASC,EACPd,EACAe,EACAzB,EACM,CACNU,EAAG,YAAYA,EAAG,WAAYe,CAAG,EAEjCf,EAAG,WACDA,EAAG,WACH,EACAA,EAAG,MACHV,EAAQ,OACR,EACA,EACAU,EAAG,KACHA,EAAG,cACHX,EAAcC,CAAO,CAAA,EAEvBU,EAAG,cAAcA,EAAG,WAAYA,EAAG,mBAAoBA,EAAG,OAAO,EACjEA,EAAG,cAAcA,EAAG,WAAYA,EAAG,mBAAoBA,EAAG,OAAO,EACjEA,EAAG,cAAcA,EAAG,WAAYA,EAAG,eAAgBA,EAAG,aAAa,EACnEA,EAAG,cAAcA,EAAG,WAAYA,EAAG,eAAgBA,EAAG,aAAa,CACrE,CAIO,MAAMgB,CAAW,CACtBC,GAAsB,CAAA,EACtBC,GAAS,IACTC,GAAU,IACVC,GAAc,EAGdC,GAAY,EACZC,GAAc,IACdC,GAAoC,QACpCC,GAAkC,QAClCC,GAAW,GACXC,GAAW,GAGFC,GAAW,CAAE,EAAG,EAAG,EAAG,EAAG,EAAG,CAAA,EAC5BC,GAAiB,CACxB,IAAK,EACL,MAAO,EACP,MAAO,EACP,WAAY,EACZ,MAAO,EACP,WAAY,EACZ,MAAO,EACP,WAAY,EACZ,IAAK,EACL,SAAU,EACV,IAAK,GACL,SAAU,EAAA,EAEHC,GAAqB,CAC5B,IAAK,EACL,MAAO,EACP,SAAU,EACV,WAAY,EACZ,gBAAiB,EACjB,SAAU,CAAA,EAIZC,GACAC,GACAC,GAAgC,KAChCC,GAAgC,KAChCC,GAAkC,KAClCC,GAAsC,KACtCC,GAAiC,KAGjCC,GAA0C,KAC1CC,GAAgD,KAGhDC,GAEA,YAAY,CACV,QAAAjD,EAAUO,EAAA,EACV,MAAA2C,EAAQ,IACR,OAAAC,EAAS,IACT,WAAAC,EAAa,OAAO,iBACpB,UAAAC,EACA,WAAAC,EAAa,QACb,eAAAC,EAAiB,QACjB,KAAAC,EAAO,IACP,SAAAC,EAAW,EACX,QAAAC,EAAU,GACV,QAAAC,EAAU,EAAA,EACW,GAAI,CACzB,KAAKhC,GAAW3B,EAChB,KAAK4B,GAASsB,EACd,KAAKrB,GAAUsB,EACf,KAAKrB,GAAcsB,EACnB,KAAKnB,GAAcqB,EACnB,KAAKpB,GAAkBqB,EACvB,KAAKvB,GAAQwB,EACb,KAAKzB,GAAY0B,EACjB,KAAKtB,GAAWuB,EAChB,KAAKtB,GAAWuB,EAChB,KAAKV,GAAaI,EAElB,KAAKb,GAAU,SAAS,cAAc,QAAQ,EAC9C,KAAKA,GAAQ,UAAU,IAAI,aAAa,EACxC,MAAM9B,EAAK,KAAK8B,GAAQ,WAAW,QAAQ,EAC3C,GAAI,CAAC9B,EAAI,MAAM,IAAI,MAAM,sBAAsB,EAC/C,KAAK+B,GAAM/B,EAKX,KAAKkC,GAAclC,EAAG,aAAA,EACtBA,EAAG,WAAWA,EAAG,aAAc,KAAKkC,EAAW,EAC/ClC,EAAG,WAAWA,EAAG,aAAc,IAAI,aAAa,CAAC,GAAI,GAAI,EAAG,GAAI,GAAI,EAAG,EAAG,CAAC,CAAC,EAAGA,EAAG,WAAW,EAE7F,KAAKmC,GAAOnC,EAAG,kBAAA,EACfA,EAAG,gBAAgB,KAAKmC,EAAI,EAC5BnC,EAAG,wBAAwB,CAAC,EAC5BA,EAAG,oBAAoB,EAAG,EAAGA,EAAG,MAAO,GAAO,EAAG,CAAC,EAClDA,EAAG,gBAAgB,IAAI,EAEvB,KAAKiC,GAAWjC,EAAG,cAAA,EACnBc,EAAqBd,EAAI,KAAKiC,GAAU,KAAKhB,EAAQ,EAErD,KAAKiC,GAAA,EACL,KAAKC,GAAS,KAAKjC,GAAQ,KAAKC,EAAO,EACvC,KAAKoB,IAAY,YAAY,KAAKT,EAAO,EACzC,KAAKsB,GAAA,CACP,CAEAC,IAAoB,CAClB,MAAO,CACL,gBAAiB,KAAKxB,GAAmB,KAAKL,EAAe,EAC7D,YAAa,KAAKI,GAAe,KAAKL,EAAW,EACjD,cAAe,KAAKI,GAAS,KAAKL,EAAK,EACvC,SAAU,KAAKG,GAAW,EAAI,GAC9B,SAAU,KAAKC,GAAW,EAAI,EAAA,CAElC,CAEAwB,IAAsB,CACpB,MAAMlD,EAAK,KAAK+B,GACZ,KAAKC,IAAUhC,EAAG,cAAc,KAAKgC,EAAQ,EACjD,KAAKA,GAAW3B,EAAaL,EAAI,KAAKqD,GAAA,EAAYvE,EAAgBD,CAAe,EACjF,KAAKwD,GAAarC,EAAG,mBAAmB,KAAKgC,GAAU,UAAU,EACjE,KAAKM,GAAmBtC,EAAG,mBAAmB,KAAKgC,GAAU,gBAAgB,CAC/E,CAEAmB,GAASG,EAAWC,EAAiB,CACnC,MAAMC,EAAK,KAAK,MAAMF,EAAI,KAAKlC,EAAW,EACpCqC,EAAK,KAAK,MAAMF,EAAI,KAAKnC,EAAW,EAC1C,KAAKU,GAAQ,MAAQ0B,EACrB,KAAK1B,GAAQ,OAAS2B,EACtB,KAAK3B,GAAQ,MAAM,MAAQ,GAAGwB,CAAC,KAC/B,KAAKxB,GAAQ,MAAM,OAAS,GAAGyB,CAAC,KAChC,KAAKxB,GAAI,SAAS,EAAG,EAAGyB,EAAIC,CAAE,CAChC,CAEAL,IAAe,CACT,KAAKhB,KAAoB,MAAM,qBAAqB,KAAKA,EAAe,EAC5E,KAAKA,GAAkB,sBAAsB,IAAM,CACjD,MAAMpC,EAAK,KAAK+B,GAChB/B,EAAG,WAAW,KAAKgC,EAAQ,EAE3BhC,EAAG,UAAU,KAAKqC,GAAY,KAAKhB,EAAS,EAE5CrB,EAAG,cAAcA,EAAG,QAAQ,EAC5BA,EAAG,YAAYA,EAAG,WAAY,KAAKiC,EAAQ,EAC3CjC,EAAG,UAAU,KAAKsC,GAAkB,CAAC,EAErCtC,EAAG,gBAAgB,KAAKmC,EAAI,EAC5BnC,EAAG,WAAWA,EAAG,eAAgB,EAAG,CAAC,EACrCA,EAAG,gBAAgB,IAAI,CACzB,CAAC,CACH,CAIA,IAAI,QAA4B,CAC9B,OAAO,KAAK8B,EACd,CACA,IAAI,OAAQ,CACV,OAAO,KAAKZ,EACd,CACA,IAAI,QAAS,CACX,OAAO,KAAKC,EACd,CAEA,OAAOqB,EAAeC,EAAwB,KAAY,CACxD,KAAKvB,GAASsB,EACd,KAAKrB,GAAUsB,GAAUD,EACzB,KAAKW,GAAS,KAAKjC,GAAQ,KAAKC,EAAO,EACvC,KAAKiC,GAAA,CACP,CAEA,SAAgB,CACV,KAAKhB,KAAoB,OAC3B,qBAAqB,KAAKA,EAAe,EACzC,KAAKA,GAAkB,MAEzB,MAAMpC,EAAK,KAAK+B,GAChB/B,EAAG,cAAc,KAAKgC,EAAQ,EAC9BhC,EAAG,cAAc,KAAKiC,EAAQ,EAC9BjC,EAAG,aAAa,KAAKkC,EAAW,EAChClC,EAAG,kBAAkB,KAAKmC,EAAI,EAC9B,KAAKL,GAAQ,OAAA,EACb9B,EAAG,aAAa,oBAAoB,GAAG,YAAA,CACzC,CAIA,IAAI,QAAQV,EAAoB,CAC9B,KAAK2B,GAAW3B,EAChBwB,EAAqB,KAAKiB,GAAK,KAAKE,GAAW3C,CAAO,EACtD,KAAK8D,GAAA,CACP,CACA,IAAI,SAAU,CACZ,OAAO,KAAKnC,EACd,CAEA,SAAShC,EAAoByE,EAAqB,CAChD,GAAIA,EAAQ,GAAKA,GAAS,KAAKzC,GAAS,OAAQ,MAAM,IAAI,MAAM,SAASyC,CAAK,eAAe,EAC7F,KAAKzC,GAASyC,CAAK,EAAIzE,EACvB6B,EAAqB,KAAKiB,GAAK,KAAKE,GAAW,KAAKhB,EAAQ,EAC5D,KAAKmC,GAAA,CACP,CAEA,SAASnE,EAAoByE,EAAsB,CACjD,KAAKzC,GAAS,OAAOyC,GAAS,KAAKzC,GAAS,OAAQ,EAAGhC,CAAK,EAC5D6B,EAAqB,KAAKiB,GAAK,KAAKE,GAAW,KAAKhB,EAAQ,EAC5D,KAAKmC,GAAA,CACP,CAIA,YAAYO,EAA0C,CACpD,MAAMD,EACJ,OAAOC,GAAiB,SAAWA,EAAe,KAAK1C,GAAS,QAAQ0C,CAAY,EACtF,GAAID,IAAU,GAAI,MAAM,IAAI,MAAM,4BAA4B,EAC9D,GAAIA,EAAQ,GAAKA,GAAS,KAAKzC,GAAS,OAAQ,MAAM,IAAI,MAAM,SAASyC,CAAK,eAAe,EAC7F,KAAKzC,GAAS,OAAOyC,EAAO,CAAC,EAC7B5C,EAAqB,KAAKiB,GAAK,KAAKE,GAAW,KAAKhB,EAAQ,EAC5D,KAAKmC,GAAA,CACP,CAIA,IAAI,SAASQ,EAAe,CAC1B,KAAKvC,GAAYuC,EACjB,KAAKR,GAAA,CACP,CACA,IAAI,UAAW,CACb,OAAO,KAAK/B,EACd,CAEA,IAAI,KAAKyB,EAAY,CACnB,GAAI,EAAEA,KAAQ,KAAKnB,IAAW,MAAM,IAAI,MAAM,+BAA+B,EAC7E,KAAKL,GAAQwB,EACb,KAAKI,GAAA,EACL,KAAKE,GAAA,CACP,CACA,IAAI,MAAO,CACT,OAAO,KAAK9B,EACd,CAEA,IAAI,WAAWuC,EAA6B,CAC1C,GAAI,EAAEA,KAAS,KAAKjC,UAAuB,IAAI,MAAM,eAAeiC,CAAK,oBAAoB,EAC7F,KAAKtC,GAAcsC,EACnB,KAAKX,GAAA,EACL,KAAKE,GAAA,CACP,CACA,IAAI,YAAa,CACf,OAAO,KAAK7B,EACd,CAEA,IAAI,eAAeuC,EAAwB,CACzC,GAAI,EAAEA,KAAU,KAAKjC,IACnB,MAAM,IAAI,MACR,mGAAA,EAEJ,KAAKL,GAAkBsC,EACvB,KAAKZ,GAAA,EACL,KAAKE,GAAA,CACP,CACA,IAAI,gBAAiB,CACnB,OAAO,KAAK5B,EACd,CAEA,IAAI,QAAQoC,EAAgB,CAC1B,KAAKnC,GAAWmC,EAChB,KAAKV,GAAA,EACL,KAAKE,GAAA,CACP,CACA,IAAI,SAAU,CACZ,OAAO,KAAK3B,EACd,CAEA,IAAI,QAAQmC,EAAgB,CAC1B,KAAKlC,GAAWkC,EAChB,KAAKV,GAAA,EACL,KAAKE,GAAA,CACP,CACA,IAAI,SAAU,CACZ,OAAO,KAAK1B,EACd,CAEA,OAAO,cAAgBrC,EAEvB,OAAO,iBAAmBA,CAC5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palette-shader",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Dependency-free WebGL2 shader that maps any colour palette across perceptual colour spaces — OKHsv, OKHsl, OKLCH and more.",
5
5
  "keywords": [
6
6
  "color",
package/src/index.ts CHANGED
@@ -126,6 +126,35 @@ void main(){
126
126
  #endif
127
127
  }`;
128
128
 
129
+ // Pass-2 shader: reads from the FBO color texture, detects edges by comparing
130
+ // N/S/E/W neighbors. Only opaque neighbors (a>0) participate in the comparison
131
+ // so polar-disc edges don't bleed into the outline.
132
+ const outlineFragmentShaderSrc = `
133
+ precision highp float;
134
+ in vec2 vUv;
135
+ out vec4 fragColor;
136
+ uniform sampler2D colorMap;
137
+ uniform float outlineWidth;
138
+ uniform vec2 resolution;
139
+
140
+ void main() {
141
+ vec4 center = texture(colorMap, vUv);
142
+ if (center.a == 0.0) { fragColor = vec4(0.0); return; }
143
+ vec2 px = outlineWidth / resolution;
144
+ vec4 n0 = texture(colorMap, vUv + vec2( px.x, 0.0));
145
+ vec4 n1 = texture(colorMap, vUv + vec2(-px.x, 0.0));
146
+ vec4 n2 = texture(colorMap, vUv + vec2(0.0, px.y));
147
+ vec4 n3 = texture(colorMap, vUv + vec2(0.0, -px.y));
148
+ if ((n0.a > 0.0 && any(notEqual(n0.rgb, center.rgb))) ||
149
+ (n1.a > 0.0 && any(notEqual(n1.rgb, center.rgb))) ||
150
+ (n2.a > 0.0 && any(notEqual(n2.rgb, center.rgb))) ||
151
+ (n3.a > 0.0 && any(notEqual(n3.rgb, center.rgb)))) {
152
+ fragColor = vec4(0.0);
153
+ return;
154
+ }
155
+ fragColor = center;
156
+ }`;
157
+
129
158
  // ── Color parsing ──────────────────────────────────────────────────────────────
130
159
  // Use a canvas 2D context as a free CSS color parser — handles hex, rgb(),
131
160
  // hsl(), named colors, etc. Lazy-initialised to avoid issues at module load time.
@@ -268,8 +297,9 @@ export class PaletteViz {
268
297
  #axis: Axis = 'y';
269
298
  #colorModel: SupportedColorModels = 'okhsv';
270
299
  #distanceMetric: DistanceMetric = 'oklab';
271
- #invertLightness = false;
300
+ #invertZ = false;
272
301
  #showRaw = false;
302
+ #outlineWidth = 0;
273
303
 
274
304
  // uniform value maps
275
305
  readonly #axisMap = { x: 0, y: 1, z: 2 } as const;
@@ -309,6 +339,14 @@ export class PaletteViz {
309
339
  #uProgress: WebGLUniformLocation | null = null;
310
340
  #uPaletteTexture: WebGLUniformLocation | null = null;
311
341
 
342
+ // outline pass (created/destroyed when outlineWidth toggles between 0 and >0)
343
+ #fbo: WebGLFramebuffer | null = null;
344
+ #fboTexture: WebGLTexture | null = null;
345
+ #outlineProgram: WebGLProgram | null = null;
346
+ #uColorMap: WebGLUniformLocation | null = null;
347
+ #uOutlineWidth: WebGLUniformLocation | null = null;
348
+ #uOutlineResolution: WebGLUniformLocation | null = null;
349
+
312
350
  // dom
313
351
  #container: HTMLElement | undefined;
314
352
 
@@ -322,8 +360,9 @@ export class PaletteViz {
322
360
  distanceMetric = 'oklab',
323
361
  axis = 'y',
324
362
  position = 0.0,
325
- invertLightness = false,
363
+ invertZ = false,
326
364
  showRaw = false,
365
+ outlineWidth = 0,
327
366
  }: PaletteVizOptions = {}) {
328
367
  this.#palette = palette;
329
368
  this.#width = width;
@@ -333,8 +372,9 @@ export class PaletteViz {
333
372
  this.#distanceMetric = distanceMetric;
334
373
  this.#axis = axis;
335
374
  this.#position = position;
336
- this.#invertLightness = invertLightness;
375
+ this.#invertZ = invertZ;
337
376
  this.#showRaw = showRaw;
377
+ this.#outlineWidth = outlineWidth;
338
378
  this.#container = container;
339
379
 
340
380
  this.#canvas = document.createElement('canvas');
@@ -361,6 +401,7 @@ export class PaletteViz {
361
401
 
362
402
  this.#buildProgram();
363
403
  this.#setSize(this.#width, this.#height);
404
+ if (this.#outlineWidth > 0) this.#buildOutlineResources();
364
405
  this.#container?.appendChild(this.#canvas);
365
406
  this.#paint();
366
407
  }
@@ -370,7 +411,7 @@ export class PaletteViz {
370
411
  DISTANCE_METRIC: this.#distanceMetricMap[this.#distanceMetric],
371
412
  COLOR_MODEL: this.#colorModelMap[this.#colorModel],
372
413
  PROGRESS_AXIS: this.#axisMap[this.#axis],
373
- INVERT_Z: this.#invertLightness ? 1 : false,
414
+ INVERT_Z: this.#invertZ ? 1 : false,
374
415
  SHOW_RAW: this.#showRaw ? 1 : false,
375
416
  };
376
417
  }
@@ -383,6 +424,39 @@ export class PaletteViz {
383
424
  this.#uPaletteTexture = gl.getUniformLocation(this.#program, 'paletteTexture');
384
425
  }
385
426
 
427
+ #buildOutlineResources(): void {
428
+ const gl = this.#gl;
429
+ this.#outlineProgram = buildProgram(gl, {}, outlineFragmentShaderSrc, vertexShaderSrc);
430
+ this.#uColorMap = gl.getUniformLocation(this.#outlineProgram, 'colorMap');
431
+ this.#uOutlineWidth = gl.getUniformLocation(this.#outlineProgram, 'outlineWidth');
432
+ this.#uOutlineResolution = gl.getUniformLocation(this.#outlineProgram, 'resolution');
433
+
434
+ this.#fboTexture = gl.createTexture()!;
435
+ this.#fbo = gl.createFramebuffer()!;
436
+ this.#resizeFBO(this.#canvas.width, this.#canvas.height);
437
+ }
438
+
439
+ #destroyOutlineResources(): void {
440
+ const gl = this.#gl;
441
+ if (this.#outlineProgram) { gl.deleteProgram(this.#outlineProgram); this.#outlineProgram = null; }
442
+ if (this.#fboTexture) { gl.deleteTexture(this.#fboTexture); this.#fboTexture = null; }
443
+ if (this.#fbo) { gl.deleteFramebuffer(this.#fbo); this.#fbo = null; }
444
+ }
445
+
446
+ #resizeFBO(pw: number, ph: number): void {
447
+ const gl = this.#gl;
448
+ gl.bindTexture(gl.TEXTURE_2D, this.#fboTexture);
449
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, pw, ph, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
450
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
451
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
452
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
453
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
454
+ gl.bindTexture(gl.TEXTURE_2D, null);
455
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.#fbo);
456
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.#fboTexture, 0);
457
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
458
+ }
459
+
386
460
  #setSize(w: number, h: number): void {
387
461
  const pw = Math.round(w * this.#pixelRatio);
388
462
  const ph = Math.round(h * this.#pixelRatio);
@@ -391,22 +465,43 @@ export class PaletteViz {
391
465
  this.#canvas.style.width = `${w}px`;
392
466
  this.#canvas.style.height = `${h}px`;
393
467
  this.#gl.viewport(0, 0, pw, ph);
468
+ if (this.#fboTexture) this.#resizeFBO(pw, ph);
394
469
  }
395
470
 
396
471
  #paint(): void {
397
472
  if (this.#animationFrame !== null) cancelAnimationFrame(this.#animationFrame);
398
473
  this.#animationFrame = requestAnimationFrame(() => {
399
474
  const gl = this.#gl;
400
- gl.useProgram(this.#program);
401
475
 
402
- gl.uniform1f(this.#uProgress, this.#position);
476
+ // ── Pass 1: closest-color render ────────────────────────────────────────
477
+ // Target: FBO when outline is active, canvas otherwise.
478
+ if (this.#fbo) gl.bindFramebuffer(gl.FRAMEBUFFER, this.#fbo);
403
479
 
480
+ gl.useProgram(this.#program);
481
+ gl.uniform1f(this.#uProgress, this.#position);
404
482
  gl.activeTexture(gl.TEXTURE0);
405
483
  gl.bindTexture(gl.TEXTURE_2D, this.#texture);
406
484
  gl.uniform1i(this.#uPaletteTexture, 0);
407
485
 
486
+ gl.clearColor(0, 0, 0, 0);
487
+ gl.clear(gl.COLOR_BUFFER_BIT);
408
488
  gl.bindVertexArray(this.#vao);
409
489
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
490
+
491
+ if (!this.#fbo) { gl.bindVertexArray(null); return; }
492
+
493
+ // ── Pass 2: edge-detection using FBO texture ─────────────────────────────
494
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
495
+ gl.useProgram(this.#outlineProgram);
496
+ gl.activeTexture(gl.TEXTURE0);
497
+ gl.bindTexture(gl.TEXTURE_2D, this.#fboTexture);
498
+ gl.uniform1i(this.#uColorMap, 0);
499
+ gl.uniform1f(this.#uOutlineWidth, this.#outlineWidth);
500
+ gl.uniform2f(this.#uOutlineResolution, this.#canvas.width, this.#canvas.height);
501
+
502
+ gl.clearColor(0, 0, 0, 0);
503
+ gl.clear(gl.COLOR_BUFFER_BIT);
504
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
410
505
  gl.bindVertexArray(null);
411
506
  });
412
507
  }
@@ -436,6 +531,7 @@ export class PaletteViz {
436
531
  this.#animationFrame = null;
437
532
  }
438
533
  const gl = this.#gl;
534
+ this.#destroyOutlineResources();
439
535
  gl.deleteProgram(this.#program);
440
536
  gl.deleteTexture(this.#texture);
441
537
  gl.deleteBuffer(this.#quadBuffer);
@@ -523,13 +619,13 @@ export class PaletteViz {
523
619
  return this.#distanceMetric;
524
620
  }
525
621
 
526
- set invertLightness(value: boolean) {
527
- this.#invertLightness = value;
622
+ set invertZ(value: boolean) {
623
+ this.#invertZ = value;
528
624
  this.#buildProgram();
529
625
  this.#paint();
530
626
  }
531
- get invertLightness() {
532
- return this.#invertLightness;
627
+ get invertZ() {
628
+ return this.#invertZ;
533
629
  }
534
630
 
535
631
  set showRaw(value: boolean) {
@@ -541,6 +637,19 @@ export class PaletteViz {
541
637
  return this.#showRaw;
542
638
  }
543
639
 
640
+ set outlineWidth(value: number) {
641
+ const wasEnabled = this.#outlineWidth > 0;
642
+ this.#outlineWidth = value;
643
+ if ((value > 0) !== wasEnabled) {
644
+ if (value > 0) this.#buildOutlineResources();
645
+ else this.#destroyOutlineResources();
646
+ }
647
+ this.#paint();
648
+ }
649
+ get outlineWidth() {
650
+ return this.#outlineWidth;
651
+ }
652
+
544
653
  static paletteToRGBA = paletteToRGBA;
545
654
  /** @deprecated use PaletteViz.paletteToRGBA */
546
655
  static paletteToTexture = paletteToRGBA;
package/src/types.ts CHANGED
@@ -34,6 +34,7 @@ export type PaletteVizOptions = {
34
34
  distanceMetric?: DistanceMetric;
35
35
  axis?: Axis;
36
36
  position?: number;
37
- invertLightness?: boolean;
37
+ invertZ?: boolean;
38
38
  showRaw?: boolean;
39
+ outlineWidth?: number;
39
40
  };