@pixerate/editor 0.95.0 → 0.97.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/THIRD_PARTY_NOTICES.md +1 -0
- package/dist/{chunk-T6H3JDGA.cjs → chunk-4AB5BHYK.cjs} +254 -63
- package/dist/chunk-4AB5BHYK.cjs.map +1 -0
- package/dist/{chunk-E2ILRYTJ.js → chunk-OEVSVA6V.js} +231 -40
- package/dist/chunk-OEVSVA6V.js.map +1 -0
- package/dist/image-editor/index.cjs +1559 -385
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +172 -5
- package/dist/image-editor/index.d.ts +172 -5
- package/dist/image-editor/index.js +1329 -155
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.d.cts +1 -1
- package/dist/paint/index.d.ts +1 -1
- package/dist/raster/index.cjs +4 -2
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +839 -5
- package/dist/raster/index.d.ts +839 -5
- package/dist/raster/index.js +3 -1
- package/dist/{surface-CgAiXVI3.d.cts → surface-DIA-XatS.d.cts} +1 -1
- package/dist/{surface-CgAiXVI3.d.ts → surface-DIA-XatS.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +13 -4
- package/dist/chunk-E2ILRYTJ.js.map +0 -1
- package/dist/chunk-T6H3JDGA.cjs.map +0 -1
- package/dist/zlib-B3LIoxF-.d.cts +0 -796
- package/dist/zlib-eiTEEj1z.d.ts +0 -796
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
isBlendMode,
|
|
7
7
|
isSeparableBlendMode,
|
|
8
8
|
mixPigment8,
|
|
9
|
+
mixPigmentRgb,
|
|
9
10
|
readRegion,
|
|
10
11
|
surfaceFromPixels,
|
|
11
12
|
tileKey,
|
|
@@ -372,6 +373,7 @@ function oneOf(r, key, path, values) {
|
|
|
372
373
|
}
|
|
373
374
|
function blend(r, key, path) {
|
|
374
375
|
if (!isBlendMode(r[key])) fail(`${path}.${key}`, `must be a blend mode such as normal, multiply or screen (got ${JSON.stringify(r[key])}).`);
|
|
376
|
+
if (r[key] === "pigment") fail(`${path}.${key}`, "pigment is a layer blend mode only; effects cannot use it.");
|
|
375
377
|
}
|
|
376
378
|
function contour(v, path) {
|
|
377
379
|
if (v === void 0) return;
|
|
@@ -1078,11 +1080,18 @@ function boxLine(src, x0, dst, bw) {
|
|
|
1078
1080
|
}
|
|
1079
1081
|
}
|
|
1080
1082
|
var BLUR_EPS = 1e-7;
|
|
1083
|
+
var BLUR_FIX = 2 ** 32;
|
|
1081
1084
|
function tentLine(src, dst, bw, mid) {
|
|
1082
1085
|
const [r] = boxGeom(bw);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
+
const m = mid.subarray(0, src.length + 2 * r);
|
|
1087
|
+
for (let i = 0; i < src.length; i++) src[i] = Math.round(src[i] * BLUR_FIX);
|
|
1088
|
+
boxLine(src, -r, m, bw);
|
|
1089
|
+
for (let i = 0; i < m.length; i++) m[i] = Math.round(m[i]);
|
|
1090
|
+
boxLine(m, r, dst, bw);
|
|
1091
|
+
for (let i = 0; i < dst.length; i++) {
|
|
1092
|
+
const v = dst[i] / BLUR_FIX;
|
|
1093
|
+
dst[i] = Math.abs(v) < BLUR_EPS ? 0 : v;
|
|
1094
|
+
}
|
|
1086
1095
|
}
|
|
1087
1096
|
function tent(m, w) {
|
|
1088
1097
|
if (!Number.isFinite(w) || m.v.length === 0) return;
|
|
@@ -1384,25 +1393,41 @@ function effectsAreLocal(items) {
|
|
|
1384
1393
|
}
|
|
1385
1394
|
});
|
|
1386
1395
|
}
|
|
1396
|
+
function shapeBounds(shape, rect) {
|
|
1397
|
+
const { width: w, height: h } = rect;
|
|
1398
|
+
const rowHit = (y) => {
|
|
1399
|
+
for (let x = 0, i = y * w; x < w; x++, i++) if (shape[i] > INSIDE_EPS) return true;
|
|
1400
|
+
return false;
|
|
1401
|
+
};
|
|
1402
|
+
let y0 = 0;
|
|
1403
|
+
while (y0 < h && !rowHit(y0)) y0++;
|
|
1404
|
+
if (y0 === h) return null;
|
|
1405
|
+
let y1 = h - 1;
|
|
1406
|
+
while (!rowHit(y1)) y1--;
|
|
1407
|
+
let x0 = w;
|
|
1408
|
+
let x1 = -1;
|
|
1409
|
+
for (let y = y0; y <= y1; y++) {
|
|
1410
|
+
const o = y * w;
|
|
1411
|
+
for (let x = 0; x < x0; x++) {
|
|
1412
|
+
if (shape[o + x] > INSIDE_EPS) {
|
|
1413
|
+
x0 = x;
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (let x = w - 1; x > x1; x--) {
|
|
1418
|
+
if (shape[o + x] > INSIDE_EPS) {
|
|
1419
|
+
x1 = x;
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return { x: rect.x + x0, y: rect.y + y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };
|
|
1425
|
+
}
|
|
1387
1426
|
function buildEffectMaps(items, shape, rect, ctx) {
|
|
1388
1427
|
const w = rect.width;
|
|
1389
1428
|
const h = rect.height;
|
|
1390
1429
|
const s = { w, h, v: shape };
|
|
1391
|
-
|
|
1392
|
-
let y0 = Infinity;
|
|
1393
|
-
let x1 = -Infinity;
|
|
1394
|
-
let y1 = -Infinity;
|
|
1395
|
-
for (let y = 0; y < h; y++) {
|
|
1396
|
-
for (let x = 0; x < w; x++) {
|
|
1397
|
-
if (shape[y * w + x] > INSIDE_EPS) {
|
|
1398
|
-
if (x < x0) x0 = x;
|
|
1399
|
-
if (x > x1) x1 = x;
|
|
1400
|
-
if (y < y0) y0 = y;
|
|
1401
|
-
if (y > y1) y1 = y;
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
const bounds = x0 === Infinity ? null : { x: rect.x + x0, y: rect.y + y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };
|
|
1430
|
+
const bounds = shapeBounds(shape, rect);
|
|
1406
1431
|
const anchor = bounds ? { x: bounds.x, y: bounds.y } : { x: rect.x, y: rect.y };
|
|
1407
1432
|
const per = [];
|
|
1408
1433
|
const bevelPaint = [];
|
|
@@ -1450,8 +1475,84 @@ function buildEffectMaps(items, shape, rect, ctx) {
|
|
|
1450
1475
|
for (const d of [din, dout, doutExact]) if (d) bytes += d.byteLength;
|
|
1451
1476
|
return { rect, items, bounds, shape, per, bevelPaint, din, dout, doutExact, frames, bytes };
|
|
1452
1477
|
}
|
|
1478
|
+
var rectArea = (r) => r.width * r.height;
|
|
1479
|
+
function clipRect(a, b) {
|
|
1480
|
+
const x0 = Math.max(a.x, b.x);
|
|
1481
|
+
const y0 = Math.max(a.y, b.y);
|
|
1482
|
+
const x1 = Math.min(a.x + a.width, b.x + b.width);
|
|
1483
|
+
const y1 = Math.min(a.y + a.height, b.y + b.height);
|
|
1484
|
+
return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;
|
|
1485
|
+
}
|
|
1486
|
+
var growRect = (r, m) => ({ x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m });
|
|
1487
|
+
function unionRect(a, b) {
|
|
1488
|
+
const x0 = Math.min(a.x, b.x);
|
|
1489
|
+
const y0 = Math.min(a.y, b.y);
|
|
1490
|
+
return { x: x0, y: y0, width: Math.max(a.x + a.width, b.x + b.width) - x0, height: Math.max(a.y + a.height, b.y + b.height) - y0 };
|
|
1491
|
+
}
|
|
1492
|
+
function rectMinus(r, cut) {
|
|
1493
|
+
const i = clipRect(r, cut);
|
|
1494
|
+
if (!i) return [r];
|
|
1495
|
+
const parts = [
|
|
1496
|
+
{ x: r.x, y: r.y, width: r.width, height: i.y - r.y },
|
|
1497
|
+
{ x: r.x, y: i.y + i.height, width: r.width, height: r.y + r.height - (i.y + i.height) },
|
|
1498
|
+
{ x: r.x, y: i.y, width: i.x - r.x, height: i.height },
|
|
1499
|
+
{ x: i.x + i.width, y: i.y, width: r.x + r.width - (i.x + i.width), height: i.height }
|
|
1500
|
+
];
|
|
1501
|
+
return parts.filter((p) => p.width > 0 && p.height > 0);
|
|
1502
|
+
}
|
|
1503
|
+
function pasteMap(dst, to, src, from, a) {
|
|
1504
|
+
for (let y = a.y; y < a.y + a.height; y++) {
|
|
1505
|
+
const s = (y - from.y) * from.width + (a.x - from.x);
|
|
1506
|
+
dst.set(src.subarray(s, s + a.width), (y - to.y) * to.width + (a.x - to.x));
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
function patchEffectMaps(prev, items, rect, changed, margin, shapeOver, ctx) {
|
|
1510
|
+
if (!effectsAreLocal(items) || rectArea(rect) <= 0) return null;
|
|
1511
|
+
let areas = [];
|
|
1512
|
+
for (const c of changed) {
|
|
1513
|
+
const a = clipRect(growRect(c, margin), rect);
|
|
1514
|
+
if (a) areas.push(a);
|
|
1515
|
+
}
|
|
1516
|
+
areas.push(...rectMinus(rect, prev.rect));
|
|
1517
|
+
for (let merged = true; merged; ) {
|
|
1518
|
+
merged = false;
|
|
1519
|
+
for (let i = 0; i < areas.length && !merged; i++) {
|
|
1520
|
+
for (let j = i + 1; j < areas.length; j++) {
|
|
1521
|
+
if (clipRect(growRect(areas[i], 2 * margin), areas[j])) {
|
|
1522
|
+
areas[i] = unionRect(areas[i], areas[j]);
|
|
1523
|
+
areas.splice(j, 1);
|
|
1524
|
+
merged = true;
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const windows = areas.map((a) => clipRect(growRect(a, margin), rect));
|
|
1531
|
+
if (windows.reduce((n, w) => n + rectArea(w), 0) > rectArea(rect) / 2) return null;
|
|
1532
|
+
const same = rect.x === prev.rect.x && rect.y === prev.rect.y && rect.width === prev.rect.width && rect.height === prev.rect.height;
|
|
1533
|
+
const take = (v) => same ? v : crop(v, prev.rect, rect, 0);
|
|
1534
|
+
const shape = take(prev.shape);
|
|
1535
|
+
const per = prev.per.map((maps) => maps.map(take));
|
|
1536
|
+
const din = prev.din && take(prev.din);
|
|
1537
|
+
const dout = prev.dout && take(prev.dout);
|
|
1538
|
+
const doutExact = prev.doutExact && take(prev.doutExact);
|
|
1539
|
+
areas.forEach((a, k) => {
|
|
1540
|
+
const w = windows[k];
|
|
1541
|
+
const part = buildEffectMaps(items, shapeOver(w), w, ctx);
|
|
1542
|
+
pasteMap(shape, rect, part.shape, w, a);
|
|
1543
|
+
per.forEach((maps, i) => maps.forEach((m, j) => pasteMap(m, rect, part.per[i][j], w, a)));
|
|
1544
|
+
if (din) pasteMap(din, rect, part.din, w, a);
|
|
1545
|
+
if (dout) pasteMap(dout, rect, part.dout, w, a);
|
|
1546
|
+
if (doutExact) pasteMap(doutExact, rect, part.doutExact, w, a);
|
|
1547
|
+
});
|
|
1548
|
+
let bytes = shape.byteLength;
|
|
1549
|
+
for (const maps of per) for (const m of maps) bytes += m.byteLength;
|
|
1550
|
+
for (const d of [din, dout, doutExact]) if (d) bytes += d.byteLength;
|
|
1551
|
+
return { rect, items, bounds: shapeBounds(shape, rect), shape, per, bevelPaint: prev.bevelPaint, din, dout, doutExact, frames: /* @__PURE__ */ new Map(), bytes };
|
|
1552
|
+
}
|
|
1453
1553
|
var rgbScratchB = [0, 0, 0];
|
|
1454
1554
|
var rgbScratchS = [0, 0, 0];
|
|
1555
|
+
var rgbScratchM = [0, 0, 0];
|
|
1455
1556
|
function compositeAt(dst, i, mode, separable, sr, sg, sb, sa) {
|
|
1456
1557
|
if (!(sa > 0)) return;
|
|
1457
1558
|
const ab = dst[i + 3];
|
|
@@ -1463,6 +1564,20 @@ function compositeAt(dst, i, mode, separable, sr, sg, sb, sa) {
|
|
|
1463
1564
|
const b0 = dst[i];
|
|
1464
1565
|
const b1 = dst[i + 1];
|
|
1465
1566
|
const b2 = dst[i + 2];
|
|
1567
|
+
if (mode === "pigment") {
|
|
1568
|
+
rgbScratchB[0] = b0;
|
|
1569
|
+
rgbScratchB[1] = b1;
|
|
1570
|
+
rgbScratchB[2] = b2;
|
|
1571
|
+
rgbScratchS[0] = sr;
|
|
1572
|
+
rgbScratchS[1] = sg;
|
|
1573
|
+
rgbScratchS[2] = sb;
|
|
1574
|
+
mixPigmentRgb(rgbScratchB, rgbScratchS, sa / ao, rgbScratchM);
|
|
1575
|
+
dst[i] = rgbScratchM[0];
|
|
1576
|
+
dst[i + 1] = rgbScratchM[1];
|
|
1577
|
+
dst[i + 2] = rgbScratchM[2];
|
|
1578
|
+
dst[i + 3] = ao;
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1466
1581
|
let m0;
|
|
1467
1582
|
let m1;
|
|
1468
1583
|
let m2;
|
|
@@ -2067,8 +2182,11 @@ var RasterCompositor = class {
|
|
|
2067
2182
|
patternTiles = /* @__PURE__ */ new WeakMap();
|
|
2068
2183
|
reachCache = /* @__PURE__ */ new WeakMap();
|
|
2069
2184
|
fxContext = null;
|
|
2070
|
-
lastStats = { tiles: 0, cached: 0, stepsRun: 0 };
|
|
2185
|
+
lastStats = { tiles: 0, cached: 0, stepsRun: 0, effectsBuilt: 0, effectsPatched: 0 };
|
|
2071
2186
|
hint = -1;
|
|
2187
|
+
/** The `dirty` rectangles of the current call, as passed. */
|
|
2188
|
+
callDirty = null;
|
|
2189
|
+
callStats = this.lastStats;
|
|
2072
2190
|
/** Drops every cached tile and plane. */
|
|
2073
2191
|
clear() {
|
|
2074
2192
|
this.tiles.clear();
|
|
@@ -2116,7 +2234,9 @@ var RasterCompositor = class {
|
|
|
2116
2234
|
const steps = this.buildSteps(layers);
|
|
2117
2235
|
this.hint = options.checkpointLayerId ? this.stepIndexOf(steps, options.checkpointLayerId) : -1;
|
|
2118
2236
|
this.stamp++;
|
|
2119
|
-
|
|
2237
|
+
this.callDirty = options.dirty ?? null;
|
|
2238
|
+
const stats = { tiles: 0, cached: 0, stepsRun: 0, effectsBuilt: 0, effectsPatched: 0 };
|
|
2239
|
+
this.callStats = stats;
|
|
2120
2240
|
const grow = options.dirty ? this.effectsReach(layers) : 0;
|
|
2121
2241
|
const dirty = grow > 0 ? options.dirty.map((d) => inflateRect(d, grow)) : options.dirty;
|
|
2122
2242
|
for (const { tx, ty } of tilesInRect(rect)) {
|
|
@@ -2132,8 +2252,10 @@ var RasterCompositor = class {
|
|
|
2132
2252
|
tile(layers, tx, ty, sources) {
|
|
2133
2253
|
this.setSources(sources);
|
|
2134
2254
|
this.stamp++;
|
|
2135
|
-
const stats = { tiles: 1, cached: 0, stepsRun: 0 };
|
|
2255
|
+
const stats = { tiles: 1, cached: 0, stepsRun: 0, effectsBuilt: 0, effectsPatched: 0 };
|
|
2256
|
+
this.callStats = stats;
|
|
2136
2257
|
this.hint = -1;
|
|
2258
|
+
this.callDirty = null;
|
|
2137
2259
|
const data = this.compositeTile(layers, this.buildSteps(layers), tx, ty, stats);
|
|
2138
2260
|
this.lastStats = stats;
|
|
2139
2261
|
this.evict();
|
|
@@ -2203,7 +2325,8 @@ var RasterCompositor = class {
|
|
|
2203
2325
|
let first = 0;
|
|
2204
2326
|
if (entry) {
|
|
2205
2327
|
while (first < sigs.length && first < entry.sigs.length && sameSig(entry.sigs[first], sigs[first])) first++;
|
|
2206
|
-
if (first === sigs.length && first === entry.sigs.length) {
|
|
2328
|
+
if (first === sigs.length && first === entry.sigs.length || this.changedOnlyNearby(steps, sigs, entry, dirty)) {
|
|
2329
|
+
entry.sigs = sigs;
|
|
2207
2330
|
entry.stamp = this.stamp;
|
|
2208
2331
|
stats.cached++;
|
|
2209
2332
|
return entry.result;
|
|
@@ -2268,6 +2391,20 @@ var RasterCompositor = class {
|
|
|
2268
2391
|
this.cacheBytes += this.entryBytes(next) - prevBytes;
|
|
2269
2392
|
return result;
|
|
2270
2393
|
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Whether a tile with no part in this call's (grown) `dirty` rectangles changed only in what local
|
|
2396
|
+
* effects read beyond it: every changed step is a pixel layer with local effects whose changes stay
|
|
2397
|
+
* inside `dirty` (and so are farther than its margin), which leaves the tile's pixels as they were.
|
|
2398
|
+
*/
|
|
2399
|
+
changedOnlyNearby(steps, sigs, entry, dirty) {
|
|
2400
|
+
if (this.callDirty === null || dirty || entry.stamp !== this.stamp - 1 || sigs.length !== entry.sigs.length) return false;
|
|
2401
|
+
return steps.every((step, i) => {
|
|
2402
|
+
if (sameSig(entry.sigs[i], sigs[i])) return true;
|
|
2403
|
+
if (step.kind !== "content" || entry.sigs[i].key !== sigs[i].key) return false;
|
|
2404
|
+
const { layer, clipped } = step;
|
|
2405
|
+
return this.hasEffects(layer) && effectsAreLocal(activeEffectItems(layer.effects)) && this.dirtyCoversShape(layer) && clipped.every((c) => this.dirtyCoversShape(c));
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2271
2408
|
entryBytes(e) {
|
|
2272
2409
|
let n = e.result === ZERO_TILE ? 0 : TILE_BYTES;
|
|
2273
2410
|
for (const cp of e.checkpoints.values()) n += cp.data.byteLength;
|
|
@@ -2398,14 +2535,20 @@ var RasterCompositor = class {
|
|
|
2398
2535
|
if (!b) return { x: 0, y: 0, width: 0, height: 0 };
|
|
2399
2536
|
return intersectRect({ x: b.x + dx - m, y: b.y + dy - m, width: b.width + 2 * m, height: b.height + 2 * m }, canvas);
|
|
2400
2537
|
}
|
|
2538
|
+
/** The signature of a layer's shape (its surface tiles and mask refs) per tile of `region`. */
|
|
2539
|
+
shapeSigs(layer, region) {
|
|
2540
|
+
const sigs = /* @__PURE__ */ new Map();
|
|
2541
|
+
if (region.width <= 0 || region.height <= 0) return sigs;
|
|
2542
|
+
const steps = isGroup(layer) ? this.buildSteps(layer.children ?? []) : null;
|
|
2543
|
+
for (const t of tilesInRect(region)) {
|
|
2544
|
+
sigs.set(tileKey(t.tx, t.ty), steps ? this.plainGroupSig(layer, steps, t.tx, t.ty) : this.plainContentSig(layer, t.tx, t.ty));
|
|
2545
|
+
}
|
|
2546
|
+
return sigs;
|
|
2547
|
+
}
|
|
2401
2548
|
/** The surface tiles (and mask refs) a layer's shape reads over `region`. */
|
|
2402
2549
|
shapeRefs(layer, region) {
|
|
2403
2550
|
const refs = [];
|
|
2404
|
-
|
|
2405
|
-
for (const t of tilesInRect(region)) {
|
|
2406
|
-
const sig = isGroup(layer) ? this.plainGroupSig(layer, this.buildSteps(layer.children ?? []), t.tx, t.ty) : this.plainContentSig(layer, t.tx, t.ty);
|
|
2407
|
-
for (const r of sig.refs) refs.push(r);
|
|
2408
|
-
}
|
|
2551
|
+
for (const sig of this.shapeSigs(layer, region).values()) for (const r of sig.refs) refs.push(r);
|
|
2409
2552
|
return refs;
|
|
2410
2553
|
}
|
|
2411
2554
|
lightKey() {
|
|
@@ -2431,18 +2574,28 @@ var RasterCompositor = class {
|
|
|
2431
2574
|
const memo = this.effectMemo.get(layer);
|
|
2432
2575
|
if (memo && memo.stamp === this.stamp) return memo.maps;
|
|
2433
2576
|
const region = this.effectRegion(layer);
|
|
2434
|
-
const key =
|
|
2435
|
-
const
|
|
2577
|
+
const key = this.propsKey(layer) + this.lightKey();
|
|
2578
|
+
const tiles = this.shapeSigs(layer, region);
|
|
2436
2579
|
const hit = this.effectCache.get(layer.id);
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
maps = hit.maps;
|
|
2580
|
+
this.effectCache.delete(layer.id);
|
|
2581
|
+
let entry;
|
|
2582
|
+
if (hit && hit.key === key && sameRect(hit.region, region) && [...tiles].every(([k, sig]) => sameSig(hit.tiles.get(k), sig))) {
|
|
2583
|
+
entry = hit;
|
|
2442
2584
|
} else {
|
|
2443
|
-
|
|
2444
|
-
this.
|
|
2445
|
-
|
|
2585
|
+
const items = activeEffectItems(layer.effects);
|
|
2586
|
+
const ctx = this.effectContext();
|
|
2587
|
+
let maps = null;
|
|
2588
|
+
if (hit && hit.key === key && items.every((e) => effectReach(e) <= MAX_EFFECT_REACH)) {
|
|
2589
|
+
const changed = this.shapeChanges(layer, hit, tiles, region);
|
|
2590
|
+
maps = patchEffectMaps(hit.maps, items, region, changed, effectsMargin(layer.effects), (r) => this.layerShape(layer, r), ctx);
|
|
2591
|
+
}
|
|
2592
|
+
if (maps) this.callStats.effectsPatched++;
|
|
2593
|
+
else this.callStats.effectsBuilt++;
|
|
2594
|
+
entry = { key, region, tiles, maps: maps ?? buildEffectMaps(items, this.layerShape(layer, region), region, ctx), stamp: 0 };
|
|
2595
|
+
}
|
|
2596
|
+
entry.stamp = this.stamp;
|
|
2597
|
+
this.effectCache.set(layer.id, entry);
|
|
2598
|
+
if (entry !== hit) {
|
|
2446
2599
|
let bytes = 0;
|
|
2447
2600
|
for (const e of this.effectCache.values()) bytes += e.maps.bytes;
|
|
2448
2601
|
while (this.effectCache.size > 1 && (this.effectCache.size > 32 || bytes > this.maxCacheBytes / 2)) {
|
|
@@ -2451,8 +2604,42 @@ var RasterCompositor = class {
|
|
|
2451
2604
|
this.effectCache.delete(oldest);
|
|
2452
2605
|
}
|
|
2453
2606
|
}
|
|
2454
|
-
this.effectMemo.set(layer, { stamp: this.stamp, maps });
|
|
2455
|
-
return maps;
|
|
2607
|
+
this.effectMemo.set(layer, { stamp: this.stamp, maps: entry.maps });
|
|
2608
|
+
return entry.maps;
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Where a layer's shape may differ from the one `prev` was built from: the tiles whose signature
|
|
2612
|
+
* changed (a tile new to the region that reads nothing did not), narrowed to this call's `dirty`
|
|
2613
|
+
* rectangles when `prev` is from the previous call and they cover every change of the shape.
|
|
2614
|
+
*/
|
|
2615
|
+
shapeChanges(layer, prev, tiles, region) {
|
|
2616
|
+
const out = [];
|
|
2617
|
+
for (const { tx, ty } of tilesInRect(region)) {
|
|
2618
|
+
const sig = tiles.get(tileKey(tx, ty));
|
|
2619
|
+
const old = prev.tiles.get(tileKey(tx, ty));
|
|
2620
|
+
if (old ? sameSig(old, sig) : sig.refs.every((r2) => r2 === null || r2 === 0)) continue;
|
|
2621
|
+
const r = intersectRect({ x: tx * TILE, y: ty * TILE, width: TILE, height: TILE }, region);
|
|
2622
|
+
if (r.width > 0 && r.height > 0) out.push(r);
|
|
2623
|
+
}
|
|
2624
|
+
const dirty = this.callDirty;
|
|
2625
|
+
if (!dirty || prev.stamp !== this.stamp - 1 || !this.dirtyCoversShape(layer)) return out;
|
|
2626
|
+
const narrowed = [];
|
|
2627
|
+
for (const r of out) {
|
|
2628
|
+
for (const d of dirty) {
|
|
2629
|
+
const a = intersectRect(r, { x: Math.floor(d.x), y: Math.floor(d.y), width: Math.ceil(d.x + d.width) - Math.floor(d.x), height: Math.ceil(d.y + d.height) - Math.floor(d.y) });
|
|
2630
|
+
if (a.width > 0 && a.height > 0) narrowed.push(a);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return narrowed;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Whether a change to a layer stays inside the `dirty` rectangles that name it: a pixel layer
|
|
2637
|
+
* without filters or a feathered or parametric mask (those spread a change beyond it).
|
|
2638
|
+
*/
|
|
2639
|
+
dirtyCoversShape(layer) {
|
|
2640
|
+
if (!isPixel(layer) || this.profile.pixelFilters?.(layer)) return false;
|
|
2641
|
+
const mask = layer.mask;
|
|
2642
|
+
return !mask || mask.enabled === false || !mask.source && !((mask.feather ?? 0) > 0);
|
|
2456
2643
|
}
|
|
2457
2644
|
/** A layer's alpha over `r` after its mask (0..1, quantised to bytes as tiles see it). */
|
|
2458
2645
|
layerShape(layer, r) {
|
|
@@ -2779,6 +2966,9 @@ function copyRect(data, r, a) {
|
|
|
2779
2966
|
function pasteRect(data, r, part, a) {
|
|
2780
2967
|
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);
|
|
2781
2968
|
}
|
|
2969
|
+
function sameRect(a, b) {
|
|
2970
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
2971
|
+
}
|
|
2782
2972
|
function inflateRect(r, m) {
|
|
2783
2973
|
return { x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m };
|
|
2784
2974
|
}
|
|
@@ -3672,6 +3862,7 @@ export {
|
|
|
3672
3862
|
activeEffectItems,
|
|
3673
3863
|
effectsAreLocal,
|
|
3674
3864
|
buildEffectMaps,
|
|
3865
|
+
patchEffectMaps,
|
|
3675
3866
|
gradientT,
|
|
3676
3867
|
compositeLayerEffects,
|
|
3677
3868
|
renderLayerEffects,
|
|
@@ -3692,4 +3883,4 @@ export {
|
|
|
3692
3883
|
parseDataUrl,
|
|
3693
3884
|
pngDataUrl
|
|
3694
3885
|
};
|
|
3695
|
-
//# sourceMappingURL=chunk-
|
|
3886
|
+
//# sourceMappingURL=chunk-OEVSVA6V.js.map
|