@pixerate/editor 0.121.0 → 0.122.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 (36) hide show
  1. package/THIRD_PARTY_NOTICES.md +1 -1
  2. package/dist/{chunk-GLOOH65Y.cjs → chunk-4PUPGALM.cjs} +196 -103
  3. package/dist/chunk-4PUPGALM.cjs.map +1 -0
  4. package/dist/{chunk-4I54JSJ6.js → chunk-CLTTLLWA.js} +1089 -981
  5. package/dist/chunk-CLTTLLWA.js.map +1 -0
  6. package/dist/{chunk-LL3RAN55.cjs → chunk-DFFBJV3U.cjs} +1839 -1731
  7. package/dist/chunk-DFFBJV3U.cjs.map +1 -0
  8. package/dist/{chunk-E6N5KLMA.js → chunk-IX3YKUVK.js} +96 -3
  9. package/dist/chunk-IX3YKUVK.js.map +1 -0
  10. package/dist/{render-B05K68_h.d.ts → deep-DLwKZDFr.d.ts} +143 -134
  11. package/dist/{render-DnAbIKcs.d.cts → deep-QS3ppbMX.d.cts} +143 -134
  12. package/dist/{export-D2D8hb_u.d.ts → export-VmTofKIk.d.ts} +11 -4
  13. package/dist/{export-fP2hZYjm.d.cts → export-jKKj8Fg8.d.cts} +11 -4
  14. package/dist/image-editor/index.cjs +678 -672
  15. package/dist/image-editor/index.cjs.map +1 -1
  16. package/dist/image-editor/index.d.cts +17 -5
  17. package/dist/image-editor/index.d.ts +17 -5
  18. package/dist/image-editor/index.js +8 -2
  19. package/dist/image-editor/index.js.map +1 -1
  20. package/dist/paint-editor/index.cjs +68 -68
  21. package/dist/paint-editor/index.d.cts +2 -2
  22. package/dist/paint-editor/index.d.ts +2 -2
  23. package/dist/paint-editor/index.js +2 -2
  24. package/dist/raster/index.cjs +2 -2
  25. package/dist/raster/index.d.cts +29 -7
  26. package/dist/raster/index.d.ts +29 -7
  27. package/dist/raster/index.js +1 -1
  28. package/package.json +1 -1
  29. package/skills/pixerate-canvas/SKILL.md +23 -1
  30. package/skills/pixerate-image-editor/SKILL.md +7 -1
  31. package/skills/pixerate-image-editor/references/photoshop-files.md +4 -3
  32. package/skills/pixerate-paint-editor/SKILL.md +32 -7
  33. package/dist/chunk-4I54JSJ6.js.map +0 -1
  34. package/dist/chunk-E6N5KLMA.js.map +0 -1
  35. package/dist/chunk-GLOOH65Y.cjs.map +0 -1
  36. package/dist/chunk-LL3RAN55.cjs.map +0 -1
@@ -464,877 +464,448 @@ function stableStringify(v) {
464
464
  return JSON.stringify(v);
465
465
  }
466
466
 
