palette-shader 0.4.0 → 0.6.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
@@ -69,6 +69,7 @@ All options are optional. The palette defaults to a random 20-colour set.
69
69
  | `position` | `number` | `0` | 0–1 position along the chosen axis |
70
70
  | `invertZ` | `boolean` | `false` | Flip the lightness/value axis |
71
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
 
@@ -83,6 +84,7 @@ viz.colorModel = 'okhslPolar';
83
84
  viz.distanceMetric = 'deltaE2000';
84
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, invertZ, showRaw, }?: PaletteVizOptions);
17
+ constructor({ palette, width, height, pixelRatio, container, colorModel, distanceMetric, axis, position, invertZ, showRaw, outlineWidth, }?: PaletteVizOptions);
18
18
  get canvas(): HTMLCanvasElement;
19
19
  get width(): number;
20
20
  get height(): number;
@@ -38,6 +38,8 @@ export declare class PaletteViz {
38
38
  get invertZ(): boolean;
39
39
  set showRaw(value: boolean);
40
40
  get showRaw(): boolean;
41
+ set outlineWidth(value: number);
42
+ get outlineWidth(): number;
41
43
  static paletteToRGBA: (palette: ColorList) => Uint8Array;
42
44
  /** @deprecated use PaletteViz.paletteToRGBA */
43
45
  static paletteToTexture: (palette: ColorList) => Uint8Array;
@@ -55,6 +57,7 @@ declare type PaletteVizOptions = {
55
57
  position?: number;
56
58
  invertZ?: boolean;
57
59
  showRaw?: boolean;
60
+ outlineWidth?: number;
58
61
  };
59
62
 
60
63
  export declare const randomPalette: (size?: number) => ColorList;
@@ -1,8 +1,8 @@
1
- const g = `// https://lygia.xyz/
1
+ const v = `// https://lygia.xyz/
2
2
  float srgb2rgb(const in float v) { return (v < 0.04045) ? v * 0.0773993808 : pow((v + 0.055) * 0.947867298578199, 2.4); }
3
3
  vec3 srgb2rgb(const in vec3 srgb) { return vec3(srgb2rgb(srgb.r), srgb2rgb(srgb.g), srgb2rgb(srgb.b)); }
4
4
  vec4 srgb2rgb(const in vec4 srgb) { return vec4(srgb2rgb(srgb.rgb), srgb.a); }
5
- `, C = `// Copyright(c) 2021 Björn Ottosson
5
+ `, L = `// Copyright(c) 2021 Björn Ottosson
6
6
  //
7
7
  // Permission is hereby granted, free of charge, to any person obtaining a copy of
8
8
  // this softwareand associated documentation files(the "Software"), to deal in
@@ -654,14 +654,14 @@ vec3 srgb_to_okhsv(vec3 rgb)
654
654
  float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
655
655
 
656
656
  return vec3 (h, s, v );
657
- }`, u = `vec3 hsl2rgb( in vec3 c ) {
657
+ }`, T = `vec3 hsl2rgb( in vec3 c ) {
658
658
  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 );
659
659
  return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));
660
- }`, v = `vec3 hsv2rgb(vec3 c) {
660
+ }`, E = `vec3 hsv2rgb(vec3 c) {
661
661
  vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
662
662
  vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
663
663
  return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
664
- }`, L = `// slightly rearranged vector components so it matches with LCH
664
+ }`, x = `// slightly rearranged vector components so it matches with LCH
665
665
  // M_PI and srgb_transfer_function are provided by oklab.frag.glsl (included before this file)
666
666
  vec3 lch2rgb(vec3 lch) {
667
667
  lch.y *= 0.34;
@@ -692,7 +692,7 @@ vec3 lch2rgb(vec3 lch) {
692
692
  srgb_transfer_function(rgb.b)
693
693
  );
694
694
  }
695
- `, x = `// Kotsarenko/Ramos weighted RGB distance.
695
+ `, R = `// Kotsarenko/Ramos weighted RGB distance.
696
696
  // Operates on sRGB values directly (no linearisation needed).
697
697
  // Weights red and blue channels by the mean red value, which improves
698
698
  // perceptual uniformity compared to plain Euclidean RGB at minimal cost.
@@ -838,7 +838,7 @@ float deltaE2000(vec3 lab1, vec3 lab2) {
838
838
 
839
839
  return sqrt(dLn*dLn + dCn*dCn + dHn*dHn + RT * dCn * dHn);
840
840
  }
