@pixerate/editor 0.214.0 → 0.216.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 (40) hide show
  1. package/dist/{chunk-UOGKSVRQ.cjs → chunk-B46UQUDC.cjs} +770 -733
  2. package/dist/{chunk-W3TOZTL7.js → chunk-F7Z2MKY2.js} +115 -4
  3. package/dist/{chunk-YBJXCQTR.js → chunk-QXY4YTRI.js} +738 -701
  4. package/dist/{chunk-H65IBXFI.cjs → chunk-S55JFWDA.cjs} +350 -239
  5. package/dist/{chunk-QYLWG22N.cjs → chunk-SS53NNZO.cjs} +304 -1
  6. package/dist/{chunk-LQ5SALHK.js → chunk-U7C655AM.js} +5 -311
  7. package/dist/{chunk-L52VMQZS.cjs → chunk-UC3PWFM3.cjs} +68 -374
  8. package/dist/{chunk-L3G23N5Y.js → chunk-ZNA3IQZF.js} +304 -1
  9. package/dist/image-editor/controller.d.cts +41 -0
  10. package/dist/image-editor/controller.d.ts +41 -0
  11. package/dist/image-editor/index.cjs +1461 -1125
  12. package/dist/image-editor/index.d.cts +1 -0
  13. package/dist/image-editor/index.d.ts +1 -0
  14. package/dist/image-editor/index.js +384 -48
  15. package/dist/image-editor/layers/edge-color.d.cts +27 -0
  16. package/dist/image-editor/layers/edge-color.d.ts +27 -0
  17. package/dist/image-editor/layers/serialize.d.cts +13 -0
  18. package/dist/image-editor/layers/serialize.d.ts +13 -0
  19. package/dist/image-editor/selection/index.d.cts +1 -0
  20. package/dist/image-editor/selection/index.d.ts +1 -0
  21. package/dist/image-editor/selection/inspect.d.cts +9 -0
  22. package/dist/image-editor/selection/inspect.d.ts +9 -0
  23. package/dist/image-editor/selection/uncertainty.d.cts +40 -0
  24. package/dist/image-editor/selection/uncertainty.d.ts +40 -0
  25. package/dist/image-editor/selection-worker.cjs +4 -4
  26. package/dist/image-editor/selection-worker.js +2 -2
  27. package/dist/paint-editor/index.cjs +79 -79
  28. package/dist/paint-editor/index.js +3 -3
  29. package/dist/raster/compositor.d.cts +10 -1
  30. package/dist/raster/compositor.d.ts +10 -1
  31. package/dist/raster/deep-compositor.d.cts +1 -1
  32. package/dist/raster/deep-compositor.d.ts +1 -1
  33. package/dist/raster/index.cjs +4 -2
  34. package/dist/raster/index.js +3 -1
  35. package/dist/raster/layers.d.cts +16 -0
  36. package/dist/raster/layers.d.ts +16 -0
  37. package/package.json +1 -1
  38. package/skills/pixerate-image-editor/SKILL.md +9 -7
  39. package/skills/pixerate-image-editor/references/paths.md +12 -0
  40. package/skills/pixerate-image-selection-masking/SKILL.md +11 -11
@@ -469,63 +469,6 @@ function stableStringify(v) {
469
469
  return JSON.stringify(v);
470
470
  }
471
471
 
472
- // src/raster/curve.ts
473
- var CURVE_LUT_SIZE = 4096;
474
- var clamp01 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
475
- function curveLut(points, size = CURVE_LUT_SIZE) {
476
- const pts = points.filter((p) => Number.isFinite(p.input) && Number.isFinite(p.output)).map((p) => [p.input, p.output]).sort((a, b) => a[0] - b[0]);
477
- const dedup = [];
478
- for (const p of pts) {
479
- if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;
480
- dedup.push(p);
481
- }
482
- const out = new Float32Array(size);
483
- const n = dedup.length;
484
- if (n < 2) {
485
- for (let k = 0; k < size; k++) out[k] = k / (size - 1);
486
- return out;
487
- }
488
- const x = dedup.map((p) => p[0]);
489
- const y = dedup.map((p) => p[1]);
490
- const m2 = new Float64Array(n);
491
- if (n > 2) {
492
- const c = new Float64Array(n);
493
- const d = new Float64Array(n);
494
- for (let i = 1; i < n - 1; i++) {
495
- const h0 = x[i] - x[i - 1];
496
- const h1 = x[i + 1] - x[i];
497
- const a = h0 / 6;
498
- const b = (h0 + h1) / 3;
499
- const cc = h1 / 6;
500
- const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;
501
- const denom = b - a * c[i - 1];
502
- c[i] = cc / denom;
503
- d[i] = (r - a * d[i - 1]) / denom;
504
- }
505
- for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];
506
- }
507
- let seg = 0;
508
- for (let k = 0; k < size; k++) {
509
- const xv = k / (size - 1);
510
- if (xv <= x[0]) {
511
- out[k] = clamp01(y[0]);
512
- continue;
513
- }
514
- if (xv >= x[n - 1]) {
515
- out[k] = clamp01(y[n - 1]);
516
- continue;
517
- }
518
- while (seg < n - 2 && xv > x[seg + 1]) seg++;
519
- const x0 = x[seg], x1 = x[seg + 1];
520
- const h = x1 - x0;
521
- const a = (x1 - xv) / h;
522
- const b = (xv - x0) / h;
523
- const v = a * y[seg] + b * y[seg + 1] + ((a * a * a - a) * m2[seg] + (b * b * b - b) * m2[seg + 1]) * (h * h) / 6;
524
- out[k] = clamp01(v);
525
- }
526
- return out;
527
- }
528
-
529
472
  // src/raster/effects/patterns.ts
530
473
  var PatternTile = class {
531
474
  w;
@@ -655,6 +598,63 @@ function hashNoise(x, y) {
655
598
  return (h & 65535) / 65536;
656
599
  }
657
600
 
601
+ // src/raster/curve.ts
602
+ var CURVE_LUT_SIZE = 4096;
603
+ var clamp01 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
604
+ function curveLut(points, size = CURVE_LUT_SIZE) {
605
+ const pts = points.filter((p) => Number.isFinite(p.input) && Number.isFinite(p.output)).map((p) => [p.input, p.output]).sort((a, b) => a[0] - b[0]);
606
+ const dedup = [];
607
+ for (const p of pts) {
608
+ if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;
609
+ dedup.push(p);
610
+ }
611
+ const out = new Float32Array(size);
612
+ const n = dedup.length;
613
+ if (n < 2) {
614
+ for (let k = 0; k < size; k++) out[k] = k / (size - 1);
615
+ return out;
616
+ }
617
+ const x = dedup.map((p) => p[0]);
618
+ const y = dedup.map((p) => p[1]);
619
+ const m2 = new Float64Array(n);
620
+ if (n > 2) {
621
+ const c = new Float64Array(n);
622
+ const d = new Float64Array(n);
623
+ for (let i = 1; i < n - 1; i++) {
624
+ const h0 = x[i] - x[i - 1];
625
+ const h1 = x[i + 1] - x[i];
626
+ const a = h0 / 6;
627
+ const b = (h0 + h1) / 3;
628
+ const cc = h1 / 6;
629
+ const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;
630
+ const denom = b - a * c[i - 1];
631
+ c[i] = cc / denom;
632
+ d[i] = (r - a * d[i - 1]) / denom;
633
+ }
634
+ for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];
635
+ }
636
+ let seg = 0;
637
+ for (let k = 0; k < size; k++) {
638
+ const xv = k / (size - 1);
639
+ if (xv <= x[0]) {
640
+ out[k] = clamp01(y[0]);
641
+ continue;
642
+ }
643
+ if (xv >= x[n - 1]) {
644
+ out[k] = clamp01(y[n - 1]);
645
+ continue;
646
+ }
647
+ while (seg < n - 2 && xv > x[seg + 1]) seg++;
648
+ const x0 = x[seg], x1 = x[seg + 1];
649
+ const h = x1 - x0;
650
+ const a = (x1 - xv) / h;
651
+ const b = (xv - x0) / h;
652
+ const v = a * y[seg] + b * y[seg + 1] + ((a * a * a - a) * m2[seg] + (b * b * b - b) * m2[seg + 1]) * (h * h) / 6;
653
+ out[k] = clamp01(v);
654
+ }
655
+ return out;
656
+ }
657
+
658
658
  // src/raster/effects/render.ts
659
659
  var INSIDE_EPS = 0.5 / 255;
660
660
  var INF = 1e20;
@@ -1927,475 +1927,83 @@ function renderLayerEffects(effects, layer, backdrop, canvas, ctx) {
1927
1927
  });
1928
1928
  }
1929
1929
 