467
- // src/raster/deep-compositor.ts
468
- var isGroup = (l) => l.type === "group";
469
- var DeepCompositor = class {
470
- constructor(base, profile, deep, depth) {
471
- this.base = base;
472
- this.profile = profile;
473
- this.deep = deep;
474
- this.depth = depth;
475
- this.linear = depth === 32;
476
- }
477
- base;
478
- profile;
479
- deep;
480
- depth;
481
- sources;
482
- linear;
483
- /** The composite of `layers` over `rect` as float RGBA in the working space (`rect.width × rect.height × 4`). */
484
- composite(layers, rect, sources) {
485
- this.sources = sources;
486
- const acc = new Float32Array(Math.max(0, rect.width * rect.height * 4));
487
- if (rect.width > 0 && rect.height > 0) this.runSteps(this.buildSteps(layers), acc, true, rect);
488
- return acc;
489
- }
490
- // --- Steps (as RasterCompositor.buildSteps) ------------------------------------
491
- isPixel(l) {
492
- return l.type === "pixel" || (this.profile.isPixelLike?.(l) ?? false);
467
+ // src/raster/curve.ts
468
+ var CURVE_LUT_SIZE = 4096;
469
+ var clamp01 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
470
+ function curveLut(points, size = CURVE_LUT_SIZE) {
471
+ 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]);
472
+ const dedup = [];
473
+ for (const p of pts) {
474
+ if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;
475
+ dedup.push(p);
493
476
  }
494
- isFilter(l) {
495
- return this.profile.isFilterLayer?.(l) ?? false;
477
+ const out = new Float32Array(size);
478
+ const n = dedup.length;
479
+ if (n < 2) {
480
+ for (let k = 0; k < size; k++) out[k] = k / (size - 1);
481
+ return out;
496
482
  }
497
- buildSteps(layers) {
498
- const steps = [];
499
- for (const layer of layers) {
500
- const last = steps[steps.length - 1];
501
- if (layer.clipped && last && (last.kind === "content" || last.kind === "group")) {
502
- last.clipped.push(layer);
503
- continue;
504
- }
505
- if (this.isFilter(layer)) {
506
- if (last && last.kind === "adjust") last.layers.push(layer);
507
- else steps.push({ kind: "adjust", layers: [layer] });
508
- } else if (isGroup(layer)) steps.push({ kind: "group", layer, clipped: [], steps: this.buildSteps(layer.children ?? []) });
509
- else steps.push({ kind: "content", layer, clipped: [] });
483
+ const x = dedup.map((p) => p[0]);
484
+ const y = dedup.map((p) => p[1]);
485
+ const m2 = new Float64Array(n);
486
+ if (n > 2) {
487
+ const c = new Float64Array(n);
488
+ const d = new Float64Array(n);
489
+ for (let i = 1; i < n - 1; i++) {
490
+ const h0 = x[i] - x[i - 1];
491
+ const h1 = x[i + 1] - x[i];
492
+ const a = h0 / 6;
493
+ const b = (h0 + h1) / 3;
494
+ const cc = h1 / 6;
495
+ const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;
496
+ const denom = b - a * c[i - 1];
497
+ c[i] = cc / denom;
498
+ d[i] = (r - a * d[i - 1]) / denom;
510
499
  }
511
- return steps;
512
- }
513
- runSteps(steps, acc, empty, r) {
514
- for (const s of steps) empty = this.runStep(s, acc, empty, r);
515
- return empty;
500
+ for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];
516
501
  }
517
- runStep(step, acc, empty, r) {
518
- if (step.kind === "adjust") {
519
- if (empty) return true;
520
- this.applyFilterRun(step.layers, acc, r);
521
- return false;
522
- }
523
- const layer = step.layer;
524
- if (!layer.visible) return empty;
525
- const clipped = step.clipped.filter((l) => l.visible);
526
- const coverage = this.renderedAlone(layer) ? null : this.base.maskCoverage(layer, r, this.sources);
527
- if (coverage && coverage.every((v) => v <= 0)) return empty;
528
- if (step.kind === "group" && !this.renderedAlone(layer)) {
529
- if (layer.blend === "pass-through" && !clipped.length) {
530
- if (layer.opacity >= 1 && !coverage) return this.runSteps(step.steps, acc, empty, r);
531
- const before = acc.slice();
532
- const stillEmpty = this.runSteps(step.steps, acc, empty, r);
533
- lerp(acc, before, layer.opacity, coverage);
534
- return empty && stillEmpty;
535
- }
536
- const buf2 = new Float32Array(acc.length);
537
- if (this.runSteps(step.steps, buf2, true, r)) return empty;
538
- this.applyClipped(clipped, buf2, r);
539
- const mode2 = layer.blend === "pass-through" ? "normal" : layer.blend;
540
- this.blend(acc, buf2, mode2, layer.opacity, coverage);
541
- return false;
502
+ let seg = 0;
503
+ for (let k = 0; k < size; k++) {
504
+ const xv = k / (size - 1);
505
+ if (xv <= x[0]) {
506
+ out[k] = clamp01(y[0]);
507
+ continue;
542
508
  }
543
- const content = this.content(layer, r);
544
- if (!content) return empty;
545
- const alone = this.renderedAlone(layer);
546
- const fill2 = !alone && this.isPixel(layer) ? layer.fillOpacity ?? 1 : 1;
547
- const mode = this.blendOf(layer);
548
- if (!clipped.length) {
549
- this.blend(acc, content, mode, layer.opacity * fill2, coverage);
550
- return false;
509
+ if (xv >= x[n - 1]) {
510
+ out[k] = clamp01(y[n - 1]);
511
+ continue;
551
512
  }
552
- const buf = new Float32Array(acc.length);
553
- this.blend(buf, content, "normal", fill2, coverage);
554
- this.applyClipped(clipped, buf, r);
555
- this.blend(acc, buf, mode, layer.opacity, null);
556
- return false;
513
+ while (seg < n - 2 && xv > x[seg + 1]) seg++;
514
+ const x0 = x[seg], x1 = x[seg + 1];
515
+ const h = x1 - x0;
516
+ const a = (x1 - xv) / h;
517
+ const b = (xv - x0) / h;
518
+ 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;
519
+ out[k] = clamp01(v);
557
520
  }
558
- applyClipped(clipped, buf, r) {
559
- let run = [];
560
- const flush = () => {
561
- if (run.length) this.applyFilterRun(run, buf, r);
562
- run = [];
563
- };
564
- for (const c of clipped) {
565
- if (this.isFilter(c)) {
566
- run.push(c);
567
- continue;
568
- }
569
- flush();
570
- const alone = this.renderedAlone(c);
571
- const coverage = alone ? null : this.base.maskCoverage(c, r, this.sources);
572
- let src;
573
- if (isGroup(c) && !alone) {
574
- src = new Float32Array(buf.length);
575
- if (this.runSteps(this.buildSteps(c.children ?? []), src, true, r)) continue;
576
- } else src = this.content(c, r);
577
- if (!src) continue;
578
- const fill2 = !alone && this.isPixel(c) ? c.fillOpacity ?? 1 : 1;
579
- const keep = new Float32Array(buf.length >> 2);
580
- for (let i = 3, p = 0; i < buf.length; i += 4, p++) keep[p] = buf[i];
581
- this.blend(buf, src, c.blend === "pass-through" ? "normal" : this.blendOf(c), c.opacity * fill2, coverage);
582
- for (let i = 3, p = 0; i < buf.length; i += 4, p++) buf[i] = keep[p];
521
+ return out;
522
+ }
523
+
524
+ // src/raster/effects/patterns.ts
525
+ var PatternTile = class {
526
+ w;
527
+ h;
528
+ px;
529
+ constructor(p) {
530
+ this.w = Math.max(1, p.width | 0);
531
+ this.h = Math.max(1, p.height | 0);
532
+ const n = this.w * this.h;
533
+ this.px = new Float32Array(n * 4);
534
+ for (let i = 0; i < n; i++) {
535
+ const a = (p.data[i * 4 + 3] ?? 0) / 255;
536
+ this.px[i * 4] = (p.data[i * 4] ?? 0) / 255 * a;
537
+ this.px[i * 4 + 1] = (p.data[i * 4 + 1] ?? 0) / 255 * a;
538
+ this.px[i * 4 + 2] = (p.data[i * 4 + 2] ?? 0) / 255 * a;
539
+ this.px[i * 4 + 3] = a;
583
540
  }
584
- flush();
585
541
  }
586
- applyFilterRun(layers, acc, r) {
587
- const visible = [];
588
- let masks;
589
- for (const node of layers) {
590
- if (!node.visible) continue;
591
- const cov = this.base.maskCoverage(node, r, this.sources);
592
- if (cov) {
593
- if (cov.every((v) => v <= 0)) continue;
594
- (masks ??= /* @__PURE__ */ new Map()).set(node.id, cov);
542
+ /** Bilinear wrapping sample at tile coordinates (pixel centres at `i + 0.5`), straight alpha into `out`. */
543
+ sample(u, v, out) {
544
+ u -= 0.5;
545
+ v -= 0.5;
546
+ const x0 = Math.floor(u);
547
+ const y0 = Math.floor(v);
548
+ const fx = u - x0;
549
+ const fy = v - y0;
550
+ let r = 0;
551
+ let g = 0;
552
+ let b = 0;
553
+ let a = 0;
554
+ for (let dy = 0; dy < 2; dy++) {
555
+ const wy2 = dy ? fy : 1 - fy;
556
+ if (wy2 === 0) continue;
557
+ const yi = ((y0 + dy) % this.h + this.h) % this.h;
558
+ for (let dx = 0; dx < 2; dx++) {
559
+ const wx2 = dx ? fx : 1 - fx;
560
+ if (wx2 === 0) continue;
561
+ const xi = ((x0 + dx) % this.w + this.w) % this.w;
562
+ const k = wx2 * wy2;
563
+ const i = (yi * this.w + xi) * 4;
564
+ r += this.px[i] * k;
565
+ g += this.px[i + 1] * k;
566
+ b += this.px[i + 2] * k;
567
+ a += this.px[i + 3] * k;
595
568
  }
596
- visible.push(node);
597
569
  }
598
- if (!visible.length) return;
599
- if (this.deep.applyFilterRunDeep) {
600
- if (this.linear) mapColour(acc, srgbEncode);
601
- this.deep.applyFilterRunDeep(visible, acc, r, masks, this.sources);
602
- if (this.linear) mapColour(acc, srgbDecode);
570
+ if (a <= 0) {
571
+ out[0] = out[1] = out[2] = out[3] = 0;
603
572
  return;
604
573
  }
605
- const bytes = this.to8(acc);
606
- this.profile.applyFilterRun?.(visible, bytes, r, masks, this.sources);
607
- acc.set(this.from8(bytes));
574
+ out[0] = r / a;
575
+ out[1] = g / a;
576
+ out[2] = b / a;
577
+ out[3] = Math.min(1, a);
608
578
  }
609
- // --- Layer content ---------------------------------------------------------------
610
- /** Layers drawn by the 8-bit compositor alone (effects, pixel filters, content the editor draws). */
611
- renderedAlone(layer) {
612
- if (hasActiveEffects(layer.effects) && !this.isFilter(layer)) return true;
613
- if (this.isPixel(layer)) return Boolean(this.profile.pixelFilters?.(layer));
614
- return !isGroup(layer);
579
+ };
580
+ var PatternPlacement = class {
581
+ ox;
582
+ oy;
583
+ c;
584
+ s;
585
+ inv;
586
+ /** `anchor`: the layer's top-left (used when `link`); `phase` in px; `scale` a fraction; `angle` degrees counter-clockwise. */
587
+ constructor(anchor, link, phase, scale, angle) {
588
+ const bx = link ? anchor.x : 0;
589
+ const by = link ? anchor.y : 0;
590
+ this.ox = bx + phase.x;
591
+ this.oy = by + phase.y;
592
+ this.inv = Number.isFinite(scale) && scale > 1e-3 ? 1 / scale : 1;
593
+ const a = (Number.isFinite(angle) ? angle : 0) * Math.PI / 180;
594
+ this.c = Math.cos(a);
595
+ this.s = Math.sin(a);
615
596
  }
616
- blendOf(layer) {
617
- return this.profile.contentBlend?.(layer) ?? layer.blend;
597
+ /** Tile coordinates of the document point `(x, y)` into `out[0..1]`. */
598
+ map(x, y, out) {
599
+ const dx = x - this.ox;
600
+ const dy = y - this.oy;
601
+ out[0] = (dx * this.c - dy * this.s) * this.inv;
602
+ out[1] = (dx * this.s + dy * this.c) * this.inv;
618
603
  }
619
- /** A layer's float RGBA over `r` (straight alpha, working space), or `null` when it has none there. */
620
- content(layer, r) {
621
- if (this.renderedAlone(layer)) {
622
- const solo = { ...layer, blend: isGroup(layer) && layer.blend === "pass-through" ? "pass-through" : "normal", opacity: 1, clipped: false };
623
- const bytes = this.base.composite([solo], r, this.sources);
624
- return this.from8(bytes);
625
- }
626
- const surface = this.sources.surfaceOf ? this.sources.surfaceOf(layer) : layer.surface;
627
- if (!surface) return null;
628
- const ox = layer.offset?.x ?? 0;
629
- const oy = layer.offset?.y ?? 0;
630
- const samples = readDeepRegion(surface, { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height }, this.depth);
631
- if (samples instanceof Float32Array) return samples;
632
- const out = new Float32Array(samples.length);
633
- for (let i = 0; i < samples.length; i++) out[i] = samples[i] / 65535;
634
- return out;
635
- }
636
- /** 8-bit RGBA to the working space. */
637
- from8(bytes) {
638
- if (this.linear) return to32From8(bytes, 4);
639
- const out = new Float32Array(bytes.length);
640
- for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;
641
- return out;
642
- }
643
- /** The working space to 8-bit RGBA. */
644
- to8(acc) {
645
- const out = new Uint8ClampedArray(acc.length);
646
- for (let i = 0; i < acc.length; i += 4) {
647
- for (let c = 0; c < 3; c++) out[i + c] = Math.round((this.linear ? srgbEncode(acc[i + c]) : acc[i + c]) * 255);
648
- out[i + 3] = Math.round(acc[i + 3] * 255);
604
+ };
605
+ var BUILTINS = {
606
+ checker: { name: "Checker", size: 16, paint: (x, y) => x < 8 !== y < 8 ? [204, 204, 204, 255] : [255, 255, 255, 255] },
607
+ stripes: { name: "Diagonal Stripes", size: 16, paint: (x, y) => (x + y) % 16 < 8 ? [40, 40, 40, 255] : [235, 235, 235, 255] },
608
+ dots: {
609
+ name: "Dots",
610
+ size: 12,
611
+ paint: (x, y) => {
612
+ const d = Math.hypot(x + 0.5 - 6, y + 0.5 - 6);
613
+ const a = Math.max(0, Math.min(1, 3.5 - d));
614
+ return [Math.round(255 - a * 215), Math.round(255 - a * 215), Math.round(255 - a * 215), 255];
649
615
  }
650
- return out;
651
- }
652
- // --- Blending --------------------------------------------------------------------
653
- /** `src` over `dst` with `mode` at `alpha` × coverage (straight alpha, float), as `blendTile`. */
654
- blend(dst, src, mode, alpha, coverage) {
655
- if (!(alpha > 0)) return;
656
- const normal = mode === "normal";
657
- const pigment = mode === "pigment";
658
- const cb = [0, 0, 0];
659
- const cs = [0, 0, 0];
660
- const mix = [0, 0, 0];
661
- for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
662
- const sa = src[i + 3];
663
- if (!(sa > 0)) continue;
664
- const k = coverage ? alpha * coverage[p] : alpha;
665
- if (!(k > 0)) continue;
666
- const as = Math.min(1, sa * k);
667
- const ab = dst[i + 3];
668
- const ao = as + ab * (1 - as);
669
- if (ao <= 0) {
670
- dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;
671
- continue;
672
- }
673
- if (normal && as >= 1) {
674
- dst[i] = src[i];
675
- dst[i + 1] = src[i + 1];
676
- dst[i + 2] = src[i + 2];
677
- dst[i + 3] = 1;
678
- continue;
679
- }
680
- for (let c = 0; c < 3; c++) {
681
- cb[c] = dst[i + c];
682
- cs[c] = src[i + c];
683
- }
684
- if (pigment) {
685
- if (this.linear) for (let c = 0; c < 3; c++) cb[c] = srgbEncode(cb[c]), cs[c] = srgbEncode(cs[c]);
686
- mixPigmentRgb(cb, cs, as / ao, mix);
687
- for (let c = 0; c < 3; c++) dst[i + c] = this.linear ? srgbDecode(mix[c]) : mix[c];
688
- dst[i + 3] = ao;
689
- continue;
690
- }
691
- const m = normal ? cs : blendRgb(mode, cb, cs);
692
- const wb = (1 - as) * ab;
693
- const ws = (1 - ab) * as;
694
- const wm = as * ab;
695
- for (let c = 0; c < 3; c++) dst[i + c] = (wb * cb[c] + ws * cs[c] + wm * m[c]) / ao;
696
- dst[i + 3] = ao;
616
+ },
617
+ grid: { name: "Grid", size: 16, paint: (x, y) => x === 0 || y === 0 ? [90, 90, 90, 255] : [250, 250, 250, 255] },
618
+ noise: {
619
+ name: "Noise",
620
+ size: 64,
621
+ paint: (x, y) => {
622
+ const v = Math.round(hashNoise(x, y) * 255);
623
+ return [v, v, v, 255];
697
624
  }
698
625
  }
699
626
  };
700
- function lerp(dst, before, alpha, coverage) {
701
- for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
702
- const k = coverage ? alpha * coverage[p] : alpha;
703
- if (k >= 1) continue;
704
- for (let c = 0; c < 4; c++) dst[i + c] = before[i + c] + (dst[i + c] - before[i + c]) * k;
705
- }
627
+ function builtinPatternIds() {
628
+ return Object.keys(BUILTINS);
706
629
  }
707
- function mapColour(data, f) {
708
- for (let i = 0; i < data.length; i += 4) {
709
- data[i] = f(data[i]);
710
- data[i + 1] = f(data[i + 1]);
711
- data[i + 2] = f(data[i + 2]);
630
+ var builtinCache = /* @__PURE__ */ new Map();
631
+ function builtinPattern(id) {
632
+ const def = BUILTINS[id];
633
+ if (!def) return null;
634
+ let p = builtinCache.get(id);
635
+ if (!p) {
636
+ const data = new Uint8ClampedArray(def.size * def.size * 4);
637
+ for (let y = 0; y < def.size; y++) {
638
+ for (let x = 0; x < def.size; x++) data.set(def.paint(x, y), (y * def.size + x) * 4);
639
+ }
640
+ p = { id, name: def.name, width: def.size, height: def.size, data };
641
+ builtinCache.set(id, p);
712
642
  }
643
+ return p;
713
644
  }
714
- function workingToSamples(data, depth) {
715
- if (depth === 32) return data;
716
- const out = new Uint16Array(data.length);
717
- for (let i = 0; i < data.length; i++) {
718
- const v = data[i];
719
- out[i] = v > 0 ? v < 1 ? Math.round(v * 65535) : 65535 : 0;
720
- }
721
- return out;
645
+ function hashNoise(x, y) {
646
+ let h = Math.imul(x | 0, 2376512323) ^ Math.imul(y | 0, 3625334849) ^ 2654435769;
647
+ h ^= h >>> 15;
648
+ h = Math.imul(h, 739982445);
649
+ h ^= h >>> 12;
650
+ return (h & 65535) / 65536;
722
651
  }
723
652
 
724
- // src/raster/blur.ts
725
- function gaussianKernel(sigma) {
726
- const r = Math.ceil(sigma * 3);
727
- const k = new Float32Array(2 * r + 1);
728
- let s = 0;
729
- for (let i = -r; i <= r; i++) {
730
- const v = Math.exp(-(i * i) / (2 * sigma * sigma));
731
- k[i + r] = v;
732
- s += v;
653
+ // src/raster/effects/render.ts
654
+ var INSIDE_EPS = 0.5 / 255;
655
+ var INF = 1e20;
656
+ var FAR = 1e9;
657
+ var STROKE_CHAMFER_MAX = 20;
658
+ var BEVEL_H_EPS = 1e-5;
659
+ var fmap = (w, h, fill2 = 0) => {
660
+ const v = new Float32Array(w * h);
661
+ if (fill2) v.fill(fill2);
662
+ return { w, h, v };
663
+ };
664
+ var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
665
+ var clamp012 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
666
+ function mapGet(m, x, y) {
667
+ return x < 0 || y < 0 || x >= m.w || y >= m.h ? 0 : m.v[y * m.w + x];
668
+ }
669
+ function shifted(m, dx, dy, outside) {
670
+ const reach = (d) => Number.isFinite(d) ? clamp(d, -1e9, 1e9) : 0;
671
+ dx = reach(dx);
672
+ dy = reach(dy);
673
+ const fx = Math.floor(dx);
674
+ const fy = Math.floor(dy);
675
+ const ax = dx - fx;
676
+ const ay = dy - fy;
677
+ const { w, h, v } = m;
678
+ const s = (x, y) => x < 0 || y < 0 || x >= w || y >= h ? outside : v[y * w + x];
679
+ const out = fmap(w, h);
680
+ for (let y = 0; y < h; y++) {
681
+ const sy = y - fy;
682
+ for (let x = 0; x < w; x++) {
683
+ const sx = x - fx;
684
+ const top = s(sx, sy) * (1 - ax) + s(sx - 1, sy) * ax;
685
+ const bot = s(sx, sy - 1) * (1 - ax) + s(sx - 1, sy - 1) * ax;
686
+ out.v[y * w + x] = top * (1 - ay) + bot * ay;
687
+ }
733
688
  }
734
- for (let i = 0; i < k.length; i++) k[i] /= s;
735
- return k;
689
+ return out;
736
690
  }
737
- var DIRECT_GAUSSIAN_MAX_SIGMA = 8;
738
- function boxRadii(sigma) {
739
- const n = 3;
740
- const wIdeal = Math.sqrt(12 * sigma * sigma / n + 1);
741
- let wl = Math.floor(wIdeal);
742
- if (wl % 2 === 0) wl--;
743
- const wu = wl + 2;
744
- const mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);
745
- const m = Math.round(mIdeal);
746
- const sizes = [0, 1, 2].map((i) => i < m ? wl : wu);
747
- return sizes.map((s) => (s - 1) / 2);
691
+ function mapped(m, f) {
692
+ const out = fmap(m.w, m.h);
693
+ for (let i = 0; i < m.v.length; i++) out.v[i] = f(m.v[i]);
694
+ return out;
748
695
  }
749
- function convolveLine(src, off, stride, len, k, edge, dst) {
750
- const r = (k.length - 1) / 2;
751
- for (let i = 0; i < len; i++) {
752
- let a = 0;
753
- const lo = i - r;
754
- for (let j = 0; j < k.length; j++) {
755
- let x = lo + j;
756
- if (x < 0 || x >= len) {
757
- if (edge === "zero") continue;
758
- x = x < 0 ? 0 : len - 1;
696
+ function dt1(f, n, out, near, v, z) {
697
+ let k = 0;
698
+ v[0] = 0;
699
+ z[0] = -Infinity;
700
+ z[1] = Infinity;
701
+ for (let q = 1; q < n; q++) {
702
+ for (; ; ) {
703
+ const p = v[k];
704
+ const s = (f[q] + q * q - (f[p] + p * p)) / (2 * q - 2 * p);
705
+ if (s <= z[k] && k > 0) {
706
+ k--;
707
+ continue;
759
708
  }
760
- a += src[off + x * stride] * k[j];
709
+ if (s <= z[k]) {
710
+ v[0] = q;
711
+ z[0] = -Infinity;
712
+ z[1] = Infinity;
713
+ break;
714
+ }
715
+ k++;
716
+ v[k] = q;
717
+ z[k] = s;
718
+ z[k + 1] = Infinity;
719
+ break;
761
720
  }
762
- dst[i] = a;
763
721
  }
764
- }
765
- function boxesLine(src, off, stride, len, radii, edge, a, b, dst) {
766
- const pad = radii.reduce((s, r) => s + r, 0);
767
- const plen = len + 2 * pad;
768
- for (let i = 0; i < plen; i++) {
769
- const j = i - pad;
770
- a[i] = j < 0 ? edge === "zero" ? 0 : src[off] : j >= len ? edge === "zero" ? 0 : src[off + (len - 1) * stride] : src[off + j * stride];
722
+ k = 0;
723
+ for (let q = 0; q < n; q++) {
724
+ while (z[k + 1] < q) k++;
725
+ const p = v[k];
726
+ const d = q - p;
727
+ out[q] = d * d + f[p];
728
+ near[q] = p;
771
729
  }
772
- let from = a;
773
- let to = b;
774
- for (const r of radii) {
775
- let sum = 0;
776
- for (let i = 0; i <= Math.min(r, plen - 1); i++) sum += from[i];
777
- const k = 2 * r + 1;
778
- for (let i = 0; i < plen; i++) {
779
- to[i] = sum / k;
780
- const add = i + r + 1;
781
- const drop = i - r;
782
- if (add < plen) sum += from[add];
783
- if (drop >= 0) sum -= from[drop];
784
- }
785
- const t = from;
786
- from = to;
787
- to = t;
788
- }
789
- for (let i = 0; i < len; i++) dst[i] = from[i + pad];
790
730
  }
791
- function gaussianBlur(src, w, h, sigma, edge = "zero") {
792
- const out = Float32Array.from(src);
793
- if (!(sigma >= 0.1) || w <= 0 || h <= 0) return out;
794
- const s = Math.min(sigma, 4 * Math.max(w, h));
795
- const line = new Float32Array(Math.max(w, h));
796
- const passes = [];
797
- if (s <= DIRECT_GAUSSIAN_MAX_SIGMA) {
798
- const k = gaussianKernel(s);
799
- passes.push((buf, off, stride, len) => convolveLine(buf, off, stride, len, k, edge, line));
800
- } else {
801
- const radii = boxRadii(s);
802
- const plen = Math.max(w, h) + 2 * radii.reduce((a2, r) => a2 + r, 0);
803
- const a = new Float32Array(plen);
804
- const b = new Float32Array(plen);
805
- passes.push((buf, off, stride, len) => boxesLine(buf, off, stride, len, radii, edge, a, b, line));
806
- }
807
- for (const pass of passes) {
731
+ function edtNearest(inside, w, h) {
732
+ const total = w * h;
733
+ const n = Math.max(w, h);
734
+ const f = new Float64Array(n);
735
+ const o = new Float64Array(n);
736
+ const nr = new Int32Array(n);
737
+ const v = new Int32Array(n);
738
+ const z = new Float64Array(n + 1);
739
+ const gc = new Float64Array(total);
740
+ const nc = new Int32Array(total);
741
+ for (let x = 0; x < w; x++) {
742
+ for (let y = 0; y < h; y++) f[y] = inside[y * w + x] ? 0 : INF;
743
+ dt1(f, h, o, nr, v, z);
808
744
  for (let y = 0; y < h; y++) {
809
- pass(out, y * w, 1, w);
810
- out.set(line.subarray(0, w), y * w);
745
+ gc[x * h + y] = o[y];
746
+ nc[x * h + y] = nr[y];
811
747
  }
812
748
  }
813
- for (const pass of passes) {
749
+ const d = new Float32Array(total);
750
+ const near = new Int32Array(total);
751
+ for (let y = 0; y < h; y++) {
752
+ for (let x = 0; x < w; x++) f[x] = gc[x * h + y];
753
+ dt1(f, w, o, nr, v, z);
814
754
  for (let x = 0; x < w; x++) {
815
- pass(out, x, w, h);
816
- for (let y = 0; y < h; y++) out[y * w + x] = line[y];
755
+ d[y * w + x] = Math.sqrt(o[x]);
756
+ const px = nr[x];
757
+ near[y * w + x] = nc[px * h + y] * w + px;
817
758
  }
818
759
  }
819
- return out;
760
+ return { d, near };
820
761
  }
821
-
822
- // src/raster/layers.ts
823
- var childrenOf = (l) => l.type === "group" ? l.children ?? null : null;
824
- function flattenLayers(layers) {
825
- const out = [];
826
- const walk = (list) => {
827
- for (const l of list) {
828
- out.push(l);
829
- const c = childrenOf(l);
830
- if (c) walk(c);
762
+ var S2 = Math.SQRT2;
763
+ var S5 = 2.236068;
764
+ var FWD = [
765
+ [-1, 0, 1],
766
+ [-1, -1, S2],
767
+ [0, -1, 1],
768
+ [1, -1, S2],
769
+ [-1, -2, S5],
770
+ [1, -2, S5],
771
+ [-2, -1, S5],
772
+ [2, -1, S5]
773
+ ];
774
+ function chamferFrom(d, w, h) {
775
+ const ox = FWD.map((f) => f[0]);
776
+ const oy = FWD.map((f) => f[1]);
777
+ const wt = FWD.map((f) => f[2]);
778
+ const k = FWD.length;
779
+ for (let pass = 0; pass < 2; pass++) {
780
+ const sign = pass === 0 ? 1 : -1;
781
+ for (let yy = 0; yy < h; yy++) {
782
+ const y = pass === 0 ? yy : h - 1 - yy;
783
+ for (let xx = 0; xx < w; xx++) {
784
+ const x = pass === 0 ? xx : w - 1 - xx;
785
+ const i = y * w + x;
786
+ let best = d[i];
787
+ for (let j = 0; j < k; j++) {
788
+ const nx = x + ox[j] * sign;
789
+ const ny = y + oy[j] * sign;
790
+ if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
791
+ const c = d[ny * w + nx] + wt[j];
792
+ if (c < best) best = c;
793
+ }
794
+ d[i] = best;
795
+ }
831
796
  }
832
- };
833
- walk(layers);
797
+ }
798
+ return d;
799
+ }
800
+ function distOutside(s, chamfer) {
801
+ const { w, h, v } = s;
802
+ const n = v.length;
803
+ const out = new Float32Array(n);
804
+ if (chamfer) {
805
+ const start = new Float32Array(n);
806
+ for (let i = 0; i < n; i++) start[i] = v[i] > INSIDE_EPS ? 1 - Math.min(v[i], 1) : INF;
807
+ const d2 = chamferFrom(start, w, h);
808
+ for (let i = 0; i < n; i++) out[i] = v[i] > INSIDE_EPS ? -0.5 : d2[i] - 0.5;
809
+ return out;
810
+ }
811
+ const inside = new Uint8Array(n);
812
+ for (let i = 0; i < n; i++) inside[i] = v[i] > INSIDE_EPS ? 1 : 0;
813
+ const { d, near } = edtNearest(inside, w, h);
814
+ for (let i = 0; i < n; i++) out[i] = inside[i] ? -0.5 : d[i] - 0.5 + (1 - Math.min(v[near[i]], 1));
834
815
  return out;
835
816
  }
836
- function findLayer(layers, id) {
837
- const walk = (list, parent, path) => {
838
- for (let i = 0; i < list.length; i++) {
839
- const l = list[i];
840
- if (l.id === id) return { layer: l, parent, index: i, path };
841
- const c = childrenOf(l);
842
- if (c) {
843
- const g = l;
844
- const hit = walk(c, g, [...path, g]);
845
- if (hit) return hit;
846
- }
817
+ function localCoverage(s) {
818
+ const out = { w: s.w, h: s.h, v: new Float32Array(s.v) };
819
+ for (let y = 0; y < s.h; y++) {
820
+ for (let x = 0; x < s.w; x++) {
821
+ const a = s.v[y * s.w + x];
822
+ if (a <= INSIDE_EPS) continue;
823
+ let mx = a;
824
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) mx = Math.max(mx, mapGet(s, x + dx, y + dy));
825
+ out.v[y * s.w + x] = Math.min(a / mx, 1);
847
826
  }
848
- return null;
849
- };
850
- return walk(layers, null, []);
827
+ }
828
+ return out;
851
829
  }
852
- function mapLayer(layers, id, fn) {
853
- let changed = false;
854
- const walk = (list) => {
855
- let out = null;
856
- for (let i = 0; i < list.length; i++) {
857
- const l = list[i];
858
- let next = l;
859
- if (l.id === id) {
860
- next = fn(l);
861
- changed = true;
830
+ function distInside(s, chamfer) {
831
+ const { w, h, v } = s;
832
+ const n = v.length;
833
+ const out = new Float32Array(n);
834
+ if (chamfer) {
835
+ const cov = localCoverage(s);
836
+ const start = new Float32Array(n);
837
+ for (let i = 0; i < n; i++) start[i] = v[i] <= INSIDE_EPS ? 0 : cov.v[i] < 1 - INSIDE_EPS ? cov.v[i] : INF;
838
+ const d2 = chamferFrom(start, w, h);
839
+ for (let i = 0; i < n; i++) out[i] = v[i] <= INSIDE_EPS ? -0.5 : d2[i] - 0.5;
840
+ return out;
841
+ }
842
+ const outside = new Uint8Array(n);
843
+ for (let i = 0; i < n; i++) outside[i] = v[i] <= INSIDE_EPS ? 1 : 0;
844
+ const { d } = edtNearest(outside, w, h);
845
+ for (let i = 0; i < n; i++) out[i] = outside[i] ? -0.5 : d[i] - 0.5;
846
+ return out;
847
+ }
848
+ function edgeDistances(s, reach) {
849
+ const { w, h, v } = s;
850
+ const n = v.length;
851
+ const edge = new Uint8Array(n);
852
+ const inside = new Uint8Array(n);
853
+ for (let y = 0; y < h; y++) {
854
+ for (let x = 0; x < w; x++) {
855
+ const i = y * w + x;
856
+ const a = v[i];
857
+ if (a <= INSIDE_EPS) continue;
858
+ inside[i] = 1;
859
+ if (a < 1 - INSIDE_EPS || mapGet(s, x - 1, y) <= INSIDE_EPS || mapGet(s, x + 1, y) <= INSIDE_EPS || mapGet(s, x, y - 1) <= INSIDE_EPS || mapGet(s, x, y + 1) <= INSIDE_EPS) edge[i] = 1;
860
+ }
861
+ }
862
+ const { d, near } = edtNearest(edge, w, h);
863
+ const din = new Float32Array(n);
864
+ const dout = new Float32Array(n);
865
+ for (let y = 0; y < h; y++) {
866
+ for (let x = 0; x < w; x++) {
867
+ const i = y * w + x;
868
+ const q = near[i];
869
+ let sd;
870
+ if (d[i] > reach || d[i] >= INF / 2) {
871
+ sd = inside[i] ? -(d[i] + 0.5) : d[i] - 0.5;
862
872
  } else {
863
- const c = childrenOf(l);
864
- if (c) {
865
- const children = walk(c);
866
- if (children !== c) next = { ...l, children };
873
+ const qx = q % w;
874
+ const qy = (q - qx) / w;
875
+ const g = (dx, dy) => mapGet(s, qx + dx, qy + dy);
876
+ let nx = -(g(1, -1) + 2 * g(1, 0) + g(1, 1) - g(-1, -1) - 2 * g(-1, 0) - g(-1, 1));
877
+ let ny = -(g(-1, 1) + 2 * g(0, 1) + g(1, 1) - g(-1, -1) - 2 * g(0, -1) - g(1, -1));
878
+ const len = Math.hypot(nx, ny);
879
+ if (len > 1e-6) {
880
+ nx /= len;
881
+ ny /= len;
882
+ } else if (d[i] > 0) {
883
+ nx = (x - qx) / d[i];
884
+ ny = (y - qy) / d[i];
885
+ } else {
886
+ nx = 1;
887
+ ny = 0;
867
888
  }
889
+ sd = (x - qx) * nx + (y - qy) * ny - (Math.min(v[q], 1) - 0.5);
868
890
  }
869
- if (next !== l) {
870
- if (!out) out = list.slice(0, i);
871
- if (next) out.push(next);
872
- } else if (out) out.push(l);
891
+ din[i] = inside[i] ? Math.max(-sd, inside[i] && !edge[i] ? 0.5 : -0.5) : -0.5;
892
+ dout[i] = inside[i] && !edge[i] ? -0.5 : Math.max(sd, -0.5);
873
893
  }
874
- return out ?? list;
875
- };
876
- const result = walk(layers);
877
- return changed ? result : layers;
894
+ }
895
+ return { din, dout };
878
896
  }
879
- function insertLayer(layers, layer, parentId, index) {
880
- const put = (list) => {
881
- const at = index === void 0 ? list.length : Math.max(0, Math.min(list.length, index));
882
- const next = list.slice();
883
- next.splice(at, 0, layer);
884
- return next;
885
- };
886
- if (!parentId) return put(layers);
887
- return mapLayer(layers, parentId, (g) => {
888
- const c = childrenOf(g);
889
- return c ? { ...g, children: put(c) } : g;
890
- });
897
+ function dilate(s, r) {
898
+ if (!(r > 0)) return s;
899
+ const d = distOutside(s, true);
900
+ const out = { w: s.w, h: s.h, v: new Float32Array(s.v) };
901
+ for (let i = 0; i < d.length; i++) {
902
+ const o = out.v[i];
903
+ out.v[i] = d[i] < 0 ? Math.min(o + r, 1) : Math.max(o, clamp012(r + 0.5 - d[i]));
904
+ }
905
+ return out;
891
906
  }
892
- function isLayerLocked(layer, what) {
893
- return Boolean(layer.locks?.all || layer.locks?.[what]);
894
- }
895
-
896
- // src/raster/effects/patterns.ts
897
- var PatternTile = class {
898
- w;
899
- h;
900
- px;
901
- constructor(p) {
902
- this.w = Math.max(1, p.width | 0);
903
- this.h = Math.max(1, p.height | 0);
904
- const n = this.w * this.h;
905
- this.px = new Float32Array(n * 4);
906
- for (let i = 0; i < n; i++) {
907
- const a = (p.data[i * 4 + 3] ?? 0) / 255;
908
- this.px[i * 4] = (p.data[i * 4] ?? 0) / 255 * a;
909
- this.px[i * 4 + 1] = (p.data[i * 4 + 1] ?? 0) / 255 * a;
910
- this.px[i * 4 + 2] = (p.data[i * 4 + 2] ?? 0) / 255 * a;
911
- this.px[i * 4 + 3] = a;
912
- }
913
- }
914
- /** Bilinear wrapping sample at tile coordinates (pixel centres at `i + 0.5`), straight alpha into `out`. */
915
- sample(u, v, out) {
916
- u -= 0.5;
917
- v -= 0.5;
918
- const x0 = Math.floor(u);
919
- const y0 = Math.floor(v);
920
- const fx = u - x0;
921
- const fy = v - y0;
922
- let r = 0;
923
- let g = 0;
924
- let b = 0;
925
- let a = 0;
926
- for (let dy = 0; dy < 2; dy++) {
927
- const wy2 = dy ? fy : 1 - fy;
928
- if (wy2 === 0) continue;
929
- const yi = ((y0 + dy) % this.h + this.h) % this.h;
930
- for (let dx = 0; dx < 2; dx++) {
931
- const wx2 = dx ? fx : 1 - fx;
932
- if (wx2 === 0) continue;
933
- const xi = ((x0 + dx) % this.w + this.w) % this.w;
934
- const k = wx2 * wy2;
935
- const i = (yi * this.w + xi) * 4;
936
- r += this.px[i] * k;
937
- g += this.px[i + 1] * k;
938
- b += this.px[i + 2] * k;
939
- a += this.px[i + 3] * k;
940
- }
941
- }
942
- if (a <= 0) {
943
- out[0] = out[1] = out[2] = out[3] = 0;
944
- return;
945
- }
946
- out[0] = r / a;
947
- out[1] = g / a;
948
- out[2] = b / a;
949
- out[3] = Math.min(1, a);
950
- }
951
- };
952
- var PatternPlacement = class {
953
- ox;
954
- oy;
955
- c;
956
- s;
957
- inv;
958
- /** `anchor`: the layer's top-left (used when `link`); `phase` in px; `scale` a fraction; `angle` degrees counter-clockwise. */
959
- constructor(anchor, link, phase, scale, angle) {
960
- const bx = link ? anchor.x : 0;
961
- const by = link ? anchor.y : 0;
962
- this.ox = bx + phase.x;
963
- this.oy = by + phase.y;
964
- this.inv = Number.isFinite(scale) && scale > 1e-3 ? 1 / scale : 1;
965
- const a = (Number.isFinite(angle) ? angle : 0) * Math.PI / 180;
966
- this.c = Math.cos(a);
967
- this.s = Math.sin(a);
968
- }
969
- /** Tile coordinates of the document point `(x, y)` into `out[0..1]`. */
970
- map(x, y, out) {
971
- const dx = x - this.ox;
972
- const dy = y - this.oy;
973
- out[0] = (dx * this.c - dy * this.s) * this.inv;
974
- out[1] = (dx * this.s + dy * this.c) * this.inv;
975
- }
976
- };
977
- var BUILTINS = {
978
- checker: { name: "Checker", size: 16, paint: (x, y) => x < 8 !== y < 8 ? [204, 204, 204, 255] : [255, 255, 255, 255] },
979
- stripes: { name: "Diagonal Stripes", size: 16, paint: (x, y) => (x + y) % 16 < 8 ? [40, 40, 40, 255] : [235, 235, 235, 255] },
980
- dots: {
981
- name: "Dots",
982
- size: 12,
983
- paint: (x, y) => {
984
- const d = Math.hypot(x + 0.5 - 6, y + 0.5 - 6);
985
- const a = Math.max(0, Math.min(1, 3.5 - d));
986
- return [Math.round(255 - a * 215), Math.round(255 - a * 215), Math.round(255 - a * 215), 255];
987
- }
988
- },
989
- grid: { name: "Grid", size: 16, paint: (x, y) => x === 0 || y === 0 ? [90, 90, 90, 255] : [250, 250, 250, 255] },
990
- noise: {
991
- name: "Noise",
992
- size: 64,
993
- paint: (x, y) => {
994
- const v = Math.round(hashNoise(x, y) * 255);
995
- return [v, v, v, 255];
996
- }
997
- }
998
- };
999
- function builtinPatternIds() {
1000
- return Object.keys(BUILTINS);
1001
- }
1002
- var builtinCache = /* @__PURE__ */ new Map();
1003
- function builtinPattern(id) {
1004
- const def = BUILTINS[id];
1005
- if (!def) return null;
1006
- let p = builtinCache.get(id);
1007
- if (!p) {
1008
- const data = new Uint8ClampedArray(def.size * def.size * 4);
1009
- for (let y = 0; y < def.size; y++) {
1010
- for (let x = 0; x < def.size; x++) data.set(def.paint(x, y), (y * def.size + x) * 4);
1011
- }
1012
- p = { id, name: def.name, width: def.size, height: def.size, data };
1013
- builtinCache.set(id, p);
1014
- }
1015
- return p;
1016
- }
1017
- function hashNoise(x, y) {
1018
- let h = Math.imul(x | 0, 2376512323) ^ Math.imul(y | 0, 3625334849) ^ 2654435769;
1019
- h ^= h >>> 15;
1020
- h = Math.imul(h, 739982445);
1021
- h ^= h >>> 12;
1022
- return (h & 65535) / 65536;
1023
- }
1024
-
1025
- // src/raster/curve.ts
1026
- var CURVE_LUT_SIZE = 4096;
1027
- var clamp01 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
1028
- function curveLut(points, size = CURVE_LUT_SIZE) {
1029
- 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]);
1030
- const dedup = [];
1031
- for (const p of pts) {
1032
- if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;
1033
- dedup.push(p);
1034
- }
1035
- const out = new Float32Array(size);
1036
- const n = dedup.length;
1037
- if (n < 2) {
1038
- for (let k = 0; k < size; k++) out[k] = k / (size - 1);
1039
- return out;
1040
- }
1041
- const x = dedup.map((p) => p[0]);
1042
- const y = dedup.map((p) => p[1]);
1043
- const m2 = new Float64Array(n);
1044
- if (n > 2) {
1045
- const c = new Float64Array(n);
1046
- const d = new Float64Array(n);
1047
- for (let i = 1; i < n - 1; i++) {
1048
- const h0 = x[i] - x[i - 1];
1049
- const h1 = x[i + 1] - x[i];
1050
- const a = h0 / 6;
1051
- const b = (h0 + h1) / 3;
1052
- const cc = h1 / 6;
1053
- const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;
1054
- const denom = b - a * c[i - 1];
1055
- c[i] = cc / denom;
1056
- d[i] = (r - a * d[i - 1]) / denom;
1057
- }
1058
- for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];
1059
- }
1060
- let seg = 0;
1061
- for (let k = 0; k < size; k++) {
1062
- const xv = k / (size - 1);
1063
- if (xv <= x[0]) {
1064
- out[k] = clamp01(y[0]);
1065
- continue;
1066
- }
1067
- if (xv >= x[n - 1]) {
1068
- out[k] = clamp01(y[n - 1]);
1069
- continue;
1070
- }
1071
- while (seg < n - 2 && xv > x[seg + 1]) seg++;
1072
- const x0 = x[seg], x1 = x[seg + 1];
1073
- const h = x1 - x0;
1074
- const a = (x1 - xv) / h;
1075
- const b = (xv - x0) / h;
1076
- 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;
1077
- out[k] = clamp01(v);
1078
- }
1079
- return out;
1080
- }
1081
-
1082
- // src/raster/effects/render.ts
1083
- var INSIDE_EPS = 0.5 / 255;
1084
- var INF = 1e20;
1085
- var FAR = 1e9;
1086
- var STROKE_CHAMFER_MAX = 20;
1087
- var BEVEL_H_EPS = 1e-5;
1088
- var fmap = (w, h, fill2 = 0) => {
1089
- const v = new Float32Array(w * h);
1090
- if (fill2) v.fill(fill2);
1091
- return { w, h, v };
1092
- };
1093
- var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
1094
- var clamp012 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
1095
- function mapGet(m, x, y) {
1096
- return x < 0 || y < 0 || x >= m.w || y >= m.h ? 0 : m.v[y * m.w + x];
1097
- }
1098
- function shifted(m, dx, dy, outside) {
1099
- const reach = (d) => Number.isFinite(d) ? clamp(d, -1e9, 1e9) : 0;
1100
- dx = reach(dx);
1101
- dy = reach(dy);
1102
- const fx = Math.floor(dx);
1103
- const fy = Math.floor(dy);
1104
- const ax = dx - fx;
1105
- const ay = dy - fy;
1106
- const { w, h, v } = m;
1107
- const s = (x, y) => x < 0 || y < 0 || x >= w || y >= h ? outside : v[y * w + x];
1108
- const out = fmap(w, h);
1109
- for (let y = 0; y < h; y++) {
1110
- const sy = y - fy;
1111
- for (let x = 0; x < w; x++) {
1112
- const sx = x - fx;
1113
- const top = s(sx, sy) * (1 - ax) + s(sx - 1, sy) * ax;
1114
- const bot = s(sx, sy - 1) * (1 - ax) + s(sx - 1, sy - 1) * ax;
1115
- out.v[y * w + x] = top * (1 - ay) + bot * ay;
1116
- }
1117
- }
1118
- return out;
1119
- }
1120
- function mapped(m, f) {
1121
- const out = fmap(m.w, m.h);
1122
- for (let i = 0; i < m.v.length; i++) out.v[i] = f(m.v[i]);
1123
- return out;
1124
- }
1125
- function dt1(f, n, out, near, v, z) {
1126
- let k = 0;
1127
- v[0] = 0;
1128
- z[0] = -Infinity;
1129
- z[1] = Infinity;
1130
- for (let q = 1; q < n; q++) {
1131
- for (; ; ) {
1132
- const p = v[k];
1133
- const s = (f[q] + q * q - (f[p] + p * p)) / (2 * q - 2 * p);
1134
- if (s <= z[k] && k > 0) {
1135
- k--;
1136
- continue;
1137
- }
1138
- if (s <= z[k]) {
1139
- v[0] = q;
1140
- z[0] = -Infinity;
1141
- z[1] = Infinity;
1142
- break;
1143
- }
1144
- k++;
1145
- v[k] = q;
1146
- z[k] = s;
1147
- z[k + 1] = Infinity;
1148
- break;
1149
- }
1150
- }
1151
- k = 0;
1152
- for (let q = 0; q < n; q++) {
1153
- while (z[k + 1] < q) k++;
1154
- const p = v[k];
1155
- const d = q - p;
1156
- out[q] = d * d + f[p];
1157
- near[q] = p;
1158
- }
1159
- }
1160
- function edtNearest(inside, w, h) {
1161
- const total = w * h;
1162
- const n = Math.max(w, h);
1163
- const f = new Float64Array(n);
1164
- const o = new Float64Array(n);
1165
- const nr = new Int32Array(n);
1166
- const v = new Int32Array(n);
1167
- const z = new Float64Array(n + 1);
1168
- const gc = new Float64Array(total);
1169
- const nc = new Int32Array(total);
1170
- for (let x = 0; x < w; x++) {
1171
- for (let y = 0; y < h; y++) f[y] = inside[y * w + x] ? 0 : INF;
1172
- dt1(f, h, o, nr, v, z);
1173
- for (let y = 0; y < h; y++) {
1174
- gc[x * h + y] = o[y];
1175
- nc[x * h + y] = nr[y];
1176
- }
1177
- }
1178
- const d = new Float32Array(total);
1179
- const near = new Int32Array(total);
1180
- for (let y = 0; y < h; y++) {
1181
- for (let x = 0; x < w; x++) f[x] = gc[x * h + y];
1182
- dt1(f, w, o, nr, v, z);
1183
- for (let x = 0; x < w; x++) {
1184
- d[y * w + x] = Math.sqrt(o[x]);
1185
- const px = nr[x];
1186
- near[y * w + x] = nc[px * h + y] * w + px;
1187
- }
1188
- }
1189
- return { d, near };
1190
- }
1191
- var S2 = Math.SQRT2;
1192
- var S5 = 2.236068;
1193
- var FWD = [
1194
- [-1, 0, 1],
1195
- [-1, -1, S2],
1196
- [0, -1, 1],
1197
- [1, -1, S2],
1198
- [-1, -2, S5],
1199
- [1, -2, S5],
1200
- [-2, -1, S5],
1201
- [2, -1, S5]
1202
- ];
1203
- function chamferFrom(d, w, h) {
1204
- const ox = FWD.map((f) => f[0]);
1205
- const oy = FWD.map((f) => f[1]);
1206
- const wt = FWD.map((f) => f[2]);
1207
- const k = FWD.length;
1208
- for (let pass = 0; pass < 2; pass++) {
1209
- const sign = pass === 0 ? 1 : -1;
1210
- for (let yy = 0; yy < h; yy++) {
1211
- const y = pass === 0 ? yy : h - 1 - yy;
1212
- for (let xx = 0; xx < w; xx++) {
1213
- const x = pass === 0 ? xx : w - 1 - xx;
1214
- const i = y * w + x;
1215
- let best = d[i];
1216
- for (let j = 0; j < k; j++) {
1217
- const nx = x + ox[j] * sign;
1218
- const ny = y + oy[j] * sign;
1219
- if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
1220
- const c = d[ny * w + nx] + wt[j];
1221
- if (c < best) best = c;
1222
- }
1223
- d[i] = best;
1224
- }
1225
- }
1226
- }
1227
- return d;
1228
- }
1229
- function distOutside(s, chamfer) {
1230
- const { w, h, v } = s;
1231
- const n = v.length;
1232
- const out = new Float32Array(n);
1233
- if (chamfer) {
1234
- const start = new Float32Array(n);
1235
- for (let i = 0; i < n; i++) start[i] = v[i] > INSIDE_EPS ? 1 - Math.min(v[i], 1) : INF;
1236
- const d2 = chamferFrom(start, w, h);
1237
- for (let i = 0; i < n; i++) out[i] = v[i] > INSIDE_EPS ? -0.5 : d2[i] - 0.5;
1238
- return out;
1239
- }
1240
- const inside = new Uint8Array(n);
1241
- for (let i = 0; i < n; i++) inside[i] = v[i] > INSIDE_EPS ? 1 : 0;
1242
- const { d, near } = edtNearest(inside, w, h);
1243
- for (let i = 0; i < n; i++) out[i] = inside[i] ? -0.5 : d[i] - 0.5 + (1 - Math.min(v[near[i]], 1));
1244
- return out;
1245
- }
1246
- function localCoverage(s) {
1247
- const out = { w: s.w, h: s.h, v: new Float32Array(s.v) };
1248
- for (let y = 0; y < s.h; y++) {
1249
- for (let x = 0; x < s.w; x++) {
1250
- const a = s.v[y * s.w + x];
1251
- if (a <= INSIDE_EPS) continue;
1252
- let mx = a;
1253
- for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) mx = Math.max(mx, mapGet(s, x + dx, y + dy));
1254
- out.v[y * s.w + x] = Math.min(a / mx, 1);
1255
- }
1256
- }
1257
- return out;
1258
- }
1259
- function distInside(s, chamfer) {
1260
- const { w, h, v } = s;
1261
- const n = v.length;
1262
- const out = new Float32Array(n);
1263
- if (chamfer) {
1264
- const cov = localCoverage(s);
1265
- const start = new Float32Array(n);
1266
- for (let i = 0; i < n; i++) start[i] = v[i] <= INSIDE_EPS ? 0 : cov.v[i] < 1 - INSIDE_EPS ? cov.v[i] : INF;
1267
- const d2 = chamferFrom(start, w, h);
1268
- for (let i = 0; i < n; i++) out[i] = v[i] <= INSIDE_EPS ? -0.5 : d2[i] - 0.5;
1269
- return out;
1270
- }
1271
- const outside = new Uint8Array(n);
1272
- for (let i = 0; i < n; i++) outside[i] = v[i] <= INSIDE_EPS ? 1 : 0;
1273
- const { d } = edtNearest(outside, w, h);
1274
- for (let i = 0; i < n; i++) out[i] = outside[i] ? -0.5 : d[i] - 0.5;
1275
- return out;
1276
- }
1277
- function edgeDistances(s, reach) {
1278
- const { w, h, v } = s;
1279
- const n = v.length;
1280
- const edge = new Uint8Array(n);
1281
- const inside = new Uint8Array(n);
1282
- for (let y = 0; y < h; y++) {
1283
- for (let x = 0; x < w; x++) {
1284
- const i = y * w + x;
1285
- const a = v[i];
1286
- if (a <= INSIDE_EPS) continue;
1287
- inside[i] = 1;
1288
- if (a < 1 - INSIDE_EPS || mapGet(s, x - 1, y) <= INSIDE_EPS || mapGet(s, x + 1, y) <= INSIDE_EPS || mapGet(s, x, y - 1) <= INSIDE_EPS || mapGet(s, x, y + 1) <= INSIDE_EPS) edge[i] = 1;
1289
- }
1290
- }
1291
- const { d, near } = edtNearest(edge, w, h);
1292
- const din = new Float32Array(n);
1293
- const dout = new Float32Array(n);
1294
- for (let y = 0; y < h; y++) {
1295
- for (let x = 0; x < w; x++) {
1296
- const i = y * w + x;
1297
- const q = near[i];
1298
- let sd;
1299
- if (d[i] > reach || d[i] >= INF / 2) {
1300
- sd = inside[i] ? -(d[i] + 0.5) : d[i] - 0.5;
1301
- } else {
1302
- const qx = q % w;
1303
- const qy = (q - qx) / w;
1304
- const g = (dx, dy) => mapGet(s, qx + dx, qy + dy);
1305
- let nx = -(g(1, -1) + 2 * g(1, 0) + g(1, 1) - g(-1, -1) - 2 * g(-1, 0) - g(-1, 1));
1306
- let ny = -(g(-1, 1) + 2 * g(0, 1) + g(1, 1) - g(-1, -1) - 2 * g(0, -1) - g(1, -1));
1307
- const len = Math.hypot(nx, ny);
1308
- if (len > 1e-6) {
1309
- nx /= len;
1310
- ny /= len;
1311
- } else if (d[i] > 0) {
1312
- nx = (x - qx) / d[i];
1313
- ny = (y - qy) / d[i];
1314
- } else {
1315
- nx = 1;
1316
- ny = 0;
1317
- }
1318
- sd = (x - qx) * nx + (y - qy) * ny - (Math.min(v[q], 1) - 0.5);
1319
- }
1320
- din[i] = inside[i] ? Math.max(-sd, inside[i] && !edge[i] ? 0.5 : -0.5) : -0.5;
1321
- dout[i] = inside[i] && !edge[i] ? -0.5 : Math.max(sd, -0.5);
1322
- }
1323
- }
1324
- return { din, dout };
1325
- }
1326
- function dilate(s, r) {
1327
- if (!(r > 0)) return s;
1328
- const d = distOutside(s, true);
1329
- const out = { w: s.w, h: s.h, v: new Float32Array(s.v) };
1330
- for (let i = 0; i < d.length; i++) {
1331
- const o = out.v[i];
1332
- out.v[i] = d[i] < 0 ? Math.min(o + r, 1) : Math.max(o, clamp012(r + 0.5 - d[i]));
1333
- }
1334
- return out;
1335
- }
1336
- function boxWidth(w) {
1337
- return Number.isNaN(w) ? 1 : clamp(w, 1, MAX_EFFECT_REACH);
907
+ function boxWidth(w) {
908
+ return Number.isNaN(w) ? 1 : clamp(w, 1, MAX_EFFECT_REACH);
1338
909
  }
1339
910
  function boxGeom(bw) {
1340
911
  bw = boxWidth(bw);
@@ -2139,10 +1710,13 @@ function compositeLayerEffects(input) {
2139
1710
  const { backdrop, content, rect, maps, ctx } = input;
2140
1711
  const items = maps.items;
2141
1712
  const n = rect.width * rect.height;
1713
+ const cs = content instanceof Float32Array ? 1 : 1 / 255;
1714
+ const bs = backdrop instanceof Float32Array ? 1 : 1 / 255;
1715
+ const floatOut = backdrop instanceof Float32Array;
2142
1716
  const shape = new Float32Array(n);
2143
- for (let i = 0; i < n; i++) shape[i] = content[i * 4 + 3] / 255;
1717
+ for (let i = 0; i < n; i++) shape[i] = content[i * 4 + 3] * cs;
2144
1718
  const before = new Float32Array(n * 4);
2145
- for (let i = 0; i < n * 4; i++) before[i] = backdrop[i] / 255;
1719
+ for (let i = 0; i < n * 4; i++) before[i] = backdrop[i] * bs;
2146
1720
  const work = new Float32Array(before);
2147
1721
  const fx = (i, k) => crop(maps.per[i][k], maps.rect, rect, 0);
2148
1722
  const bounds = maps.bounds ?? rect;
@@ -2170,178 +1744,692 @@ function compositeLayerEffects(input) {
2170
1744
  knockOut(m, fill2);
2171
1745
  paintGlow(work, m, e, env);
2172
1746
  }
2173
- const lay = new Float32Array(n * 4);
2174
- for (let i = 0; i < n; i++) {
2175
- lay[i * 4] = content[i * 4] / 255;
2176
- lay[i * 4 + 1] = content[i * 4 + 1] / 255;
2177
- lay[i * 4 + 2] = content[i * 4 + 2] / 255;
2178
- lay[i * 4 + 3] = inside(shape[i]) ? fill2 : 0;
1747
+ const lay = new Float32Array(n * 4);
1748
+ for (let i = 0; i < n; i++) {
1749
+ lay[i * 4] = content[i * 4] * cs;
1750
+ lay[i * 4 + 1] = content[i * 4 + 1] * cs;
1751
+ lay[i * 4 + 2] = content[i * 4 + 2] * cs;
1752
+ lay[i * 4 + 3] = inside(shape[i]) ? fill2 : 0;
1753
+ }
1754
+ const premerge = !normalLike;
1755
+ if (premerge) {
1756
+ const sep2 = isSeparableBlendMode(input.blend);
1757
+ for (let i = 0; i < n; i++) {
1758
+ const p = i * 4;
1759
+ const sr = lay[p];
1760
+ const sg = lay[p + 1];
1761
+ const sb = lay[p + 2];
1762
+ const sa = lay[p + 3];
1763
+ lay[p] = work[p];
1764
+ lay[p + 1] = work[p + 1];
1765
+ lay[p + 2] = work[p + 2];
1766
+ lay[p + 3] = work[p + 3];
1767
+ compositeAt(lay, p, input.blend, sep2, sr, sg, sb, sa);
1768
+ }
1769
+ }
1770
+ const rel = (m) => {
1771
+ for (let i = 0; i < n; i++) m[i] = inside(shape[i]) ? Math.min(m[i] / shape[i], 1) : 0;
1772
+ return m;
1773
+ };
1774
+ const full = new Float32Array(n);
1775
+ for (let i = 0; i < n; i++) full[i] = inside(shape[i]) ? 1 : 0;
1776
+ for (const i of rev) {
1777
+ const e = items[i];
1778
+ if (e.kind === "pattern-overlay") paintPattern(lay, full, e, env, e.blend, e.opacity);
1779
+ }
1780
+ for (const i of rev) {
1781
+ const e = items[i];
1782
+ if (e.kind === "gradient-overlay") paintGradient(lay, full, e.gradient, e.gradient.align ? bounds : input.canvas, env, e.blend, e.opacity);
1783
+ }
1784
+ for (const i of rev) {
1785
+ const e = items[i];
1786
+ if (e.kind === "color-overlay") paintColor(lay, full, e.color, e.blend, e.opacity);
1787
+ }
1788
+ for (const i of rev) {
1789
+ const e = items[i];
1790
+ if (e.kind === "satin") paintColor(lay, rel(fx(i, 0)), e.color, e.blend, e.opacity);
1791
+ }
1792
+ for (const i of rev) {
1793
+ const e = items[i];
1794
+ if (e.kind === "inner-glow") paintGlow(lay, rel(fx(i, 0)), e, env);
1795
+ }
1796
+ for (const i of rev) {
1797
+ const e = items[i];
1798
+ if (e.kind === "inner-shadow") paintColor(lay, rel(fx(i, 0)), e.color, e.blend, e.opacity);
1799
+ }
1800
+ const strokeIdx = items.map((e, i) => e.kind === "stroke" ? i : -1).filter((i) => i >= 0);
1801
+ const din = strokeIdx.length && maps.din ? crop(maps.din, maps.rect, rect, 0) : null;
1802
+ const frameOf = (i) => maps.frames.get(i) ?? bounds;
1803
+ for (let k = strokeIdx.length - 1; k >= 0; k--) {
1804
+ const st = items[strokeIdx[k]];
1805
+ const [inW] = strokeWidths(st);
1806
+ if (!(inW > 0) || !din) continue;
1807
+ const m = new Float32Array(n);
1808
+ for (let i = 0; i < n; i++) m[i] = inside(shape[i]) ? clamp012(inW + 0.5 - din[i]) : 0;
1809
+ paintFill(lay, m, st.fill, frameOf(strokeIdx[k]), env, st.blend, st.opacity);
1810
+ }
1811
+ for (const i of rev) {
1812
+ const e = items[i];
1813
+ if (e.kind !== "bevel-emboss" || maps.bevelPaint[i] !== "inner") continue;
1814
+ paintColor(lay, rel(fx(i, 0)), e.highlightColor, e.highlightBlend, e.highlightOpacity);
1815
+ paintColor(lay, rel(fx(i, 1)), e.shadowColor, e.shadowBlend, e.shadowOpacity);
1816
+ }
1817
+ for (let i = 0; i < n; i++) lay[i * 4 + 3] *= Math.min(shape[i], 1);
1818
+ const outside = strokeIdx.filter((i) => strokeWidths(items[i])[1] > 0);
1819
+ if (outside.length) {
1820
+ const dout = maps.dout ? crop(maps.dout, maps.rect, rect, FAR) : null;
1821
+ const doutExact = maps.doutExact ? crop(maps.doutExact, maps.rect, rect, FAR) : null;
1822
+ const base = new Float32Array(work);
1823
+ const acc = new Float32Array(n * 4);
1824
+ const cover = new Float32Array(n);
1825
+ for (const si of outside) {
1826
+ const st = items[si];
1827
+ const outW = strokeWidths(st)[1];
1828
+ const d = (outW >= STROKE_CHAMFER_MAX ? doutExact : dout) ?? new Float32Array(n).fill(FAR);
1829
+ const share = new Float32Array(n);
1830
+ const ones = new Float32Array(n);
1831
+ for (let i = 0; i < n; i++) {
1832
+ const band = clamp012(outW + 0.5 - d[i]);
1833
+ let k;
1834
+ if (inside(shape[i])) {
1835
+ const c = clamp012(lay[i * 4 + 3]);
1836
+ k = c >= 1 ? 1 : clamp012((1 - clamp012(shape[i])) / (1 - c));
1837
+ } else k = band;
1838
+ share[i] = k * st.opacity * (1 - cover[i]);
1839
+ cover[i] += share[i];
1840
+ ones[i] = share[i] > 0 ? 1 : 0;
1841
+ }
1842
+ const blended = new Float32Array(base);
1843
+ paintFill(blended, ones, st.fill, frameOf(si), env, st.blend, 1);
1844
+ for (let i = 0; i < n; i++) {
1845
+ const s = share[i];
1846
+ if (!(s > 0)) continue;
1847
+ const p = i * 4;
1848
+ const ba = blended[p + 3];
1849
+ acc[p] += s * blended[p] * ba;
1850
+ acc[p + 1] += s * blended[p + 1] * ba;
1851
+ acc[p + 2] += s * blended[p + 2] * ba;
1852
+ acc[p + 3] += s * ba;
1853
+ }
1854
+ }
1855
+ for (let i = 0; i < n; i++) {
1856
+ const c = cover[i];
1857
+ if (c <= 0) continue;
1858
+ const p = i * 4;
1859
+ const k = 1 - Math.min(c, 1);
1860
+ const a = work[p + 3] * k + acc[p + 3];
1861
+ if (a > 0) {
1862
+ work[p] = (work[p] * work[p + 3] * k + acc[p]) / a;
1863
+ work[p + 1] = (work[p + 1] * work[p + 3] * k + acc[p + 1]) / a;
1864
+ work[p + 2] = (work[p + 2] * work[p + 3] * k + acc[p + 2]) / a;
1865
+ work[p + 3] = Math.min(a, 1);
1866
+ }
1867
+ }
1868
+ }
1869
+ for (const i of rev) {
1870
+ const e = items[i];
1871
+ if (e.kind !== "bevel-emboss" || maps.bevelPaint[i] !== "outer") continue;
1872
+ paintColor(work, fx(i, 0), e.highlightColor, e.highlightBlend, e.highlightOpacity);
1873
+ paintColor(work, fx(i, 1), e.shadowColor, e.shadowBlend, e.shadowOpacity);
1874
+ }
1875
+ const mode = premerge ? "normal" : input.blend;
1876
+ const sep = isSeparableBlendMode(mode);
1877
+ for (let i = 0; i < n; i++) {
1878
+ const p = i * 4;
1879
+ if (lay[p + 3] > 0) compositeAt(work, p, mode, sep, lay[p], lay[p + 1], lay[p + 2], lay[p + 3]);
1880
+ }
1881
+ for (const i of rev) {
1882
+ const e = items[i];
1883
+ if (e.kind !== "bevel-emboss" || maps.bevelPaint[i] !== "both") continue;
1884
+ paintColor(work, fx(i, 0), e.highlightColor, e.highlightBlend, e.highlightOpacity);
1885
+ paintColor(work, fx(i, 1), e.shadowColor, e.shadowBlend, e.shadowOpacity);
1886
+ }
1887
+ const op = clamp012(input.opacity);
1888
+ for (let i = 0; i < n; i++) {
1889
+ const p = i * 4;
1890
+ const aa = before[p + 3];
1891
+ const ba = work[p + 3];
1892
+ const alpha = aa + (ba - aa) * op;
1893
+ if (alpha <= 0) {
1894
+ backdrop[p] = backdrop[p + 1] = backdrop[p + 2] = backdrop[p + 3] = 0;
1895
+ continue;
1896
+ }
1897
+ if (floatOut) {
1898
+ for (let c = 0; c < 3; c++) backdrop[p + c] = (before[p + c] * aa + (work[p + c] * ba - before[p + c] * aa) * op) / alpha;
1899
+ backdrop[p + 3] = alpha;
1900
+ continue;
1901
+ }
1902
+ for (let c = 0; c < 3; c++) backdrop[p + c] = Math.round((before[p + c] * aa + (work[p + c] * ba - before[p + c] * aa) * op) / alpha * 255);
1903
+ backdrop[p + 3] = Math.round(alpha * 255);
1904
+ }
1905
+ }
1906
+ function renderLayerEffects(effects, layer, backdrop, canvas, ctx) {
1907
+ const items = activeEffectItems(effects);
1908
+ const n = layer.rect.width * layer.rect.height;
1909
+ const shape = new Float32Array(n);
1910
+ for (let i = 0; i < n; i++) shape[i] = layer.content[i * 4 + 3] / 255;
1911
+ const maps = buildEffectMaps(items, shape, layer.rect, ctx);
1912
+ compositeLayerEffects({
1913
+ backdrop,
1914
+ content: layer.content,
1915
+ rect: layer.rect,
1916
+ maps,
1917
+ blend: layer.blend,
1918
+ opacity: layer.opacity,
1919
+ fillOpacity: layer.fillOpacity ?? 1,
1920
+ canvas,
1921
+ ctx
1922
+ });
1923
+ }
1924
+
1925
+ // src/raster/deep-compositor.ts
1926
+ var isGroup = (l) => l.type === "group";
1927
+ var DeepCompositor = class {
1928
+ constructor(base, profile, deep, depth) {
1929
+ this.base = base;
1930
+ this.profile = profile;
1931
+ this.deep = deep;
1932
+ this.depth = depth;
1933
+ this.linear = depth === 32;
1934
+ }
1935
+ base;
1936
+ profile;
1937
+ deep;
1938
+ depth;
1939
+ sources;
1940
+ linear;
1941
+ /** The composite of `layers` over `rect` as float RGBA in the working space (`rect.width × rect.height × 4`). */
1942
+ composite(layers, rect, sources) {
1943
+ this.sources = sources;
1944
+ const acc = new Float32Array(Math.max(0, rect.width * rect.height * 4));
1945
+ if (rect.width > 0 && rect.height > 0) this.runSteps(this.buildSteps(layers), acc, true, rect);
1946
+ return acc;
1947
+ }
1948
+ // --- Steps (as RasterCompositor.buildSteps) ------------------------------------
1949
+ isPixel(l) {
1950
+ return l.type === "pixel" || (this.profile.isPixelLike?.(l) ?? false);
1951
+ }
1952
+ isFilter(l) {
1953
+ return this.profile.isFilterLayer?.(l) ?? false;
1954
+ }
1955
+ buildSteps(layers) {
1956
+ const steps = [];
1957
+ for (const layer of layers) {
1958
+ const last = steps[steps.length - 1];
1959
+ if (layer.clipped && last && (last.kind === "content" || last.kind === "group")) {
1960
+ last.clipped.push(layer);
1961
+ continue;
1962
+ }
1963
+ if (this.isFilter(layer)) {
1964
+ if (last && last.kind === "adjust") last.layers.push(layer);
1965
+ else steps.push({ kind: "adjust", layers: [layer] });
1966
+ } else if (isGroup(layer)) steps.push({ kind: "group", layer, clipped: [], steps: this.buildSteps(layer.children ?? []) });
1967
+ else steps.push({ kind: "content", layer, clipped: [] });
1968
+ }
1969
+ return steps;
1970
+ }
1971
+ runSteps(steps, acc, empty, r) {
1972
+ for (const s of steps) empty = this.runStep(s, acc, empty, r);
1973
+ return empty;
1974
+ }
1975
+ runStep(step, acc, empty, r) {
1976
+ if (step.kind === "adjust") {
1977
+ if (empty) return true;
1978
+ this.applyFilterRun(step.layers, acc, r);
1979
+ return false;
1980
+ }
1981
+ const layer = step.layer;
1982
+ if (!layer.visible) return empty;
1983
+ const clipped = step.clipped.filter((l) => l.visible);
1984
+ if (step.kind === "content" && this.deepEffects(layer)) return this.runEffects(layer, clipped, acc, r) ? false : empty;
1985
+ const coverage = this.renderedAlone(layer) ? null : this.base.maskCoverage(layer, r, this.sources);
1986
+ if (coverage && coverage.every((v) => v <= 0)) return empty;
1987
+ if (step.kind === "group" && !this.renderedAlone(layer)) {
1988
+ if (layer.blend === "pass-through" && !clipped.length) {
1989
+ if (layer.opacity >= 1 && !coverage) return this.runSteps(step.steps, acc, empty, r);
1990
+ const before = acc.slice();
1991
+ const stillEmpty = this.runSteps(step.steps, acc, empty, r);
1992
+ lerp(acc, before, layer.opacity, coverage);
1993
+ return empty && stillEmpty;
1994
+ }
1995
+ const buf2 = new Float32Array(acc.length);
1996
+ if (this.runSteps(step.steps, buf2, true, r)) return empty;
1997
+ this.applyClipped(clipped, buf2, r);
1998
+ const mode2 = layer.blend === "pass-through" ? "normal" : layer.blend;
1999
+ this.blend(acc, buf2, mode2, layer.opacity, coverage);
2000
+ return false;
2001
+ }
2002
+ const content = this.content(layer, r);
2003
+ if (!content) return empty;
2004
+ const alone = this.renderedAlone(layer);
2005
+ const fill2 = !alone && this.isPixel(layer) ? layer.fillOpacity ?? 1 : 1;
2006
+ const mode = this.blendOf(layer);
2007
+ if (!clipped.length) {
2008
+ this.blend(acc, content, mode, layer.opacity * fill2, coverage);
2009
+ return false;
2010
+ }
2011
+ const buf = new Float32Array(acc.length);
2012
+ this.blend(buf, content, "normal", fill2, coverage);
2013
+ this.applyClipped(clipped, buf, r);
2014
+ this.blend(acc, buf, mode, layer.opacity, null);
2015
+ return false;
2016
+ }
2017
+ /**
2018
+ * A pixel layer with effects onto `acc` over `r`: its deep content (filters, mask, clipped
2019
+ * layers) and the effects composited in float. Effect maps come from the 8-bit shape. At 32
2020
+ * bits the effects composite in sRGB-encoded values (their colours and blends are defined
2021
+ * there) and the result is decoded back. Returns whether anything was drawn.
2022
+ */
2023
+ runEffects(layer, clipped, acc, r) {
2024
+ const fx = this.base.effectsInput(layer, r, this.sources);
2025
+ if (!fx) return false;
2026
+ const a = fx.rect;
2027
+ const content = this.content(layer, a) ?? new Float32Array(a.width * a.height * 4);
2028
+ const coverage = this.base.maskCoverage(layer, a, this.sources);
2029
+ if (coverage) for (let i = 0; i < coverage.length; i++) content[i * 4 + 3] *= coverage[i];
2030
+ if (clipped.length) this.applyClipped(clipped, content, a);
2031
+ const sub = new Float32Array(a.width * a.height * 4);
2032
+ for (let y = 0; y < a.height; y++) {
2033
+ const s = ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4;
2034
+ sub.set(acc.subarray(s, s + a.width * 4), y * a.width * 4);
2035
+ }
2036
+ if (this.linear) {
2037
+ encodeInPlace(sub);
2038
+ encodeInPlace(content);
2039
+ }
2040
+ compositeLayerEffects({ ...fx, backdrop: sub, content });
2041
+ if (this.linear) decodeInPlace(sub);
2042
+ 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);
2043
+ return true;
2179
2044
  }
2180
- const premerge = !normalLike;
2181
- if (premerge) {
2182
- const sep2 = isSeparableBlendMode(input.blend);
2183
- for (let i = 0; i < n; i++) {
2184
- const p = i * 4;
2185
- const sr = lay[p];
2186
- const sg = lay[p + 1];
2187
- const sb = lay[p + 2];
2188
- const sa = lay[p + 3];
2189
- lay[p] = work[p];
2190
- lay[p + 1] = work[p + 1];
2191
- lay[p + 2] = work[p + 2];
2192
- lay[p + 3] = work[p + 3];
2193
- compositeAt(lay, p, input.blend, sep2, sr, sg, sb, sa);
2045
+ applyClipped(clipped, buf, r) {
2046
+ let run = [];
2047
+ const flush = () => {
2048
+ if (run.length) this.applyFilterRun(run, buf, r);
2049
+ run = [];
2050
+ };
2051
+ for (const c of clipped) {
2052
+ if (this.isFilter(c)) {
2053
+ run.push(c);
2054
+ continue;
2055
+ }
2056
+ flush();
2057
+ const alone = this.renderedAlone(c);
2058
+ const coverage = alone ? null : this.base.maskCoverage(c, r, this.sources);
2059
+ let src;
2060
+ if (isGroup(c) && !alone) {
2061
+ src = new Float32Array(buf.length);
2062
+ if (this.runSteps(this.buildSteps(c.children ?? []), src, true, r)) continue;
2063
+ } else src = this.content(c, r);
2064
+ if (!src) continue;
2065
+ const fill2 = !alone && this.isPixel(c) ? c.fillOpacity ?? 1 : 1;
2066
+ const keep = new Float32Array(buf.length >> 2);
2067
+ for (let i = 3, p = 0; i < buf.length; i += 4, p++) keep[p] = buf[i];
2068
+ this.blend(buf, src, c.blend === "pass-through" ? "normal" : this.blendOf(c), c.opacity * fill2, coverage);
2069
+ for (let i = 3, p = 0; i < buf.length; i += 4, p++) buf[i] = keep[p];
2194
2070
  }
2071
+ flush();
2195
2072
  }
2196
- const rel = (m) => {
2197
- for (let i = 0; i < n; i++) m[i] = inside(shape[i]) ? Math.min(m[i] / shape[i], 1) : 0;
2198
- return m;
2199
- };
2200
- const full = new Float32Array(n);
2201
- for (let i = 0; i < n; i++) full[i] = inside(shape[i]) ? 1 : 0;
2202
- for (const i of rev) {
2203
- const e = items[i];
2204
- if (e.kind === "pattern-overlay") paintPattern(lay, full, e, env, e.blend, e.opacity);
2073
+ applyFilterRun(layers, acc, r) {
2074
+ const visible = [];
2075
+ let masks;
2076
+ for (const node of layers) {
2077
+ if (!node.visible) continue;
2078
+ const cov = this.base.maskCoverage(node, r, this.sources);
2079
+ if (cov) {
2080
+ if (cov.every((v) => v <= 0)) continue;
2081
+ (masks ??= /* @__PURE__ */ new Map()).set(node.id, cov);
2082
+ }
2083
+ visible.push(node);
2084
+ }
2085
+ if (!visible.length) return;
2086
+ if (this.deep.applyFilterRunDeep) {
2087
+ if (this.linear) mapColour(acc, srgbEncode);
2088
+ this.deep.applyFilterRunDeep(visible, acc, r, masks, this.sources);
2089
+ if (this.linear) mapColour(acc, srgbDecode);
2090
+ return;
2091
+ }
2092
+ const bytes = this.to8(acc);
2093
+ this.profile.applyFilterRun?.(visible, bytes, r, masks, this.sources);
2094
+ acc.set(this.from8(bytes));
2205
2095
  }
2206
- for (const i of rev) {
2207
- const e = items[i];
2208
- if (e.kind === "gradient-overlay") paintGradient(lay, full, e.gradient, e.gradient.align ? bounds : input.canvas, env, e.blend, e.opacity);
2096
+ // --- Layer content ---------------------------------------------------------------
2097
+ /** Layers drawn by the 8-bit compositor alone (effects, pixel filters, content the editor draws). */
2098
+ /** A pixel(-like) layer with effects: composited in float through `compositeLayerEffects`. */
2099
+ deepEffects(layer) {
2100
+ return hasActiveEffects(layer.effects) && !this.isFilter(layer) && this.isPixel(layer) && !isGroup(layer);
2209
2101
  }
2210
- for (const i of rev) {
2211
- const e = items[i];
2212
- if (e.kind === "color-overlay") paintColor(lay, full, e.color, e.blend, e.opacity);
2102
+ renderedAlone(layer) {
2103
+ if (hasActiveEffects(layer.effects) && !this.isFilter(layer) && !this.deepEffects(layer)) return true;
2104
+ if (this.isPixel(layer)) return Boolean(this.profile.pixelFilters?.(layer)) && !this.deep.pixelFiltersDeep?.(layer);
2105
+ return !isGroup(layer);
2213
2106
  }
2214
- for (const i of rev) {
2215
- const e = items[i];
2216
- if (e.kind === "satin") paintColor(lay, rel(fx(i, 0)), e.color, e.blend, e.opacity);
2107
+ blendOf(layer) {
2108
+ return this.profile.contentBlend?.(layer) ?? layer.blend;
2217
2109
  }
2218
- for (const i of rev) {
2219
- const e = items[i];
2220
- if (e.kind === "inner-glow") paintGlow(lay, rel(fx(i, 0)), e, env);
2110
+ /** A layer's float RGBA over `r` (straight alpha, working space), or `null` when it has none there. */
2111
+ content(layer, r) {
2112
+ if (this.renderedAlone(layer)) {
2113
+ const solo = { ...layer, blend: isGroup(layer) && layer.blend === "pass-through" ? "pass-through" : "normal", opacity: 1, clipped: false };
2114
+ const bytes = this.base.composite([solo], r, this.sources);
2115
+ return this.from8(bytes);
2116
+ }
2117
+ const surface = this.sources.surfaceOf ? this.sources.surfaceOf(layer) : layer.surface;
2118
+ if (!surface) return null;
2119
+ const ox = layer.offset?.x ?? 0;
2120
+ const oy = layer.offset?.y ?? 0;
2121
+ const filters = this.isPixel(layer) ? this.deep.pixelFiltersDeep?.(layer) ?? null : null;
2122
+ if (!filters) return this.samples(surface, { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height });
2123
+ const m = filters.reach;
2124
+ const W = this.sources.width;
2125
+ const H = this.sources.height;
2126
+ const x0 = m ? Math.max(0, r.x - m) : r.x;
2127
+ const y0 = m ? Math.max(0, r.y - m) : r.y;
2128
+ const x1 = m ? Math.min(W, r.x + r.width + m) : r.x + r.width;
2129
+ const y1 = m ? Math.min(H, r.y + r.height + m) : r.y + r.height;
2130
+ if (x1 <= x0 || y1 <= y0) return new Float32Array(r.width * r.height * 4);
2131
+ const big = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
2132
+ const data = this.samples(surface, { ...big, x: big.x - ox, y: big.y - oy });
2133
+ filters.apply(data, big.width, big.height, this.linear);
2134
+ if (big.x === r.x && big.y === r.y && big.width === r.width && big.height === r.height) return data;
2135
+ const out = new Float32Array(r.width * r.height * 4);
2136
+ for (let y = 0; y < r.height; y++) {
2137
+ const sy = r.y + y - big.y;
2138
+ if (sy < 0 || sy >= big.height) continue;
2139
+ for (let x = 0; x < r.width; x++) {
2140
+ const sx = r.x + x - big.x;
2141
+ if (sx < 0 || sx >= big.width) continue;
2142
+ const s = (sy * big.width + sx) * 4;
2143
+ const d = (y * r.width + x) * 4;
2144
+ out[d] = data[s];
2145
+ out[d + 1] = data[s + 1];
2146
+ out[d + 2] = data[s + 2];
2147
+ out[d + 3] = data[s + 3];
2148
+ }
2149
+ }
2150
+ return out;
2221
2151
  }
2222
- for (const i of rev) {
2223
- const e = items[i];
2224
- if (e.kind === "inner-shadow") paintColor(lay, rel(fx(i, 0)), e.color, e.blend, e.opacity);
2152
+ /** A surface's samples over `rect` (layer space) in the working space. */
2153
+ samples(surface, rect) {
2154
+ const samples = readDeepRegion(surface, rect, this.depth);
2155
+ if (samples instanceof Float32Array) return samples;
2156
+ const out = new Float32Array(samples.length);
2157
+ for (let i = 0; i < samples.length; i++) out[i] = samples[i] / 65535;
2158
+ return out;
2225
2159
  }
2226
- const strokeIdx = items.map((e, i) => e.kind === "stroke" ? i : -1).filter((i) => i >= 0);
2227
- const din = strokeIdx.length && maps.din ? crop(maps.din, maps.rect, rect, 0) : null;
2228
- const frameOf = (i) => maps.frames.get(i) ?? bounds;
2229
- for (let k = strokeIdx.length - 1; k >= 0; k--) {
2230
- const st = items[strokeIdx[k]];
2231
- const [inW] = strokeWidths(st);
2232
- if (!(inW > 0) || !din) continue;
2233
- const m = new Float32Array(n);
2234
- for (let i = 0; i < n; i++) m[i] = inside(shape[i]) ? clamp012(inW + 0.5 - din[i]) : 0;
2235
- paintFill(lay, m, st.fill, frameOf(strokeIdx[k]), env, st.blend, st.opacity);
2160
+ /** 8-bit RGBA to the working space. */
2161
+ from8(bytes) {
2162
+ if (this.linear) return to32From8(bytes, 4);
2163
+ const out = new Float32Array(bytes.length);
2164
+ for (let i = 0; i < bytes.length; i++) out[i] = bytes[i] / 255;
2165
+ return out;
2236
2166
  }
2237
- for (const i of rev) {
2238
- const e = items[i];
2239
- if (e.kind !== "bevel-emboss" || maps.bevelPaint[i] !== "inner") continue;
2240
- paintColor(lay, rel(fx(i, 0)), e.highlightColor, e.highlightBlend, e.highlightOpacity);
2241
- paintColor(lay, rel(fx(i, 1)), e.shadowColor, e.shadowBlend, e.shadowOpacity);
2167
+ /** The working space to 8-bit RGBA. */
2168
+ to8(acc) {
2169
+ const out = new Uint8ClampedArray(acc.length);
2170
+ for (let i = 0; i < acc.length; i += 4) {
2171
+ for (let c = 0; c < 3; c++) out[i + c] = Math.round((this.linear ? srgbEncode(acc[i + c]) : acc[i + c]) * 255);
2172
+ out[i + 3] = Math.round(acc[i + 3] * 255);
2173
+ }
2174
+ return out;
2242
2175
  }
2243
- for (let i = 0; i < n; i++) lay[i * 4 + 3] *= Math.min(shape[i], 1);
2244
- const outside = strokeIdx.filter((i) => strokeWidths(items[i])[1] > 0);
2245
- if (outside.length) {
2246
- const dout = maps.dout ? crop(maps.dout, maps.rect, rect, FAR) : null;
2247
- const doutExact = maps.doutExact ? crop(maps.doutExact, maps.rect, rect, FAR) : null;
2248
- const base = new Float32Array(work);
2249
- const acc = new Float32Array(n * 4);
2250
- const cover = new Float32Array(n);
2251
- for (const si of outside) {
2252
- const st = items[si];
2253
- const outW = strokeWidths(st)[1];
2254
- const d = (outW >= STROKE_CHAMFER_MAX ? doutExact : dout) ?? new Float32Array(n).fill(FAR);
2255
- const share = new Float32Array(n);
2256
- const ones = new Float32Array(n);
2257
- for (let i = 0; i < n; i++) {
2258
- const band = clamp012(outW + 0.5 - d[i]);
2259
- let k;
2260
- if (inside(shape[i])) {
2261
- const c = clamp012(lay[i * 4 + 3]);
2262
- k = c >= 1 ? 1 : clamp012((1 - clamp012(shape[i])) / (1 - c));
2263
- } else k = band;
2264
- share[i] = k * st.opacity * (1 - cover[i]);
2265
- cover[i] += share[i];
2266
- ones[i] = share[i] > 0 ? 1 : 0;
2176
+ // --- Blending --------------------------------------------------------------------
2177
+ /** `src` over `dst` with `mode` at `alpha` × coverage (straight alpha, float), as `blendTile`. */
2178
+ blend(dst, src, mode, alpha, coverage) {
2179
+ if (!(alpha > 0)) return;
2180
+ const normal = mode === "normal";
2181
+ const pigment = mode === "pigment";
2182
+ const cb = [0, 0, 0];
2183
+ const cs = [0, 0, 0];
2184
+ const mix = [0, 0, 0];
2185
+ for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
2186
+ const sa = src[i + 3];
2187
+ if (!(sa > 0)) continue;
2188
+ const k = coverage ? alpha * coverage[p] : alpha;
2189
+ if (!(k > 0)) continue;
2190
+ const as = Math.min(1, sa * k);
2191
+ const ab = dst[i + 3];
2192
+ const ao = as + ab * (1 - as);
2193
+ if (ao <= 0) {
2194
+ dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;
2195
+ continue;
2267
2196
  }
2268
- const blended = new Float32Array(base);
2269
- paintFill(blended, ones, st.fill, frameOf(si), env, st.blend, 1);
2270
- for (let i = 0; i < n; i++) {
2271
- const s = share[i];
2272
- if (!(s > 0)) continue;
2273
- const p = i * 4;
2274
- const ba = blended[p + 3];
2275
- acc[p] += s * blended[p] * ba;
2276
- acc[p + 1] += s * blended[p + 1] * ba;
2277
- acc[p + 2] += s * blended[p + 2] * ba;
2278
- acc[p + 3] += s * ba;
2197
+ if (normal && as >= 1) {
2198
+ dst[i] = src[i];
2199
+ dst[i + 1] = src[i + 1];
2200
+ dst[i + 2] = src[i + 2];
2201
+ dst[i + 3] = 1;
2202
+ continue;
2203
+ }
2204
+ for (let c = 0; c < 3; c++) {
2205
+ cb[c] = dst[i + c];
2206
+ cs[c] = src[i + c];
2207
+ }
2208
+ if (pigment) {
2209
+ if (this.linear) for (let c = 0; c < 3; c++) cb[c] = srgbEncode(cb[c]), cs[c] = srgbEncode(cs[c]);
2210
+ mixPigmentRgb(cb, cs, as / ao, mix);
2211
+ for (let c = 0; c < 3; c++) dst[i + c] = this.linear ? srgbDecode(mix[c]) : mix[c];
2212
+ dst[i + 3] = ao;
2213
+ continue;
2279
2214
  }
2215
+ const m = normal ? cs : blendRgb(mode, cb, cs);
2216
+ const wb = (1 - as) * ab;
2217
+ const ws = (1 - ab) * as;
2218
+ const wm = as * ab;
2219
+ for (let c = 0; c < 3; c++) dst[i + c] = (wb * cb[c] + ws * cs[c] + wm * m[c]) / ao;
2220
+ dst[i + 3] = ao;
2280
2221
  }
2281
- for (let i = 0; i < n; i++) {
2282
- const c = cover[i];
2283
- if (c <= 0) continue;
2284
- const p = i * 4;
2285
- const k = 1 - Math.min(c, 1);
2286
- const a = work[p + 3] * k + acc[p + 3];
2287
- if (a > 0) {
2288
- work[p] = (work[p] * work[p + 3] * k + acc[p]) / a;
2289
- work[p + 1] = (work[p + 1] * work[p + 3] * k + acc[p + 1]) / a;
2290
- work[p + 2] = (work[p + 2] * work[p + 3] * k + acc[p + 2]) / a;
2291
- work[p + 3] = Math.min(a, 1);
2222
+ }
2223
+ };
2224
+ function lerp(dst, before, alpha, coverage) {
2225
+ for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
2226
+ const k = coverage ? alpha * coverage[p] : alpha;
2227
+ if (k >= 1) continue;
2228
+ for (let c = 0; c < 4; c++) dst[i + c] = before[i + c] + (dst[i + c] - before[i + c]) * k;
2229
+ }
2230
+ }
2231
+ function mapColour(data, f) {
2232
+ for (let i = 0; i < data.length; i += 4) {
2233
+ data[i] = f(data[i]);
2234
+ data[i + 1] = f(data[i + 1]);
2235
+ data[i + 2] = f(data[i + 2]);
2236
+ }
2237
+ }
2238
+ function workingToSamples(data, depth) {
2239
+ if (depth === 32) return data;
2240
+ const out = new Uint16Array(data.length);
2241
+ for (let i = 0; i < data.length; i++) {
2242
+ const v = data[i];
2243
+ out[i] = v > 0 ? v < 1 ? Math.round(v * 65535) : 65535 : 0;
2244
+ }
2245
+ return out;
2246
+ }
2247
+ function encodeInPlace(d) {
2248
+ for (let i = 0; i < d.length; i += 4) {
2249
+ d[i] = srgbEncode(d[i]);
2250
+ d[i + 1] = srgbEncode(d[i + 1]);
2251
+ d[i + 2] = srgbEncode(d[i + 2]);
2252
+ }
2253
+ }
2254
+ function decodeInPlace(d) {
2255
+ for (let i = 0; i < d.length; i += 4) {
2256
+ d[i] = srgbDecode(d[i]);
2257
+ d[i + 1] = srgbDecode(d[i + 1]);
2258
+ d[i + 2] = srgbDecode(d[i + 2]);
2259
+ }
2260
+ }
2261
+
2262
+ // src/raster/blur.ts
2263
+ function gaussianKernel(sigma) {
2264
+ const r = Math.ceil(sigma * 3);
2265
+ const k = new Float32Array(2 * r + 1);
2266
+ let s = 0;
2267
+ for (let i = -r; i <= r; i++) {
2268
+ const v = Math.exp(-(i * i) / (2 * sigma * sigma));
2269
+ k[i + r] = v;
2270
+ s += v;
2271
+ }
2272
+ for (let i = 0; i < k.length; i++) k[i] /= s;
2273
+ return k;
2274
+ }
2275
+ var DIRECT_GAUSSIAN_MAX_SIGMA = 8;
2276
+ function boxRadii(sigma) {
2277
+ const n = 3;
2278
+ const wIdeal = Math.sqrt(12 * sigma * sigma / n + 1);
2279
+ let wl = Math.floor(wIdeal);
2280
+ if (wl % 2 === 0) wl--;
2281
+ const wu = wl + 2;
2282
+ const mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);
2283
+ const m = Math.round(mIdeal);
2284
+ const sizes = [0, 1, 2].map((i) => i < m ? wl : wu);
2285
+ return sizes.map((s) => (s - 1) / 2);
2286
+ }
2287
+ function convolveLine(src, off, stride, len, k, edge, dst) {
2288
+ const r = (k.length - 1) / 2;
2289
+ for (let i = 0; i < len; i++) {
2290
+ let a = 0;
2291
+ const lo = i - r;
2292
+ for (let j = 0; j < k.length; j++) {
2293
+ let x = lo + j;
2294
+ if (x < 0 || x >= len) {
2295
+ if (edge === "zero") continue;
2296
+ x = x < 0 ? 0 : len - 1;
2292
2297
  }
2298
+ a += src[off + x * stride] * k[j];
2293
2299
  }
2300
+ dst[i] = a;
2294
2301
  }
2295
- for (const i of rev) {
2296
- const e = items[i];
2297
- if (e.kind !== "bevel-emboss" || maps.bevelPaint[i] !== "outer") continue;
2298
- paintColor(work, fx(i, 0), e.highlightColor, e.highlightBlend, e.highlightOpacity);
2299
- paintColor(work, fx(i, 1), e.shadowColor, e.shadowBlend, e.shadowOpacity);
2302
+ }
2303
+ function boxesLine(src, off, stride, len, radii, edge, a, b, dst) {
2304
+ const pad = radii.reduce((s, r) => s + r, 0);
2305
+ const plen = len + 2 * pad;
2306
+ for (let i = 0; i < plen; i++) {
2307
+ const j = i - pad;
2308
+ a[i] = j < 0 ? edge === "zero" ? 0 : src[off] : j >= len ? edge === "zero" ? 0 : src[off + (len - 1) * stride] : src[off + j * stride];
2300
2309
  }
2301
- const mode = premerge ? "normal" : input.blend;
2302
- const sep = isSeparableBlendMode(mode);
2303
- for (let i = 0; i < n; i++) {
2304
- const p = i * 4;
2305
- if (lay[p + 3] > 0) compositeAt(work, p, mode, sep, lay[p], lay[p + 1], lay[p + 2], lay[p + 3]);
2310
+ let from = a;
2311
+ let to = b;
2312
+ for (const r of radii) {
2313
+ let sum = 0;
2314
+ for (let i = 0; i <= Math.min(r, plen - 1); i++) sum += from[i];
2315
+ const k = 2 * r + 1;
2316
+ for (let i = 0; i < plen; i++) {
2317
+ to[i] = sum / k;
2318
+ const add = i + r + 1;
2319
+ const drop = i - r;
2320
+ if (add < plen) sum += from[add];
2321
+ if (drop >= 0) sum -= from[drop];
2322
+ }
2323
+ const t = from;
2324
+ from = to;
2325
+ to = t;
2306
2326
  }
2307
- for (const i of rev) {
2308
- const e = items[i];
2309
- if (e.kind !== "bevel-emboss" || maps.bevelPaint[i] !== "both") continue;
2310
- paintColor(work, fx(i, 0), e.highlightColor, e.highlightBlend, e.highlightOpacity);
2311
- paintColor(work, fx(i, 1), e.shadowColor, e.shadowBlend, e.shadowOpacity);
2327
+ for (let i = 0; i < len; i++) dst[i] = from[i + pad];
2328
+ }
2329
+ function gaussianBlur(src, w, h, sigma, edge = "zero") {
2330
+ const out = Float32Array.from(src);
2331
+ if (!(sigma >= 0.1) || w <= 0 || h <= 0) return out;
2332
+ const s = Math.min(sigma, 4 * Math.max(w, h));
2333
+ const line = new Float32Array(Math.max(w, h));
2334
+ const passes = [];
2335
+ if (s <= DIRECT_GAUSSIAN_MAX_SIGMA) {
2336
+ const k = gaussianKernel(s);
2337
+ passes.push((buf, off, stride, len) => convolveLine(buf, off, stride, len, k, edge, line));
2338
+ } else {
2339
+ const radii = boxRadii(s);
2340
+ const plen = Math.max(w, h) + 2 * radii.reduce((a2, r) => a2 + r, 0);
2341
+ const a = new Float32Array(plen);
2342
+ const b = new Float32Array(plen);
2343
+ passes.push((buf, off, stride, len) => boxesLine(buf, off, stride, len, radii, edge, a, b, line));
2312
2344
  }
2313
- const op = clamp012(input.opacity);
2314
- for (let i = 0; i < n; i++) {
2315
- const p = i * 4;
2316
- const aa = before[p + 3];
2317
- const ba = work[p + 3];
2318
- const alpha = aa + (ba - aa) * op;
2319
- if (alpha <= 0) {
2320
- backdrop[p] = backdrop[p + 1] = backdrop[p + 2] = backdrop[p + 3] = 0;
2321
- continue;
2345
+ for (const pass of passes) {
2346
+ for (let y = 0; y < h; y++) {
2347
+ pass(out, y * w, 1, w);
2348
+ out.set(line.subarray(0, w), y * w);
2322
2349
  }
2323
- for (let c = 0; c < 3; c++) backdrop[p + c] = Math.round((before[p + c] * aa + (work[p + c] * ba - before[p + c] * aa) * op) / alpha * 255);
2324
- backdrop[p + 3] = Math.round(alpha * 255);
2325
2350
  }
2351
+ for (const pass of passes) {
2352
+ for (let x = 0; x < w; x++) {
2353
+ pass(out, x, w, h);
2354
+ for (let y = 0; y < h; y++) out[y * w + x] = line[y];
2355
+ }
2356
+ }
2357
+ return out;
2326
2358
  }
2327
- function renderLayerEffects(effects, layer, backdrop, canvas, ctx) {
2328
- const items = activeEffectItems(effects);
2329
- const n = layer.rect.width * layer.rect.height;
2330
- const shape = new Float32Array(n);
2331
- for (let i = 0; i < n; i++) shape[i] = layer.content[i * 4 + 3] / 255;
2332
- const maps = buildEffectMaps(items, shape, layer.rect, ctx);
2333
- compositeLayerEffects({
2334
- backdrop,
2335
- content: layer.content,
2336
- rect: layer.rect,
2337
- maps,
2338
- blend: layer.blend,
2339
- opacity: layer.opacity,
2340
- fillOpacity: layer.fillOpacity ?? 1,
2341
- canvas,
2342
- ctx
2359
+
2360
+ // src/raster/layers.ts
2361
+ var childrenOf = (l) => l.type === "group" ? l.children ?? null : null;
2362
+ function flattenLayers(layers) {
2363
+ const out = [];
2364
+ const walk = (list) => {
2365
+ for (const l of list) {
2366
+ out.push(l);
2367
+ const c = childrenOf(l);
2368
+ if (c) walk(c);
2369
+ }
2370
+ };
2371
+ walk(layers);
2372
+ return out;
2373
+ }
2374
+ function findLayer(layers, id) {
2375
+ const walk = (list, parent, path) => {
2376
+ for (let i = 0; i < list.length; i++) {
2377
+ const l = list[i];
2378
+ if (l.id === id) return { layer: l, parent, index: i, path };
2379
+ const c = childrenOf(l);
2380
+ if (c) {
2381
+ const g = l;
2382
+ const hit = walk(c, g, [...path, g]);
2383
+ if (hit) return hit;
2384
+ }
2385
+ }
2386
+ return null;
2387
+ };
2388
+ return walk(layers, null, []);
2389
+ }
2390
+ function mapLayer(layers, id, fn) {
2391
+ let changed = false;
2392
+ const walk = (list) => {
2393
+ let out = null;
2394
+ for (let i = 0; i < list.length; i++) {
2395
+ const l = list[i];
2396
+ let next = l;
2397
+ if (l.id === id) {
2398
+ next = fn(l);
2399
+ changed = true;
2400
+ } else {
2401
+ const c = childrenOf(l);
2402
+ if (c) {
2403
+ const children = walk(c);
2404
+ if (children !== c) next = { ...l, children };
2405
+ }
2406
+ }
2407
+ if (next !== l) {
2408
+ if (!out) out = list.slice(0, i);
2409
+ if (next) out.push(next);
2410
+ } else if (out) out.push(l);
2411
+ }
2412
+ return out ?? list;
2413
+ };
2414
+ const result = walk(layers);
2415
+ return changed ? result : layers;
2416
+ }
2417
+ function insertLayer(layers, layer, parentId, index) {
2418
+ const put = (list) => {
2419
+ const at = index === void 0 ? list.length : Math.max(0, Math.min(list.length, index));
2420
+ const next = list.slice();
2421
+ next.splice(at, 0, layer);
2422
+ return next;
2423
+ };
2424
+ if (!parentId) return put(layers);
2425
+ return mapLayer(layers, parentId, (g) => {
2426
+ const c = childrenOf(g);
2427
+ return c ? { ...g, children: put(c) } : g;
2343
2428
  });
2344
2429
  }
2430
+ function isLayerLocked(layer, what) {
2431
+ return Boolean(layer.locks?.all || layer.locks?.[what]);
2432
+ }
2345
2433
 
2346
2434
  // src/raster/compositor.ts
2347
2435
  var TILE_PIXELS = TILE * TILE;
@@ -2591,6 +2679,26 @@ var RasterCompositor = class {
2591
2679
  this.setSources(sources);
2592
2680
  return this.maskTile(layer, r);
2593
2681
  }
2682
+ /**
2683
+ * For the float compositor: everything `compositeLayerEffects` needs for `layer` over `r` but
2684
+ * the pixels (the area its effects reach inside `r`, the effect maps built from its 8-bit
2685
+ * shape, blend, opacities, canvas and context), or `null` when nothing reaches `r`.
2686
+ */
2687
+ effectsInput(layer, r, sources) {
2688
+ this.setSources(sources);
2689
+ const maps = this.effectMapsOf(layer);
2690
+ const rect = this.effectArea(layer, maps, r);
2691
+ if (!rect) return null;
2692
+ return {
2693
+ rect,
2694
+ maps,
2695
+ blend: isGroup2(layer) ? layer.blend === "pass-through" ? "normal" : layer.blend : this.contentBlend(layer),
2696
+ opacity: layer.opacity,
2697
+ fillOpacity: this.isPixel(layer) ? layer.fillOpacity ?? 1 : 1,
2698
+ canvas: { x: 0, y: 0, width: this.sources.width, height: this.sources.height },
2699
+ ctx: this.effectContext()
2700
+ };
2701
+ }
2594
2702
  // --- Steps -------------------------------------------------------------------
2595
2703
  /** Pixel layers, and the layer kinds the profile composites like them. */
2596
2704
  isPixel(l) {
@@ -4162,22 +4270,13 @@ export {
4162
4270
  validateLayerEffects,
4163
4271
  normalizeLayerEffects,
4164
4272
  effectsFingerprint,
4165
- DeepCompositor,
4166
- workingToSamples,
4167
- DIRECT_GAUSSIAN_MAX_SIGMA,
4168
- gaussianBlur,
4169
- flattenLayers,
4170
- findLayer,
4171
- mapLayer,
4172
- insertLayer,
4173
- isLayerLocked,
4273
+ CURVE_LUT_SIZE,
4274
+ curveLut,
4174
4275
  PatternTile,
4175
4276
  PatternPlacement,
4176
4277
  builtinPatternIds,
4177
4278
  builtinPattern,
4178
4279
  hashNoise,
4179
- CURVE_LUT_SIZE,
4180
- curveLut,
4181
4280
  STROKE_CHAMFER_MAX,
4182
4281
  contourLut,
4183
4282
  activeEffectItems,
@@ -4188,6 +4287,15 @@ export {
4188
4287
  effectFillColors,
4189
4288
  compositeLayerEffects,
4190
4289
  renderLayerEffects,
4290
+ DeepCompositor,
4291
+ workingToSamples,
4292
+ DIRECT_GAUSSIAN_MAX_SIGMA,
4293
+ gaussianBlur,
4294
+ flattenLayers,
4295
+ findLayer,
4296
+ mapLayer,
4297
+ insertLayer,
4298
+ isLayerLocked,
4191
4299
  blendTile,
4192
4300
  RasterCompositor,
4193
4301
  changedTiles,
@@ -4202,4 +4310,4 @@ export {
4202
4310
  warpSurface,
4203
4311
  compositeSurfaceOver
4204
4312
  };
4205
- //# sourceMappingURL=chunk-4I54JSJ6.js.map
4313
+ //# sourceMappingURL=chunk-CLTTLLWA.js.map