841
- `, T = `// DISTANCE_METRIC define: 0=rgb, 1=oklab, 2=deltaE76, 3=deltaE2000, 4=kotsarenkoRamos, 5=deltaE94
841
+ `, S = `// DISTANCE_METRIC define: 0=rgb, 1=oklab, 2=deltaE76, 3=deltaE2000, 4=kotsarenkoRamos, 5=deltaE94
842
842
  vec3 closestColor(vec3 color, sampler2D paletteTexture) {
843
843
  int paletteSize = textureSize(paletteTexture, 0).x;
844
844
  float minDist = 1000000.0;
@@ -879,14 +879,14 @@ vec3 closestColor(vec3 color, sampler2D paletteTexture) {
879
879
 
880
880
  return closest;
881
881
  }
882
- `, k = `
882
+ `, u = `
883
883
  precision highp float;
884
884
  layout(location = 0) in vec2 a_position;
885
885
  out vec2 vUv;
886
886
  void main() {
887
887
  vUv = a_position * 0.5 + 0.5;
888
888
  gl_Position = vec4(a_position, 0.0, 1.0);
889
- }`, S = `
889
+ }`, k = `
890
890
  precision highp float;
891
891
  #define TWO_PI 6.28318530718
892
892
  in vec2 vUv;
@@ -894,13 +894,13 @@ out vec4 fragColor;
894
894
  uniform float progress;
895
895
  uniform sampler2D paletteTexture;
896
896
 
897
- ${g}
898
- ${C}
899
- ${u}
900
897
  ${v}
901
898
  ${L}
902
- ${x}
903
899
  ${T}
900
+ ${E}
901
+ ${x}
902
+ ${R}
903
+ ${S}
904
904
 
905
905
  // COLOR_MODEL: 0=rgb, 1=oklab, 2=okhsv, 3=okhsvPolar, 4=okhsl, 5=okhslPolar,
906
906
  // 6=oklch, 7=oklchPolar, 8=hsv, 9=hsvPolar, 10=hsl, 11=hslPolar
