napari-js 0.1.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/cache/lru.d.ts +19 -0
  4. package/dist/camera/camera.d.ts +25 -0
  5. package/dist/camera/camera3d.d.ts +28 -0
  6. package/dist/camera/controls.d.ts +7 -0
  7. package/dist/camera/controls3d.d.ts +6 -0
  8. package/dist/color/checkerboard.d.ts +5 -0
  9. package/dist/color/colormap.d.ts +26 -0
  10. package/dist/color/display-pipeline.d.ts +22 -0
  11. package/dist/color/histogram.d.ts +14 -0
  12. package/dist/color/label-colormap.d.ts +7 -0
  13. package/dist/color/lut.d.ts +9 -0
  14. package/dist/engine/canvas.d.ts +20 -0
  15. package/dist/engine/device.d.ts +23 -0
  16. package/dist/engine/readback.d.ts +13 -0
  17. package/dist/engine/renderer.d.ts +42 -0
  18. package/dist/engine/viewport.d.ts +12 -0
  19. package/dist/index.d.ts +34 -0
  20. package/dist/io/pyramid.d.ts +41 -0
  21. package/dist/io/texture-source.d.ts +68 -0
  22. package/dist/layers/image-layer.d.ts +47 -0
  23. package/dist/layers/labels-layer.d.ts +32 -0
  24. package/dist/layers/layer.d.ts +32 -0
  25. package/dist/layers/points-layer.d.ts +59 -0
  26. package/dist/layers/volume-layer.d.ts +46 -0
  27. package/dist/math/mat4.d.ts +22 -0
  28. package/dist/napari-js.js +1986 -0
  29. package/dist/napari-js.js.map +1 -0
  30. package/dist/picking/pick.d.ts +6 -0
  31. package/dist/scene/dims.d.ts +20 -0
  32. package/dist/scene/events.d.ts +9 -0
  33. package/dist/scene/layer-list.d.ts +16 -0
  34. package/dist/scene/viewer-model.d.ts +20 -0
  35. package/dist/version.d.ts +1 -0
  36. package/dist/viewer.d.ts +76 -0
  37. package/dist/visuals/blend.d.ts +6 -0
  38. package/dist/visuals/format-plan.d.ts +19 -0
  39. package/dist/visuals/image-colormap-shader.d.ts +1 -0
  40. package/dist/visuals/image-visual.d.ts +45 -0
  41. package/dist/visuals/labels-shader.d.ts +1 -0
  42. package/dist/visuals/labels-visual.d.ts +23 -0
  43. package/dist/visuals/layer-visual.d.ts +23 -0
  44. package/dist/visuals/points-shader.d.ts +1 -0
  45. package/dist/visuals/points-visual.d.ts +22 -0
  46. package/dist/visuals/tiled-image-visual.d.ts +46 -0
  47. package/dist/visuals/volume-shader.d.ts +1 -0
  48. package/dist/visuals/volume-visual.d.ts +32 -0
  49. package/package.json +60 -0