1930
- // src/raster/deep-compositor.ts
1931
- var isGroup = (l) => l.type === "group";
1932
- var DeepCompositor = class {
1933
- constructor(base, profile, deep, depth) {
1934
- this.base = base;
1935
- this.profile = profile;
1936
- this.deep = deep;
1937
- this.depth = depth;
1938
- this.linear = depth === 32;
1939
- }
1940
- base;
1941
- profile;
1942
- deep;
1943
- depth;
1944
- sources;
1945
- linear;
1946
- /** The composite of `layers` over `rect` as float RGBA in the working space (`rect.width × rect.height × 4`). */
1947
- composite(layers, rect, sources) {
1948
- this.sources = sources;
1949
- const acc = new Float32Array(Math.max(0, rect.width * rect.height * 4));
1950
- if (rect.width > 0 && rect.height > 0) this.runSteps(this.buildSteps(layers), acc, true, rect);
1951
- return acc;
1952
- }
1953
- // --- Steps (as RasterCompositor.buildSteps) ------------------------------------
1954
- isPixel(l) {
1955
- return l.type === "pixel" || (this.profile.isPixelLike?.(l) ?? false);
1956
- }
1957
- isFilter(l) {
1958
- return this.profile.isFilterLayer?.(l) ?? false;
1959
- }
1960
- buildSteps(layers) {
1961
- const steps = [];
1962
- for (const layer of layers) {
1963
- const last = steps[steps.length - 1];
1964
- if (layer.clipped && last && (last.kind === "content" || last.kind === "group")) {
1965
- last.clipped.push(layer);
1966
- continue;
1967
- }
1968
- if (this.isFilter(layer)) {
1969
- if (last && last.kind === "adjust") last.layers.push(layer);
1970
- else steps.push({ kind: "adjust", layers: [layer] });
1971
- } else if (isGroup(layer)) steps.push({ kind: "group", layer, clipped: [], steps: this.buildSteps(layer.children ?? []) });
1972
- else steps.push({ kind: "content", layer, clipped: [] });
1930
+ // src/raster/compositor.ts
1931
+ var TILE_PIXELS = TILE * TILE;
1932
+ var TILE_BYTES = TILE_PIXELS * 4;
1933
+ function applyEdgeAmbient(content, below, coverage, amount, color2, max) {
1934
+ const k = Math.min(1, Math.max(0, amount));
1935
+ if (!(k > 0)) return;
1936
+ const n = content.length / 4;
1937
+ for (let i = 0; i < n; i++) {
1938
+ const a = content[i * 4 + 3] / max * (coverage ? coverage[i] : 1);
1939
+ if (a <= 0 || a >= 1) continue;
1940
+ let r;
1941
+ let g;
1942
+ let b;
1943
+ if (color2) [r, g, b] = color2;
1944
+ else {
1945
+ if (!(below[i * 4 + 3] > 0)) continue;
1946
+ r = below[i * 4];
1947
+ g = below[i * 4 + 1];
1948
+ b = below[i * 4 + 2];
1973
1949
  }
1974
- return steps;
1950
+ const t = k * (1 - a);
1951
+ content[i * 4] += t * (r - content[i * 4]);
1952
+ content[i * 4 + 1] += t * (g - content[i * 4 + 1]);
1953
+ content[i * 4 + 2] += t * (b - content[i * 4 + 2]);
1975
1954
  }
1976
- runSteps(steps, acc, empty, r) {
1977
- for (const s of steps) empty = this.runStep(s, acc, empty, r);
1978
- return empty;
1955
+ }
1956
+ function sameSig(a, b) {
1957
+ if (!a || a.key !== b.key || a.refs.length !== b.refs.length) return false;
1958
+ for (let i = 0; i < a.refs.length; i++) if (a.refs[i] !== b.refs[i]) return false;
1959
+ return true;
1960
+ }
1961
+ function dirtyInTile(rects, tx, ty) {
1962
+ let x0 = Infinity;
1963
+ let y0 = Infinity;
1964
+ let x1 = -Infinity;
1965
+ let y1 = -Infinity;
1966
+ const ox = tx * TILE;
1967
+ const oy = ty * TILE;
1968
+ for (const r of rects) {
1969
+ const a = Math.max(ox, Math.floor(r.x));
1970
+ const b = Math.max(oy, Math.floor(r.y));
1971
+ const c = Math.min(ox + TILE, Math.ceil(r.x + r.width));
1972
+ const d = Math.min(oy + TILE, Math.ceil(r.y + r.height));
1973
+ if (c <= a || d <= b) continue;
1974
+ x0 = Math.min(x0, a);
1975
+ y0 = Math.min(y0, b);
1976
+ x1 = Math.max(x1, c);
1977
+ y1 = Math.max(y1, d);
1979
1978
  }
1980
- runStep(step, acc, empty, r) {
1981
- if (step.kind === "adjust") {
1982
- if (empty) return true;
1983
- this.applyFilterRun(step.layers, acc, r);
1984
- return false;
1985
- }
1986
- const layer = step.layer;
1987
- if (!layer.visible) return empty;
1988
- const clipped = step.clipped.filter((l) => l.visible);
1989
- if ((step.kind === "content" || step.kind === "group") && this.deepEffects(layer)) return this.runEffects(layer, clipped, acc, r, step.kind === "group" ? step.steps : null) ? false : empty;
1990
- const coverage = this.renderedAlone(layer) ? null : this.base.maskCoverage(layer, r, this.sources);
1991
- if (coverage && coverage.every((v) => v <= 0)) return empty;
1992
- if (step.kind === "group" && !this.renderedAlone(layer)) {
1993
- if (layer.blend === "pass-through" && !clipped.length) {
1994
- if (layer.opacity >= 1 && !coverage) return this.runSteps(step.steps, acc, empty, r);
1995
- const before = acc.slice();
1996
- const stillEmpty = this.runSteps(step.steps, acc, empty, r);
1997
- lerp(acc, before, layer.opacity, coverage);
1998
- return empty && stillEmpty;
1999
- }
2000
- const buf2 = new Float32Array(acc.length);
2001
- if (this.runSteps(step.steps, buf2, true, r)) return empty;
2002
- this.applyClipped(clipped, buf2, r);
2003
- const mode2 = layer.blend === "pass-through" ? "normal" : layer.blend;
2004
- this.blend(acc, buf2, mode2, layer.opacity, coverage);
2005
- return false;
1979
+ return x0 === Infinity ? null : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
1980
+ }
1981
+ function blendTile(dst, src, mode, alpha, coverage) {
1982
+ if (!(alpha > 0)) return;
1983
+ const normal = mode === "normal";
1984
+ const pigment = mode === "pigment";
1985
+ const lut = !normal && isSeparableBlendMode(mode) ? separableLut(mode) : null;
1986
+ const cb = [0, 0, 0];
1987
+ const cs = [0, 0, 0];
1988
+ const n = dst.length;
1989
+ for (let i = 0, p = 0; i < n; i += 4, p++) {
1990
+ const sa = src[i + 3];
1991
+ if (sa === 0) continue;
1992
+ const k = coverage ? alpha * coverage[p] : alpha;
1993
+ if (!(k > 0)) continue;
1994
+ if (normal && sa === 255 && k >= 1) {
1995
+ dst[i] = src[i];
1996
+ dst[i + 1] = src[i + 1];
1997
+ dst[i + 2] = src[i + 2];
1998
+ dst[i + 3] = 255;
1999
+ continue;
2006
2000
  }
2007
- const content = this.content(layer, r);
2008
- if (!content) return empty;
2009
- const alone = this.renderedAlone(layer);
2010
- const fill2 = !alone && this.isPixel(layer) ? layer.fillOpacity ?? 1 : 1;
2011
- const mode = this.blendOf(layer);
2012
- if (!clipped.length) {
2013
- this.blend(acc, content, mode, layer.opacity * fill2, coverage);
2014
- return false;
2015
- }
2016
- const buf = new Float32Array(acc.length);
2017
- this.blend(buf, content, "normal", fill2, coverage);
2018
- this.applyClipped(clipped, buf, r);
2019
- this.blend(acc, buf, mode, layer.opacity, null);
2020
- return false;
2021
- }
2022
- /**
2023
- * A pixel layer with effects onto `acc` over `r`: its deep content (filters, mask, clipped
2024
- * layers) and the effects composited in float. Effect maps come from the 8-bit shape. At 32
2025
- * bits the effects composite in sRGB-encoded values (their colours and blends are defined
2026
- * there) and the result is decoded back. Returns whether anything was drawn.
2027
- */
2028
- runEffects(layer, clipped, acc, r, groupSteps = null) {
2029
- const fx = this.base.effectsInput(layer, r, this.sources);
2030
- if (!fx) return false;
2031
- const a = fx.rect;
2032
- let content;
2033
- if (groupSteps) {
2034
- content = new Float32Array(a.width * a.height * 4);
2035
- this.runSteps(groupSteps, content, true, a);
2036
- } else content = this.content(layer, a) ?? new Float32Array(a.width * a.height * 4);
2037
- const coverage = this.base.maskCoverage(layer, a, this.sources);
2038
- if (coverage) for (let i = 0; i < coverage.length; i++) content[i * 4 + 3] *= coverage[i];
2039
- if (clipped.length) this.applyClipped(clipped, content, a);
2040
- const sub = new Float32Array(a.width * a.height * 4);
2041
- for (let y = 0; y < a.height; y++) {
2042
- const s = ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4;
2043
- sub.set(acc.subarray(s, s + a.width * 4), y * a.width * 4);
2044
- }
2045
- if (this.linear) {
2046
- encodeInPlace(sub);
2047
- encodeInPlace(content);
2048
- }
2049
- compositeLayerEffects({ ...fx, backdrop: sub, content });
2050
- if (this.linear) decodeInPlace(sub);
2051
- for (let y = 0; y < a.height; y++) acc.set(sub.subarray(y * a.width * 4, (y + 1) * a.width * 4), ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4);
2052
- return true;
2053
- }
2054
- applyClipped(clipped, buf, r) {
2055
- let run = [];
2056
- const flush = () => {
2057
- if (run.length) this.applyFilterRun(run, buf, r);
2058
- run = [];
2059
- };
2060
- for (const c of clipped) {
2061
- if (this.isFilter(c)) {
2062
- run.push(c);
2063
- continue;
2064
- }
2065
- flush();
2066
- const alone = this.renderedAlone(c);
2067
- const coverage = alone ? null : this.base.maskCoverage(c, r, this.sources);
2068
- let src;
2069
- if (isGroup(c) && !alone) {
2070
- src = new Float32Array(buf.length);
2071
- if (this.runSteps(this.buildSteps(c.children ?? []), src, true, r)) continue;
2072
- } else src = this.content(c, r);
2073
- if (!src) continue;
2074
- const fill2 = !alone && this.isPixel(c) ? c.fillOpacity ?? 1 : 1;
2075
- const keep = new Float32Array(buf.length >> 2);
2076
- for (let i = 3, p = 0; i < buf.length; i += 4, p++) keep[p] = buf[i];
2077
- this.blend(buf, src, c.blend === "pass-through" ? "normal" : this.blendOf(c), c.opacity * fill2, coverage);
2078
- for (let i = 3, p = 0; i < buf.length; i += 4, p++) buf[i] = keep[p];
2079
- }
2080
- flush();
2081
- }
2082
- applyFilterRun(layers, acc, r) {
2083
- const visible = [];
2084
- let masks;
2085
- for (const node of layers) {
2086
- if (!node.visible) continue;
2087
- const cov = this.base.maskCoverage(node, r, this.sources);
2088
- if (cov) {
2089
- if (cov.every((v) => v <= 0)) continue;
2090
- (masks ??= /* @__PURE__ */ new Map()).set(node.id, cov);
2091
- }
2092
- visible.push(node);
2093
- }
2094
- if (!visible.length) return;
2095
- if (this.deep.applyFilterRunDeep) {
2096
- if (this.linear) mapColour(acc, srgbEncode);
2097
- this.deep.applyFilterRunDeep(visible, acc, r, masks, this.sources);
2098
- if (this.linear) mapColour(acc, srgbDecode);
2099
- return;
2100
- }
2101
- const bytes = this.to8(acc);
2102
- this.profile.applyFilterRun?.(visible, bytes, r, masks, this.sources);
2103
- acc.set(this.from8(bytes));
2104
- }
2105
- // --- Layer content ---------------------------------------------------------------
2106
- /** Layers drawn by the 8-bit compositor alone (effects, pixel filters, content the editor draws). */
2107
- /** A pixel(-like) layer or a group with effects: composited in float through `compositeLayerEffects`. */
2108
- deepEffects(layer) {
2109
- return hasActiveEffects(layer.effects) && !this.isFilter(layer) && (isGroup(layer) || this.isPixel(layer));
2110
- }
2111
- renderedAlone(layer) {
2112
- if (hasActiveEffects(layer.effects) && !this.isFilter(layer) && !this.deepEffects(layer)) return true;
2113
- if (this.isPixel(layer)) return Boolean(this.profile.pixelFilters?.(layer)) && !this.deep.pixelFiltersDeep?.(layer);
2114
- return !isGroup(layer);
2115
- }
2116
- blendOf(layer) {
2117
- return this.profile.contentBlend?.(layer) ?? layer.blend;
2118
- }
2119
- /** A layer's float RGBA over `r` (straight alpha, working space), or `null` when it has none there. */
2120
- content(layer, r) {
2121
- if (this.renderedAlone(layer)) {
2122
- const solo = { ...layer, blend: isGroup(layer) && layer.blend === "pass-through" ? "pass-through" : "normal", opacity: 1, clipped: false };
2123
- const bytes = this.base.composite([solo], r, this.sources);
2124
- return this.from8(bytes);
2125
- }
2126
- const surface = this.sources.surfaceOf ? this.sources.surfaceOf(layer) : layer.surface;
2127
- if (!surface) return null;
2128
- const ox = layer.offset?.x ?? 0;
2129
- const oy = layer.offset?.y ?? 0;
2130
- const filters = this.isPixel(layer) ? this.deep.pixelFiltersDeep?.(layer) ?? null : null;
2131
- if (!filters) return this.samples(surface, { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height });
2132
- const m = filters.reach;
2133
- const W = this.sources.width;
2134
- const H = this.sources.height;
2135
- const x0 = m ? Math.max(0, r.x - m) : r.x;
2136
- const y0 = m ? Math.max(0, r.y - m) : r.y;
2137
- const x1 = m ? Math.min(W, r.x + r.width + m) : r.x + r.width;
2138
- const y1 = m ? Math.min(H, r.y + r.height + m) : r.y + r.height;
2139
- if (x1 <= x0 || y1 <= y0) return new Float32Array(r.width * r.height * 4);
2140
- const big = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
2141
- const data = this.samples(surface, { ...big, x: big.x - ox, y: big.y - oy });
2142
- filters.apply(data, big.width, big.height, this.linear);
2143
- if (big.x === r.x && big.y === r.y && big.width === r.width && big.height === r.height) return data;
2144
- const out = new Float32Array(r.width * r.height * 4);
2145
- for (let y = 0; y < r.height; y++) {
2146
- const sy = r.y + y - big.y;
2147
- if (sy < 0 || sy >= big.height) continue;
2148
- for (let x = 0; x < r.width; x++) {
2149
- const sx = r.x + x - big.x;
2150
- if (sx < 0 || sx >= big.width) continue;
2151
- const s = (sy * big.width + sx) * 4;
2152
- const d = (y * r.width + x) * 4;
2153
- out[d] = data[s];
2154
- out[d + 1] = data[s + 1];
2155
- out[d + 2] = data[s + 2];
2156
- out[d + 3] = data[s + 3];
2157
- }
2158
- }
2159
- return out;
2160
- }
2161
- /** A surface's samples over `rect` (layer space) in the working space. */
2162
- samples(surface, rect) {
2163
- const samples = readDeepRegion(surface, rect, this.depth);
2164
- if (samples instanceof Float32Array) return samples;
2165
- const out = new Float32Array(samples.length);
2166
- for (let i = 0; i < samples.length; i++) out[i] = samples[i] / 65535;
2167
- return out;
2168
- }
2169
- /** 8-bit RGBA to the working space. */
2170
- from8(bytes) {
2171
- if (this.linear) return to32From8(bytes, 4);
2172
- const out = new Float32Array(bytes.length);
2173
- for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;
2174
- return out;
2175
- }
2176
- /** The working space to 8-bit RGBA. */
2177
- to8(acc) {
2178
- const out = new Uint8ClampedArray(acc.length);
2179
- for (let i = 0; i < acc.length; i += 4) {
2180
- for (let c = 0; c < 3; c++) out[i + c] = Math.round((this.linear ? srgbEncode(acc[i + c]) : acc[i + c]) * 255);
2181
- out[i + 3] = Math.round(acc[i + 3] * 255);
2182
- }
2183
- return out;
2184
- }
2185
- // --- Blending --------------------------------------------------------------------
2186
- /** `src` over `dst` with `mode` at `alpha` × coverage (straight alpha, float), as `blendTile`. */
2187
- blend(dst, src, mode, alpha, coverage) {
2188
- if (!(alpha > 0)) return;
2189
- const normal = mode === "normal";
2190
- const pigment = mode === "pigment";
2191
- const cb = [0, 0, 0];
2192
- const cs = [0, 0, 0];
2193
- const mix = [0, 0, 0];
2194
- for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
2195
- const sa = src[i + 3];
2196
- if (!(sa > 0)) continue;
2197
- const k = coverage ? alpha * coverage[p] : alpha;
2198
- if (!(k > 0)) continue;
2199
- const as = Math.min(1, sa * k);
2200
- const ab = dst[i + 3];
2201
- const ao = as + ab * (1 - as);
2202
- if (ao <= 0) {
2203
- dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;
2204
- continue;
2205
- }
2206
- if (normal && as >= 1) {
2207
- dst[i] = src[i];
2208
- dst[i + 1] = src[i + 1];
2209
- dst[i + 2] = src[i + 2];
2210
- dst[i + 3] = 1;
2211
- continue;
2212
- }
2213
- for (let c = 0; c < 3; c++) {
2214
- cb[c] = dst[i + c];
2215
- cs[c] = src[i + c];
2216
- }
2217
- if (pigment) {
2218
- if (this.linear) for (let c = 0; c < 3; c++) cb[c] = srgbEncode(cb[c]), cs[c] = srgbEncode(cs[c]);
2219
- mixPigmentRgb(cb, cs, as / ao, mix);
2220
- for (let c = 0; c < 3; c++) dst[i + c] = this.linear ? srgbDecode(mix[c]) : mix[c];
2221
- dst[i + 3] = ao;
2222
- continue;
2223
- }
2224
- const m = normal ? cs : blendRgb(mode, cb, cs);
2225
- const wb = (1 - as) * ab;
2226
- const ws = (1 - ab) * as;
2227
- const wm = as * ab;
2228
- for (let c = 0; c < 3; c++) dst[i + c] = (wb * cb[c] + ws * cs[c] + wm * m[c]) / ao;
2229
- dst[i + 3] = ao;
2230
- }
2231
- }
2232
- };
2233
- function lerp(dst, before, alpha, coverage) {
2234
- for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
2235
- const k = coverage ? alpha * coverage[p] : alpha;
2236
- if (k >= 1) continue;
2237
- for (let c = 0; c < 4; c++) dst[i + c] = before[i + c] + (dst[i + c] - before[i + c]) * k;
2238
- }
2239
- }
2240
- function mapColour(data, f) {
2241
- for (let i = 0; i < data.length; i += 4) {
2242
- data[i] = f(data[i]);
2243
- data[i + 1] = f(data[i + 1]);
2244
- data[i + 2] = f(data[i + 2]);
2245
- }
2246
- }
2247
- function workingToSamples(data, depth) {
2248
- if (depth === 32) return data;
2249
- const out = new Uint16Array(data.length);
2250
- for (let i = 0; i < data.length; i++) {
2251
- const v = data[i];
2252
- out[i] = v > 0 ? v < 1 ? Math.round(v * 65535) : 65535 : 0;
2253
- }
2254
- return out;
2255
- }
2256
- function encodeInPlace(d) {
2257
- for (let i = 0; i < d.length; i += 4) {
2258
- d[i] = srgbEncode(d[i]);
2259
- d[i + 1] = srgbEncode(d[i + 1]);
2260
- d[i + 2] = srgbEncode(d[i + 2]);
2261
- }
2262
- }
2263
- function decodeInPlace(d) {
2264
- for (let i = 0; i < d.length; i += 4) {
2265
- d[i] = srgbDecode(d[i]);
2266
- d[i + 1] = srgbDecode(d[i + 1]);
2267
- d[i + 2] = srgbDecode(d[i + 2]);
2268
- }
2269
- }
2270
-
2271
- // src/raster/layers.ts
2272
- var childrenOf = (l) => l.type === "group" ? l.children ?? null : null;
2273
- function flattenLayers(layers) {
2274
- const out = [];
2275
- const walk = (list) => {
2276
- for (const l of list) {
2277
- out.push(l);
2278
- const c = childrenOf(l);
2279
- if (c) walk(c);
2280
- }
2281
- };
2282
- walk(layers);
2283
- return out;
2284
- }
2285
- function findLayer(layers, id) {
2286
- const walk = (list, parent, path) => {
2287
- for (let i = 0; i < list.length; i++) {
2288
- const l = list[i];
2289
- if (l.id === id) return { layer: l, parent, index: i, path };
2290
- const c = childrenOf(l);
2291
- if (c) {
2292
- const g = l;
2293
- const hit = walk(c, g, [...path, g]);
2294
- if (hit) return hit;
2295
- }
2296
- }
2297
- return null;
2298
- };
2299
- return walk(layers, null, []);
2300
- }
2301
- function mapLayer(layers, id, fn) {
2302
- let changed = false;
2303
- const walk = (list) => {
2304
- let out = null;
2305
- for (let i = 0; i < list.length; i++) {
2306
- const l = list[i];
2307
- let next = l;
2308
- if (l.id === id) {
2309
- next = fn(l);
2310
- changed = true;
2311
- } else {
2312
- const c = childrenOf(l);
2313
- if (c) {
2314
- const children = walk(c);
2315
- if (children !== c) next = { ...l, children };
2316
- }
2317
- }
2318
- if (next !== l) {
2319
- if (!out) out = list.slice(0, i);
2320
- if (next) out.push(next);
2321
- } else if (out) out.push(l);
2322
- }
2323
- return out ?? list;
2324
- };
2325
- const result = walk(layers);
2326
- return changed ? result : layers;
2327
- }
2328
- function insertLayer(layers, layer, parentId, index) {
2329
- const put = (list) => {
2330
- const at = index === void 0 ? list.length : Math.max(0, Math.min(list.length, index));
2331
- const next = list.slice();
2332
- next.splice(at, 0, layer);
2333
- return next;
2334
- };
2335
- if (!parentId) return put(layers);
2336
- return mapLayer(layers, parentId, (g) => {
2337
- const c = childrenOf(g);
2338
- return c ? { ...g, children: put(c) } : g;
2339
- });
2340
- }
2341
- function isLayerLocked(layer, what) {
2342
- return Boolean(layer.locks?.all || layer.locks?.[what]);
2343
- }
2344
-
2345
- // src/raster/compositor.ts
2346
- var TILE_PIXELS = TILE * TILE;
2347
- var TILE_BYTES = TILE_PIXELS * 4;
2348
- function sameSig(a, b) {
2349
- if (!a || a.key !== b.key || a.refs.length !== b.refs.length) return false;
2350
- for (let i = 0; i < a.refs.length; i++) if (a.refs[i] !== b.refs[i]) return false;
2351
- return true;
2352
- }
2353
- function dirtyInTile(rects, tx, ty) {
2354
- let x0 = Infinity;
2355
- let y0 = Infinity;
2356
- let x1 = -Infinity;
2357
- let y1 = -Infinity;
2358
- const ox = tx * TILE;
2359
- const oy = ty * TILE;
2360
- for (const r of rects) {
2361
- const a = Math.max(ox, Math.floor(r.x));
2362
- const b = Math.max(oy, Math.floor(r.y));
2363
- const c = Math.min(ox + TILE, Math.ceil(r.x + r.width));
2364
- const d = Math.min(oy + TILE, Math.ceil(r.y + r.height));
2365
- if (c <= a || d <= b) continue;
2366
- x0 = Math.min(x0, a);
2367
- y0 = Math.min(y0, b);
2368
- x1 = Math.max(x1, c);
2369
- y1 = Math.max(y1, d);
2370
- }
2371
- return x0 === Infinity ? null : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
2372
- }
2373
- function blendTile(dst, src, mode, alpha, coverage) {
2374
- if (!(alpha > 0)) return;
2375
- const normal = mode === "normal";
2376
- const pigment = mode === "pigment";
2377
- const lut = !normal && isSeparableBlendMode(mode) ? separableLut(mode) : null;
2378
- const cb = [0, 0, 0];
2379
- const cs = [0, 0, 0];
2380
- const n = dst.length;
2381
- for (let i = 0, p = 0; i < n; i += 4, p++) {
2382
- const sa = src[i + 3];
2383
- if (sa === 0) continue;
2384
- const k = coverage ? alpha * coverage[p] : alpha;
2385
- if (!(k > 0)) continue;
2386
- if (normal && sa === 255 && k >= 1) {
2387
- dst[i] = src[i];
2388
- dst[i + 1] = src[i + 1];
2389
- dst[i + 2] = src[i + 2];
2390
- dst[i + 3] = 255;
2391
- continue;
2392
- }
2393
- const as = sa / 255 * k;
2394
- const ab = dst[i + 3] / 255;
2395
- const ao = as + ab * (1 - as);
2396
- if (ao <= 0) {
2397
- dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;
2398
- continue;
2001
+ const as = sa / 255 * k;
2002
+ const ab = dst[i + 3] / 255;
2003
+ const ao = as + ab * (1 - as);
2004
+ if (ao <= 0) {
2005
+ dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;
2006
+ continue;
2399
2007
  }
2400
2008
  const d0 = dst[i];
2401
2009
  const d1 = dst[i + 1];
@@ -2470,7 +2078,7 @@ function lerpTile(dst, before, alpha, coverage) {
2470
2078
  }
2471
2079
  }
2472
2080
  var ZERO_TILE = new Uint8ClampedArray(TILE_BYTES);
2473
- var isGroup2 = (l) => l.type === "group";
2081
+ var isGroup = (l) => l.type === "group";
2474
2082
  var maskDepths = /* @__PURE__ */ new WeakMap();
2475
2083
  function maskDepth(surface) {
2476
2084
  let d = maskDepths.get(surface);
@@ -2603,7 +2211,7 @@ var RasterCompositor = class {
2603
2211
  return {
2604
2212
  rect,
2605
2213
  maps,
2606
- blend: isGroup2(layer) ? layer.blend === "pass-through" ? "normal" : layer.blend : this.contentBlend(layer),
2214
+ blend: isGroup(layer) ? layer.blend === "pass-through" ? "normal" : layer.blend : this.contentBlend(layer),
2607
2215
  opacity: layer.opacity,
2608
2216
  fillOpacity: this.isPixel(layer) ? layer.fillOpacity ?? 1 : 1,
2609
2217
  canvas: { x: 0, y: 0, width: this.sources.width, height: this.sources.height },
@@ -2635,7 +2243,7 @@ var RasterCompositor = class {
2635
2243
  if (this.isFilter(layer)) {
2636
2244
  if (last && last.kind === "adjust") last.layers.push(layer);
2637
2245
  else steps.push({ kind: "adjust", layers: [layer] });
2638
- } else if (isGroup2(layer)) {
2246
+ } else if (isGroup(layer)) {
2639
2247
  steps.push({ kind: "group", layer, clipped: [], steps: this.buildSteps(layer.children ?? []) });
2640
2248
  } else {
2641
2249
  steps.push({ kind: "content", layer, clipped: [] });
@@ -2646,7 +2254,7 @@ var RasterCompositor = class {
2646
2254
  }
2647
2255
  /** The index of the top-level step that holds layer `id` (searching groups and clipped layers), or -1. */
2648
2256
  stepIndexOf(steps, id) {
2649
- const holds = (l) => l.id === id || isGroup2(l) && (l.children ?? []).some(holds);
2257
+ const holds = (l) => l.id === id || isGroup(l) && (l.children ?? []).some(holds);
2650
2258
  return steps.findIndex((s) => s.kind === "adjust" ? s.layers.some(holds) : holds(s.layer) || s.clipped.some(holds));
2651
2259
  }
2652
2260
  /** Properties of a layer that change pixels, minus its surfaces (memoised per object). */
@@ -2657,6 +2265,7 @@ var RasterCompositor = class {
2657
2265
  const base = [layer.type, layer.id, layer.visible, layer.opacity, layer.blend, Boolean(layer.clipped), mask];
2658
2266
  if (this.isPixel(layer)) base.push(layer.offset?.x ?? 0, layer.offset?.y ?? 0, layer.fillOpacity ?? 1);
2659
2267
  if (this.hasEffects(layer)) base.push(effectsFingerprint(layer.effects));
2268
+ if (layer.edge) base.push(layer.edge);
2660
2269
  const extra = this.profile.layerKey?.(layer);
2661
2270
  if (extra !== void 0) base.push(extra);
2662
2271
  key = JSON.stringify(base);
@@ -2794,7 +2403,7 @@ var RasterCompositor = class {
2794
2403
  }
2795
2404
  layerSig(layer, tx, ty) {
2796
2405
  if (this.isFilter(layer)) return { key: this.propsKey(layer), refs: this.maskRefs(layer, tx, ty, 0, 0) };
2797
- if (isGroup2(layer)) return this.groupSig(layer, this.buildSteps(layer.children ?? []), tx, ty);
2406
+ if (isGroup(layer)) return this.groupSig(layer, this.buildSteps(layer.children ?? []), tx, ty);
2798
2407
  return this.contentSig(layer, tx, ty);
2799
2408
  }
2800
2409
  groupSig(group, steps, tx, ty) {
@@ -2884,7 +2493,7 @@ var RasterCompositor = class {
2884
2493
  shapeSigs(layer, region) {
2885
2494
  const sigs = /* @__PURE__ */ new Map();
2886
2495
  if (region.width <= 0 || region.height <= 0) return sigs;
2887
- const steps = isGroup2(layer) ? this.buildSteps(layer.children ?? []) : null;
2496
+ const steps = isGroup(layer) ? this.buildSteps(layer.children ?? []) : null;
2888
2497
  for (const t of tilesInRect(region)) {
2889
2498
  sigs.set(tileKey(t.tx, t.ty), steps ? this.plainGroupSig(layer, steps, t.tx, t.ty) : this.plainContentSig(layer, t.tx, t.ty));
2890
2499
  }
@@ -2906,7 +2515,7 @@ var RasterCompositor = class {
2906
2515
  * laid out over the layer's bounds.
2907
2516
  */
2908
2517
  effectSig(layer, tx, ty) {
2909
- const key = (isGroup2(layer) ? "Gfx" : "Cfx") + this.propsKey(layer) + this.lightKey();
2518
+ const key = (isGroup(layer) ? "Gfx" : "Cfx") + this.propsKey(layer) + this.lightKey();
2910
2519
  const region = this.effectRegion(layer);
2911
2520
  const m = effectsMargin(layer.effects);
2912
2521
  if (effectsAreLocal(activeEffectItems(layer.effects))) {
@@ -2999,7 +2608,7 @@ var RasterCompositor = class {
2999
2608
  /** A content or group layer's own pixels over `r` with its mask applied to alpha, or `null` when empty. */
3000
2609
  ownPixels(layer, r) {
3001
2610
  let rgba;
3002
- if (isGroup2(layer)) {
2611
+ if (isGroup(layer)) {
3003
2612
  const buf = new Uint8ClampedArray(r.width * r.height * 4);
3004
2613
  rgba = this.runSteps(this.buildSteps(layer.children ?? []), buf, true, r) ? null : buf;
3005
2614
  } else {
@@ -3033,7 +2642,7 @@ var RasterCompositor = class {
3033
2642
  const content = this.ownPixels(layer, a) ?? new Uint8ClampedArray(a.width * a.height * 4);
3034
2643
  if (clipped.length) this.applyClipped(clipped, content, a);
3035
2644
  const keep = keepAlpha ? sub.filter((_, i) => (i & 3) === 3) : null;
3036
- const blend2 = isGroup2(layer) ? layer.blend === "pass-through" ? "normal" : layer.blend : this.contentBlend(layer);
2645
+ const blend2 = isGroup(layer) ? layer.blend === "pass-through" ? "normal" : layer.blend : this.contentBlend(layer);
3037
2646
  compositeLayerEffects({
3038
2647
  backdrop: sub,
3039
2648
  content,
@@ -3130,82 +2739,272 @@ var RasterCompositor = class {
3130
2739
  }
3131
2740
  return `${id}:${ox},${oy}:${W}x${H}:${mask.feather ?? 0}:${mask.density ?? 1}:${mask.source ? JSON.stringify(mask.source) : ""}`;
3132
2741
  }
3133
- /** Canvas-sized coverage of a feathered or parametric mask (cached). */
3134
- planeFor(mask, ox, oy) {
3135
- const key = this.planeKey(mask, ox, oy);
3136
- let plane = this.planeCache.get(key);
3137
- if (plane) {
3138
- this.planeCache.delete(key);
3139
- this.planeCache.set(key, plane);
3140
- return plane;
3141
- }
3142
- const W = this.sources.width;
3143
- const H = this.sources.height;
3144
- let v = new Float32Array(W * H);
3145
- if (mask.source) {
3146
- const generated = this.profile.maskSourcePlane?.(mask.source, W, H, this.sources) ?? null;
3147
- if (!generated || generated.length !== W * H) v.fill(1);
3148
- else v.set(generated);
3149
- } else {
3150
- const bytes = readRegion(mask.surface, { x: -ox, y: -oy, width: W, height: H });
3151
- for (let i = 0; i < v.length; i++) v[i] = bytes[i] / 255;
3152
- }
3153
- const feather = mask.feather ?? 0;
3154
- if (feather > 0) v = gaussianBlur(v, W, H, feather, "clamp");
3155
- const density = mask.density ?? 1;
3156
- if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);
3157
- plane = { data: v, width: W, height: H };
3158
- this.planeCache.set(key, plane);
3159
- while (this.planeCache.size > 8) this.planeCache.delete(this.planeCache.keys().next().value);
3160
- return plane;
2742
+ /** Canvas-sized coverage of a feathered or parametric mask (cached). */
2743
+ planeFor(mask, ox, oy) {
2744
+ const key = this.planeKey(mask, ox, oy);
2745
+ let plane = this.planeCache.get(key);
2746
+ if (plane) {
2747
+ this.planeCache.delete(key);
2748
+ this.planeCache.set(key, plane);
2749
+ return plane;
2750
+ }
2751
+ const W = this.sources.width;
2752
+ const H = this.sources.height;
2753
+ let v = new Float32Array(W * H);
2754
+ if (mask.source) {
2755
+ const generated = this.profile.maskSourcePlane?.(mask.source, W, H, this.sources) ?? null;
2756
+ if (!generated || generated.length !== W * H) v.fill(1);
2757
+ else v.set(generated);
2758
+ } else {
2759
+ const bytes = readRegion(mask.surface, { x: -ox, y: -oy, width: W, height: H });
2760
+ for (let i = 0; i < v.length; i++) v[i] = bytes[i] / 255;
2761
+ }
2762
+ const feather = mask.feather ?? 0;
2763
+ if (feather > 0) v = gaussianBlur(v, W, H, feather, "clamp");
2764
+ const density = mask.density ?? 1;
2765
+ if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);
2766
+ plane = { data: v, width: W, height: H };
2767
+ this.planeCache.set(key, plane);
2768
+ while (this.planeCache.size > 8) this.planeCache.delete(this.planeCache.keys().next().value);
2769
+ return plane;
2770
+ }
2771
+ maskTile(layer, r) {
2772
+ const mask = layer.mask;
2773
+ if (!mask || mask.enabled === false) return null;
2774
+ const ox = this.isPixel(layer) ? layer.offset?.x ?? 0 : 0;
2775
+ const oy = this.isPixel(layer) ? layer.offset?.y ?? 0 : 0;
2776
+ const n = r.width * r.height;
2777
+ const out = new Float32Array(n);
2778
+ const density = mask.density ?? 1;
2779
+ if (mask.source || (mask.feather ?? 0) > 0) {
2780
+ const plane = this.planeFor(mask, ox, oy);
2781
+ const W = plane.width;
2782
+ const H = plane.height;
2783
+ const outside = mask.source ? 1 : 1 - density * (1 - mask.defaultValue / 255);
2784
+ out.fill(outside);
2785
+ const x0 = Math.max(0, r.x);
2786
+ const y0 = Math.max(0, r.y);
2787
+ const x1 = Math.min(W, r.x + r.width);
2788
+ const y1 = Math.min(H, r.y + r.height);
2789
+ for (let y = y0; y < y1; y++) {
2790
+ const s = y * W;
2791
+ const d = (y - r.y) * r.width - r.x;
2792
+ for (let x = x0; x < x1; x++) out[d + x] = plane.data[s + x];
2793
+ }
2794
+ } else {
2795
+ const rect = { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height };
2796
+ let all = true;
2797
+ if (maskDepth(mask.surface) !== 8) {
2798
+ const d = readDeepRegion(mask.surface, rect, 32);
2799
+ for (let i = 0; i < n; i++) {
2800
+ const v = d[i] <= 0 ? 0 : d[i] >= 1 ? 1 : d[i];
2801
+ out[i] = v;
2802
+ if (v !== 1) all = false;
2803
+ }
2804
+ } else {
2805
+ const bytes = readRegion(mask.surface, rect);
2806
+ for (let i = 0; i < n; i++) {
2807
+ out[i] = bytes[i] / 255;
2808
+ if (bytes[i] !== 255) all = false;
2809
+ }
2810
+ }
2811
+ if (all) return null;
2812
+ if (density !== 1) for (let i = 0; i < n; i++) out[i] = 1 - density * (1 - out[i]);
2813
+ }
2814
+ return out;
2815
+ }
2816
+ // --- Running steps ---------------------------------------------------------
2817
+ /** Runs one step onto `acc`; returns whether `acc` is still empty. */
2818
+ runStep(step, acc, empty, r) {
2819
+ if (step.kind === "adjust") {
2820
+ if (empty) return true;
2821
+ this.applyFilterRun(step.layers, acc, r);
2822
+ return false;
2823
+ }
2824
+ const layer = step.layer;
2825
+ if (!layer.visible) return empty;
2826
+ const clipped = step.clipped.filter((l) => l.visible);
2827
+ if (this.hasEffects(layer)) return this.runEffectStep(layer, clipped, acc, empty, r);
2828
+ const coverage = this.maskTile(layer, r);
2829
+ if (coverage && coverage.every((v) => v <= 0)) return empty;
2830
+ if (step.kind === "group") {
2831
+ if (layer.blend === "pass-through" && !clipped.length) {
2832
+ if (layer.opacity >= 1 && !coverage) return this.runSteps(step.steps, acc, empty, r);
2833
+ const before = new Uint8ClampedArray(acc);
2834
+ const stillEmpty = this.runSteps(step.steps, acc, empty, r);
2835
+ lerpTile(acc, before, layer.opacity, coverage);
2836
+ return empty && stillEmpty;
2837
+ }
2838
+ const buf2 = new Uint8ClampedArray(acc.length);
2839
+ if (this.runSteps(step.steps, buf2, true, r)) return empty;
2840
+ this.applyClipped(clipped, buf2, r);
2841
+ const mode = layer.blend === "pass-through" ? "normal" : layer.blend;
2842
+ return this.blendOnto(acc, buf2, empty, mode, layer.opacity, coverage);
2843
+ }
2844
+ let content = this.contentTile(step.layer, r);
2845
+ if (!content) return empty;
2846
+ const ambient = layer.edge?.ambient ?? 0;
2847
+ if (ambient > 0 && (!empty || layer.edge?.ambientColor)) {
2848
+ content = new Uint8ClampedArray(content);
2849
+ applyEdgeAmbient(content, acc, coverage, ambient, layer.edge?.ambientColor ?? null, 255);
2850
+ }
2851
+ const fill2 = this.isPixel(step.layer) ? step.layer.fillOpacity ?? 1 : 1;
2852
+ const blend2 = this.contentBlend(step.layer);
2853
+ if (!clipped.length) return this.blendOnto(acc, content, empty, blend2, layer.opacity * fill2, coverage);
2854
+ const buf = new Uint8ClampedArray(acc.length);
2855
+ blendTile(buf, content, "normal", fill2, coverage);
2856
+ this.applyClipped(clipped, buf, r);
2857
+ return this.blendOnto(acc, buf, empty, blend2, layer.opacity, null);
2858
+ }
2859
+ runSteps(steps, acc, empty, r) {
2860
+ for (const s of steps) empty = this.runStep(s, acc, empty, r);
2861
+ return empty;
2862
+ }
2863
+ contentBlend(layer) {
2864
+ return this.profile.contentBlend?.(layer) ?? layer.blend;
2865
+ }
2866
+ blendOnto(acc, src, empty, mode, alpha, coverage) {
2867
+ if (!(alpha > 0)) return empty;
2868
+ if (empty && mode === "normal" && alpha >= 1 && !coverage) {
2869
+ acc.set(src);
2870
+ return false;
2871
+ }
2872
+ blendTile(acc, src, mode, alpha, coverage);
2873
+ return false;
2874
+ }
2875
+ applyClipped(clipped, buf, r) {
2876
+ let run = [];
2877
+ const flush = () => {
2878
+ if (run.length) this.applyFilterRun(run, buf, r);
2879
+ run = [];
2880
+ };
2881
+ for (const c of clipped) {
2882
+ if (this.isFilter(c)) {
2883
+ run.push(c);
2884
+ continue;
2885
+ }
2886
+ flush();
2887
+ if (this.hasEffects(c) && !isGroup(c)) {
2888
+ this.compositeEffects(c, [], buf, r, true);
2889
+ continue;
2890
+ }
2891
+ const coverage = this.maskTile(c, r);
2892
+ if (isGroup(c)) {
2893
+ const inner = new Uint8ClampedArray(buf.length);
2894
+ if (this.runSteps(this.buildSteps(c.children ?? []), inner, true, r)) continue;
2895
+ const mode = c.blend === "pass-through" ? "normal" : c.blend;
2896
+ blendAtop(buf, inner, mode, c.opacity, coverage);
2897
+ continue;
2898
+ }
2899
+ const content = this.contentTile(c, r);
2900
+ if (!content) continue;
2901
+ const fill2 = this.isPixel(c) ? c.fillOpacity ?? 1 : 1;
2902
+ blendAtop(buf, content, this.contentBlend(c), c.opacity * fill2, coverage);
2903
+ }
2904
+ flush();
2905
+ }
2906
+ applyFilterRun(layers, acc, r) {
2907
+ const visible = [];
2908
+ let masks;
2909
+ for (const node of layers) {
2910
+ if (!node.visible) continue;
2911
+ const cov = this.maskTile(node, r);
2912
+ if (cov) {
2913
+ if (cov.every((v) => v <= 0)) continue;
2914
+ (masks ??= /* @__PURE__ */ new Map()).set(node.id, cov);
2915
+ }
2916
+ visible.push(node);
2917
+ }
2918
+ if (visible.length) this.profile.applyFilterRun?.(visible, acc, r, masks, this.sources);
2919
+ }
2920
+ };
2921
+ function filterOverCanvas(surface, ox, oy, filters, width, height) {
2922
+ if (width <= 0 || height <= 0) return emptySurface(4);
2923
+ const data = readRegion(surface, { x: -ox, y: -oy, width, height });
2924
+ filters.apply(data, width, height);
2925
+ return surfaceFromPixels({ width, height, data }, 4, 0, 0);
2926
+ }
2927
+ function copyRect(data, r, a) {
2928
+ const out = new Uint8ClampedArray(a.width * a.height * 4);
2929
+ for (let y = 0; y < a.height; y++) {
2930
+ const s = ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4;
2931
+ out.set(data.subarray(s, s + a.width * 4), y * a.width * 4);
2932
+ }
2933
+ return out;
2934
+ }
2935
+ function pasteRect(data, r, part, a) {
2936
+ for (let y = 0; y < a.height; y++) data.set(part.subarray(y * a.width * 4, (y + 1) * a.width * 4), ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4);
2937
+ }
2938
+ function sameRect(a, b) {
2939
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
2940
+ }
2941
+ function inflateRect(r, m) {
2942
+ return { x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m };
2943
+ }
2944
+ function intersectRect(a, b) {
2945
+ const x0 = Math.max(a.x, b.x);
2946
+ const y0 = Math.max(a.y, b.y);
2947
+ const x1 = Math.min(a.x + a.width, b.x + b.width);
2948
+ const y1 = Math.min(a.y + a.height, b.y + b.height);
2949
+ return x1 <= x0 || y1 <= y0 ? { x: x0, y: y0, width: 0, height: 0 } : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
2950
+ }
2951
+ function changedTiles(a, b) {
2952
+ const out = [];
2953
+ for (const [k, t] of b.tiles) if (a.tiles.get(k) !== t) out.push(k);
2954
+ for (const k of a.tiles.keys()) if (!b.tiles.has(k)) out.push(k);
2955
+ return out;
2956
+ }
2957
+
2958
+ // src/raster/deep-compositor.ts
2959
+ var isGroup2 = (l) => l.type === "group";
2960
+ var DeepCompositor = class {
2961
+ constructor(base, profile, deep, depth) {
2962
+ this.base = base;
2963
+ this.profile = profile;
2964
+ this.deep = deep;
2965
+ this.depth = depth;
2966
+ this.linear = depth === 32;
2967
+ }
2968
+ base;
2969
+ profile;
2970
+ deep;
2971
+ depth;
2972
+ sources;
2973
+ linear;
2974
+ /** The composite of `layers` over `rect` as float RGBA in the working space (`rect.width × rect.height × 4`). */
2975
+ composite(layers, rect, sources) {
2976
+ this.sources = sources;
2977
+ const acc = new Float32Array(Math.max(0, rect.width * rect.height * 4));
2978
+ if (rect.width > 0 && rect.height > 0) this.runSteps(this.buildSteps(layers), acc, true, rect);
2979
+ return acc;
2980
+ }
2981
+ // --- Steps (as RasterCompositor.buildSteps) ------------------------------------
2982
+ isPixel(l) {
2983
+ return l.type === "pixel" || (this.profile.isPixelLike?.(l) ?? false);
2984
+ }
2985
+ isFilter(l) {
2986
+ return this.profile.isFilterLayer?.(l) ?? false;
3161
2987
  }
3162
- maskTile(layer, r) {
3163
- const mask = layer.mask;
3164
- if (!mask || mask.enabled === false) return null;
3165
- const ox = this.isPixel(layer) ? layer.offset?.x ?? 0 : 0;
3166
- const oy = this.isPixel(layer) ? layer.offset?.y ?? 0 : 0;
3167
- const n = r.width * r.height;
3168
- const out = new Float32Array(n);
3169
- const density = mask.density ?? 1;
3170
- if (mask.source || (mask.feather ?? 0) > 0) {
3171
- const plane = this.planeFor(mask, ox, oy);
3172
- const W = plane.width;
3173
- const H = plane.height;
3174
- const outside = mask.source ? 1 : 1 - density * (1 - mask.defaultValue / 255);
3175
- out.fill(outside);
3176
- const x0 = Math.max(0, r.x);
3177
- const y0 = Math.max(0, r.y);
3178
- const x1 = Math.min(W, r.x + r.width);
3179
- const y1 = Math.min(H, r.y + r.height);
3180
- for (let y = y0; y < y1; y++) {
3181
- const s = y * W;
3182
- const d = (y - r.y) * r.width - r.x;
3183
- for (let x = x0; x < x1; x++) out[d + x] = plane.data[s + x];
3184
- }
3185
- } else {
3186
- const rect = { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height };
3187
- let all = true;
3188
- if (maskDepth(mask.surface) !== 8) {
3189
- const d = readDeepRegion(mask.surface, rect, 32);
3190
- for (let i = 0; i < n; i++) {
3191
- const v = d[i] <= 0 ? 0 : d[i] >= 1 ? 1 : d[i];
3192
- out[i] = v;
3193
- if (v !== 1) all = false;
3194
- }
3195
- } else {
3196
- const bytes = readRegion(mask.surface, rect);
3197
- for (let i = 0; i < n; i++) {
3198
- out[i] = bytes[i] / 255;
3199
- if (bytes[i] !== 255) all = false;
3200
- }
2988
+ buildSteps(layers) {
2989
+ const steps = [];
2990
+ for (const layer of layers) {
2991
+ const last = steps[steps.length - 1];
2992
+ if (layer.clipped && last && (last.kind === "content" || last.kind === "group")) {
2993
+ last.clipped.push(layer);
2994
+ continue;
3201
2995
  }
3202
- if (all) return null;
3203
- if (density !== 1) for (let i = 0; i < n; i++) out[i] = 1 - density * (1 - out[i]);
2996
+ if (this.isFilter(layer)) {
2997
+ if (last && last.kind === "adjust") last.layers.push(layer);
2998
+ else steps.push({ kind: "adjust", layers: [layer] });
2999
+ } else if (isGroup2(layer)) steps.push({ kind: "group", layer, clipped: [], steps: this.buildSteps(layer.children ?? []) });
3000
+ else steps.push({ kind: "content", layer, clipped: [] });
3204
3001
  }
3205
- return out;
3002
+ return steps;
3003
+ }
3004
+ runSteps(steps, acc, empty, r) {
3005
+ for (const s of steps) empty = this.runStep(s, acc, empty, r);
3006
+ return empty;
3206
3007
  }
3207
- // --- Running steps ---------------------------------------------------------
3208
- /** Runs one step onto `acc`; returns whether `acc` is still empty. */
3209
3008
  runStep(step, acc, empty, r) {
3210
3009
  if (step.kind === "adjust") {
3211
3010
  if (empty) return true;
@@ -3215,131 +3014,368 @@ var RasterCompositor = class {
3215
3014
  const layer = step.layer;
3216
3015
  if (!layer.visible) return empty;
3217
3016
  const clipped = step.clipped.filter((l) => l.visible);
3218
- if (this.hasEffects(layer)) return this.runEffectStep(layer, clipped, acc, empty, r);
3219
- const coverage = this.maskTile(layer, r);
3017
+ if ((step.kind === "content" || step.kind === "group") && this.deepEffects(layer)) return this.runEffects(layer, clipped, acc, r, step.kind === "group" ? step.steps : null) ? false : empty;
3018
+ const coverage = this.renderedAlone(layer) ? null : this.base.maskCoverage(layer, r, this.sources);
3220
3019
  if (coverage && coverage.every((v) => v <= 0)) return empty;
3221
- if (step.kind === "group") {
3020
+ if (step.kind === "group" && !this.renderedAlone(layer)) {
3222
3021
  if (layer.blend === "pass-through" && !clipped.length) {
3223
3022
  if (layer.opacity >= 1 && !coverage) return this.runSteps(step.steps, acc, empty, r);
3224
- const before = new Uint8ClampedArray(acc);
3023
+ const before = acc.slice();
3225
3024
  const stillEmpty = this.runSteps(step.steps, acc, empty, r);
3226
- lerpTile(acc, before, layer.opacity, coverage);
3025
+ lerp(acc, before, layer.opacity, coverage);
3227
3026
  return empty && stillEmpty;
3228
3027
  }
3229
- const buf2 = new Uint8ClampedArray(acc.length);
3028
+ const buf2 = new Float32Array(acc.length);
3230
3029
  if (this.runSteps(step.steps, buf2, true, r)) return empty;
3231
3030
  this.applyClipped(clipped, buf2, r);
3232
- const mode = layer.blend === "pass-through" ? "normal" : layer.blend;
3233
- return this.blendOnto(acc, buf2, empty, mode, layer.opacity, coverage);
3031
+ const mode2 = layer.blend === "pass-through" ? "normal" : layer.blend;
3032
+ this.blend(acc, buf2, mode2, layer.opacity, coverage);
3033
+ return false;
3034
+ }
3035
+ const content = this.content(layer, r);
3036
+ if (!content) return empty;
3037
+ const ambient = layer.edge?.ambient ?? 0;
3038
+ if (ambient > 0) {
3039
+ const c = layer.edge?.ambientColor;
3040
+ const color2 = c ? c.map((v) => this.linear ? srgbDecode(v / 255) : v / 255) : null;
3041
+ if (color2 || !empty) applyEdgeAmbient(content, acc, coverage, ambient, color2, 1);
3042
+ }
3043
+ const alone = this.renderedAlone(layer);
3044
+ const fill2 = !alone && this.isPixel(layer) ? layer.fillOpacity ?? 1 : 1;
3045
+ const mode = this.blendOf(layer);
3046
+ if (!clipped.length) {
3047
+ this.blend(acc, content, mode, layer.opacity * fill2, coverage);
3048
+ return false;
3049
+ }
3050
+ const buf = new Float32Array(acc.length);
3051
+ this.blend(buf, content, "normal", fill2, coverage);
3052
+ this.applyClipped(clipped, buf, r);
3053
+ this.blend(acc, buf, mode, layer.opacity, null);
3054
+ return false;
3055
+ }
3056
+ /**
3057
+ * A pixel layer with effects onto `acc` over `r`: its deep content (filters, mask, clipped
3058
+ * layers) and the effects composited in float. Effect maps come from the 8-bit shape. At 32
3059
+ * bits the effects composite in sRGB-encoded values (their colours and blends are defined
3060
+ * there) and the result is decoded back. Returns whether anything was drawn.
3061
+ */
3062
+ runEffects(layer, clipped, acc, r, groupSteps = null) {
3063
+ const fx = this.base.effectsInput(layer, r, this.sources);
3064
+ if (!fx) return false;
3065
+ const a = fx.rect;
3066
+ let content;
3067
+ if (groupSteps) {
3068
+ content = new Float32Array(a.width * a.height * 4);
3069
+ this.runSteps(groupSteps, content, true, a);
3070
+ } else content = this.content(layer, a) ?? new Float32Array(a.width * a.height * 4);
3071
+ const coverage = this.base.maskCoverage(layer, a, this.sources);
3072
+ if (coverage) for (let i = 0; i < coverage.length; i++) content[i * 4 + 3] *= coverage[i];
3073
+ if (clipped.length) this.applyClipped(clipped, content, a);
3074
+ const sub = new Float32Array(a.width * a.height * 4);
3075
+ for (let y = 0; y < a.height; y++) {
3076
+ const s = ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4;
3077
+ sub.set(acc.subarray(s, s + a.width * 4), y * a.width * 4);
3078
+ }
3079
+ if (this.linear) {
3080
+ encodeInPlace(sub);
3081
+ encodeInPlace(content);
3082
+ }
3083
+ compositeLayerEffects({ ...fx, backdrop: sub, content });
3084
+ if (this.linear) decodeInPlace(sub);
3085
+ for (let y = 0; y < a.height; y++) acc.set(sub.subarray(y * a.width * 4, (y + 1) * a.width * 4), ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4);
3086
+ return true;
3087
+ }
3088
+ applyClipped(clipped, buf, r) {
3089
+ let run = [];
3090
+ const flush = () => {
3091
+ if (run.length) this.applyFilterRun(run, buf, r);
3092
+ run = [];
3093
+ };
3094
+ for (const c of clipped) {
3095
+ if (this.isFilter(c)) {
3096
+ run.push(c);
3097
+ continue;
3098
+ }
3099
+ flush();
3100
+ const alone = this.renderedAlone(c);
3101
+ const coverage = alone ? null : this.base.maskCoverage(c, r, this.sources);
3102
+ let src;
3103
+ if (isGroup2(c) && !alone) {
3104
+ src = new Float32Array(buf.length);
3105
+ if (this.runSteps(this.buildSteps(c.children ?? []), src, true, r)) continue;
3106
+ } else src = this.content(c, r);
3107
+ if (!src) continue;
3108
+ const fill2 = !alone && this.isPixel(c) ? c.fillOpacity ?? 1 : 1;
3109
+ const keep = new Float32Array(buf.length >> 2);
3110
+ for (let i = 3, p = 0; i < buf.length; i += 4, p++) keep[p] = buf[i];
3111
+ this.blend(buf, src, c.blend === "pass-through" ? "normal" : this.blendOf(c), c.opacity * fill2, coverage);
3112
+ for (let i = 3, p = 0; i < buf.length; i += 4, p++) buf[i] = keep[p];
3113
+ }
3114
+ flush();
3115
+ }
3116
+ applyFilterRun(layers, acc, r) {
3117
+ const visible = [];
3118
+ let masks;
3119
+ for (const node of layers) {
3120
+ if (!node.visible) continue;
3121
+ const cov = this.base.maskCoverage(node, r, this.sources);
3122
+ if (cov) {
3123
+ if (cov.every((v) => v <= 0)) continue;
3124
+ (masks ??= /* @__PURE__ */ new Map()).set(node.id, cov);
3125
+ }
3126
+ visible.push(node);
3127
+ }
3128
+ if (!visible.length) return;
3129
+ if (this.deep.applyFilterRunDeep) {
3130
+ if (this.linear) mapColour(acc, srgbEncode);
3131
+ this.deep.applyFilterRunDeep(visible, acc, r, masks, this.sources);
3132
+ if (this.linear) mapColour(acc, srgbDecode);
3133
+ return;
3134
+ }
3135
+ const bytes = this.to8(acc);
3136
+ this.profile.applyFilterRun?.(visible, bytes, r, masks, this.sources);
3137
+ acc.set(this.from8(bytes));
3138
+ }
3139
+ // --- Layer content ---------------------------------------------------------------
3140
+ /** Layers drawn by the 8-bit compositor alone (effects, pixel filters, content the editor draws). */
3141
+ /** A pixel(-like) layer or a group with effects: composited in float through `compositeLayerEffects`. */
3142
+ deepEffects(layer) {
3143
+ return hasActiveEffects(layer.effects) && !this.isFilter(layer) && (isGroup2(layer) || this.isPixel(layer));
3144
+ }
3145
+ renderedAlone(layer) {
3146
+ if (hasActiveEffects(layer.effects) && !this.isFilter(layer) && !this.deepEffects(layer)) return true;
3147
+ if (this.isPixel(layer)) return Boolean(this.profile.pixelFilters?.(layer)) && !this.deep.pixelFiltersDeep?.(layer);
3148
+ return !isGroup2(layer);
3149
+ }
3150
+ blendOf(layer) {
3151
+ return this.profile.contentBlend?.(layer) ?? layer.blend;
3152
+ }
3153
+ /** A layer's float RGBA over `r` (straight alpha, working space), or `null` when it has none there. */
3154
+ content(layer, r) {
3155
+ if (this.renderedAlone(layer)) {
3156
+ const edge = layer.edge?.ambient ? { ...layer.edge, ambient: 0 } : layer.edge;
3157
+ const solo = { ...layer, blend: isGroup2(layer) && layer.blend === "pass-through" ? "pass-through" : "normal", opacity: 1, clipped: false, edge };
3158
+ const bytes = this.base.composite([solo], r, this.sources);
3159
+ return this.from8(bytes);
3160
+ }
3161
+ const surface = this.sources.surfaceOf ? this.sources.surfaceOf(layer) : layer.surface;
3162
+ if (!surface) return null;
3163
+ const ox = layer.offset?.x ?? 0;
3164
+ const oy = layer.offset?.y ?? 0;
3165
+ const filters = this.isPixel(layer) ? this.deep.pixelFiltersDeep?.(layer) ?? null : null;
3166
+ if (!filters) return this.samples(surface, { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height });
3167
+ const m = filters.reach;
3168
+ const W = this.sources.width;
3169
+ const H = this.sources.height;
3170
+ const x0 = m ? Math.max(0, r.x - m) : r.x;
3171
+ const y0 = m ? Math.max(0, r.y - m) : r.y;
3172
+ const x1 = m ? Math.min(W, r.x + r.width + m) : r.x + r.width;
3173
+ const y1 = m ? Math.min(H, r.y + r.height + m) : r.y + r.height;
3174
+ if (x1 <= x0 || y1 <= y0) return new Float32Array(r.width * r.height * 4);
3175
+ const big = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
3176
+ const data = this.samples(surface, { ...big, x: big.x - ox, y: big.y - oy });
3177
+ filters.apply(data, big.width, big.height, this.linear);
3178
+ if (big.x === r.x && big.y === r.y && big.width === r.width && big.height === r.height) return data;
3179
+ const out = new Float32Array(r.width * r.height * 4);
3180
+ for (let y = 0; y < r.height; y++) {
3181
+ const sy = r.y + y - big.y;
3182
+ if (sy < 0 || sy >= big.height) continue;
3183
+ for (let x = 0; x < r.width; x++) {
3184
+ const sx = r.x + x - big.x;
3185
+ if (sx < 0 || sx >= big.width) continue;
3186
+ const s = (sy * big.width + sx) * 4;
3187
+ const d = (y * r.width + x) * 4;
3188
+ out[d] = data[s];
3189
+ out[d + 1] = data[s + 1];
3190
+ out[d + 2] = data[s + 2];
3191
+ out[d + 3] = data[s + 3];
3192
+ }
3234
3193
  }
3235
- const content = this.contentTile(step.layer, r);
3236
- if (!content) return empty;
3237
- const fill2 = this.isPixel(step.layer) ? step.layer.fillOpacity ?? 1 : 1;
3238
- const blend2 = this.contentBlend(step.layer);
3239
- if (!clipped.length) return this.blendOnto(acc, content, empty, blend2, layer.opacity * fill2, coverage);
3240
- const buf = new Uint8ClampedArray(acc.length);
3241
- blendTile(buf, content, "normal", fill2, coverage);
3242
- this.applyClipped(clipped, buf, r);
3243
- return this.blendOnto(acc, buf, empty, blend2, layer.opacity, null);
3194
+ return out;
3244
3195
  }
3245
- runSteps(steps, acc, empty, r) {
3246
- for (const s of steps) empty = this.runStep(s, acc, empty, r);
3247
- return empty;
3196
+ /** A surface's samples over `rect` (layer space) in the working space. */
3197
+ samples(surface, rect) {
3198
+ const samples = readDeepRegion(surface, rect, this.depth);
3199
+ if (samples instanceof Float32Array) return samples;
3200
+ const out = new Float32Array(samples.length);
3201
+ for (let i = 0; i < samples.length; i++) out[i] = samples[i] / 65535;
3202
+ return out;
3248
3203
  }
3249
- contentBlend(layer) {
3250
- return this.profile.contentBlend?.(layer) ?? layer.blend;
3204
+ /** 8-bit RGBA to the working space. */
3205
+ from8(bytes) {
3206
+ if (this.linear) return to32From8(bytes, 4);
3207
+ const out = new Float32Array(bytes.length);
3208
+ for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;
3209
+ return out;
3251
3210
  }
3252
- blendOnto(acc, src, empty, mode, alpha, coverage) {
3253
- if (!(alpha > 0)) return empty;
3254
- if (empty && mode === "normal" && alpha >= 1 && !coverage) {
3255
- acc.set(src);
3256
- return false;
3211
+ /** The working space to 8-bit RGBA. */
3212
+ to8(acc) {
3213
+ const out = new Uint8ClampedArray(acc.length);
3214
+ for (let i = 0; i < acc.length; i += 4) {
3215
+ for (let c = 0; c < 3; c++) out[i + c] = Math.round((this.linear ? srgbEncode(acc[i + c]) : acc[i + c]) * 255);
3216
+ out[i + 3] = Math.round(acc[i + 3] * 255);
3257
3217
  }
3258
- blendTile(acc, src, mode, alpha, coverage);
3259
- return false;
3218
+ return out;
3260
3219
  }
3261
- applyClipped(clipped, buf, r) {
3262
- let run = [];
3263
- const flush = () => {
3264
- if (run.length) this.applyFilterRun(run, buf, r);
3265
- run = [];
3266
- };
3267
- for (const c of clipped) {
3268
- if (this.isFilter(c)) {
3269
- run.push(c);
3220
+ // --- Blending --------------------------------------------------------------------
3221
+ /** `src` over `dst` with `mode` at `alpha` × coverage (straight alpha, float), as `blendTile`. */
3222
+ blend(dst, src, mode, alpha, coverage) {
3223
+ if (!(alpha > 0)) return;
3224
+ const normal = mode === "normal";
3225
+ const pigment = mode === "pigment";
3226
+ const cb = [0, 0, 0];
3227
+ const cs = [0, 0, 0];
3228
+ const mix = [0, 0, 0];
3229
+ for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
3230
+ const sa = src[i + 3];
3231
+ if (!(sa > 0)) continue;
3232
+ const k = coverage ? alpha * coverage[p] : alpha;
3233
+ if (!(k > 0)) continue;
3234
+ const as = Math.min(1, sa * k);
3235
+ const ab = dst[i + 3];
3236
+ const ao = as + ab * (1 - as);
3237
+ if (ao <= 0) {
3238
+ dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;
3270
3239
  continue;
3271
3240
  }
3272
- flush();
3273
- if (this.hasEffects(c) && !isGroup2(c)) {
3274
- this.compositeEffects(c, [], buf, r, true);
3241
+ if (normal && as >= 1) {
3242
+ dst[i] = src[i];
3243
+ dst[i + 1] = src[i + 1];
3244
+ dst[i + 2] = src[i + 2];
3245
+ dst[i + 3] = 1;
3275
3246
  continue;
3276
3247
  }
3277
- const coverage = this.maskTile(c, r);
3278
- if (isGroup2(c)) {
3279
- const inner = new Uint8ClampedArray(buf.length);
3280
- if (this.runSteps(this.buildSteps(c.children ?? []), inner, true, r)) continue;
3281
- const mode = c.blend === "pass-through" ? "normal" : c.blend;
3282
- blendAtop(buf, inner, mode, c.opacity, coverage);
3283
- continue;
3248
+ for (let c = 0; c < 3; c++) {
3249
+ cb[c] = dst[i + c];
3250
+ cs[c] = src[i + c];
3284
3251
  }
3285
- const content = this.contentTile(c, r);
3286
- if (!content) continue;
3287
- const fill2 = this.isPixel(c) ? c.fillOpacity ?? 1 : 1;
3288
- blendAtop(buf, content, this.contentBlend(c), c.opacity * fill2, coverage);
3289
- }
3290
- flush();
3291
- }
3292
- applyFilterRun(layers, acc, r) {
3293
- const visible = [];
3294
- let masks;
3295
- for (const node of layers) {
3296
- if (!node.visible) continue;
3297
- const cov = this.maskTile(node, r);
3298
- if (cov) {
3299
- if (cov.every((v) => v <= 0)) continue;
3300
- (masks ??= /* @__PURE__ */ new Map()).set(node.id, cov);
3252
+ if (pigment) {
3253
+ if (this.linear) for (let c = 0; c < 3; c++) cb[c] = srgbEncode(cb[c]), cs[c] = srgbEncode(cs[c]);
3254
+ mixPigmentRgb(cb, cs, as / ao, mix);
3255
+ for (let c = 0; c < 3; c++) dst[i + c] = this.linear ? srgbDecode(mix[c]) : mix[c];
3256
+ dst[i + 3] = ao;
3257
+ continue;
3301
3258
  }
3302
- visible.push(node);
3259
+ const m = normal ? cs : blendRgb(mode, cb, cs);
3260
+ const wb = (1 - as) * ab;
3261
+ const ws = (1 - ab) * as;
3262
+ const wm = as * ab;
3263
+ for (let c = 0; c < 3; c++) dst[i + c] = (wb * cb[c] + ws * cs[c] + wm * m[c]) / ao;
3264
+ dst[i + 3] = ao;
3303
3265
  }
3304
- if (visible.length) this.profile.applyFilterRun?.(visible, acc, r, masks, this.sources);
3305
3266
  }
3306
3267
  };
3307
- function filterOverCanvas(surface, ox, oy, filters, width, height) {
3308
- if (width <= 0 || height <= 0) return emptySurface(4);
3309
- const data = readRegion(surface, { x: -ox, y: -oy, width, height });
3310
- filters.apply(data, width, height);
3311
- return surfaceFromPixels({ width, height, data }, 4, 0, 0);
3312
- }
3313
- function copyRect(data, r, a) {
3314
- const out = new Uint8ClampedArray(a.width * a.height * 4);
3315
- for (let y = 0; y < a.height; y++) {
3316
- const s = ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4;
3317
- out.set(data.subarray(s, s + a.width * 4), y * a.width * 4);
3268
+ function lerp(dst, before, alpha, coverage) {
3269
+ for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
3270
+ const k = coverage ? alpha * coverage[p] : alpha;
3271
+ if (k >= 1) continue;
3272
+ for (let c = 0; c < 4; c++) dst[i + c] = before[i + c] + (dst[i + c] - before[i + c]) * k;
3318
3273
  }
3319
- return out;
3320
3274
  }
3321
- function pasteRect(data, r, part, a) {
3322
- for (let y = 0; y < a.height; y++) data.set(part.subarray(y * a.width * 4, (y + 1) * a.width * 4), ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4);
3275
+ function mapColour(data, f) {
3276
+ for (let i = 0; i < data.length; i += 4) {
3277
+ data[i] = f(data[i]);
3278
+ data[i + 1] = f(data[i + 1]);
3279
+ data[i + 2] = f(data[i + 2]);
3280
+ }
3323
3281
  }
3324
- function sameRect(a, b) {
3325
- return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
3282
+ function workingToSamples(data, depth) {
3283
+ if (depth === 32) return data;
3284
+ const out = new Uint16Array(data.length);
3285
+ for (let i = 0; i < data.length; i++) {
3286
+ const v = data[i];
3287
+ out[i] = v > 0 ? v < 1 ? Math.round(v * 65535) : 65535 : 0;
3288
+ }
3289
+ return out;
3326
3290
  }
3327
- function inflateRect(r, m) {
3328
- return { x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m };
3291
+ function encodeInPlace(d) {
3292
+ for (let i = 0; i < d.length; i += 4) {
3293
+ d[i] = srgbEncode(d[i]);
3294
+ d[i + 1] = srgbEncode(d[i + 1]);
3295
+ d[i + 2] = srgbEncode(d[i + 2]);
3296
+ }
3329
3297
  }
3330
- function intersectRect(a, b) {
3331
- const x0 = Math.max(a.x, b.x);
3332
- const y0 = Math.max(a.y, b.y);
3333
- const x1 = Math.min(a.x + a.width, b.x + b.width);
3334
- const y1 = Math.min(a.y + a.height, b.y + b.height);
3335
- return x1 <= x0 || y1 <= y0 ? { x: x0, y: y0, width: 0, height: 0 } : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
3298
+ function decodeInPlace(d) {
3299
+ for (let i = 0; i < d.length; i += 4) {
3300
+ d[i] = srgbDecode(d[i]);
3301
+ d[i + 1] = srgbDecode(d[i + 1]);
3302
+ d[i + 2] = srgbDecode(d[i + 2]);
3303
+ }
3336
3304
  }
3337
- function changedTiles(a, b) {
3305
+
3306
+ // src/raster/layers.ts
3307
+ var childrenOf = (l) => l.type === "group" ? l.children ?? null : null;
3308
+ function flattenLayers(layers) {
3338
3309
  const out = [];
3339
- for (const [k, t] of b.tiles) if (a.tiles.get(k) !== t) out.push(k);
3340
- for (const k of a.tiles.keys()) if (!b.tiles.has(k)) out.push(k);
3310
+ const walk = (list) => {
3311
+ for (const l of list) {
3312
+ out.push(l);
3313
+ const c = childrenOf(l);
3314
+ if (c) walk(c);
3315
+ }
3316
+ };
3317
+ walk(layers);
3341
3318
  return out;
3342
3319
  }
3320
+ function findLayer(layers, id) {
3321
+ const walk = (list, parent, path) => {
3322
+ for (let i = 0; i < list.length; i++) {
3323
+ const l = list[i];
3324
+ if (l.id === id) return { layer: l, parent, index: i, path };
3325
+ const c = childrenOf(l);
3326
+ if (c) {
3327
+ const g = l;
3328
+ const hit = walk(c, g, [...path, g]);
3329
+ if (hit) return hit;
3330
+ }
3331
+ }
3332
+ return null;
3333
+ };
3334
+ return walk(layers, null, []);
3335
+ }
3336
+ function mapLayer(layers, id, fn) {
3337
+ let changed = false;
3338
+ const walk = (list) => {
3339
+ let out = null;
3340
+ for (let i = 0; i < list.length; i++) {
3341
+ const l = list[i];
3342
+ let next = l;
3343
+ if (l.id === id) {
3344
+ next = fn(l);
3345
+ changed = true;
3346
+ } else {
3347
+ const c = childrenOf(l);
3348
+ if (c) {
3349
+ const children = walk(c);
3350
+ if (children !== c) next = { ...l, children };
3351
+ }
3352
+ }
3353
+ if (next !== l) {
3354
+ if (!out) out = list.slice(0, i);
3355
+ if (next) out.push(next);
3356
+ } else if (out) out.push(l);
3357
+ }
3358
+ return out ?? list;
3359
+ };
3360
+ const result = walk(layers);
3361
+ return changed ? result : layers;
3362
+ }
3363
+ function insertLayer(layers, layer, parentId, index) {
3364
+ const put = (list) => {
3365
+ const at = index === void 0 ? list.length : Math.max(0, Math.min(list.length, index));
3366
+ const next = list.slice();
3367
+ next.splice(at, 0, layer);
3368
+ return next;
3369
+ };
3370
+ if (!parentId) return put(layers);
3371
+ return mapLayer(layers, parentId, (g) => {
3372
+ const c = childrenOf(g);
3373
+ return c ? { ...g, children: put(c) } : g;
3374
+ });
3375
+ }
3376
+ function isLayerLocked(layer, what) {
3377
+ return Boolean(layer.locks?.all || layer.locks?.[what]);
3378
+ }
3343
3379
 
3344
3380
  // src/raster/transform.ts
3345
3381
  var INTERPOLATIONS = ["nearest", "bilinear", "bicubic"];
@@ -4181,13 +4217,13 @@ export {
4181
4217
  validateLayerEffects,
4182
4218
  normalizeLayerEffects,
4183
4219
  effectsFingerprint,
4184
- CURVE_LUT_SIZE,
4185
- curveLut,
4186
4220
  PatternTile,
4187
4221
  PatternPlacement,
4188
4222
  builtinPatternIds,
4189
4223
  builtinPattern,
4190
4224
  hashNoise,
4225
+ CURVE_LUT_SIZE,
4226
+ curveLut,
4191
4227
  STROKE_CHAMFER_MAX,
4192
4228
  contourLut,
4193
4229
  activeEffectItems,
@@ -4198,6 +4234,10 @@ export {
4198
4234
  effectFillColors,
4199
4235
  compositeLayerEffects,
4200
4236
  renderLayerEffects,
4237
+ applyEdgeAmbient,
4238
+ blendTile,
4239
+ RasterCompositor,
4240
+ changedTiles,
4201
4241
  DeepCompositor,
4202
4242
  workingToSamples,
4203
4243
  flattenLayers,
@@ -4205,9 +4245,6 @@ export {
4205
4245
  mapLayer,
4206
4246
  insertLayer,
4207
4247
  isLayerLocked,
4208
- blendTile,
4209
- RasterCompositor,
4210
- changedTiles,
4211
4248
  INTERPOLATIONS,
4212
4249
  isInterpolation,
4213
4250
  MAX_TRANSFORM_PIXELS,