@@ -960,14 +960,38 @@ void main(){
960
960
  #else
961
961
  fragColor = vec4(closestColor(rgb, paletteTexture), 1.);
962
962
  #endif
963
+ }`, y = `
964
+ precision highp float;
965
+ in vec2 vUv;
966
+ out vec4 fragColor;
967
+ uniform sampler2D colorMap;
968
+ uniform float outlineWidth;
969
+ uniform vec2 resolution;
970
+
971
+ void main() {
972
+ vec4 center = texture(colorMap, vUv);
973
+ if (center.a == 0.0) { fragColor = vec4(0.0); return; }
974
+ vec2 px = outlineWidth / resolution;
975
+ vec4 n0 = texture(colorMap, vUv + vec2( px.x, 0.0));
976
+ vec4 n1 = texture(colorMap, vUv + vec2(-px.x, 0.0));
977
+ vec4 n2 = texture(colorMap, vUv + vec2(0.0, px.y));
978
+ vec4 n3 = texture(colorMap, vUv + vec2(0.0, -px.y));
979
+ if ((n0.a > 0.0 && any(notEqual(n0.rgb, center.rgb))) ||
980
+ (n1.a > 0.0 && any(notEqual(n1.rgb, center.rgb))) ||
981
+ (n2.a > 0.0 && any(notEqual(n2.rgb, center.rgb))) ||
982
+ (n3.a > 0.0 && any(notEqual(n3.rgb, center.rgb)))) {
983
+ fragColor = vec4(0.0);
984
+ return;
985
+ }
986
+ fragColor = center;
963
987
  }`;
964
988
  let _ = null;
965
- function E(n) {
989
+ function I(e) {
966
990
  if (!_) {
967
- const a = document.createElement("canvas");
968
- a.width = a.height = 1, _ = a.getContext("2d");
991
+ const n = document.createElement("canvas");
992
+ n.width = n.height = 1, _ = n.getContext("2d");
969
993
  }
970
- _.fillStyle = "#000000", _.fillStyle = n;
994
+ _.fillStyle = "#000000", _.fillStyle = e;
971
995
  const t = _.fillStyle;
972
996
  if (t[0] === "#")
973
997
  return [
@@ -975,72 +999,73 @@ function E(n) {
975
999
  parseInt(t.slice(3, 5), 16) / 255,
976
1000
  parseInt(t.slice(5, 7), 16) / 255
977
1001
  ];
978
- const e = t.match(/[\d.]+/g);
979
- return [+e[0] / 255, +e[1] / 255, +e[2] / 255];
1002
+ const a = t.match(/[\d.]+/g);
1003
+ return [+a[0] / 255, +a[1] / 255, +a[2] / 255];
980
1004
  }
981
- const d = (n) => {
982
- const t = new Uint8Array(n.length * 4);
983
- return n.forEach((e, a) => {
1005
+ const h = (e) => {
1006
+ const t = new Uint8Array(e.length * 4);
1007
+ return e.forEach((a, n) => {
984
1008
  try {
985
- const [r, f, i] = E(e);
986
- t[a * 4 + 0] = Math.round(r * 255), t[a * 4 + 1] = Math.round(f * 255), t[a * 4 + 2] = Math.round(i * 255), t[a * 4 + 3] = 255;
1009
+ const [r, f, i] = I(a);
1010
+ t[n * 4 + 0] = Math.round(r * 255), t[n * 4 + 1] = Math.round(f * 255), t[n * 4 + 2] = Math.round(i * 255), t[n * 4 + 3] = 255;
987
1011
  } catch {
988
- console.error(`Invalid color: ${e}`);
1012
+ console.error(`Invalid color: ${a}`);
989
1013
  }
990
1014
  }), t;
991
- }, I = d, R = (n = 20) => Array.from(
992
- { length: n },
1015
+ }, A = h, O = (e = 20) => Array.from(
1016
+ { length: e },
993
1017
  () => `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`
994
1018
  );
995
- function p(n, t, e) {
996
- const a = n.createShader(t);
997
- if (n.shaderSource(a, e), n.compileShader(a), !n.getShaderParameter(a, n.COMPILE_STATUS)) {
998
- const r = n.getShaderInfoLog(a);
999
- throw n.deleteShader(a), new Error(`Shader compile error:
1019
+ function p(e, t, a) {
1020
+ const n = e.createShader(t);
1021
+ if (e.shaderSource(n, a), e.compileShader(n), !e.getShaderParameter(n, e.COMPILE_STATUS)) {
1022
+ const r = e.getShaderInfoLog(n);
1023
+ throw e.deleteShader(n), new Error(`Shader compile error:
1000
1024
  ${r}`);
1001
1025
  }
1002
- return a;
1026
+ return n;
1003
1027
  }
