@pixerate/editor 0.213.0 → 0.215.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UOGKSVRQ.cjs → chunk-B46UQUDC.cjs} +770 -733
- package/dist/{chunk-W3TOZTL7.js → chunk-F7Z2MKY2.js} +115 -4
- package/dist/{chunk-YBJXCQTR.js → chunk-QXY4YTRI.js} +738 -701
- package/dist/{chunk-H65IBXFI.cjs → chunk-S55JFWDA.cjs} +350 -239
- package/dist/{chunk-QYLWG22N.cjs → chunk-SS53NNZO.cjs} +304 -1
- package/dist/{chunk-LQ5SALHK.js → chunk-U7C655AM.js} +5 -311
- package/dist/{chunk-L52VMQZS.cjs → chunk-UC3PWFM3.cjs} +68 -374
- package/dist/{chunk-L3G23N5Y.js → chunk-ZNA3IQZF.js} +304 -1
- package/dist/image-editor/controller.d.cts +44 -0
- package/dist/image-editor/controller.d.ts +44 -0
- package/dist/image-editor/index.cjs +1768 -1140
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +660 -32
- package/dist/image-editor/layers/edge-color.d.cts +27 -0
- package/dist/image-editor/layers/edge-color.d.ts +27 -0
- package/dist/image-editor/layers/serialize.d.cts +13 -0
- package/dist/image-editor/layers/serialize.d.ts +13 -0
- package/dist/image-editor/selection/index.d.cts +1 -0
- package/dist/image-editor/selection/index.d.ts +1 -0
- package/dist/image-editor/selection/inspect.d.cts +70 -0
- package/dist/image-editor/selection/inspect.d.ts +70 -0
- package/dist/image-editor/selection-worker.cjs +4 -4
- package/dist/image-editor/selection-worker.js +2 -2
- package/dist/paint-editor/index.cjs +79 -79
- package/dist/paint-editor/index.js +3 -3
- package/dist/raster/compositor.d.cts +10 -1
- package/dist/raster/compositor.d.ts +10 -1
- package/dist/raster/deep-compositor.d.cts +1 -1
- package/dist/raster/deep-compositor.d.ts +1 -1
- package/dist/raster/index.cjs +4 -2
- package/dist/raster/index.js +3 -1
- package/dist/raster/layers.d.cts +16 -0
- package/dist/raster/layers.d.ts +16 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +9 -6
- package/skills/pixerate-image-editor/references/paths.md +12 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +17 -17
|
@@ -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/
|
|
1931
|
-
var
|
|
1932
|
-
var
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
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
|
|
2008
|
-
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
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
|
|
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:
|
|
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 (
|
|
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 ||
|
|
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 (
|
|
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 =
|
|
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 = (
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
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 (
|
|
3203
|
-
|
|
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
|
|
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.
|
|
3219
|
-
const coverage = this.
|
|
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 =
|
|
3023
|
+
const before = acc.slice();
|
|
3225
3024
|
const stillEmpty = this.runSteps(step.steps, acc, empty, r);
|
|
3226
|
-
|
|
3025
|
+
lerp(acc, before, layer.opacity, coverage);
|
|
3227
3026
|
return empty && stillEmpty;
|
|
3228
3027
|
}
|
|
3229
|
-
const buf2 = new
|
|
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
|
|
3233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
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
|
-
|
|
3250
|
-
|
|
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
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
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
|
-
|
|
3259
|
-
return false;
|
|
3218
|
+
return out;
|
|
3260
3219
|
}
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
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
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
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
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
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
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
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
|
-
|
|
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
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
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
|
|
3322
|
-
for (let
|
|
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
|
|
3325
|
-
|
|
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
|
|
3328
|
-
|
|
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
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3340
|
-
|
|
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,
|