@@ -0,0 +1,1986 @@
1
+ class $ extends Error {
2
+ constructor(e) {
3
+ super(e), this.name = "WebGPUUnsupportedError";
4
+ }
5
+ }
6
+ async function pe(r = {}) {
7
+ if (typeof navigator > "u" || !("gpu" in navigator) || !navigator.gpu)
8
+ throw new $(
9
+ "WebGPU is not available in this environment (navigator.gpu is missing)."
10
+ );
11
+ const e = await navigator.gpu.requestAdapter({
12
+ powerPreference: r.powerPreference ?? "high-performance"
13
+ });
14
+ if (!e)
15
+ throw new $("No suitable GPUAdapter was found.");
16
+ const t = e.features.has("float32-filterable"), i = t ? ["float32-filterable"] : [];
17
+ try {
18
+ const s = await e.requestDevice({ requiredFeatures: i });
19
+ return { adapter: e, device: s, features: { float32Filterable: t } };
20
+ } catch (s) {
21
+ throw new $(
22
+ `Failed to create a GPUDevice: ${s instanceof Error ? s.message : String(s)}`
23
+ );
24
+ }
25
+ }
26
+ function ge(r, e, t, i) {
27
+ const s = t > 0 ? t : 1;
28
+ return {
29
+ width: Z(Math.round(r * s), i),
30
+ height: Z(Math.round(e * s), i)
31
+ };
32
+ }
33
+ function Z(r, e) {
34
+ return !Number.isFinite(r) || r < 1 ? 1 : Math.min(r, Math.max(1, e));
35
+ }
36
+ class be {
37
+ constructor(e, t, i) {
38
+ this.canvas = e, this.device = t;
39
+ const s = e.getContext("webgpu");
40
+ if (!s)
41
+ throw new Error('Failed to acquire a "webgpu" canvas context.');
42
+ this.context = s, this.format = i ?? navigator.gpu.getPreferredCanvasFormat(), this.configure();
43
+ }
44
+ context;
45
+ format;
46
+ /** (Re)configure the swapchain for the current device/format. */
47
+ configure() {
48
+ this.context.configure({
49
+ device: this.device,
50
+ format: this.format,
51
+ alphaMode: "premultiplied"
52
+ });
53
+ }
54
+ /**
55
+ * Resize the backing buffer to match the canvas's CSS size × DPR, clamped to the device's
56
+ * max texture dimension. Returns `true` when the size actually changed.
57
+ */
58
+ syncSize(e = typeof devicePixelRatio < "u" ? devicePixelRatio : 1) {
59
+ const t = this.device.limits.maxTextureDimension2D, { width: i, height: s } = ge(
60
+ this.canvas.clientWidth,
61
+ this.canvas.clientHeight,
62
+ e,
63
+ t
64
+ );
65
+ return this.canvas.width === i && this.canvas.height === s ? !1 : (this.canvas.width = i, this.canvas.height = s, !0);
66
+ }
67
+ /** The current swapchain texture view for this frame. */
68
+ get view() {
69
+ return this.context.getCurrentTexture().createView();
70
+ }
71
+ }
72
+ class S {
73
+ listeners = /* @__PURE__ */ new Set();
74
+ /** Subscribe; returns an unsubscribe function. */
75
+ connect(e) {
76
+ return this.listeners.add(e), () => {
77
+ this.listeners.delete(e);
78
+ };
79
+ }
80
+ emit(e) {
81
+ for (const t of this.listeners)
82
+ t(e);
83
+ }
84
+ clear() {
85
+ this.listeners.clear();
86
+ }
87
+ }
88
+ let ve = 0;
89
+ class D {
90
+ id = `layer-${ve++}`;
91
+ changed = new S();
92
+ name;
93
+ /** Data→world scale (e.g. physical pixel size). */
94
+ scale;
95
+ /** Data→world translation. */
96
+ translate;
97
+ _opacity = 1;
98
+ _visible = !0;
99
+ _blending = "translucent";
100
+ constructor(e = {}) {
101
+ this.name = e.name ?? this.id, this.scale = e.scale ?? [1, 1], this.translate = e.translate ?? [0, 0];
102
+ }
103
+ get opacity() {
104
+ return this._opacity;
105
+ }
106
+ set opacity(e) {
107
+ this._opacity = ye(e), this.changed.emit(this);
108
+ }
109
+ get visible() {
110
+ return this._visible;
111
+ }
112
+ set visible(e) {
113
+ this._visible = e, this.changed.emit(this);
114
+ }
115
+ get blending() {
116
+ return this._blending;
117
+ }
118
+ set blending(e) {
119
+ this._blending = e, this.changed.emit(this);
120
+ }
121
+ }
122
+ function ye(r) {
123
+ return r < 0 ? 0 : r > 1 ? 1 : r;
124
+ }
125
+ class N {
126
+ constructor(e, t) {
127
+ if (this.name = e, t.length < 2)
128
+ throw new Error(`Colormap "${e}" needs at least two stops.`);
129
+ this.stops = [...t].sort((i, s) => i.t - s.t);
130
+ }
131
+ stops;
132
+ /** Sample the colormap at `t` (clamped to 0..1), returning linear RGB. */
133
+ sample(e) {
134
+ const t = e <= 0 ? 0 : e >= 1 ? 1 : e, { stops: i } = this;
135
+ if (t <= i[0].t) return [...i[0].color];
136
+ const s = i[i.length - 1];
137
+ if (t >= s.t) return [...s.color];
138
+ for (let n = 1; n < i.length; n++) {
139
+ const a = i[n];
140
+ if (t <= a.t) {
141
+ const o = i[n - 1], c = a.t - o.t || 1, l = (t - o.t) / c;
142
+ return [
143
+ o.color[0] + (a.color[0] - o.color[0]) * l,
144
+ o.color[1] + (a.color[1] - o.color[1]) * l,
145
+ o.color[2] + (a.color[2] - o.color[2]) * l
146
+ ];
147
+ }
148
+ }
149
+ return [...s.color];
150
+ }
151
+ }
152
+ function Y(r, e) {
153
+ return new N(r, [
154
+ { t: 0, color: [0, 0, 0] },
155
+ { t: 1, color: e }
156
+ ]);
157
+ }
158
+ const O = Y("gray", [1, 1, 1]), we = Y("red", [1, 0, 0]), xe = Y("green", [0, 1, 0]), _e = Y("blue", [0, 0, 1]), Te = new N("viridis", [
159
+ { t: 0, color: [0.267, 5e-3, 0.329] },
160
+ { t: 0.25, color: [0.275, 0.227, 0.494] },
161
+ { t: 0.5, color: [0.149, 0.443, 0.541] },
162
+ { t: 0.75, color: [0.122, 0.633, 0.531] },
163
+ { t: 0.9, color: [0.478, 0.821, 0.318] },
164
+ { t: 1, color: [0.993, 0.906, 0.144] }
165
+ ]), Se = new N("magma", [
166
+ { t: 0, color: [1e-3, 0, 0.014] },
167
+ { t: 0.25, color: [0.232, 0.059, 0.437] },
168
+ { t: 0.5, color: [0.55, 0.161, 0.506] },
169
+ { t: 0.75, color: [0.868, 0.288, 0.41] },
170
+ { t: 0.9, color: [0.987, 0.6, 0.392] },
171
+ { t: 1, color: [0.987, 0.991, 0.749] }
172
+ ]), K = {
173
+ gray: O,
174
+ grey: O,
175
+ red: we,
176
+ green: xe,
177
+ blue: _e,
178
+ viridis: Te,
179
+ magma: Se
180
+ };
181
+ function A(r) {
182
+ if (r instanceof N) return r;
183
+ const e = K[r.toLowerCase()];
184
+ if (!e)
185
+ throw new Error(
186
+ `Unknown colormap "${r}". Known: ${Object.keys(K).join(", ")}.`
187
+ );
188
+ return e;
189
+ }
190
+ function Pe(r) {
191
+ return r.kind === "typed" || r.kind === "tiled" ? r.channels : 4;
192
+ }
193
+ function Le(r) {
194
+ return r.kind === "tiled" ? r.depth : 1;
195
+ }
196
+ function Ue(r) {
197
+ return Pe(r) === 1;
198
+ }
199
+ function Be(r) {
200
+ if (r.kind === "typed" || r.kind === "tiled") {
201
+ if (r.dtype === "float32") return [0, 1];
202
+ if (r.dtype === "uint16") return [0, 65535];
203
+ }
204
+ return [0, 255];
205
+ }
206
+ function Me(r) {
207
+ if (typeof r == "object" && "kind" in r && (r.kind === "typed" || r.kind === "tiled"))
208
+ return r;
209
+ const e = r, t = "width" in e ? Number(e.width) : 0, i = "height" in e ? Number(e.height) : 0;
210
+ return { kind: "external", width: t, height: i, image: e };
211
+ }
212
+ class ie extends D {
213
+ kind = "image";
214
+ source;
215
+ grayscale;
216
+ /** Bumped whenever the colormap changes so the visual knows to rebuild its LUT. */
217
+ colormapVersion = 0;
218
+ _colormap;
219
+ _contrastLimits;
220
+ _gamma;
221
+ _invert;
222
+ _interpolation;
223
+ constructor(e, t = {}) {
224
+ super({ name: t.name, scale: t.scale, translate: t.translate }), this.source = e, this.grayscale = Ue(e), this._colormap = this.grayscale ? A(t.colormap ?? "gray") : null, this._contrastLimits = t.contrastLimits ?? Be(e), this._gamma = t.gamma ?? 1, this._invert = t.invert ?? !1, this._interpolation = t.interpolation ?? "linear", t.opacity !== void 0 && (this._opacity = t.opacity), t.blending !== void 0 && (this._blending = t.blending), t.visible !== void 0 && (this._visible = t.visible);
225
+ }
226
+ get colormap() {
227
+ return this._colormap;
228
+ }
229
+ set colormap(e) {
230
+ this._colormap = e === null ? null : this.grayscale ? A(e) : null, this.colormapVersion++, this.changed.emit(this);
231
+ }
232
+ get contrastLimits() {
233
+ return [this._contrastLimits[0], this._contrastLimits[1]];
234
+ }
235
+ set contrastLimits(e) {
236
+ this._contrastLimits = [e[0], e[1]], this.changed.emit(this);
237
+ }
238
+ get gamma() {
239
+ return this._gamma;
240
+ }
241
+ set gamma(e) {
242
+ this._gamma = e > 0 ? e : this._gamma, this.changed.emit(this);
243
+ }
244
+ get invert() {
245
+ return this._invert;
246
+ }
247
+ set invert(e) {
248
+ this._invert = e, this.changed.emit(this);
249
+ }
250
+ get interpolation() {
251
+ return this._interpolation;
252
+ }
253
+ set interpolation(e) {
254
+ this._interpolation = e, this.changed.emit(this);
255
+ }
256
+ }
257
+ const X = 12;
258
+ function Ge(r) {
259
+ if (r instanceof Float32Array) return r;
260
+ const e = new Float32Array(r.length * 2);
261
+ return r.forEach((t, i) => {
262
+ e[i * 2] = t[0], e[i * 2 + 1] = t[1];
263
+ }), e;
264
+ }
265
+ class re extends D {
266
+ kind = "points";
267
+ count;
268
+ positions;
269
+ dataVersion = 0;
270
+ _size;
271
+ _faceColor;
272
+ _borderColor;
273
+ _borderWidth;
274
+ _symbol;
275
+ constructor(e, t = {}) {
276
+ super({ name: t.name, scale: t.scale, translate: t.translate }), this.positions = Ge(e), this.count = this.positions.length / 2, this._size = t.size ?? 10, this._faceColor = t.faceColor ?? [1, 1, 1, 1], this._borderColor = t.borderColor ?? [0, 0, 0, 1], this._borderWidth = t.borderWidth ?? 0, this._symbol = t.symbol ?? "disc", t.opacity !== void 0 && (this._opacity = t.opacity), t.blending !== void 0 && (this._blending = t.blending), t.visible !== void 0 && (this._visible = t.visible);
277
+ }
278
+ get size() {
279
+ return this._size;
280
+ }
281
+ set size(e) {
282
+ this._size = e, this.dataVersion++, this.changed.emit(this);
283
+ }
284
+ get faceColor() {
285
+ return this._faceColor;
286
+ }
287
+ set faceColor(e) {
288
+ this._faceColor = e, this.dataVersion++, this.changed.emit(this);
289
+ }
290
+ get borderColor() {
291
+ return this._borderColor;
292
+ }
293
+ set borderColor(e) {
294
+ this._borderColor = e, this.dataVersion++, this.changed.emit(this);
295
+ }
296
+ get borderWidth() {
297
+ return this._borderWidth;
298
+ }
299
+ set borderWidth(e) {
300
+ this._borderWidth = e, this.dataVersion++, this.changed.emit(this);
301
+ }
302
+ get symbol() {
303
+ return this._symbol;
304
+ }
305
+ set symbol(e) {
306
+ this._symbol = e, this.changed.emit(this);
307
+ }
308
+ symbolCode() {
309
+ return this._symbol === "disc" ? 0 : this._symbol === "ring" ? 1 : 2;
310
+ }
311
+ /** Per-point size at index `i`. */
312
+ sizeAt(e) {
313
+ const t = this._size;
314
+ return typeof t == "number" ? t : t[e];
315
+ }
316
+ /** Build the interleaved instance buffer (count × 12 floats) for the GPU. */
317
+ buildInstanceData() {
318
+ const e = new Float32Array(this.count * X);
319
+ for (let t = 0; t < this.count; t++) {
320
+ const i = t * X;
321
+ e[i] = this.positions[t * 2], e[i + 1] = this.positions[t * 2 + 1], e[i + 2] = this.sizeAt(t), J(e, i + 3, this._faceColor, t), J(e, i + 7, this._borderColor, t), e[i + 11] = this._borderWidth;
322
+ }
323
+ return e;
324
+ }
325
+ }
326
+ function J(r, e, t, i) {
327
+ const s = Array.isArray(t[0]) ? t[i] : t;
328
+ r[e] = s[0], r[e + 1] = s[1], r[e + 2] = s[2], r[e + 3] = s[3];
329
+ }
330
+ const Ee = X;
331
+ class se extends D {
332
+ kind = "labels";
333
+ width;
334
+ height;
335
+ data;
336
+ _selectedLabel;
337
+ _showSelectedOnly;
338
+ constructor(e, t, i, s = {}) {
339
+ if (super({ name: s.name, scale: s.scale, translate: s.translate }), e.length < t * i)
340
+ throw new Error(`Labels data (${e.length}) smaller than ${t}×${i}.`);
341
+ this.data = e, this.width = t, this.height = i, this._selectedLabel = s.selectedLabel ?? 0, this._showSelectedOnly = s.showSelectedOnly ?? !1, this._blending = s.blending ?? "translucent", s.opacity !== void 0 && (this._opacity = s.opacity), s.visible !== void 0 && (this._visible = s.visible);
342
+ }
343
+ get selectedLabel() {
344
+ return this._selectedLabel;
345
+ }
346
+ set selectedLabel(e) {
347
+ this._selectedLabel = Math.max(0, Math.round(e)), this.changed.emit(this);
348
+ }
349
+ get showSelectedOnly() {
350
+ return this._showSelectedOnly;
351
+ }
352
+ set showSelectedOnly(e) {
353
+ this._showSelectedOnly = e, this.changed.emit(this);
354
+ }
355
+ /** Label id at data pixel `(x, y)`, or 0 (background) if out of bounds. */
356
+ labelAt(e, t) {
357
+ const i = Math.floor(e), s = Math.floor(t);
358
+ return i < 0 || s < 0 || i >= this.width || s >= this.height ? 0 : this.data[s * this.width + i];
359
+ }
360
+ }
361
+ class ne extends D {
362
+ kind = "volume";
363
+ width;
364
+ height;
365
+ depth;
366
+ data;
367
+ colormapVersion = 0;
368
+ _colormap;
369
+ _contrastLimits;
370
+ _gamma;
371
+ _rendering;
372
+ _isoThreshold;
373
+ constructor(e, t, i, s, n = {}) {
374
+ if (super({ name: n.name }), e.length < t * i * s)
375
+ throw new Error(`Volume data (${e.length}) smaller than ${t}×${i}×${s}.`);
376
+ this.data = e, this.width = t, this.height = i, this.depth = s, this._colormap = A(n.colormap ?? "viridis"), this._contrastLimits = n.contrastLimits ?? [0, 255], this._gamma = n.gamma ?? 1, this._rendering = n.rendering ?? "mip", this._isoThreshold = n.isoThreshold ?? 0.5, this._blending = n.blending ?? "translucent", n.opacity !== void 0 && (this._opacity = n.opacity), n.visible !== void 0 && (this._visible = n.visible);
377
+ }
378
+ get colormap() {
379
+ return this._colormap;
380
+ }
381
+ set colormap(e) {
382
+ this._colormap = A(e), this.colormapVersion++, this.changed.emit(this);
383
+ }
384
+ get contrastLimits() {
385
+ return [this._contrastLimits[0], this._contrastLimits[1]];
386
+ }
387
+ set contrastLimits(e) {
388
+ this._contrastLimits = [e[0], e[1]], this.changed.emit(this);
389
+ }
390
+ get gamma() {
391
+ return this._gamma;
392
+ }
393
+ set gamma(e) {
394
+ this._gamma = e > 0 ? e : this._gamma, this.changed.emit(this);
395
+ }
396
+ get rendering() {
397
+ return this._rendering;
398
+ }
399
+ set rendering(e) {
400
+ this._rendering = e, this.changed.emit(this);
401
+ }
402
+ renderingCode() {
403
+ return this._rendering === "mip" ? 0 : this._rendering === "translucent" ? 1 : 2;
404
+ }
405
+ get isoThreshold() {
406
+ return this._isoThreshold;
407
+ }
408
+ set isoThreshold(e) {
409
+ this._isoThreshold = e, this.changed.emit(this);
410
+ }
411
+ }
412
+ function q() {
413
+ const r = new Float32Array(16);
414
+ return r[0] = r[5] = r[10] = r[15] = 1, r;
415
+ }
416
+ function P(r, e) {
417
+ const t = new Float32Array(16);
418
+ for (let i = 0; i < 4; i++)
419
+ for (let s = 0; s < 4; s++) {
420
+ let n = 0;
421
+ for (let a = 0; a < 4; a++)
422
+ n += r[a * 4 + s] * e[i * 4 + a];
423
+ t[i * 4 + s] = n;
424
+ }
425
+ return t;
426
+ }
427
+ function W(r, e, t, i) {
428
+ const s = q();
429
+ return s[0] = r, s[5] = e, s[12] = t, s[13] = i, s;
430
+ }
431
+ function Fe(r, e, t) {
432
+ const i = q();
433
+ return i[0] = r, i[5] = e, i[10] = t, i;
434
+ }
435
+ function Ie(r, e, t) {
436
+ const i = q();
437
+ return i[12] = r, i[13] = e, i[14] = t, i;
438
+ }
439
+ function Re(r, e, t, i) {
440
+ const s = 1 / Math.tan(r / 2), n = new Float32Array(16);
441
+ return n[0] = s / e, n[5] = s, n[10] = i / (t - i), n[11] = -1, n[14] = i * t / (t - i), n;
442
+ }
443
+ function Ce(r, e, t) {
444
+ const i = e[0] - r[0], s = e[1] - r[1], n = e[2] - r[2];
445
+ let a = 1 / Math.hypot(i, s, n);
446
+ const o = [i * a, s * a, n * a];
447
+ let c = o[1] * t[2] - o[2] * t[1], l = o[2] * t[0] - o[0] * t[2], h = o[0] * t[1] - o[1] * t[0];
448
+ a = 1 / Math.hypot(c, l, h), c *= a, l *= a, h *= a;
449
+ const u = l * o[2] - h * o[1], f = h * o[0] - c * o[2], m = c * o[1] - l * o[0], d = new Float32Array(16);
450
+ return d[0] = c, d[1] = u, d[2] = -o[0], d[3] = 0, d[4] = l, d[5] = f, d[6] = -o[1], d[7] = 0, d[8] = h, d[9] = m, d[10] = -o[2], d[11] = 0, d[12] = -(c * r[0] + l * r[1] + h * r[2]), d[13] = -(u * r[0] + f * r[1] + m * r[2]), d[14] = o[0] * r[0] + o[1] * r[1] + o[2] * r[2], d[15] = 1, d;
451
+ }
452
+ function Ve(r) {
453
+ const e = r[0], t = r[1], i = r[2], s = r[3], n = r[4], a = r[5], o = r[6], c = r[7], l = r[8], h = r[9], u = r[10], f = r[11], m = r[12], d = r[13], _ = r[14], x = r[15], v = e * a - t * n, y = e * o - i * n, w = e * c - s * n, T = t * o - i * a, L = t * c - s * a, U = i * c - s * o, B = l * d - h * m, M = l * _ - u * m, G = l * x - f * m, E = h * _ - u * d, F = h * x - f * d, I = u * x - f * _, j = v * I - y * F + w * E + T * G - L * M + U * B;
454
+ if (!j) return q();
455
+ const g = 1 / j, p = new Float32Array(16);
456
+ return p[0] = (a * I - o * F + c * E) * g, p[1] = (i * F - t * I - s * E) * g, p[2] = (d * U - _ * L + x * T) * g, p[3] = (u * L - h * U - f * T) * g, p[4] = (o * G - n * I - c * M) * g, p[5] = (e * I - i * G + s * M) * g, p[6] = (_ * w - m * U - x * y) * g, p[7] = (l * U - u * w + f * y) * g, p[8] = (n * F - a * G + c * B) * g, p[9] = (t * G - e * F - s * B) * g, p[10] = (m * L - d * w + x * v) * g, p[11] = (h * w - l * L - f * v) * g, p[12] = (a * M - n * E - o * B) * g, p[13] = (e * E - t * M + i * B) * g, p[14] = (d * y - m * T - _ * v) * g, p[15] = (l * T - h * y + u * v) * g, p;
457
+ }
458
+ function ze(r, e, t, i) {
459
+ const s = 2 * e / Math.max(t, 1), n = 2 * e / Math.max(i, 1), a = new Float32Array(16);
460
+ return a[0] = s, a[5] = -n, a[10] = 1, a[15] = 1, a[12] = -s * r[0], a[13] = n * r[1], a;
461
+ }
462
+ const b = 256;
463
+ function H(r, e = b) {
464
+ const t = new Uint8Array(e * 4), i = e - 1;
465
+ for (let s = 0; s < e; s++) {
466
+ const [n, a, o] = r.sample(i === 0 ? 0 : s / i), c = s * 4;
467
+ t[c] = k(n), t[c + 1] = k(a), t[c + 2] = k(o), t[c + 3] = 255;
468
+ }
469
+ return t;
470
+ }
471
+ function k(r) {
472
+ const e = r <= 0 ? 0 : r >= 1 ? 1 : r;
473
+ return Math.round(e * 255);
474
+ }
475
+ function ae(r, e, t) {
476
+ return r === 4 ? {
477
+ format: "rgba8unorm",
478
+ bytesPerPixel: 4,
479
+ filterable: !0,
480
+ sampleScale: 1 / 255,
481
+ isRgba: !0
482
+ } : e === "uint8" ? {
483
+ format: "r8unorm",
484
+ bytesPerPixel: 1,
485
+ filterable: !0,
486
+ sampleScale: 1 / 255,
487
+ isRgba: !1
488
+ } : {
489
+ format: "r32float",
490
+ bytesPerPixel: 4,
491
+ filterable: t,
492
+ sampleScale: 1,
493
+ isRgba: !1
494
+ };
495
+ }
496
+ function oe(r, e) {
497
+ return e === "r32float" && !(r instanceof Float32Array) ? Float32Array.from(r) : r;
498
+ }
499
+ const le = (
500
+ /* wgsl */
501
+ `
502
+ struct U {
503
+ mvp : mat4x4<f32>,
504
+ imageSize : vec2<f32>,
505
+ origin : vec2<f32>, // data-space origin of this quad (0 for a full image; tile origin for tiles)
506
+ params : vec4<f32>, // climLo, climHi, gamma, opacity (clim already normalized to sample space)
507
+ flags : vec4<f32>, // isRgba, invert, 0, 0
508
+ };
509
+
510
+ @group(0) @binding(0) var<uniform> u : U;
511
+ @group(0) @binding(1) var srcSamp : sampler;
512
+ @group(0) @binding(2) var srcTex : texture_2d<f32>;
513
+ @group(0) @binding(3) var lutSamp : sampler;
514
+ @group(0) @binding(4) var lutTex : texture_2d<f32>;
515
+
516
+ struct VSOut {
517
+ @builtin(position) position : vec4<f32>,
518
+ @location(0) uv : vec2<f32>,
519
+ };
520
+
521
+ @vertex
522
+ fn vs(@builtin(vertex_index) vi : u32) -> VSOut {
523
+ var corners = array<vec2<f32>, 6>(
524
+ vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0),
525
+ vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 1.0),
526
+ );
527
+ let c = corners[vi];
528
+ var out : VSOut;
529
+ out.position = u.mvp * vec4<f32>(u.origin + c * u.imageSize, 0.0, 1.0);
530
+ out.uv = c;
531
+ return out;
532
+ }
533
+
534
+ @fragment
535
+ fn fs(in : VSOut) -> @location(0) vec4<f32> {
536
+ let raw = textureSample(srcTex, srcSamp, in.uv);
537
+ let climLo = u.params.x;
538
+ let climHi = u.params.y;
539
+ let gamma = u.params.z;
540
+ let opacity = u.params.w;
541
+ let denom = max(climHi - climLo, 1e-8);
542
+
543
+ // Scalar path: window → invert → gamma → LUT.
544
+ var t = clamp((raw.r - climLo) / denom, 0.0, 1.0);
545
+ if (u.flags.y > 0.5) { t = 1.0 - t; }
546
+ t = pow(t, gamma);
547
+ let mapped = textureSample(lutTex, lutSamp, vec2<f32>(t, 0.5)).rgb;
548
+
549
+ // RGB path: per-channel window → gamma.
550
+ var direct = clamp((raw.rgb - vec3<f32>(climLo)) / vec3<f32>(denom), vec3<f32>(0.0), vec3<f32>(1.0));
551
+ direct = pow(direct, vec3<f32>(gamma));
552
+
553
+ let isRgba = u.flags.x > 0.5;
554
+ let rgb = select(mapped, direct, isRgba);
555
+ let a = select(opacity, raw.a * opacity, isRgba);
556
+ return vec4<f32>(rgb * a, a);
557
+ }
558
+ `
559
+ );
560
+ function C(r) {
561
+ switch (r) {
562
+ case "opaque":
563
+ return;
564
+ case "translucent":
565
+ return {
566
+ color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
567
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
568
+ };
569
+ case "additive":
570
+ return {
571
+ color: { srcFactor: "one", dstFactor: "one", operation: "add" },
572
+ alpha: { srcFactor: "one", dstFactor: "one", operation: "add" }
573
+ };
574
+ case "minimum":
575
+ return {
576
+ color: { srcFactor: "one", dstFactor: "one", operation: "min" },
577
+ alpha: { srcFactor: "one", dstFactor: "one", operation: "min" }
578
+ };
579
+ }
580
+ }
581
+ const ce = 28, Oe = ce * 4;
582
+ function Ae(r, e) {
583
+ if (r.kind === "external")
584
+ return {
585
+ format: "rgba8unorm",
586
+ bytesPerPixel: 4,
587
+ filterable: !0,
588
+ sampleScale: 1 / 255,
589
+ isRgba: !0,
590
+ data: null
591
+ };
592
+ if (r.kind === "tiled")
593
+ throw new Error("ImageVisual does not render tiled sources; use TiledImageVisual.");
594
+ const t = ae(r.channels, r.dtype, e);
595
+ return { ...t, data: oe(r.data, t.format) };
596
+ }
597
+ class De {
598
+ constructor(e, t, i, s = { float32Filterable: !1 }) {
599
+ this.device = e, this.format = t, this.layer = i, this.plan = Ae(i.source, s.float32Filterable), this.module = e.createShaderModule({ code: le }), this.uniformBuffer = e.createBuffer({
600
+ size: Oe,
601
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
602
+ }), this.bindGroupLayout = this.buildBindGroupLayout(), this.pipelineLayout = e.createPipelineLayout({ bindGroupLayouts: [this.bindGroupLayout] }), this.uploadTexture(), this.lutTexture = this.createLutTexture(), this.currentInterp = i.interpolation, this.srcSampler = this.createSrcSampler(i.interpolation), this.lutSampler = e.createSampler({
603
+ magFilter: "linear",
604
+ minFilter: "linear",
605
+ addressModeU: "clamp-to-edge",
606
+ addressModeV: "clamp-to-edge"
607
+ }), this.currentBlend = i.blending, this.pipeline = this.buildPipeline(i.blending), this.bindGroup = this.buildBindGroup(), this.lutVersion = i.colormapVersion;
608
+ }
609
+ ndisplay = 2;
610
+ module;
611
+ uniformBuffer;
612
+ scratch = new Float32Array(ce);
613
+ bindGroupLayout;
614
+ pipelineLayout;
615
+ plan;
616
+ texture;
617
+ lutTexture;
618
+ srcSampler;
619
+ lutSampler;
620
+ pipeline;
621
+ bindGroup;
622
+ currentBlend;
623
+ currentInterp;
624
+ lutVersion;
625
+ buildBindGroupLayout() {
626
+ const e = this.plan.filterable;
627
+ return this.device.createBindGroupLayout({
628
+ entries: [
629
+ {
630
+ binding: 0,
631
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
632
+ buffer: { type: "uniform" }
633
+ },
634
+ {
635
+ binding: 1,
636
+ visibility: GPUShaderStage.FRAGMENT,
637
+ sampler: { type: e ? "filtering" : "non-filtering" }
638
+ },
639
+ {
640
+ binding: 2,
641
+ visibility: GPUShaderStage.FRAGMENT,
642
+ texture: { sampleType: e ? "float" : "unfilterable-float" }
643
+ },
644
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
645
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } }
646
+ ]
647
+ });
648
+ }
649
+ uploadTexture() {
650
+ const e = this.layer.source;
651
+ if (e.kind === "external") {
652
+ this.texture = this.device.createTexture({
653
+ size: [e.width, e.height],
654
+ format: this.plan.format,
655
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
656
+ }), this.device.queue.copyExternalImageToTexture(
657
+ { source: e.image },
658
+ { texture: this.texture },
659
+ [e.width, e.height]
660
+ );
661
+ return;
662
+ }
663
+ this.texture = this.device.createTexture({
664
+ size: [e.width, e.height],
665
+ format: this.plan.format,
666
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
667
+ }), this.device.queue.writeTexture(
668
+ { texture: this.texture },
669
+ this.plan.data,
670
+ { bytesPerRow: e.width * this.plan.bytesPerPixel, rowsPerImage: e.height },
671
+ { width: e.width, height: e.height }
672
+ );
673
+ }
674
+ createLutTexture() {
675
+ const e = this.device.createTexture({
676
+ size: [b, 1],
677
+ format: "rgba8unorm",
678
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
679
+ });
680
+ return this.writeLut(e), e;
681
+ }
682
+ writeLut(e) {
683
+ const t = this.layer.colormap ?? O;
684
+ this.device.queue.writeTexture(
685
+ { texture: e },
686
+ H(t, b),
687
+ { bytesPerRow: b * 4, rowsPerImage: 1 },
688
+ { width: b, height: 1 }
689
+ );
690
+ }
691
+ createSrcSampler(e) {
692
+ const t = !this.plan.filterable || e === "nearest" ? "nearest" : "linear";
693
+ return this.device.createSampler({
694
+ magFilter: t,
695
+ minFilter: t,
696
+ addressModeU: "clamp-to-edge",
697
+ addressModeV: "clamp-to-edge"
698
+ });
699
+ }
700
+ buildPipeline(e) {
701
+ return this.device.createRenderPipeline({
702
+ layout: this.pipelineLayout,
703
+ vertex: { module: this.module, entryPoint: "vs" },
704
+ fragment: {
705
+ module: this.module,
706
+ entryPoint: "fs",
707
+ targets: [{ format: this.format, blend: C(e) }]
708
+ },
709
+ primitive: { topology: "triangle-list" }
710
+ });
711
+ }
712
+ buildBindGroup() {
713
+ return this.device.createBindGroup({
714
+ layout: this.bindGroupLayout,
715
+ entries: [
716
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
717
+ { binding: 1, resource: this.srcSampler },
718
+ { binding: 2, resource: this.texture.createView() },
719
+ { binding: 3, resource: this.lutSampler },
720
+ { binding: 4, resource: this.lutTexture.createView() }
721
+ ]
722
+ });
723
+ }
724
+ /** Reconcile GPU state with the layer's current properties (cheap; called before draw). */
725
+ sync() {
726
+ this.layer.blending !== this.currentBlend && (this.currentBlend = this.layer.blending, this.pipeline = this.buildPipeline(this.currentBlend)), this.layer.interpolation !== this.currentInterp && (this.currentInterp = this.layer.interpolation, this.srcSampler = this.createSrcSampler(this.currentInterp), this.bindGroup = this.buildBindGroup()), this.layer.colormapVersion !== this.lutVersion && (this.lutVersion = this.layer.colormapVersion, this.writeLut(this.lutTexture));
727
+ }
728
+ /** Encode a draw of this layer for the current view. */
729
+ draw(e, t) {
730
+ const i = this.layer.source, s = W(
731
+ this.layer.scale[0],
732
+ this.layer.scale[1],
733
+ this.layer.translate[0],
734
+ this.layer.translate[1]
735
+ ), n = P(t.camera2d.viewProjection(t.vw, t.vh), s), a = this.scratch;
736
+ a.set(n, 0), a[16] = i.width, a[17] = i.height, a[18] = 0, a[19] = 0;
737
+ const [o, c] = this.layer.contrastLimits;
738
+ a[20] = o * this.plan.sampleScale, a[21] = c * this.plan.sampleScale, a[22] = this.layer.gamma, a[23] = this.layer.opacity, a[24] = this.plan.isRgba ? 1 : 0, a[25] = this.layer.invert ? 1 : 0, a[26] = 0, a[27] = 0, this.device.queue.writeBuffer(this.uniformBuffer, 0, a), e.setPipeline(this.pipeline), e.setBindGroup(0, this.bindGroup), e.draw(6);
739
+ }
740
+ dispose() {
741
+ this.texture.destroy(), this.lutTexture.destroy(), this.uniformBuffer.destroy();
742
+ }
743
+ }
744
+ function he(r) {
745
+ return 2 ** r;
746
+ }
747
+ function Ne(r, e, t) {
748
+ const i = he(t);
749
+ return { width: Math.max(1, Math.ceil(r / i)), height: Math.max(1, Math.ceil(e / i)) };
750
+ }
751
+ function Ye(r, e, t, i) {
752
+ const s = Ne(r, e, t);
753
+ return { cols: Math.ceil(s.width / i), rows: Math.ceil(s.height / i) };
754
+ }
755
+ function qe(r, e) {
756
+ const t = Math.floor(Math.log2(1 / Math.max(r, 1e-9)));
757
+ return Math.min(e - 1, Math.max(0, t));
758
+ }
759
+ function We(r, e, t, i, s) {
760
+ const n = i / 2 / Math.max(t, 1e-9), a = s / 2 / Math.max(t, 1e-9);
761
+ return { x: r - n, y: e - a, width: 2 * n, height: 2 * a };
762
+ }
763
+ function Q(r, e, t, i, s) {
764
+ const n = s * he(i), { cols: a, rows: o } = Ye(e, t, i, s), c = Math.max(0, r.x), l = Math.max(0, r.y), h = Math.min(e, r.x + r.width), u = Math.min(t, r.y + r.height);
765
+ if (h <= c || u <= l) return [];
766
+ const f = Math.max(0, Math.floor(c / n)), m = Math.min(a - 1, Math.floor((h - 1e-6) / n)), d = Math.max(0, Math.floor(l / n)), _ = Math.min(o - 1, Math.floor((u - 1e-6) / n)), x = [];
767
+ for (let v = d; v <= _; v++)
768
+ for (let y = f; y <= m; y++) {
769
+ const w = y * n, T = v * n;
770
+ x.push({ col: y, row: v, x: w, y: T, w: Math.min(n, e - w), h: Math.min(n, t - T) });
771
+ }
772
+ return x;
773
+ }
774
+ class $e {
775
+ constructor(e, t) {
776
+ if (this.capacity = e, this.onEvict = t, e < 1) throw new Error("LruCache capacity must be >= 1.");
777
+ }
778
+ map = /* @__PURE__ */ new Map();
779
+ get size() {
780
+ return this.map.size;
781
+ }
782
+ has(e) {
783
+ return this.map.has(e);
784
+ }
785
+ /** Get a value and mark it most-recently-used. */
786
+ get(e) {
787
+ const t = this.map.get(e);
788
+ if (t !== void 0)
789
+ return this.map.delete(e), this.map.set(e, t), t;
790
+ }
791
+ /** Insert/update a value (most-recently-used), evicting the oldest beyond capacity. */
792
+ set(e, t) {
793
+ for (this.map.has(e) && this.map.delete(e), this.map.set(e, t); this.map.size > this.capacity; ) {
794
+ const i = this.map.keys().next().value;
795
+ if (i === void 0) break;
796
+ const s = this.map.get(i);
797
+ this.map.delete(i), this.onEvict?.(s, i);
798
+ }
799
+ }
800
+ delete(e) {
801
+ const t = this.map.get(e);
802
+ return t === void 0 ? !1 : (this.map.delete(e), this.onEvict?.(t, e), !0);
803
+ }
804
+ clear() {
805
+ for (const [e, t] of this.map) this.onEvict?.(t, e);
806
+ this.map.clear();
807
+ }
808
+ }
809
+ const de = 28, ke = de * 4, Xe = 192;
810
+ class He {
811
+ constructor(e, t, i, s) {
812
+ if (this.device = e, this.format = t, this.layer = i, this.opts = s, i.source.kind !== "tiled")
813
+ throw new Error("TiledImageVisual requires a tiled source.");
814
+ this.source = i.source, this.plan = ae(this.source.channels, this.source.dtype, s.float32Filterable), this.module = e.createShaderModule({ code: le }), this.bindGroupLayout = this.buildBindGroupLayout(), this.pipelineLayout = e.createPipelineLayout({ bindGroupLayouts: [this.bindGroupLayout] }), this.lutSampler = e.createSampler({
815
+ magFilter: "linear",
816
+ minFilter: "linear",
817
+ addressModeU: "clamp-to-edge",
818
+ addressModeV: "clamp-to-edge"
819
+ }), this.lutTexture = e.createTexture({
820
+ size: [b, 1],
821
+ format: "rgba8unorm",
822
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
823
+ }), this.writeLut(), this.lutVersion = i.colormapVersion, this.currentInterp = i.interpolation, this.srcSampler = this.createSrcSampler(i.interpolation), this.currentBlend = i.blending, this.pipeline = this.buildPipeline(i.blending), this.cache = new $e(Xe, (n) => {
824
+ n.texture.destroy(), n.uniformBuffer.destroy();
825
+ });
826
+ }
827
+ ndisplay = 2;
828
+ source;
829
+ plan;
830
+ module;
831
+ bindGroupLayout;
832
+ pipelineLayout;
833
+ lutTexture;
834
+ lutSampler;
835
+ scratch = new Float32Array(de);
836
+ cache;
837
+ pending = /* @__PURE__ */ new Set();
838
+ srcSampler;
839
+ pipeline;
840
+ currentBlend;
841
+ currentInterp;
842
+ lutVersion;
843
+ disposed = !1;
844
+ buildBindGroupLayout() {
845
+ const e = this.plan.filterable;
846
+ return this.device.createBindGroupLayout({
847
+ entries: [
848
+ {
849
+ binding: 0,
850
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
851
+ buffer: { type: "uniform" }
852
+ },
853
+ {
854
+ binding: 1,
855
+ visibility: GPUShaderStage.FRAGMENT,
856
+ sampler: { type: e ? "filtering" : "non-filtering" }
857
+ },
858
+ {
859
+ binding: 2,
860
+ visibility: GPUShaderStage.FRAGMENT,
861
+ texture: { sampleType: e ? "float" : "unfilterable-float" }
862
+ },
863
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
864
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } }
865
+ ]
866
+ });
867
+ }
868
+ buildPipeline(e) {
869
+ return this.device.createRenderPipeline({
870
+ layout: this.pipelineLayout,
871
+ vertex: { module: this.module, entryPoint: "vs" },
872
+ fragment: {
873
+ module: this.module,
874
+ entryPoint: "fs",
875
+ targets: [{ format: this.format, blend: C(e) }]
876
+ },
877
+ primitive: { topology: "triangle-list" }
878
+ });
879
+ }
880
+ createSrcSampler(e) {
881
+ const t = !this.plan.filterable || e === "nearest" ? "nearest" : "linear";
882
+ return this.device.createSampler({
883
+ magFilter: t,
884
+ minFilter: t,
885
+ addressModeU: "clamp-to-edge",
886
+ addressModeV: "clamp-to-edge"
887
+ });
888
+ }
889
+ writeLut() {
890
+ const e = this.layer.colormap ?? O;
891
+ this.device.queue.writeTexture(
892
+ { texture: this.lutTexture },
893
+ H(e, b),
894
+ { bytesPerRow: b * 4, rowsPerImage: 1 },
895
+ { width: b, height: 1 }
896
+ );
897
+ }
898
+ keyOf(e, t, i, s) {
899
+ return `${s}:${e}:${t}:${i}`;
900
+ }
901
+ /** Return a ready tile, or kick an async fetch and return undefined. */
902
+ ensureTile(e, t, i, s) {
903
+ const n = this.keyOf(e, t, i, s), a = this.cache.get(n);
904
+ if (a) return a;
905
+ this.pending.has(n) || (this.pending.add(n), this.source.fetchTile({ level: e, col: t, row: i, z: s }).then((o) => {
906
+ if (this.pending.delete(n), this.disposed) return;
907
+ const c = this.device.createTexture({
908
+ size: [o.width, o.height],
909
+ format: this.plan.format,
910
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
911
+ });
912
+ this.device.queue.writeTexture(
913
+ { texture: c },
914
+ oe(o.data, this.plan.format),
915
+ { bytesPerRow: o.width * this.plan.bytesPerPixel, rowsPerImage: o.height },
916
+ { width: o.width, height: o.height }
917
+ );
918
+ const l = this.device.createBuffer({
919
+ size: ke,
920
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
921
+ }), h = this.device.createBindGroup({
922
+ layout: this.bindGroupLayout,
923
+ entries: [
924
+ { binding: 0, resource: { buffer: l } },
925
+ { binding: 1, resource: this.srcSampler },
926
+ { binding: 2, resource: c.createView() },
927
+ { binding: 3, resource: this.lutSampler },
928
+ { binding: 4, resource: this.lutTexture.createView() }
929
+ ]
930
+ });
931
+ this.cache.set(n, { texture: c, uniformBuffer: l, bindGroup: h }), this.opts.onNeedsRedraw();
932
+ }).catch(() => this.pending.delete(n)));
933
+ }
934
+ drawTile(e, t, i, s) {
935
+ const n = this.scratch;
936
+ n.set(i, 0), n[16] = s.w, n[17] = s.h, n[18] = s.x, n[19] = s.y;
937
+ const [a, o] = this.layer.contrastLimits;
938
+ n[20] = a * this.plan.sampleScale, n[21] = o * this.plan.sampleScale, n[22] = this.layer.gamma, n[23] = this.layer.opacity, n[24] = this.plan.isRgba ? 1 : 0, n[25] = this.layer.invert ? 1 : 0, n[26] = 0, n[27] = 0, this.device.queue.writeBuffer(t.uniformBuffer, 0, n), e.setBindGroup(0, t.bindGroup), e.draw(6);
939
+ }
940
+ sync() {
941
+ this.layer.blending !== this.currentBlend && (this.currentBlend = this.layer.blending, this.pipeline = this.buildPipeline(this.currentBlend)), this.layer.interpolation !== this.currentInterp && (this.currentInterp = this.layer.interpolation, this.srcSampler = this.createSrcSampler(this.currentInterp), this.cache.clear()), this.layer.colormapVersion !== this.lutVersion && (this.lutVersion = this.layer.colormapVersion, this.writeLut());
942
+ }
943
+ draw(e, t) {
944
+ const { width: i, height: s, tileSize: n, levels: a } = this.source, o = t.camera2d, c = t.z, l = We(o.center[0], o.center[1], o.zoom, t.vw, t.vh), h = qe(o.zoom, a), u = P(
945
+ o.viewProjection(t.vw, t.vh),
946
+ W(
947
+ this.layer.scale[0],
948
+ this.layer.scale[1],
949
+ this.layer.translate[0],
950
+ this.layer.translate[1]
951
+ )
952
+ );
953
+ e.setPipeline(this.pipeline);
954
+ const f = a - 1;
955
+ if (this.layer.blending !== "additive" && h !== f)
956
+ for (const m of Q(l, i, s, f, n)) {
957
+ const d = this.ensureTile(f, m.col, m.row, c);
958
+ d && this.drawTile(e, d, u, m);
959
+ }
960
+ for (const m of Q(l, i, s, h, n)) {
961
+ const d = this.ensureTile(h, m.col, m.row, c);
962
+ d && this.drawTile(e, d, u, m);
963
+ }
964
+ }
965
+ dispose() {
966
+ this.disposed = !0, this.cache.clear(), this.lutTexture.destroy();
967
+ }
968
+ }
969
+ const je = (
970
+ /* wgsl */
971
+ `
972
+ struct U {
973
+ mvp : mat4x4<f32>,
974
+ params : vec4<f32>, // symbolCode (0=disc,1=ring,2=square), opacity, 0, 0
975
+ };
976
+ @group(0) @binding(0) var<uniform> u : U;
977
+
978
+ struct VSOut {
979
+ @builtin(position) position : vec4<f32>,
980
+ @location(0) local : vec2<f32>,
981
+ @location(1) face : vec4<f32>,
982
+ @location(2) border : vec4<f32>,
983
+ @location(3) borderFrac : f32,
984
+ };
985
+
986
+ @vertex
987
+ fn vs(
988
+ @builtin(vertex_index) vi : u32,
989
+ @location(0) pos : vec2<f32>,
990
+ @location(1) size : f32,
991
+ @location(2) face : vec4<f32>,
992
+ @location(3) border : vec4<f32>,
993
+ @location(4) borderWidth : f32,
994
+ ) -> VSOut {
995
+ var corners = array<vec2<f32>, 6>(
996
+ vec2<f32>(-1.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(-1.0, 1.0),
997
+ vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0),
998
+ );
999
+ let c = corners[vi];
1000
+ let world = pos + c * (size * 0.5);
1001
+ var out : VSOut;
1002
+ out.position = u.mvp * vec4<f32>(world, 0.0, 1.0);
1003
+ out.local = c;
1004
+ out.face = face;
1005
+ out.border = border;
1006
+ out.borderFrac = clamp(borderWidth / max(size, 1e-6), 0.0, 1.0);
1007
+ return out;
1008
+ }
1009
+
1010
+ @fragment
1011
+ fn fs(in : VSOut) -> @location(0) vec4<f32> {
1012
+ let symbol = u.params.x;
1013
+ let opacity = u.params.y;
1014
+ var d : f32;
1015
+ if (symbol > 1.5) { d = max(abs(in.local.x), abs(in.local.y)); } // square
1016
+ else { d = length(in.local); } // disc / ring
1017
+
1018
+ let aa = max(fwidth(d), 1e-5);
1019
+ let inside = 1.0 - smoothstep(1.0 - aa, 1.0, d);
1020
+ if (inside <= 0.0) { discard; }
1021
+
1022
+ let borderEdge = 1.0 - in.borderFrac;
1023
+ let borderMix = smoothstep(borderEdge - aa, borderEdge, d);
1024
+ let rgb = mix(in.face.rgb, in.border.rgb, borderMix);
1025
+ var a = mix(in.face.a, in.border.a, borderMix);
1026
+ if (symbol > 0.5 && symbol < 1.5) { a = a * borderMix; } // ring: only the border ring shows
1027
+ a = a * inside * opacity;
1028
+ return vec4<f32>(rgb * a, a);
1029
+ }
1030
+ `
1031
+ ), Ze = Ee * 4, ue = 20, Ke = ue * 4;
1032
+ class Je {
1033
+ constructor(e, t, i) {
1034
+ this.device = e, this.format = t, this.layer = i, this.module = e.createShaderModule({ code: je }), this.uniformBuffer = e.createBuffer({
1035
+ size: Ke,
1036
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1037
+ }), this.currentBlend = i.blending, this.pipeline = this.buildPipeline(i.blending);
1038
+ }
1039
+ ndisplay = 2;
1040
+ module;
1041
+ uniformBuffer;
1042
+ scratch = new Float32Array(ue);
1043
+ instanceBuffer = null;
1044
+ pipeline;
1045
+ currentBlend;
1046
+ dataVersion = -1;
1047
+ buildPipeline(e) {
1048
+ return this.device.createRenderPipeline({
1049
+ layout: "auto",
1050
+ vertex: {
1051
+ module: this.module,
1052
+ entryPoint: "vs",
1053
+ buffers: [
1054
+ {
1055
+ arrayStride: Ze,
1056
+ stepMode: "instance",
1057
+ attributes: [
1058
+ { shaderLocation: 0, offset: 0, format: "float32x2" },
1059
+ // pos
1060
+ { shaderLocation: 1, offset: 8, format: "float32" },
1061
+ // size
1062
+ { shaderLocation: 2, offset: 12, format: "float32x4" },
1063
+ // face
1064
+ { shaderLocation: 3, offset: 28, format: "float32x4" },
1065
+ // border
1066
+ { shaderLocation: 4, offset: 44, format: "float32" }
1067
+ // borderWidth
1068
+ ]
1069
+ }
1070
+ ]
1071
+ },
1072
+ fragment: {
1073
+ module: this.module,
1074
+ entryPoint: "fs",
1075
+ targets: [{ format: this.format, blend: C(e) }]
1076
+ },
1077
+ primitive: { topology: "triangle-list" }
1078
+ });
1079
+ }
1080
+ sync() {
1081
+ this.layer.blending !== this.currentBlend && (this.currentBlend = this.layer.blending, this.pipeline = this.buildPipeline(this.currentBlend)), (this.layer.dataVersion !== this.dataVersion || !this.instanceBuffer) && (this.dataVersion = this.layer.dataVersion, this.rebuildInstances());
1082
+ }
1083
+ rebuildInstances() {
1084
+ if (this.instanceBuffer?.destroy(), this.layer.count === 0) {
1085
+ this.instanceBuffer = null;
1086
+ return;
1087
+ }
1088
+ const e = this.layer.buildInstanceData();
1089
+ this.instanceBuffer = this.device.createBuffer({
1090
+ size: e.byteLength,
1091
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
1092
+ }), this.device.queue.writeBuffer(this.instanceBuffer, 0, e);
1093
+ }
1094
+ draw(e, t) {
1095
+ if (!this.instanceBuffer || this.layer.count === 0) return;
1096
+ const i = P(
1097
+ t.camera2d.viewProjection(t.vw, t.vh),
1098
+ W(
1099
+ this.layer.scale[0],
1100
+ this.layer.scale[1],
1101
+ this.layer.translate[0],
1102
+ this.layer.translate[1]
1103
+ )
1104
+ ), s = this.scratch;
1105
+ s.set(i, 0), s[16] = this.layer.symbolCode(), s[17] = this.layer.opacity, s[18] = 0, s[19] = 0, this.device.queue.writeBuffer(this.uniformBuffer, 0, s);
1106
+ const n = this.device.createBindGroup({
1107
+ layout: this.pipeline.getBindGroupLayout(0),
1108
+ entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }]
1109
+ });
1110
+ e.setPipeline(this.pipeline), e.setBindGroup(0, n), e.setVertexBuffer(0, this.instanceBuffer), e.draw(6, this.layer.count);
1111
+ }
1112
+ dispose() {
1113
+ this.instanceBuffer?.destroy(), this.uniformBuffer.destroy();
1114
+ }
1115
+ }
1116
+ function Qe(r, e, t) {
1117
+ const i = Math.floor(r * 6), s = r * 6 - i, n = t * (1 - e), a = t * (1 - s * e), o = t * (1 - (1 - s) * e);
1118
+ switch (i % 6) {
1119
+ case 0:
1120
+ return [t, o, n];
1121
+ case 1:
1122
+ return [a, t, n];
1123
+ case 2:
1124
+ return [n, t, o];
1125
+ case 3:
1126
+ return [n, a, t];
1127
+ case 4:
1128
+ return [o, n, t];
1129
+ default:
1130
+ return [t, n, a];
1131
+ }
1132
+ }
1133
+ function et(r = 256) {
1134
+ const e = new Uint8Array(r * 4), t = 0.618033988749895;
1135
+ for (let i = 1; i < r; i++) {
1136
+ const s = i * t % 1, [n, a, o] = Qe(s, 0.6, 0.95), c = i * 4;
1137
+ e[c] = Math.round(n * 255), e[c + 1] = Math.round(a * 255), e[c + 2] = Math.round(o * 255), e[c + 3] = 255;
1138
+ }
1139
+ return e;
1140
+ }
1141
+ const tt = (
1142
+ /* wgsl */
1143
+ `
1144
+ struct U {
1145
+ mvp : mat4x4<f32>,
1146
+ imageSize : vec2<f32>,
1147
+ origin : vec2<f32>,
1148
+ params : vec4<f32>, // selectedLabel, showSelectedOnly, opacity, lutSize
1149
+ };
1150
+ @group(0) @binding(0) var<uniform> u : U;
1151
+ @group(0) @binding(1) var samp : sampler;
1152
+ @group(0) @binding(2) var labelTex : texture_2d<f32>;
1153
+ @group(0) @binding(3) var lutSamp : sampler;
1154
+ @group(0) @binding(4) var lut : texture_2d<f32>;
1155
+
1156
+ struct VSOut {
1157
+ @builtin(position) position : vec4<f32>,
1158
+ @location(0) uv : vec2<f32>,
1159
+ };
1160
+
1161
+ @vertex
1162
+ fn vs(@builtin(vertex_index) vi : u32) -> VSOut {
1163
+ var corners = array<vec2<f32>, 6>(
1164
+ vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0),
1165
+ vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 1.0),
1166
+ );
1167
+ let c = corners[vi];
1168
+ var out : VSOut;
1169
+ out.position = u.mvp * vec4<f32>(u.origin + c * u.imageSize, 0.0, 1.0);
1170
+ out.uv = c;
1171
+ return out;
1172
+ }
1173
+
1174
+ @fragment
1175
+ fn fs(in : VSOut) -> @location(0) vec4<f32> {
1176
+ let raw = textureSample(labelTex, samp, in.uv).r;
1177
+ let id = round(raw * 255.0);
1178
+ let lutSize = u.params.w;
1179
+ var rgba = textureSample(lut, lutSamp, vec2<f32>((id + 0.5) / lutSize, 0.5));
1180
+ if (u.params.y > 0.5 && abs(id - u.params.x) > 0.5) { rgba.a = 0.0; } // show-selected-only
1181
+ let a = rgba.a * u.params.z;
1182
+ return vec4<f32>(rgba.rgb * a, a);
1183
+ }
1184
+ `
1185
+ ), R = 256, me = 24, it = me * 4;
1186
+ class rt {
1187
+ constructor(e, t, i) {
1188
+ this.device = e, this.format = t, this.layer = i, this.module = e.createShaderModule({ code: tt }), this.uniformBuffer = e.createBuffer({
1189
+ size: it,
1190
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1191
+ }), this.texture = e.createTexture({
1192
+ size: [i.width, i.height],
1193
+ format: "r8unorm",
1194
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
1195
+ }), e.queue.writeTexture(
1196
+ { texture: this.texture },
1197
+ i.data,
1198
+ { bytesPerRow: i.width, rowsPerImage: i.height },
1199
+ { width: i.width, height: i.height }
1200
+ ), this.lutTexture = e.createTexture({
1201
+ size: [R, 1],
1202
+ format: "rgba8unorm",
1203
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
1204
+ }), e.queue.writeTexture(
1205
+ { texture: this.lutTexture },
1206
+ et(R),
1207
+ { bytesPerRow: R * 4, rowsPerImage: 1 },
1208
+ { width: R, height: 1 }
1209
+ ), this.sampler = e.createSampler({
1210
+ magFilter: "nearest",
1211
+ minFilter: "nearest",
1212
+ addressModeU: "clamp-to-edge",
1213
+ addressModeV: "clamp-to-edge"
1214
+ }), this.currentBlend = i.blending, this.pipeline = this.buildPipeline(i.blending), this.bindGroup = e.createBindGroup({
1215
+ layout: this.pipeline.getBindGroupLayout(0),
1216
+ entries: [
1217
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
1218
+ { binding: 1, resource: this.sampler },
1219
+ { binding: 2, resource: this.texture.createView() },
1220
+ { binding: 3, resource: this.sampler },
1221
+ { binding: 4, resource: this.lutTexture.createView() }
1222
+ ]
1223
+ });
1224
+ }
1225
+ ndisplay = 2;
1226
+ module;
1227
+ uniformBuffer;
1228
+ scratch = new Float32Array(me);
1229
+ texture;
1230
+ lutTexture;
1231
+ sampler;
1232
+ bindGroup;
1233
+ pipeline;
1234
+ currentBlend;
1235
+ buildPipeline(e) {
1236
+ return this.device.createRenderPipeline({
1237
+ layout: "auto",
1238
+ vertex: { module: this.module, entryPoint: "vs" },
1239
+ fragment: {
1240
+ module: this.module,
1241
+ entryPoint: "fs",
1242
+ targets: [{ format: this.format, blend: C(e) }]
1243
+ },
1244
+ primitive: { topology: "triangle-list" }
1245
+ });
1246
+ }
1247
+ sync() {
1248
+ this.layer.blending !== this.currentBlend && (this.currentBlend = this.layer.blending, this.pipeline = this.buildPipeline(this.currentBlend));
1249
+ }
1250
+ draw(e, t) {
1251
+ const i = P(
1252
+ t.camera2d.viewProjection(t.vw, t.vh),
1253
+ W(
1254
+ this.layer.scale[0],
1255
+ this.layer.scale[1],
1256
+ this.layer.translate[0],
1257
+ this.layer.translate[1]
1258
+ )
1259
+ ), s = this.scratch;
1260
+ s.set(i, 0), s[16] = this.layer.width, s[17] = this.layer.height, s[18] = 0, s[19] = 0, s[20] = this.layer.selectedLabel, s[21] = this.layer.showSelectedOnly ? 1 : 0, s[22] = this.layer.opacity, s[23] = R, this.device.queue.writeBuffer(this.uniformBuffer, 0, s), e.setPipeline(this.pipeline), e.setBindGroup(0, this.bindGroup), e.draw(6);
1261
+ }
1262
+ dispose() {
1263
+ this.texture.destroy(), this.lutTexture.destroy(), this.uniformBuffer.destroy();
1264
+ }
1265
+ }
1266
+ const st = (
1267
+ /* wgsl */
1268
+ `
1269
+ struct U {
1270
+ invMvp : mat4x4<f32>,
1271
+ params : vec4<f32>, // climLo, climHi (normalized 0..1), gamma, opacity
1272
+ params2 : vec4<f32>, // renderingCode (0=mip,1=translucent,2=iso), isoThreshold, steps, 0
1273
+ };
1274
+ @group(0) @binding(0) var<uniform> u : U;
1275
+ @group(0) @binding(1) var volSamp : sampler;
1276
+ @group(0) @binding(2) var volTex : texture_3d<f32>;
1277
+ @group(0) @binding(3) var lutSamp : sampler;
1278
+ @group(0) @binding(4) var lut : texture_2d<f32>;
1279
+
1280
+ struct VSOut {
1281
+ @builtin(position) position : vec4<f32>,
1282
+ @location(0) ndc : vec2<f32>,
1283
+ };
1284
+
1285
+ @vertex
1286
+ fn vs(@builtin(vertex_index) vi : u32) -> VSOut {
1287
+ var corners = array<vec2<f32>, 6>(
1288
+ vec2<f32>(-1.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(-1.0, 1.0),
1289
+ vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0),
1290
+ );
1291
+ let c = corners[vi];
1292
+ var out : VSOut;
1293
+ out.position = vec4<f32>(c, 0.0, 1.0);
1294
+ out.ndc = c;
1295
+ return out;
1296
+ }
1297
+
1298
+ fn unproject(ndc : vec2<f32>, z : f32) -> vec3<f32> {
1299
+ let p = u.invMvp * vec4<f32>(ndc, z, 1.0);
1300
+ return p.xyz / p.w;
1301
+ }
1302
+
1303
+ fn sampleWindowed(pos : vec3<f32>) -> f32 {
1304
+ let s = textureSampleLevel(volTex, volSamp, pos, 0.0).r;
1305
+ let lo = u.params.x;
1306
+ let hi = u.params.y;
1307
+ let t = clamp((s - lo) / max(hi - lo, 1e-6), 0.0, 1.0);
1308
+ return pow(t, u.params.z);
1309
+ }
1310
+
1311
+ fn lutColor(t : f32) -> vec3<f32> {
1312
+ return textureSampleLevel(lut, lutSamp, vec2<f32>(clamp(t, 0.0, 1.0), 0.5), 0.0).rgb;
1313
+ }
1314
+
1315
+ @fragment
1316
+ fn fs(in : VSOut) -> @location(0) vec4<f32> {
1317
+ let ro = unproject(in.ndc, 0.0); // near point, volume space
1318
+ let rf = unproject(in.ndc, 1.0); // far point
1319
+ let rd = rf - ro;
1320
+
1321
+ // Intersect ray with the unit box [0,1]^3 (parameter t along rd).
1322
+ let inv = 1.0 / rd;
1323
+ let t0 = (vec3<f32>(0.0) - ro) * inv;
1324
+ let t1 = (vec3<f32>(1.0) - ro) * inv;
1325
+ let tmin = min(t0, t1);
1326
+ let tmax = max(t0, t1);
1327
+ let tNear = max(max(max(tmin.x, tmin.y), tmin.z), 0.0);
1328
+ let tFar = min(min(tmax.x, tmax.y), 1.0 * min(tmax.z, 1.0e9));
1329
+ if (tFar <= tNear) { discard; }
1330
+
1331
+ let steps = i32(u.params2.z);
1332
+ let dt = (tFar - tNear) / f32(steps);
1333
+ let mode = u.params2.x;
1334
+ let opacity = u.params.w;
1335
+
1336
+ var maxT = 0.0;
1337
+ var col = vec3<f32>(0.0);
1338
+ var acc = 0.0;
1339
+
1340
+ for (var i = 0; i < steps; i = i + 1) {
1341
+ let t = tNear + (f32(i) + 0.5) * dt;
1342
+ let pos = ro + rd * t;
1343
+ let w = sampleWindowed(pos);
1344
+
1345
+ if (mode < 0.5) {
1346
+ // MIP
1347
+ maxT = max(maxT, w);
1348
+ } else if (mode < 1.5) {
1349
+ // Front-to-back translucent DVR
1350
+ let a = w * opacity;
1351
+ let c = lutColor(w);
1352
+ col = col + (1.0 - acc) * c * a;
1353
+ acc = acc + (1.0 - acc) * a;
1354
+ if (acc >= 0.99) { break; }
1355
+ } else {
1356
+ // Iso-surface: first crossing → gradient + lambert
1357
+ if (w >= u.params2.y) {
1358
+ let e = 1.0 / 128.0;
1359
+ let gx = sampleWindowed(pos + vec3<f32>(e, 0.0, 0.0)) - sampleWindowed(pos - vec3<f32>(e, 0.0, 0.0));
1360
+ let gy = sampleWindowed(pos + vec3<f32>(0.0, e, 0.0)) - sampleWindowed(pos - vec3<f32>(0.0, e, 0.0));
1361
+ let gz = sampleWindowed(pos + vec3<f32>(0.0, 0.0, e)) - sampleWindowed(pos - vec3<f32>(0.0, 0.0, e));
1362
+ let n = normalize(vec3<f32>(gx, gy, gz) + vec3<f32>(1e-5));
1363
+ let lightDir = normalize(vec3<f32>(0.5, 0.7, 1.0));
1364
+ let lambert = max(dot(n, lightDir), 0.0) * 0.8 + 0.2;
1365
+ col = lutColor(w) * lambert;
1366
+ acc = opacity;
1367
+ break;
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ if (mode < 0.5) {
1373
+ if (maxT <= 0.0) { discard; }
1374
+ col = lutColor(maxT);
1375
+ acc = opacity;
1376
+ }
1377
+ if (acc <= 0.0) { discard; }
1378
+ return vec4<f32>(col * acc, acc);
1379
+ }
1380
+ `
1381
+ ), fe = 24, nt = fe * 4, at = 192;
1382
+ class ot {
1383
+ constructor(e, t, i) {
1384
+ this.device = e, this.format = t, this.layer = i, this.module = e.createShaderModule({ code: st }), this.uniformBuffer = e.createBuffer({
1385
+ size: nt,
1386
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1387
+ }), this.texture = e.createTexture({
1388
+ size: [i.width, i.height, i.depth],
1389
+ dimension: "3d",
1390
+ format: "r8unorm",
1391
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
1392
+ }), e.queue.writeTexture(
1393
+ { texture: this.texture },
1394
+ i.data,
1395
+ { bytesPerRow: i.width, rowsPerImage: i.height },
1396
+ { width: i.width, height: i.height, depthOrArrayLayers: i.depth }
1397
+ ), this.lutTexture = e.createTexture({
1398
+ size: [b, 1],
1399
+ format: "rgba8unorm",
1400
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
1401
+ }), this.writeLut(), this.lutVersion = i.colormapVersion, this.volSampler = e.createSampler({
1402
+ magFilter: "linear",
1403
+ minFilter: "linear",
1404
+ addressModeU: "clamp-to-edge",
1405
+ addressModeV: "clamp-to-edge",
1406
+ addressModeW: "clamp-to-edge"
1407
+ }), this.lutSampler = e.createSampler({ magFilter: "linear", minFilter: "linear" }), this.model = P(
1408
+ Fe(i.width, i.height, i.depth),
1409
+ Ie(-0.5, -0.5, -0.5)
1410
+ ), this.currentBlend = i.blending, this.pipeline = this.buildPipeline(i.blending), this.bindGroup = this.buildBindGroup();
1411
+ }
1412
+ ndisplay = 3;
1413
+ module;
1414
+ uniformBuffer;
1415
+ scratch = new Float32Array(fe);
1416
+ texture;
1417
+ lutTexture;
1418
+ volSampler;
1419
+ lutSampler;
1420
+ model;
1421
+ bindGroup;
1422
+ pipeline;
1423
+ currentBlend;
1424
+ lutVersion;
1425
+ buildPipeline(e) {
1426
+ return this.device.createRenderPipeline({
1427
+ layout: "auto",
1428
+ vertex: { module: this.module, entryPoint: "vs" },
1429
+ fragment: {
1430
+ module: this.module,
1431
+ entryPoint: "fs",
1432
+ targets: [{ format: this.format, blend: C(e) }]
1433
+ },
1434
+ primitive: { topology: "triangle-list" }
1435
+ });
1436
+ }
1437
+ buildBindGroup() {
1438
+ return this.device.createBindGroup({
1439
+ layout: this.pipeline.getBindGroupLayout(0),
1440
+ entries: [
1441
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
1442
+ { binding: 1, resource: this.volSampler },
1443
+ { binding: 2, resource: this.texture.createView() },
1444
+ { binding: 3, resource: this.lutSampler },
1445
+ { binding: 4, resource: this.lutTexture.createView() }
1446
+ ]
1447
+ });
1448
+ }
1449
+ writeLut() {
1450
+ this.device.queue.writeTexture(
1451
+ { texture: this.lutTexture },
1452
+ H(this.layer.colormap, b),
1453
+ { bytesPerRow: b * 4, rowsPerImage: 1 },
1454
+ { width: b, height: 1 }
1455
+ );
1456
+ }
1457
+ sync() {
1458
+ this.layer.blending !== this.currentBlend && (this.currentBlend = this.layer.blending, this.pipeline = this.buildPipeline(this.currentBlend), this.bindGroup = this.buildBindGroup()), this.layer.colormapVersion !== this.lutVersion && (this.lutVersion = this.layer.colormapVersion, this.writeLut());
1459
+ }
1460
+ draw(e, t) {
1461
+ const i = P(t.camera3d.viewProjection(t.vw, t.vh), this.model), s = Ve(i), n = this.scratch;
1462
+ n.set(s, 0);
1463
+ const [a, o] = this.layer.contrastLimits;
1464
+ n[16] = a / 255, n[17] = o / 255, n[18] = this.layer.gamma, n[19] = this.layer.opacity, n[20] = this.layer.renderingCode(), n[21] = this.layer.isoThreshold, n[22] = at, n[23] = 0, this.device.queue.writeBuffer(this.uniformBuffer, 0, n), e.setPipeline(this.pipeline), e.setBindGroup(0, this.bindGroup), e.draw(6);
1465
+ }
1466
+ dispose() {
1467
+ this.texture.destroy(), this.lutTexture.destroy(), this.uniformBuffer.destroy();
1468
+ }
1469
+ }
1470
+ class lt {
1471
+ constructor(e, t, i = {
1472
+ float32Filterable: !1,
1473
+ onNeedsRedraw: () => {
1474
+ }
1475
+ }) {
1476
+ this.device = e, this.target = t, this.options = i;
1477
+ }
1478
+ visuals = /* @__PURE__ */ new Map();
1479
+ addLayer(e) {
1480
+ if (this.visuals.has(e.id)) return;
1481
+ const t = this.createVisual(e);
1482
+ t && this.visuals.set(e.id, t);
1483
+ }
1484
+ createVisual(e) {
1485
+ const t = this.target.format;
1486
+ return e instanceof ie ? e.source.kind === "tiled" ? new He(this.device, t, e, {
1487
+ float32Filterable: this.options.float32Filterable,
1488
+ onNeedsRedraw: this.options.onNeedsRedraw
1489
+ }) : new De(this.device, t, e, {
1490
+ float32Filterable: this.options.float32Filterable
1491
+ }) : e instanceof re ? new Je(this.device, t, e) : e instanceof se ? new rt(this.device, t, e) : e instanceof ne ? new ot(this.device, t, e) : null;
1492
+ }
1493
+ removeLayer(e) {
1494
+ this.visuals.get(e)?.dispose(), this.visuals.delete(e);
1495
+ }
1496
+ has(e) {
1497
+ return this.visuals.has(e);
1498
+ }
1499
+ /** Draw the given layers (in order) into the swapchain for the current cameras/dims. */
1500
+ render(e, t, i = { r: 0.07, g: 0.07, b: 0.09, a: 1 }) {
1501
+ const s = this.target.canvas.clientWidth || this.target.canvas.width, n = this.target.canvas.clientHeight || this.target.canvas.height;
1502
+ this.renderInto(this.target.view, e, t, s, n, i);
1503
+ }
1504
+ /**
1505
+ * Draw into an arbitrary color-attachment view. `vw`/`vh` are the CSS-pixel projection size
1506
+ * (resolution-independent); the attachment may be a different device-pixel size. Only
1507
+ * visuals whose `ndisplay` matches `inputs.ndisplay` are drawn. Used for both the swapchain
1508
+ * and offscreen readback.
1509
+ */
1510
+ renderInto(e, t, i, s, n, a = { r: 0.07, g: 0.07, b: 0.09, a: 1 }) {
1511
+ const o = {
1512
+ camera2d: t.camera2d,
1513
+ camera3d: t.camera3d,
1514
+ vw: s,
1515
+ vh: n,
1516
+ z: t.z,
1517
+ ndisplay: t.ndisplay
1518
+ }, c = this.device.createCommandEncoder(), l = c.beginRenderPass({
1519
+ colorAttachments: [{ view: e, clearValue: a, loadOp: "clear", storeOp: "store" }]
1520
+ });
1521
+ for (const h of i) {
1522
+ if (!h.visible) continue;
1523
+ const u = this.visuals.get(h.id);
1524
+ !u || u.ndisplay !== o.ndisplay || (u.sync(), u.draw(l, o));
1525
+ }
1526
+ l.end(), this.device.queue.submit([c.finish()]);
1527
+ }
1528
+ dispose() {
1529
+ for (const e of this.visuals.values()) e.dispose();
1530
+ this.visuals.clear();
1531
+ }
1532
+ }
1533
+ class ct {
1534
+ _items = [];
1535
+ added = new S();
1536
+ removed = new S();
1537
+ /** Fires on any structural change (add/remove/move/clear). */
1538
+ changed = new S();
1539
+ get items() {
1540
+ return this._items;
1541
+ }
1542
+ get length() {
1543
+ return this._items.length;
1544
+ }
1545
+ add(e) {
1546
+ return this._items.push(e), this.added.emit(e), this.changed.emit(this), e;
1547
+ }
1548
+ remove(e) {
1549
+ const t = this._items.indexOf(e);
1550
+ return t < 0 ? !1 : (this._items.splice(t, 1), this.removed.emit(e), this.changed.emit(this), !0);
1551
+ }
1552
+ clear() {
1553
+ const e = this._items.splice(0, this._items.length);
1554
+ for (const t of e) this.removed.emit(t);
1555
+ this.changed.emit(this);
1556
+ }
1557
+ [Symbol.iterator]() {
1558
+ return this._items[Symbol.iterator]();
1559
+ }
1560
+ }
1561
+ class ht {
1562
+ changed = new S();
1563
+ _ndisplay = 2;
1564
+ _depth = 1;
1565
+ _z = 0;
1566
+ get ndisplay() {
1567
+ return this._ndisplay;
1568
+ }
1569
+ set ndisplay(e) {
1570
+ this._ndisplay = e, this.changed.emit(this);
1571
+ }
1572
+ /** Number of z-slices. Setting it re-clamps `z`. */
1573
+ get depth() {
1574
+ return this._depth;
1575
+ }
1576
+ set depth(e) {
1577
+ this._depth = Math.max(1, Math.floor(e)), this._z = ee(this._z, 0, this._depth - 1), this.changed.emit(this);
1578
+ }
1579
+ /** Current z-slice index, clamped to `[0, depth-1]`. */
1580
+ get z() {
1581
+ return this._z;
1582
+ }
1583
+ set z(e) {
1584
+ const t = ee(Math.round(e), 0, this._depth - 1);
1585
+ t !== this._z && (this._z = t, this.changed.emit(this));
1586
+ }
1587
+ }
1588
+ function ee(r, e, t) {
1589
+ return r < e ? e : r > t ? t : r;
1590
+ }
1591
+ class dt {
1592
+ changed = new S();
1593
+ _center = [0, 0];
1594
+ _zoom = 1;
1595
+ get center() {
1596
+ return [this._center[0], this._center[1]];
1597
+ }
1598
+ set center(e) {
1599
+ this._center = [e[0], e[1]], this.changed.emit(this);
1600
+ }
1601
+ get zoom() {
1602
+ return this._zoom;
1603
+ }
1604
+ set zoom(e) {
1605
+ this._zoom = e > 0 ? e : this._zoom, this.changed.emit(this);
1606
+ }
1607
+ /** Set center + zoom in one mutation (single change event). */
1608
+ set(e, t) {
1609
+ this._center = [e[0], e[1]], t > 0 && (this._zoom = t), this.changed.emit(this);
1610
+ }
1611
+ /** World→clip view-projection matrix for a `vw`×`vh` viewport. */
1612
+ viewProjection(e, t) {
1613
+ return ze(this._center, this._zoom, e, t);
1614
+ }
1615
+ /**
1616
+ * Frame a `width`×`height` region centered in a `vw`×`vh` viewport (with a little margin),
1617
+ * e.g. to fit a freshly loaded image. No-op for degenerate inputs.
1618
+ */
1619
+ fit(e, t, i, s, n = 0.95) {
1620
+ if (e <= 0 || t <= 0 || i <= 0 || s <= 0) return;
1621
+ const a = Math.min(i / e, s / t) * n;
1622
+ this.set([e / 2, t / 2], a);
1623
+ }
1624
+ }
1625
+ const te = Math.PI / 2, V = 1e-3;
1626
+ class ut {
1627
+ changed = new S();
1628
+ azimuth = 0.7;
1629
+ elevation = 0.5;
1630
+ fov = 45 * Math.PI / 180;
1631
+ _distance = 3;
1632
+ _target = [0, 0, 0];
1633
+ get distance() {
1634
+ return this._distance;
1635
+ }
1636
+ set distance(e) {
1637
+ this._distance = Math.max(V, e), this.changed.emit(this);
1638
+ }
1639
+ get target() {
1640
+ return [...this._target];
1641
+ }
1642
+ set target(e) {
1643
+ this._target = [e[0], e[1], e[2]], this.changed.emit(this);
1644
+ }
1645
+ /** Rotate the orbit by deltas (radians); elevation is clamped to avoid gimbal flip. */
1646
+ orbit(e, t) {
1647
+ this.azimuth += e, this.elevation = mt(this.elevation + t, -te + V, te - V), this.changed.emit(this);
1648
+ }
1649
+ zoomBy(e) {
1650
+ this.distance = this._distance * e;
1651
+ }
1652
+ /** Eye position in world space. */
1653
+ eye() {
1654
+ const e = Math.cos(this.elevation);
1655
+ return [
1656
+ this._target[0] + this._distance * e * Math.sin(this.azimuth),
1657
+ this._target[1] + this._distance * Math.sin(this.elevation),
1658
+ this._target[2] + this._distance * e * Math.cos(this.azimuth)
1659
+ ];
1660
+ }
1661
+ /** Perspective view-projection for a `vw`×`vh` viewport. */
1662
+ viewProjection(e, t) {
1663
+ const i = t > 0 ? e / t : 1, s = Math.max(V, this._distance * 0.05), n = this._distance * 4 + 1, a = Re(this.fov, i, s, n), o = Ce(this.eye(), this._target, [0, 1, 0]);
1664
+ return P(a, o);
1665
+ }
1666
+ /** Frame a `[w,h,d]` volume centered at the origin. */
1667
+ frame(e, t, i) {
1668
+ this._target = [0, 0, 0], this.distance = Math.max(e, t, i) * 1.8;
1669
+ }
1670
+ }
1671
+ function mt(r, e, t) {
1672
+ return r < e ? e : r > t ? t : r;
1673
+ }
1674
+ class ft {
1675
+ layers = new ct();
1676
+ camera = new dt();
1677
+ camera3d = new ut();
1678
+ dims = new ht();
1679
+ changed = new S();
1680
+ layerDisposers = /* @__PURE__ */ new Map();
1681
+ constructor() {
1682
+ this.layers.changed.connect(() => this.changed.emit(this)), this.camera.changed.connect(() => this.changed.emit(this)), this.camera3d.changed.connect(() => this.changed.emit(this)), this.dims.changed.connect(() => this.changed.emit(this)), this.layers.added.connect((e) => {
1683
+ this.layerDisposers.set(
1684
+ e,
1685
+ e.changed.connect(() => this.changed.emit(this))
1686
+ );
1687
+ }), this.layers.removed.connect((e) => {
1688
+ this.layerDisposers.get(e)?.(), this.layerDisposers.delete(e);
1689
+ });
1690
+ }
1691
+ }
1692
+ function pt(r, e) {
1693
+ let t = !1, i = 0, s = 0;
1694
+ const n = (l) => {
1695
+ t = !0, i = l.clientX, s = l.clientY, r.setPointerCapture(l.pointerId);
1696
+ }, a = (l) => {
1697
+ if (!t) return;
1698
+ const h = l.clientX - i, u = l.clientY - s;
1699
+ i = l.clientX, s = l.clientY;
1700
+ const { zoom: f } = e, [m, d] = e.center;
1701
+ e.center = [m - h / f, d - u / f];
1702
+ }, o = (l) => {
1703
+ t = !1, r.hasPointerCapture(l.pointerId) && r.releasePointerCapture(l.pointerId);
1704
+ }, c = (l) => {
1705
+ l.preventDefault();
1706
+ const h = r.getBoundingClientRect(), u = l.clientX - h.left - h.width / 2, f = l.clientY - h.top - h.height / 2, { zoom: m } = e, [d, _] = e.center, x = d + u / m, v = _ + f / m, y = Math.exp(-l.deltaY * 15e-4), w = m * y;
1707
+ e.set([x - u / w, v - f / w], w);
1708
+ };
1709
+ return r.addEventListener("pointerdown", n), r.addEventListener("pointermove", a), r.addEventListener("pointerup", o), r.addEventListener("pointercancel", o), r.addEventListener("wheel", c, { passive: !1 }), () => {
1710
+ r.removeEventListener("pointerdown", n), r.removeEventListener("pointermove", a), r.removeEventListener("pointerup", o), r.removeEventListener("pointercancel", o), r.removeEventListener("wheel", c);
1711
+ };
1712
+ }
1713
+ function gt(r, e) {
1714
+ let t = !1, i = 0, s = 0;
1715
+ const n = (l) => {
1716
+ t = !0, i = l.clientX, s = l.clientY, r.setPointerCapture(l.pointerId);
1717
+ }, a = (l) => {
1718
+ if (!t) return;
1719
+ const h = l.clientX - i, u = l.clientY - s;
1720
+ i = l.clientX, s = l.clientY, e.orbit(-h * 0.01, -u * 0.01);
1721
+ }, o = (l) => {
1722
+ t = !1, r.hasPointerCapture(l.pointerId) && r.releasePointerCapture(l.pointerId);
1723
+ }, c = (l) => {
1724
+ l.preventDefault(), e.zoomBy(Math.exp(l.deltaY * 15e-4));
1725
+ };
1726
+ return r.addEventListener("pointerdown", n), r.addEventListener("pointermove", a), r.addEventListener("pointerup", o), r.addEventListener("pointercancel", o), r.addEventListener("wheel", c, { passive: !1 }), () => {
1727
+ r.removeEventListener("pointerdown", n), r.removeEventListener("pointermove", a), r.removeEventListener("pointerup", o), r.removeEventListener("pointercancel", o), r.removeEventListener("wheel", c);
1728
+ };
1729
+ }
1730
+ function bt(r) {
1731
+ return Math.ceil(r / 256) * 256;
1732
+ }
1733
+ async function vt(r, e, t, i) {
1734
+ const s = t * 4, n = bt(s), a = r.createBuffer({
1735
+ size: n * i,
1736
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
1737
+ }), o = r.createCommandEncoder();
1738
+ o.copyTextureToBuffer(
1739
+ { texture: e },
1740
+ { buffer: a, bytesPerRow: n, rowsPerImage: i },
1741
+ { width: t, height: i }
1742
+ ), r.queue.submit([o.finish()]), await a.mapAsync(GPUMapMode.READ);
1743
+ const c = new Uint8Array(a.getMappedRange()), l = new Uint8ClampedArray(s * i);
1744
+ for (let h = 0; h < i; h++) {
1745
+ const u = h * n;
1746
+ l.set(c.subarray(u, u + s), h * s);
1747
+ }
1748
+ return a.unmap(), a.destroy(), l;
1749
+ }
1750
+ function yt(r, e, t) {
1751
+ return 0.299 * r + 0.587 * e + 0.114 * t;
1752
+ }
1753
+ function wt(r, e) {
1754
+ if (e < 1) throw new Error("histogram bins must be >= 1.");
1755
+ const t = new Uint32Array(e), i = e / 256;
1756
+ for (let s = 0; s + 3 < r.length; s += 4) {
1757
+ const n = yt(r[s], r[s + 1], r[s + 2]);
1758
+ let a = Math.floor(n * i);
1759
+ a >= e ? a = e - 1 : a < 0 && (a = 0), t[a]++;
1760
+ }
1761
+ return { counts: t, bins: e, min: 0, max: 255 };
1762
+ }
1763
+ class _t {
1764
+ ready;
1765
+ model = new ft();
1766
+ canvas;
1767
+ background;
1768
+ useControls;
1769
+ ctx;
1770
+ target;
1771
+ renderer;
1772
+ detachControls;
1773
+ lastControlsNdisplay;
1774
+ frameScheduled = !1;
1775
+ firstImageFitted = !1;
1776
+ constructor(e) {
1777
+ this.canvas = e.canvas, this.background = e.background ?? { r: 0.07, g: 0.07, b: 0.09, a: 1 }, this.useControls = e.controls ?? !0, this.ready = this.init();
1778
+ }
1779
+ get camera() {
1780
+ return this.model.camera;
1781
+ }
1782
+ get layers() {
1783
+ return this.model.layers;
1784
+ }
1785
+ get dims() {
1786
+ return this.model.dims;
1787
+ }
1788
+ get camera3d() {
1789
+ return this.model.camera3d;
1790
+ }
1791
+ get device() {
1792
+ return this.ctx?.device;
1793
+ }
1794
+ async init() {
1795
+ this.ctx = await pe(), this.target = new be(this.canvas, this.ctx.device), this.target.syncSize(), this.renderer = new lt(this.ctx.device, this.target, {
1796
+ float32Filterable: this.ctx.features.float32Filterable,
1797
+ onNeedsRedraw: () => this.requestRender()
1798
+ });
1799
+ for (const e of this.model.layers)
1800
+ this.renderer.addLayer(e);
1801
+ this.model.layers.added.connect((e) => {
1802
+ this.renderer?.addLayer(e), this.requestRender();
1803
+ }), this.model.layers.removed.connect((e) => {
1804
+ this.renderer?.removeLayer(e.id), this.requestRender();
1805
+ }), this.model.changed.connect(() => this.requestRender()), this.useControls && (this.installControls(), this.model.dims.changed.connect(() => this.installControls())), this.requestRender();
1806
+ }
1807
+ /** Attach the 2D pan/zoom or 3D orbit controls to match `dims.ndisplay`. */
1808
+ installControls() {
1809
+ const e = this.model.dims.ndisplay;
1810
+ e !== this.lastControlsNdisplay && (this.lastControlsNdisplay = e, this.detachControls?.(), this.detachControls = e === 3 ? gt(this.canvas, this.model.camera3d) : pt(this.canvas, this.model.camera));
1811
+ }
1812
+ renderInputs() {
1813
+ return {
1814
+ camera2d: this.model.camera,
1815
+ camera3d: this.model.camera3d,
1816
+ ndisplay: this.model.dims.ndisplay,
1817
+ z: this.model.dims.z
1818
+ };
1819
+ }
1820
+ /** Add an image layer. Accepts typed pixels or a decoded image (see {@link ImageInput}). */
1821
+ addImage(e, t = {}) {
1822
+ const i = Me(e), s = new ie(i, t);
1823
+ return this.model.layers.add(s), this.model.dims.depth = Math.max(this.model.dims.depth, Le(i)), this.maybeFitFirst(i.width, i.height), s;
1824
+ }
1825
+ /** Add a points (scatter) layer. Positions are `[x, y]` pairs in data coordinates. */
1826
+ addPoints(e, t = {}) {
1827
+ const i = new re(e, t);
1828
+ return this.model.layers.add(i), i;
1829
+ }
1830
+ /** Add a labels (segmentation) layer from an 8-bit id image. */
1831
+ addLabels(e, t, i, s = {}) {
1832
+ const n = new se(e, t, i, s);
1833
+ return this.model.layers.add(n), this.maybeFitFirst(t, i), n;
1834
+ }
1835
+ /**
1836
+ * Add a 3D volume layer (uint8 scalar field, x-fastest). Switches the viewer to 3D
1837
+ * (`dims.ndisplay = 3`) and frames the orbit camera on the volume.
1838
+ */
1839
+ addVolume(e, t, i, s, n = {}) {
1840
+ const a = new ne(e, t, i, s, n);
1841
+ return this.model.layers.add(a), this.model.camera3d.frame(t, i, s), this.model.dims.ndisplay = 3, a;
1842
+ }
1843
+ /** Convert canvas client coordinates to data/world coordinates (for picking). */
1844
+ canvasToWorld(e, t) {
1845
+ const i = this.canvas.getBoundingClientRect(), s = e - i.left - i.width / 2, n = t - i.top - i.height / 2, { zoom: a } = this.model.camera, [o, c] = this.model.camera.center;
1846
+ return [o + s / a, c + n / a];
1847
+ }
1848
+ maybeFitFirst(e, t) {
1849
+ if (this.firstImageFitted) return;
1850
+ const i = this.canvas.clientWidth, s = this.canvas.clientHeight;
1851
+ i > 0 && s > 0 && (this.model.camera.fit(e, t, i, s), this.firstImageFitted = !0);
1852
+ }
1853
+ /** Request a coalesced redraw on the next animation frame. No-op until {@link ready}. */
1854
+ requestRender() {
1855
+ this.frameScheduled || !this.renderer || !this.target || (this.frameScheduled = !0, requestAnimationFrame(() => {
1856
+ this.frameScheduled = !1, this.renderFrame();
1857
+ }));
1858
+ }
1859
+ renderFrame() {
1860
+ !this.renderer || !this.target || (this.target.syncSize(), this.renderer.render(this.renderInputs(), this.allLayers(), this.background));
1861
+ }
1862
+ allLayers() {
1863
+ return this.model.layers.items;
1864
+ }
1865
+ /**
1866
+ * Read back the composited displayed pixels as RGBA8 (top row first), by rendering the
1867
+ * current scene into an offscreen texture at the canvas's device-pixel size.
1868
+ */
1869
+ async readDisplayedPixels() {
1870
+ if (!this.renderer || !this.target || !this.ctx)
1871
+ throw new Error("Viewer is not ready — await `viewer.ready` first.");
1872
+ const e = Math.max(1, this.canvas.width), t = Math.max(1, this.canvas.height), i = this.canvas.clientWidth || e, s = this.canvas.clientHeight || t, n = this.ctx.device.createTexture({
1873
+ size: [e, t],
1874
+ format: "rgba8unorm",
1875
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
1876
+ });
1877
+ this.renderer.renderInto(
1878
+ n.createView(),
1879
+ this.renderInputs(),
1880
+ this.allLayers(),
1881
+ i,
1882
+ s,
1883
+ this.background
1884
+ );
1885
+ const a = await vt(this.ctx.device, n, e, t);
1886
+ return n.destroy(), { width: e, height: t, channels: 4, data: a };
1887
+ }
1888
+ /** Composite the displayed image to a PNG `Blob`. */
1889
+ async screenshot() {
1890
+ const e = await this.readDisplayedPixels();
1891
+ if (typeof OffscreenCanvas < "u") {
1892
+ const n = new OffscreenCanvas(e.width, e.height), a = n.getContext("2d"), o = a.createImageData(e.width, e.height);
1893
+ return o.data.set(e.data), a.putImageData(o, 0, 0), n.convertToBlob({ type: "image/png" });
1894
+ }
1895
+ const t = document.createElement("canvas");
1896
+ t.width = e.width, t.height = e.height;
1897
+ const i = t.getContext("2d"), s = i.createImageData(e.width, e.height);
1898
+ return s.data.set(e.data), i.putImageData(s, 0, 0), new Promise((n, a) => {
1899
+ t.toBlob((o) => o ? n(o) : a(new Error("toBlob returned null")), "image/png");
1900
+ });
1901
+ }
1902
+ /** Luminance histogram (over `bins` bins) of the currently displayed composite. */
1903
+ async histogram(e = 256) {
1904
+ const t = await this.readDisplayedPixels();
1905
+ return wt(t.data, e);
1906
+ }
1907
+ dispose() {
1908
+ this.detachControls?.(), this.renderer?.dispose(), this.ctx?.device.destroy(), this.ctx = void 0, this.target = void 0, this.renderer = void 0;
1909
+ }
1910
+ }
1911
+ function Tt(r, e, t, i) {
1912
+ let s = -1, n = 1 / 0;
1913
+ const a = r.length >> 1;
1914
+ for (let o = 0; o < a; o++) {
1915
+ const c = r[o * 2] - t, l = r[o * 2 + 1] - i, h = c * c + l * l, u = e(o) / 2;
1916
+ h <= u * u && h < n && (n = h, s = o);
1917
+ }
1918
+ return s;
1919
+ }
1920
+ function xt(r, e, t, i, s) {
1921
+ const n = Math.max(t - e, 1e-8);
1922
+ let a = z((r - e) / n);
1923
+ return s && (a = 1 - a), Math.pow(a, i);
1924
+ }
1925
+ function St(r, e) {
1926
+ return e.colormap.sample(
1927
+ xt(r, e.climLo, e.climHi, e.gamma, e.invert)
1928
+ );
1929
+ }
1930
+ function Pt(r) {
1931
+ const e = [0, 0, 0];
1932
+ for (const t of r)
1933
+ e[0] += t[0], e[1] += t[1], e[2] += t[2];
1934
+ return [z(e[0]), z(e[1]), z(e[2])];
1935
+ }
1936
+ function z(r) {
1937
+ return r < 0 ? 0 : r > 1 ? 1 : r;
1938
+ }
1939
+ const Lt = "0.0.0";
1940
+ export {
1941
+ _e as BLUE,
1942
+ dt as Camera,
1943
+ ut as Camera3D,
1944
+ N as Colormap,
1945
+ ht as Dims,
1946
+ O as GRAY,
1947
+ xe as GREEN,
1948
+ ie as ImageLayer,
1949
+ b as LUT_SIZE,
1950
+ se as LabelsLayer,
1951
+ D as Layer,
1952
+ ct as LayerList,
1953
+ $e as LruCache,
1954
+ Se as MAGMA,
1955
+ K as NAMED_COLORMAPS,
1956
+ re as PointsLayer,
1957
+ we as RED,
1958
+ Lt as VERSION,
1959
+ Te as VIRIDIS,
1960
+ _t as Viewer,
1961
+ ft as ViewerModel,
1962
+ ne as VolumeLayer,
1963
+ $ as WebGPUUnsupportedError,
1964
+ pe as acquireDevice,
1965
+ Pt as additiveComposite,
1966
+ et as buildLabelLut,
1967
+ H as buildLut,
1968
+ Pe as channelsOf,
1969
+ Be as defaultContrastLimits,
1970
+ Le as depthOf,
1971
+ wt as histogramRGBA,
1972
+ Ue as isGrayscale,
1973
+ Ne as levelDims,
1974
+ he as levelScale,
1975
+ yt as luminance8,
1976
+ St as mapScalar,
1977
+ Tt as nearestPointIndex,
1978
+ A as resolveColormap,
1979
+ qe as selectLevel,
1980
+ Ye as tileGrid,
1981
+ Me as toTextureSource,
1982
+ Q as visibleTiles,
1983
+ xt as windowGamma,
1984
+ We as worldViewport
1985
+ };
1986
+ //# sourceMappingURL=napari-js.js.map