1004
- function y(n, t, e, a) {
1028
+ function m(e, t, a, n) {
1005
1029
  const f = `#version 300 es
1006
- ` + (Object.entries(t).filter(([, s]) => s !== !1).map(([s, h]) => `#define ${s} ${h}`).join(`
1030
+ ` + (Object.entries(t).filter(([, s]) => s !== !1).map(([s, d]) => `#define ${s} ${d}`).join(`
1007
1031
  `) + `
1008
- `), i = p(n, n.VERTEX_SHADER, f + a), b = p(n, n.FRAGMENT_SHADER, f + e), l = n.createProgram();
1009
- if (n.attachShader(l, i), n.attachShader(l, b), n.linkProgram(l), n.deleteShader(i), n.deleteShader(b), !n.getProgramParameter(l, n.LINK_STATUS)) {
1010
- const s = n.getProgramInfoLog(l);
1011
- throw n.deleteProgram(l), new Error(`Program link error:
1032
+ `), i = p(e, e.VERTEX_SHADER, f + n), b = p(e, e.FRAGMENT_SHADER, f + a), l = e.createProgram();
1033
+ if (e.attachShader(l, i), e.attachShader(l, b), e.linkProgram(l), e.deleteShader(i), e.deleteShader(b), !e.getProgramParameter(l, e.LINK_STATUS)) {
1034
+ const s = e.getProgramInfoLog(l);
1035
+ throw e.deleteProgram(l), new Error(`Program link error:
1012
1036
  ${s}`);
1013
1037
  }
1014
1038
  return l;
1015
1039
  }
1016
- function c(n, t, e) {
1017
- n.bindTexture(n.TEXTURE_2D, t), n.texImage2D(
1018
- n.TEXTURE_2D,
1040
+ function c(e, t, a) {
1041
+ e.bindTexture(e.TEXTURE_2D, t), e.texImage2D(
1042
+ e.TEXTURE_2D,
1019
1043
  0,
1020
- n.RGBA8,
1021
- e.length,
1044
+ e.RGBA8,
1045
+ a.length,
1022
1046
  1,
1023
1047
  0,
1024
- n.RGBA,
1025
- n.UNSIGNED_BYTE,
1026
- d(e)
1027
- ), 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);
1048
+ e.RGBA,
1049
+ e.UNSIGNED_BYTE,
1050
+ h(a)
1051
+ ), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE);
1028
1052
  }
1029
- class O {
1053
+ class w {
1030
1054
  #t = [];
1031
- #f = 512;
1032
- #i = 512;
1033
- #g = 1;
1055
+ #b = 512;
1056
+ #h = 512;
1057
+ #L = 1;
1034
1058
  // shader state
1035
- #_ = 0;
1036
- #c = "y";
1037
- #b = "okhsv";
1038
- #d = "oklab";
1039
- #h = !1;
1040
- #p = !1;
1059
+ #d = 0;
1060
+ #u = "y";
1061
+ #p = "okhsv";
1062
+ #m = "oklab";
1063
+ #g = !1;
1064
+ #C = !1;
1065
+ #i = 0;
1041
1066
  // uniform value maps
1042
- #u = { x: 0, y: 1, z: 2 };
1043
- #v = {
1067
+ #E = { x: 0, y: 1, z: 2 };
1068
+ #x = {
1044
1069
  rgb: 0,
1045
1070
  oklab: 1,
1046
1071
  okhsv: 2,
@@ -1054,7 +1079,7 @@ class O {
1054
1079
  hsl: 10,
1055
1080
  hslPolar: 11
1056
1081
  };
1057
- #L = {
1082
+ #R = {
1058
1083
  rgb: 0,
1059
1084
  oklab: 1,
1060
1085
  deltaE76: 2,
@@ -1063,149 +1088,180 @@ class O {
1063
1088
  deltaE94: 5
1064
1089
  };
1065
1090
  // WebGL
1066
- #e;
1091
+ #n;
1067
1092
  #a;
1068
- #r = null;
1093
+ #f = null;
1069
1094
  #o = null;
1070
- #C = null;
1071
- #m = null;
1072
- #l = null;
1073
- // cached uniform locations (re-queried after each program rebuild)
1074
- #x = null;
1075
1095
  #T = null;
1096
+ #v = null;
1097
+ #_ = null;
1098
+ // cached uniform locations (re-queried after each program rebuild)
1099
+ #S = null;
1100
+ #k = null;
1101
+ // outline pass (created/destroyed when outlineWidth toggles between 0 and >0)
1102
+ #r = null;
1103
+ #l = null;
1104
+ #s = null;
1105
+ #y = null;
1106
+ #I = null;
1107
+ #O = null;
1076
1108
  // dom
1077
- #k;
1109
+ #A;
1078
1110
  constructor({
1079
- palette: t = R(),
1080
- width: e = 512,
1081
- height: a = 512,
1111
+ palette: t = O(),
1112
+ width: a = 512,
1113
+ height: n = 512,
1082
1114
  pixelRatio: r = window.devicePixelRatio,
1083
1115
  container: f,
1084
1116
  colorModel: i = "okhsv",
1085
1117
  distanceMetric: b = "oklab",
1086
1118
  axis: l = "y",
1087
1119
  position: s = 0,
1088
- invertZ: h = !1,
1089
- showRaw: m = !1
1120
+ invertZ: d = !1,
1121
+ showRaw: g = !1,
1122
+ outlineWidth: C = 0
1090
1123
  } = {}) {
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");
1092
- const o = this.#e.getContext("webgl2");
1124
+ this.#t = t, this.#b = a, this.#h = n, this.#L = r, this.#p = i, this.#m = b, this.#u = l, this.#d = s, this.#g = d, this.#C = g, this.#i = C, this.#A = f, this.#n = document.createElement("canvas"), this.#n.classList.add("palette-viz");
1125
+ const o = this.#n.getContext("webgl2");
1093
1126
  if (!o) throw new Error("WebGL2 not supported");
1094
- this.#a = o, this.#C = o.createBuffer(), o.bindBuffer(o.ARRAY_BUFFER, this.#C), 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(), c(o, this.#o, this.#t), this.#s(), this.#S(this.#f, this.#i), this.#k?.appendChild(this.#e), this.#n();
1127
+ this.#a = o, this.#T = o.createBuffer(), o.bindBuffer(o.ARRAY_BUFFER, this.#T), o.bufferData(o.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), o.STATIC_DRAW), this.#v = o.createVertexArray(), o.bindVertexArray(this.#v), o.enableVertexAttribArray(0), o.vertexAttribPointer(0, 2, o.FLOAT, !1, 0, 0), o.bindVertexArray(null), this.#o = o.createTexture(), c(o, this.#o, this.#t), this.#c(), this.#D(this.#b, this.#h), this.#i > 0 && this.#w(), this.#A?.appendChild(this.#n), this.#e();
1095
1128
  }
1096
- #E() {
1129
+ #z() {
1097
1130
  return {
1098
- DISTANCE_METRIC: this.#L[this.#d],
1099
- COLOR_MODEL: this.#v[this.#b],
1100
- PROGRESS_AXIS: this.#u[this.#c],
1101
- INVERT_Z: this.#h ? 1 : !1,
1102
- SHOW_RAW: this.#p ? 1 : !1
1131
+ DISTANCE_METRIC: this.#R[this.#m],
1132
+ COLOR_MODEL: this.#x[this.#p],
1133
+ PROGRESS_AXIS: this.#E[this.#u],
1134
+ INVERT_Z: this.#g ? 1 : !1,
1135
+ SHOW_RAW: this.#C ? 1 : !1
1103
1136
  };
1104
1137
  }
1105
- #s() {
1138
+ #c() {
1106
1139
  const t = this.#a;
1107
- this.#r && t.deleteProgram(this.#r), this.#r = y(t, this.#E(), S, k), this.#x = t.getUniformLocation(this.#r, "progress"), this.#T = t.getUniformLocation(this.#r, "paletteTexture");
1140
+ this.#f && t.deleteProgram(this.#f), this.#f = m(t, this.#z(), k, u), this.#S = t.getUniformLocation(this.#f, "progress"), this.#k = t.getUniformLocation(this.#f, "paletteTexture");
1108
1141
  }
1109
- #S(t, e) {
1110
- const a = Math.round(t * this.#g), r = Math.round(e * this.#g);
1111
- this.#e.width = a, this.#e.height = r, this.#e.style.width = `${t}px`, this.#e.style.height = `${e}px`, this.#a.viewport(0, 0, a, r);
1142
+ #w() {
1143
+ const t = this.#a;
1144
+ this.#s = m(t, {}, y, u), this.#y = t.getUniformLocation(this.#s, "colorMap"), this.#I = t.getUniformLocation(this.#s, "outlineWidth"), this.#O = t.getUniformLocation(this.#s, "resolution"), this.#l = t.createTexture(), this.#r = t.createFramebuffer(), this.#P(this.#n.width, this.#n.height);
1112
1145
  }
1113
- #n() {
1114
- this.#l !== null && cancelAnimationFrame(this.#l), this.#l = requestAnimationFrame(() => {
1146
+ #M() {
1147
+ const t = this.#a;
1148
+ this.#s && (t.deleteProgram(this.#s), this.#s = null), this.#l && (t.deleteTexture(this.#l), this.#l = null), this.#r && (t.deleteFramebuffer(this.#r), this.#r = null);
1149
+ }
1150
+ #P(t, a) {
1151
+ const n = this.#a;
1152
+ n.bindTexture(n.TEXTURE_2D, this.#l), n.texImage2D(n.TEXTURE_2D, 0, n.RGBA8, t, a, 0, n.RGBA, n.UNSIGNED_BYTE, null), 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), n.bindTexture(n.TEXTURE_2D, null), n.bindFramebuffer(n.FRAMEBUFFER, this.#r), n.framebufferTexture2D(n.FRAMEBUFFER, n.COLOR_ATTACHMENT0, n.TEXTURE_2D, this.#l, 0), n.bindFramebuffer(n.FRAMEBUFFER, null);
1153
+ }
1154
+ #D(t, a) {
1155
+ const n = Math.round(t * this.#L), r = Math.round(a * this.#L);
1156
+ this.#n.width = n, this.#n.height = r, this.#n.style.width = `${t}px`, this.#n.style.height = `${a}px`, this.#a.viewport(0, 0, n, r), this.#l && this.#P(n, r);
1157
+ }
1158
+ #e() {
1159
+ this.#_ !== null && cancelAnimationFrame(this.#_), this.#_ = requestAnimationFrame(() => {
1115
1160
  const t = this.#a;
1116
- 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);
1161
+ if (this.#r && t.bindFramebuffer(t.FRAMEBUFFER, this.#r), t.useProgram(this.#f), t.uniform1f(this.#S, this.#d), t.activeTexture(t.TEXTURE0), t.bindTexture(t.TEXTURE_2D, this.#o), t.uniform1i(this.#k, 0), t.clearColor(0, 0, 0, 0), t.clear(t.COLOR_BUFFER_BIT), t.bindVertexArray(this.#v), t.drawArrays(t.TRIANGLE_STRIP, 0, 4), !this.#r) {
1162
+ t.bindVertexArray(null);
1163
+ return;
1164
+ }
1165
+ t.bindFramebuffer(t.FRAMEBUFFER, null), t.useProgram(this.#s), t.activeTexture(t.TEXTURE0), t.bindTexture(t.TEXTURE_2D, this.#l), t.uniform1i(this.#y, 0), t.uniform1f(this.#I, this.#i), t.uniform2f(this.#O, this.#n.width, this.#n.height), t.clearColor(0, 0, 0, 0), t.clear(t.COLOR_BUFFER_BIT), t.drawArrays(t.TRIANGLE_STRIP, 0, 4), t.bindVertexArray(null);
1117
1166
  });
1118
1167
  }
1119
1168
  // ── Public API ──────────────────────────────────────────────────────────────
1120
1169
  get canvas() {
1121
- return this.#e;
1170
+ return this.#n;
1122
1171
  }
1123
1172
  get width() {
1124
- return this.#f;
1173
+ return this.#b;
1125
1174
  }
1126
1175
  get height() {
1127
- return this.#i;
1176
+ return this.#h;
1128
1177
  }
1129
- resize(t, e = null) {
1130
- this.#f = t, this.#i = e ?? t, this.#S(this.#f, this.#i), this.#n();
1178
+ resize(t, a = null) {
1179
+ this.#b = t, this.#h = a ?? t, this.#D(this.#b, this.#h), this.#e();
1131
1180
  }
1132
1181
  destroy() {
1133
- this.#l !== null && (cancelAnimationFrame(this.#l), this.#l = null);
1182
+ this.#_ !== null && (cancelAnimationFrame(this.#_), this.#_ = null);
1134
1183
  const t = this.#a;
1135
- t.deleteProgram(this.#r), t.deleteTexture(this.#o), t.deleteBuffer(this.#C), t.deleteVertexArray(this.#m), this.#e.remove(), t.getExtension("WEBGL_lose_context")?.loseContext();
1184
+ this.#M(), t.deleteProgram(this.#f), t.deleteTexture(this.#o), t.deleteBuffer(this.#T), t.deleteVertexArray(this.#v), this.#n.remove(), t.getExtension("WEBGL_lose_context")?.loseContext();
1136
1185
  }
1137
1186
  // ── Palette ─────────────────────────────────────────────────────────────────
1138
1187
  set palette(t) {
1139
- this.#t = t, c(this.#a, this.#o, t), this.#n();
1188
+ this.#t = t, c(this.#a, this.#o, t), this.#e();
1140
1189
  }
1141
1190
  get palette() {
1142
1191
  return this.#t;
1143
1192
  }
1144
- setColor(t, e) {
1145
- if (e < 0 || e >= this.#t.length) throw new Error(`Index ${e} out of range`);
1146
- this.#t[e] = t, c(this.#a, this.#o, this.#t), this.#n();
1193
+ setColor(t, a) {
1194
+ if (a < 0 || a >= this.#t.length) throw new Error(`Index ${a} out of range`);
1195
+ this.#t[a] = t, c(this.#a, this.#o, this.#t), this.#e();
1147
1196
  }
1148
- addColor(t, e) {
1149
- this.#t.splice(e ?? this.#t.length, 0, t), c(this.#a, this.#o, this.#t), this.#n();
1197
+ addColor(t, a) {
1198
+ this.#t.splice(a ?? this.#t.length, 0, t), c(this.#a, this.#o, this.#t), this.#e();
1150
1199
  }
1151
1200
  removeColor(t) {
1152
- const e = typeof t == "number" ? t : this.#t.indexOf(t);
1153
- if (e === -1) throw new Error("Color not found in palette");
1154
- if (e < 0 || e >= this.#t.length) throw new Error(`Index ${e} out of range`);
1155
- this.#t.splice(e, 1), c(this.#a, this.#o, this.#t), this.#n();
1201
+ const a = typeof t == "number" ? t : this.#t.indexOf(t);
1202
+ if (a === -1) throw new Error("Color not found in palette");
1203
+ if (a < 0 || a >= this.#t.length) throw new Error(`Index ${a} out of range`);
1204
+ this.#t.splice(a, 1), c(this.#a, this.#o, this.#t), this.#e();
1156
1205
  }
1157
1206
  // ── Shader properties ────────────────────────────────────────────────────────
1158
1207
  set position(t) {
1159
- this.#_ = t, this.#n();
1208
+ this.#d = t, this.#e();
1160
1209
  }
1161
1210
  get position() {
1162
- return this.#_;
1211
+ return this.#d;
1163
1212
  }
1164
1213
  set axis(t) {
1165
- if (!(t in this.#u)) throw new Error("axis must be 'x', 'y', or 'z'");
1166
- this.#c = t, this.#s(), this.#n();
1214
+ if (!(t in this.#E)) throw new Error("axis must be 'x', 'y', or 'z'");
1215
+ this.#u = t, this.#c(), this.#e();
1167
1216
  }
1168
1217
  get axis() {
1169
- return this.#c;
1218
+ return this.#u;
1170
1219
  }
1171
1220
  set colorModel(t) {
1172
- if (!(t in this.#v)) throw new Error(`colorModel '${t}' is not supported`);
1173
- this.#b = t, this.#s(), this.#n();
1221
+ if (!(t in this.#x)) throw new Error(`colorModel '${t}' is not supported`);
1222
+ this.#p = t, this.#c(), this.#e();
1174
1223
  }
1175
1224
  get colorModel() {
1176
- return this.#b;
1225
+ return this.#p;
1177
1226
  }
1178
1227
  set distanceMetric(t) {
1179
- if (!(t in this.#L))
1228
+ if (!(t in this.#R))
1180
1229
  throw new Error(
1181
1230
  "distanceMetric must be 'rgb', 'oklab', 'deltaE76', 'deltaE94', 'deltaE2000', or 'kotsarenkoRamos'"
1182
1231
  );
1183
- this.#d = t, this.#s(), this.#n();
1232
+ this.#m = t, this.#c(), this.#e();
1184
1233
  }
1185
1234
  get distanceMetric() {
1186
- return this.#d;
1235
+ return this.#m;
1187
1236
  }
1188
1237
  set invertZ(t) {
1189
- this.#h = t, this.#s(), this.#n();
1238
+ this.#g = t, this.#c(), this.#e();
1190
1239
  }
1191
1240
  get invertZ() {
1192
- return this.#h;
1241
+ return this.#g;
1193
1242
  }
1194
1243
  set showRaw(t) {
1195
- this.#p = t, this.#s(), this.#n();
1244
+ this.#C = t, this.#c(), this.#e();
1196
1245
  }
1197
1246
  get showRaw() {
1198
- return this.#p;
1247
+ return this.#C;
1248
+ }
1249
+ set outlineWidth(t) {
1250
+ const a = this.#i > 0;
1251
+ this.#i = t, t > 0 !== a && (t > 0 ? this.#w() : this.#M()), this.#e();
1252
+ }
1253
+ get outlineWidth() {
1254
+ return this.#i;
1199
1255
  }
1200
- static paletteToRGBA = d;
1256
+ static paletteToRGBA = h;
1201
1257
  /** @deprecated use PaletteViz.paletteToRGBA */
1202
- static paletteToTexture = d;
1258
+ static paletteToTexture = h;
1203
1259
  }
1204
1260
  export {
1205
- O as PaletteViz,
1206
- S as fragmentShader,
1207
- d as paletteToRGBA,
1208
- I as paletteToTexture,
1209
- R as randomPalette
1261
+ w as PaletteViz,
1262
+ k as fragmentShader,
1263
+ h as paletteToRGBA,
1264
+ A as paletteToTexture,
1265
+ O as randomPalette
1210
1266
  };
1211
1267
  //# sourceMappingURL=palette-shader.js.map