@pixerate/editor 0.211.0 → 0.213.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-5OC22IEO.js → chunk-DJBYJOHM.js} +419 -1
- package/dist/{chunk-3T5VCDIV.cjs → chunk-LCCMOWR7.cjs} +419 -1
- package/dist/image-editor/content-aware-fill-session.d.cts +17 -5
- package/dist/image-editor/content-aware-fill-session.d.ts +17 -5
- package/dist/image-editor/controller.d.cts +31 -6
- package/dist/image-editor/controller.d.ts +31 -6
- package/dist/image-editor/index.cjs +1172 -567
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +900 -295
- package/dist/image-editor/path-edit.d.cts +97 -0
- package/dist/image-editor/path-edit.d.ts +97 -0
- package/dist/image-editor/paths.d.cts +14 -0
- package/dist/image-editor/paths.d.ts +14 -0
- package/dist/image-editor/renderer.d.cts +3 -0
- package/dist/image-editor/renderer.d.ts +3 -0
- package/dist/image-editor/tool-groups.d.cts +2 -2
- package/dist/image-editor/tool-groups.d.ts +2 -2
- package/dist/image-editor/types.d.cts +3 -1
- package/dist/image-editor/types.d.ts +3 -1
- package/dist/vector/index.cjs +46 -433
- package/dist/vector/index.js +30 -417
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +3 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
bendSegment,
|
|
2
3
|
loadOpenTypeFont,
|
|
3
4
|
parseOpenTypeFont,
|
|
5
|
+
removeAnchorsKeepingShape,
|
|
4
6
|
traceField
|
|
5
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-DJBYJOHM.js";
|
|
6
8
|
import {
|
|
7
9
|
ColorGmm,
|
|
8
10
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
@@ -668,6 +670,8 @@ import {
|
|
|
668
670
|
applyMatrix,
|
|
669
671
|
bezierToSubpaths,
|
|
670
672
|
caretAt,
|
|
673
|
+
cloneAnchor,
|
|
674
|
+
convertAnchor,
|
|
671
675
|
createCanvasTextShaper,
|
|
672
676
|
createMetricTextShaper,
|
|
673
677
|
createOutlineTextShaper,
|
|
@@ -679,23 +683,30 @@ import {
|
|
|
679
683
|
fitRuns,
|
|
680
684
|
fontOf,
|
|
681
685
|
identityWarpMesh,
|
|
686
|
+
inferAnchorType,
|
|
687
|
+
insertAnchor,
|
|
682
688
|
invertMatrix,
|
|
683
689
|
layoutText,
|
|
684
690
|
lineToTextSpace,
|
|
691
|
+
moveAnchorHandle,
|
|
685
692
|
multiplyMatrices,
|
|
686
693
|
paragraphRanges,
|
|
687
694
|
pullWarpMesh,
|
|
695
|
+
removeAnchors,
|
|
688
696
|
resizeWarpMesh,
|
|
689
697
|
restyleRange,
|
|
690
698
|
rotateMatrix,
|
|
691
699
|
roundSubpaths,
|
|
692
700
|
spliceRuns,
|
|
693
701
|
styleAt,
|
|
702
|
+
subpathSegment,
|
|
703
|
+
subpathSegmentCount,
|
|
694
704
|
subpathsToBezier,
|
|
695
705
|
textArrowStep,
|
|
696
706
|
textIndexAt,
|
|
697
707
|
textLineMove,
|
|
698
708
|
textSelectionQuads,
|
|
709
|
+
translateAnchor,
|
|
699
710
|
translateMatrix,
|
|
700
711
|
warpMeshColumns,
|
|
701
712
|
warpMeshEditPoints,
|
|
@@ -707,7 +718,9 @@ import {
|
|
|
707
718
|
import "../chunk-F7YA3MMC.js";
|
|
708
719
|
import "../chunk-M6DNZNVY.js";
|
|
709
720
|
import {
|
|
710
|
-
bezierPathToSvg
|
|
721
|
+
bezierPathToSvg,
|
|
722
|
+
pointInBezierPath,
|
|
723
|
+
segmentNearest
|
|
711
724
|
} from "../chunk-WEIK3UYB.js";
|
|
712
725
|
import "../chunk-PWBZH3FK.js";
|
|
713
726
|
import {
|
|
@@ -1175,10 +1188,10 @@ function emptyHistogram() {
|
|
|
1175
1188
|
max: 0
|
|
1176
1189
|
};
|
|
1177
1190
|
}
|
|
1178
|
-
function addToHistogram(h, data, stride,
|
|
1179
|
-
for (let y =
|
|
1180
|
-
let i = (y * stride +
|
|
1181
|
-
for (let x = 0; x <
|
|
1191
|
+
function addToHistogram(h, data, stride, rect2) {
|
|
1192
|
+
for (let y = rect2.y; y < rect2.y + rect2.height; y++) {
|
|
1193
|
+
let i = (y * stride + rect2.x) * 4;
|
|
1194
|
+
for (let x = 0; x < rect2.width; x++, i += 4) {
|
|
1182
1195
|
if (!(data[i + 3] > 0)) {
|
|
1183
1196
|
h.transparent++;
|
|
1184
1197
|
continue;
|
|
@@ -1639,6 +1652,63 @@ var ImageEditorRenderer = class {
|
|
|
1639
1652
|
}
|
|
1640
1653
|
ctx.restore();
|
|
1641
1654
|
}
|
|
1655
|
+
if (options.pathEdit) {
|
|
1656
|
+
const e = options.pathEdit;
|
|
1657
|
+
ctx.save();
|
|
1658
|
+
try {
|
|
1659
|
+
const scr = (p) => projectImagePoint(p, projection);
|
|
1660
|
+
const trace2 = (c) => {
|
|
1661
|
+
const [a, b, d, f] = c.map(scr);
|
|
1662
|
+
ctx.moveTo(a.x, a.y);
|
|
1663
|
+
if (f) ctx.bezierCurveTo(b.x, b.y, d.x, d.y, f.x, f.y);
|
|
1664
|
+
else ctx.lineTo(b.x, b.y);
|
|
1665
|
+
};
|
|
1666
|
+
ctx.beginPath();
|
|
1667
|
+
e.curves.forEach(trace2);
|
|
1668
|
+
ctx.lineWidth = 1.5 * u;
|
|
1669
|
+
ctx.strokeStyle = OVERLAY_HANDLE_EDGE;
|
|
1670
|
+
ctx.stroke();
|
|
1671
|
+
if (e.preview) {
|
|
1672
|
+
ctx.beginPath();
|
|
1673
|
+
trace2(e.preview);
|
|
1674
|
+
ctx.setLineDash([4 * u, 3 * u]);
|
|
1675
|
+
ctx.stroke();
|
|
1676
|
+
ctx.setLineDash([]);
|
|
1677
|
+
}
|
|
1678
|
+
ctx.beginPath();
|
|
1679
|
+
for (const h of e.handles) {
|
|
1680
|
+
const a = scr(h.anchor);
|
|
1681
|
+
const q = scr(h.point);
|
|
1682
|
+
ctx.moveTo(a.x, a.y);
|
|
1683
|
+
ctx.lineTo(q.x, q.y);
|
|
1684
|
+
}
|
|
1685
|
+
ctx.lineWidth = 1 * u;
|
|
1686
|
+
ctx.stroke();
|
|
1687
|
+
for (const h of e.handles) {
|
|
1688
|
+
const q = scr(h.point);
|
|
1689
|
+
ctx.beginPath();
|
|
1690
|
+
ctx.arc(q.x, q.y, 3 * u, 0, Math.PI * 2);
|
|
1691
|
+
ctx.fillStyle = OVERLAY_HANDLE_EDGE;
|
|
1692
|
+
ctx.fill();
|
|
1693
|
+
}
|
|
1694
|
+
const size = 6 * u;
|
|
1695
|
+
ctx.lineWidth = 1 * u;
|
|
1696
|
+
for (const a of e.anchors) {
|
|
1697
|
+
const q = scr(a.point);
|
|
1698
|
+
ctx.fillStyle = a.selected ? OVERLAY_HANDLE_EDGE : "#ffffff";
|
|
1699
|
+
ctx.fillRect(q.x - size / 2, q.y - size / 2, size, size);
|
|
1700
|
+
ctx.strokeRect(q.x - size / 2, q.y - size / 2, size, size);
|
|
1701
|
+
}
|
|
1702
|
+
if (e.marquee) {
|
|
1703
|
+
const a = scr({ x: e.marquee.x, y: e.marquee.y });
|
|
1704
|
+
const b = scr({ x: e.marquee.x + e.marquee.width, y: e.marquee.y + e.marquee.height });
|
|
1705
|
+
ctx.setLineDash([4 * u, 3 * u]);
|
|
1706
|
+
ctx.strokeRect(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
1707
|
+
}
|
|
1708
|
+
} catch {
|
|
1709
|
+
}
|
|
1710
|
+
ctx.restore();
|
|
1711
|
+
}
|
|
1642
1712
|
if (isActivelyCropping) {
|
|
1643
1713
|
const activeCrop = options.draftCrop !== void 0 && options.draftCrop !== null ? options.draftCrop : crop ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight };
|
|
1644
1714
|
this.drawCropOverlay(ctx, getViewportMetrics(geoState, vpWidth, vpHeight, true, activeCrop), u);
|
|
@@ -1742,7 +1812,7 @@ var ImageEditorRenderer = class {
|
|
|
1742
1812
|
if (!source.enabled || !source.maskSource) return null;
|
|
1743
1813
|
return this.depthMaps.get(`${source.maskSource}|${width}x${height}`) ?? null;
|
|
1744
1814
|
},
|
|
1745
|
-
rasterizeAnnotation: (annotation,
|
|
1815
|
+
rasterizeAnnotation: (annotation, rect2) => this.rasterizeAnnotation(annotation, rect2),
|
|
1746
1816
|
version: `${host?.version() ?? ""}:${this.assetVersion}`,
|
|
1747
1817
|
globalLight: host?.globalLight?.(),
|
|
1748
1818
|
pattern: host?.pattern ? (id) => host.pattern(id) : void 0
|
|
@@ -1754,15 +1824,15 @@ var ImageEditorRenderer = class {
|
|
|
1754
1824
|
for (const l of flattenLayers(layers)) {
|
|
1755
1825
|
const source = l.mask?.source;
|
|
1756
1826
|
if (source?.enabled && source.maskSource) {
|
|
1757
|
-
const
|
|
1758
|
-
if (this.depthMaps.has(
|
|
1827
|
+
const key2 = `${source.maskSource}|${width}x${height}`;
|
|
1828
|
+
if (this.depthMaps.has(key2)) continue;
|
|
1759
1829
|
try {
|
|
1760
1830
|
const maskImg = await this.imageCache.get(source.maskSource);
|
|
1761
1831
|
const canvas = this.createCanvas(width, height);
|
|
1762
1832
|
const mctx = canvas.getContext("2d");
|
|
1763
1833
|
const data = mctx ? (mctx.drawImage(maskImg, 0, 0, width, height), mctx.getImageData?.(0, 0, width, height)) : null;
|
|
1764
1834
|
if (data) {
|
|
1765
|
-
this.depthMaps.set(
|
|
1835
|
+
this.depthMaps.set(key2, { width, height, data: data.data });
|
|
1766
1836
|
this.assetVersion++;
|
|
1767
1837
|
}
|
|
1768
1838
|
} catch {
|
|
@@ -1783,34 +1853,34 @@ var ImageEditorRenderer = class {
|
|
|
1783
1853
|
return Boolean(ctx && typeof ctx.getImageData === "function");
|
|
1784
1854
|
}
|
|
1785
1855
|
/** One annotation drawn into a tile-sized RGBA buffer at `rect` (document space), or `null` without a canvas. */
|
|
1786
|
-
rasterizeAnnotation(annotation,
|
|
1787
|
-
if (!this.scratch) this.scratch = this.createCanvas(
|
|
1856
|
+
rasterizeAnnotation(annotation, rect2) {
|
|
1857
|
+
if (!this.scratch) this.scratch = this.createCanvas(rect2.width, rect2.height);
|
|
1788
1858
|
const canvas = this.scratch;
|
|
1789
|
-
if (canvas.width !==
|
|
1790
|
-
canvas.width =
|
|
1791
|
-
canvas.height =
|
|
1859
|
+
if (canvas.width !== rect2.width || canvas.height !== rect2.height) {
|
|
1860
|
+
canvas.width = rect2.width;
|
|
1861
|
+
canvas.height = rect2.height;
|
|
1792
1862
|
}
|
|
1793
1863
|
const ctx = canvas.getContext("2d");
|
|
1794
1864
|
if (!ctx || typeof ctx.getImageData !== "function") return null;
|
|
1795
1865
|
try {
|
|
1796
1866
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1797
|
-
ctx.clearRect(0, 0,
|
|
1798
|
-
ctx.setTransform(1, 0, 0, 1, -
|
|
1867
|
+
ctx.clearRect(0, 0, rect2.width, rect2.height);
|
|
1868
|
+
ctx.setTransform(1, 0, 0, 1, -rect2.x, -rect2.y);
|
|
1799
1869
|
const img = annotation.type === "image" ? this.imageCache.peek(annotation.src) ?? null : null;
|
|
1800
1870
|
this.drawAnnotationShape(ctx, annotation, img);
|
|
1801
1871
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1802
|
-
return new Uint8ClampedArray(ctx.getImageData(0, 0,
|
|
1872
|
+
return new Uint8ClampedArray(ctx.getImageData(0, 0, rect2.width, rect2.height).data);
|
|
1803
1873
|
} catch {
|
|
1804
1874
|
return null;
|
|
1805
1875
|
}
|
|
1806
1876
|
}
|
|
1807
|
-
putTile(ctx, data, tx, ty,
|
|
1808
|
-
const ox = tx * TILE -
|
|
1809
|
-
const oy = ty * TILE -
|
|
1877
|
+
putTile(ctx, data, tx, ty, rect2) {
|
|
1878
|
+
const ox = tx * TILE - rect2.x;
|
|
1879
|
+
const oy = ty * TILE - rect2.y;
|
|
1810
1880
|
const x0 = Math.max(0, -ox);
|
|
1811
1881
|
const y0 = Math.max(0, -oy);
|
|
1812
|
-
const x1 = Math.min(TILE,
|
|
1813
|
-
const y1 = Math.min(TILE,
|
|
1882
|
+
const x1 = Math.min(TILE, rect2.width - ox);
|
|
1883
|
+
const y1 = Math.min(TILE, rect2.height - oy);
|
|
1814
1884
|
if (x1 <= x0 || y1 <= y0) return;
|
|
1815
1885
|
const img = ctx.createImageData(TILE, TILE);
|
|
1816
1886
|
img.data.set(data);
|
|
@@ -1822,23 +1892,23 @@ var ImageEditorRenderer = class {
|
|
|
1822
1892
|
* uploading only the tiles that changed since the last render.
|
|
1823
1893
|
*/
|
|
1824
1894
|
updateComposite(layers, region, width, height, options = {}) {
|
|
1825
|
-
const
|
|
1895
|
+
const rect2 = coveringRect(region);
|
|
1826
1896
|
let comp = this.comp;
|
|
1827
1897
|
let fresh = false;
|
|
1828
|
-
if (!comp || comp.rect.x !==
|
|
1829
|
-
const canvas = this.createCanvas(
|
|
1830
|
-
comp = this.comp = new CompositeCanvas(canvas,
|
|
1898
|
+
if (!comp || comp.rect.x !== rect2.x || comp.rect.y !== rect2.y || comp.rect.width !== rect2.width || comp.rect.height !== rect2.height) {
|
|
1899
|
+
const canvas = this.createCanvas(rect2.width, rect2.height);
|
|
1900
|
+
comp = this.comp = new CompositeCanvas(canvas, rect2);
|
|
1831
1901
|
fresh = true;
|
|
1832
1902
|
}
|
|
1833
1903
|
const ctx = comp.canvas.getContext("2d");
|
|
1834
1904
|
if (!ctx || typeof ctx.createImageData !== "function") return null;
|
|
1835
1905
|
let changed = false;
|
|
1836
|
-
this.compositor.compositeTiles(layers,
|
|
1837
|
-
const
|
|
1838
|
-
if (comp.tiles.get(
|
|
1839
|
-
comp.tiles.set(
|
|
1840
|
-
comp.histograms.delete(
|
|
1841
|
-
this.putTile(ctx, data, tx, ty,
|
|
1906
|
+
this.compositor.compositeTiles(layers, rect2, this.compositorSources(width, height), (tx, ty, data) => {
|
|
1907
|
+
const key2 = tileKey(tx, ty);
|
|
1908
|
+
if (comp.tiles.get(key2) === data) return;
|
|
1909
|
+
comp.tiles.set(key2, data);
|
|
1910
|
+
comp.histograms.delete(key2);
|
|
1911
|
+
this.putTile(ctx, data, tx, ty, rect2);
|
|
1842
1912
|
changed = true;
|
|
1843
1913
|
}, { checkpointLayerId: options.checkpointLayerId, dirty: fresh ? void 0 : options.dirty });
|
|
1844
1914
|
if (changed) comp.version++;
|
|
@@ -1857,8 +1927,8 @@ var ImageEditorRenderer = class {
|
|
|
1857
1927
|
const rotated = getRotatedSize(region.width, region.height, transform);
|
|
1858
1928
|
const baseW = Math.max(1, Math.round(rotated.width));
|
|
1859
1929
|
const baseH = Math.max(1, Math.round(rotated.height));
|
|
1860
|
-
const
|
|
1861
|
-
if (this.cachedPostCanvas && this.cachedPostKey ===
|
|
1930
|
+
const key2 = `${this.displayKey}:${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
|
|
1931
|
+
if (this.cachedPostCanvas && this.cachedPostKey === key2) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
|
|
1862
1932
|
const canvas = this.cachedPostCanvas && this.cachedPostCanvas.width === baseW && this.cachedPostCanvas.height === baseH ? this.cachedPostCanvas : this.createCanvas(baseW, baseH);
|
|
1863
1933
|
const ctx = canvas.getContext("2d");
|
|
1864
1934
|
if (!ctx) return null;
|
|
@@ -1872,7 +1942,7 @@ var ImageEditorRenderer = class {
|
|
|
1872
1942
|
ctx.restore();
|
|
1873
1943
|
const histogram = this.applyPost(ctx, baseW, baseH, state);
|
|
1874
1944
|
this.cachedPostCanvas = canvas;
|
|
1875
|
-
this.cachedPostKey =
|
|
1945
|
+
this.cachedPostKey = key2;
|
|
1876
1946
|
this.cachedPostHistogram = histogram;
|
|
1877
1947
|
return { canvas, histogram };
|
|
1878
1948
|
}
|
|
@@ -1908,11 +1978,11 @@ var ImageEditorRenderer = class {
|
|
|
1908
1978
|
/** A layer's mask as canvas-sized coverage for its mask view, kept while the mask is unchanged. */
|
|
1909
1979
|
maskViewSelection(layer, width, height) {
|
|
1910
1980
|
if (!layer.mask || layer.mask.source || width < 1 || height < 1) return null;
|
|
1911
|
-
const
|
|
1981
|
+
const key2 = `${width}x${height}:${layer.type === "pixel" ? `${layer.offset.x},${layer.offset.y}` : ""}`;
|
|
1912
1982
|
const cached = this.maskViewCache.get(layer.mask.surface);
|
|
1913
|
-
if (cached?.key ===
|
|
1983
|
+
if (cached?.key === key2) return cached.mask;
|
|
1914
1984
|
const mask = layerMaskSelection(layer, width, height);
|
|
1915
|
-
if (mask) this.maskViewCache.set(layer.mask.surface, { key, mask });
|
|
1985
|
+
if (mask) this.maskViewCache.set(layer.mask.surface, { key: key2, mask });
|
|
1916
1986
|
return mask;
|
|
1917
1987
|
}
|
|
1918
1988
|
/** A mask alone in grey (white = revealed), opaque, drawn in natural image space. */
|
|
@@ -1931,16 +2001,16 @@ var ImageEditorRenderer = class {
|
|
|
1931
2001
|
}
|
|
1932
2002
|
/** A channel's tint (colour, opacity, masked or selected area), drawn in natural image space. */
|
|
1933
2003
|
drawMaskOverlay(ctx, mask, style) {
|
|
1934
|
-
const
|
|
2004
|
+
const key2 = `${style.color?.join(",")}|${style.opacity}|${style.indicates}`;
|
|
1935
2005
|
let cached = this.overlayCache.get(mask);
|
|
1936
|
-
if (!cached || cached.key !==
|
|
2006
|
+
if (!cached || cached.key !== key2) {
|
|
1937
2007
|
const canvas = this.createCanvas(mask.width, mask.height);
|
|
1938
2008
|
const mctx = canvas.getContext("2d");
|
|
1939
2009
|
if (!mctx || typeof mctx.createImageData !== "function") return;
|
|
1940
2010
|
const img = mctx.createImageData(mask.width, mask.height);
|
|
1941
2011
|
img.data.set(quickMaskPixels(mask, style));
|
|
1942
2012
|
mctx.putImageData(img, 0, 0);
|
|
1943
|
-
cached = { key, canvas };
|
|
2013
|
+
cached = { key: key2, canvas };
|
|
1944
2014
|
this.overlayCache.set(mask, cached);
|
|
1945
2015
|
}
|
|
1946
2016
|
ctx.drawImage(cached.canvas, 0, 0, mask.width, mask.height);
|
|
@@ -2001,11 +2071,11 @@ var ImageEditorRenderer = class {
|
|
|
2001
2071
|
if (typeof ctx.setLineDash === "function") ctx.setLineDash([]);
|
|
2002
2072
|
}
|
|
2003
2073
|
getSourceId(img) {
|
|
2004
|
-
const
|
|
2005
|
-
let id = this.sourceIds.get(
|
|
2074
|
+
const key2 = img;
|
|
2075
|
+
let id = this.sourceIds.get(key2);
|
|
2006
2076
|
if (id === void 0) {
|
|
2007
2077
|
id = ++this.nextSourceId;
|
|
2008
|
-
this.sourceIds.set(
|
|
2078
|
+
this.sourceIds.set(key2, id);
|
|
2009
2079
|
}
|
|
2010
2080
|
return id;
|
|
2011
2081
|
}
|
|
@@ -2104,15 +2174,15 @@ var ImageEditorRenderer = class {
|
|
|
2104
2174
|
break;
|
|
2105
2175
|
}
|
|
2106
2176
|
case "rect": {
|
|
2107
|
-
const
|
|
2108
|
-
ctx.strokeStyle =
|
|
2109
|
-
ctx.lineWidth =
|
|
2110
|
-
if (
|
|
2111
|
-
ctx.fillStyle =
|
|
2112
|
-
ctx.fillRect(
|
|
2177
|
+
const rect2 = ann;
|
|
2178
|
+
ctx.strokeStyle = rect2.strokeColor;
|
|
2179
|
+
ctx.lineWidth = rect2.strokeWidth;
|
|
2180
|
+
if (rect2.fillColor) {
|
|
2181
|
+
ctx.fillStyle = rect2.fillColor;
|
|
2182
|
+
ctx.fillRect(rect2.x, rect2.y, rect2.width, rect2.height);
|
|
2113
2183
|
}
|
|
2114
|
-
if (
|
|
2115
|
-
ctx.strokeRect(
|
|
2184
|
+
if (rect2.strokeWidth > 0) {
|
|
2185
|
+
ctx.strokeRect(rect2.x, rect2.y, rect2.width, rect2.height);
|
|
2116
2186
|
}
|
|
2117
2187
|
break;
|
|
2118
2188
|
}
|
|
@@ -2303,12 +2373,12 @@ var ImageEditorRenderer = class {
|
|
|
2303
2373
|
const split = splitOverlayAnnotations(layers);
|
|
2304
2374
|
overlay = split.overlay.map((l) => l.annotation);
|
|
2305
2375
|
await this.prepareLayerAssets(split.composite, naturalWidth, naturalHeight);
|
|
2306
|
-
const
|
|
2307
|
-
const comp = this.createCanvas(
|
|
2376
|
+
const rect2 = coveringRect(region);
|
|
2377
|
+
const comp = this.createCanvas(rect2.width, rect2.height);
|
|
2308
2378
|
const cctx = comp.getContext("2d");
|
|
2309
2379
|
if (cctx && typeof cctx.createImageData === "function") {
|
|
2310
|
-
this.exportCompositor.compositeTiles(split.composite,
|
|
2311
|
-
this.putTile(cctx, data, tx, ty,
|
|
2380
|
+
this.exportCompositor.compositeTiles(split.composite, rect2, this.compositorSources(naturalWidth, naturalHeight), (tx, ty, data) => {
|
|
2381
|
+
this.putTile(cctx, data, tx, ty, rect2);
|
|
2312
2382
|
});
|
|
2313
2383
|
}
|
|
2314
2384
|
baseCanvas = this.createCanvas(baseW, baseH);
|
|
@@ -2319,7 +2389,7 @@ var ImageEditorRenderer = class {
|
|
|
2319
2389
|
bctx.beginPath();
|
|
2320
2390
|
bctx.rect(region.x, region.y, region.width, region.height);
|
|
2321
2391
|
bctx.clip();
|
|
2322
|
-
bctx.drawImage(comp,
|
|
2392
|
+
bctx.drawImage(comp, rect2.x, rect2.y);
|
|
2323
2393
|
bctx.restore();
|
|
2324
2394
|
this.applyPost(bctx, baseW, baseH, state);
|
|
2325
2395
|
}
|
|
@@ -2447,10 +2517,10 @@ function getBaseProjection(region, transform, baseW, baseH) {
|
|
|
2447
2517
|
};
|
|
2448
2518
|
}
|
|
2449
2519
|
var CompositeCanvas = class {
|
|
2450
|
-
constructor(canvas,
|
|
2520
|
+
constructor(canvas, rect2) {
|
|
2451
2521
|
this.canvas = canvas;
|
|
2452
|
-
this.rect =
|
|
2453
|
-
this.region =
|
|
2522
|
+
this.rect = rect2;
|
|
2523
|
+
this.region = rect2;
|
|
2454
2524
|
}
|
|
2455
2525
|
canvas;
|
|
2456
2526
|
rect;
|
|
@@ -2463,18 +2533,18 @@ var CompositeCanvas = class {
|
|
|
2463
2533
|
if (!this.tiles.size) return null;
|
|
2464
2534
|
const r = coveringRect(this.region);
|
|
2465
2535
|
const parts = [];
|
|
2466
|
-
for (const [
|
|
2467
|
-
let h = this.histograms.get(
|
|
2536
|
+
for (const [key2, data] of this.tiles) {
|
|
2537
|
+
let h = this.histograms.get(key2);
|
|
2468
2538
|
if (!h) {
|
|
2469
|
-
const tx =
|
|
2470
|
-
const ty = Math.floor(
|
|
2539
|
+
const tx = key2 % 65536 - 32768;
|
|
2540
|
+
const ty = Math.floor(key2 / 65536) - 32768;
|
|
2471
2541
|
const x0 = Math.max(r.x, tx * TILE);
|
|
2472
2542
|
const y0 = Math.max(r.y, ty * TILE);
|
|
2473
2543
|
const x1 = Math.min(r.x + r.width, tx * TILE + TILE);
|
|
2474
2544
|
const y1 = Math.min(r.y + r.height, ty * TILE + TILE);
|
|
2475
2545
|
h = emptyHistogram();
|
|
2476
2546
|
if (x1 > x0 && y1 > y0) addToHistogram(h, data, TILE, { x: x0 - tx * TILE, y: y0 - ty * TILE, width: x1 - x0, height: y1 - y0 });
|
|
2477
|
-
this.histograms.set(
|
|
2547
|
+
this.histograms.set(key2, h);
|
|
2478
2548
|
}
|
|
2479
2549
|
parts.push(h);
|
|
2480
2550
|
}
|
|
@@ -2547,12 +2617,12 @@ function pathToSelection(components, width, height, options = {}) {
|
|
|
2547
2617
|
const rx1 = Math.min(width, Math.ceil(x1));
|
|
2548
2618
|
const ry1 = Math.min(height, Math.ceil(y1));
|
|
2549
2619
|
if (!(rx1 > rx0 && ry1 > ry0)) return createSelectionMask(width, height);
|
|
2550
|
-
const
|
|
2551
|
-
const cov = componentsCoverage(components,
|
|
2620
|
+
const rect2 = { x: rx0, y: ry0, width: rx1 - rx0, height: ry1 - ry0 };
|
|
2621
|
+
const cov = componentsCoverage(components, rect2, options.fillRule ?? "nonzero");
|
|
2552
2622
|
const hard = options.antiAlias === false;
|
|
2553
2623
|
const bytes = new Uint8Array(cov.length);
|
|
2554
2624
|
for (let i = 0; i < cov.length; i++) bytes[i] = hard ? cov[i] >= 0.5 ? 255 : 0 : coverageToByte(cov[i]);
|
|
2555
|
-
const mask = selectionFromRegion(width, height,
|
|
2625
|
+
const mask = selectionFromRegion(width, height, rect2, bytes);
|
|
2556
2626
|
const feather = options.feather ?? 0;
|
|
2557
2627
|
return feather > 0 && mask.bounds ? modifySelection(mask, "feather", feather) : mask;
|
|
2558
2628
|
}
|
|
@@ -2591,11 +2661,11 @@ var EdgeMap = class {
|
|
|
2591
2661
|
block(x, y) {
|
|
2592
2662
|
const bx = Math.floor(x / BLOCK);
|
|
2593
2663
|
const by = Math.floor(y / BLOCK);
|
|
2594
|
-
const
|
|
2595
|
-
let b = this.blocks.get(
|
|
2664
|
+
const key2 = by * 65536 + bx;
|
|
2665
|
+
let b = this.blocks.get(key2);
|
|
2596
2666
|
if (!b) {
|
|
2597
2667
|
b = this.computeBlock(bx * BLOCK, by * BLOCK);
|
|
2598
|
-
this.blocks.set(
|
|
2668
|
+
this.blocks.set(key2, b);
|
|
2599
2669
|
}
|
|
2600
2670
|
return b;
|
|
2601
2671
|
}
|
|
@@ -2689,7 +2759,7 @@ var Heap = class {
|
|
|
2689
2759
|
ids = new Int32Array(256);
|
|
2690
2760
|
keys = new Float64Array(256);
|
|
2691
2761
|
size = 0;
|
|
2692
|
-
push(id,
|
|
2762
|
+
push(id, key2) {
|
|
2693
2763
|
if (this.size === this.ids.length) {
|
|
2694
2764
|
const ids = new Int32Array(this.size * 2);
|
|
2695
2765
|
ids.set(this.ids);
|
|
@@ -2701,31 +2771,31 @@ var Heap = class {
|
|
|
2701
2771
|
let i = this.size++;
|
|
2702
2772
|
while (i > 0) {
|
|
2703
2773
|
const p = i - 1 >> 1;
|
|
2704
|
-
if (this.keys[p] <=
|
|
2774
|
+
if (this.keys[p] <= key2) break;
|
|
2705
2775
|
this.ids[i] = this.ids[p];
|
|
2706
2776
|
this.keys[i] = this.keys[p];
|
|
2707
2777
|
i = p;
|
|
2708
2778
|
}
|
|
2709
2779
|
this.ids[i] = id;
|
|
2710
|
-
this.keys[i] =
|
|
2780
|
+
this.keys[i] = key2;
|
|
2711
2781
|
}
|
|
2712
2782
|
/** Removes the cheapest entry; read it from `topId` / `topKey` first. */
|
|
2713
2783
|
pop() {
|
|
2714
2784
|
const n = --this.size;
|
|
2715
2785
|
const id = this.ids[n];
|
|
2716
|
-
const
|
|
2786
|
+
const key2 = this.keys[n];
|
|
2717
2787
|
let i = 0;
|
|
2718
2788
|
for (; ; ) {
|
|
2719
2789
|
let c = 2 * i + 1;
|
|
2720
2790
|
if (c >= n) break;
|
|
2721
2791
|
if (c + 1 < n && this.keys[c + 1] < this.keys[c]) c++;
|
|
2722
|
-
if (this.keys[c] >=
|
|
2792
|
+
if (this.keys[c] >= key2) break;
|
|
2723
2793
|
this.ids[i] = this.ids[c];
|
|
2724
2794
|
this.keys[i] = this.keys[c];
|
|
2725
2795
|
i = c;
|
|
2726
2796
|
}
|
|
2727
2797
|
this.ids[i] = id;
|
|
2728
|
-
this.keys[i] =
|
|
2798
|
+
this.keys[i] = key2;
|
|
2729
2799
|
}
|
|
2730
2800
|
get topId() {
|
|
2731
2801
|
return this.ids[0];
|
|
@@ -3885,29 +3955,29 @@ var ImageEffectsEngine = class {
|
|
|
3885
3955
|
return this.pending.size > 0;
|
|
3886
3956
|
}
|
|
3887
3957
|
/** See `SmartEffectResolver`. */
|
|
3888
|
-
resolve(filter, input, width, height,
|
|
3889
|
-
const hit = this.cache.get(
|
|
3958
|
+
resolve(filter, input, width, height, key2) {
|
|
3959
|
+
const hit = this.cache.get(key2);
|
|
3890
3960
|
if (hit) {
|
|
3891
|
-
this.cache.delete(
|
|
3892
|
-
this.cache.set(
|
|
3961
|
+
this.cache.delete(key2);
|
|
3962
|
+
this.cache.set(key2, hit);
|
|
3893
3963
|
return { result: hit, pending: false };
|
|
3894
3964
|
}
|
|
3895
|
-
if (this.pending.has(
|
|
3965
|
+
if (this.pending.has(key2)) return { result: null, pending: true };
|
|
3896
3966
|
const provider = this.slot.provider;
|
|
3897
|
-
if (!provider || this.failures.has(
|
|
3967
|
+
if (!provider || this.failures.has(key2)) return { result: null, pending: false };
|
|
3898
3968
|
const generation = this.generation;
|
|
3899
3969
|
const source = { width, height, data: new Uint8ClampedArray(input) };
|
|
3900
3970
|
const run = this.filter(provider, filter.effect, filter.values, source).then((out) => {
|
|
3901
3971
|
if (generation !== this.generation) return;
|
|
3902
|
-
this.cache.set(
|
|
3972
|
+
this.cache.set(key2, out);
|
|
3903
3973
|
while (this.cache.size > this.maxEntries) this.cache.delete(this.cache.keys().next().value);
|
|
3904
3974
|
}).catch((error) => {
|
|
3905
|
-
if (generation === this.generation) this.failures.set(
|
|
3975
|
+
if (generation === this.generation) this.failures.set(key2, error instanceof Error ? error : new Error(String(error)));
|
|
3906
3976
|
}).finally(() => {
|
|
3907
|
-
if (this.pending.get(
|
|
3977
|
+
if (this.pending.get(key2) === run) this.pending.delete(key2);
|
|
3908
3978
|
if (generation === this.generation) this.notify();
|
|
3909
3979
|
});
|
|
3910
|
-
this.pending.set(
|
|
3980
|
+
this.pending.set(key2, run);
|
|
3911
3981
|
return { result: null, pending: true };
|
|
3912
3982
|
}
|
|
3913
3983
|
/** A filter effect over `source`: its result, or (for an overlay) the effect drawn over it. */
|
|
@@ -3928,12 +3998,12 @@ var ImageEffectsEngine = class {
|
|
|
3928
3998
|
return out.width === size.width && out.height === size.height ? out : createEffectPixels(size.width, size.height);
|
|
3929
3999
|
}
|
|
3930
4000
|
/** Why the result for `key` failed, if it did. */
|
|
3931
|
-
failure(
|
|
3932
|
-
return this.failures.get(
|
|
4001
|
+
failure(key2) {
|
|
4002
|
+
return this.failures.get(key2);
|
|
3933
4003
|
}
|
|
3934
4004
|
/** Every failure so far. */
|
|
3935
4005
|
failuresList() {
|
|
3936
|
-
return [...this.failures].map(([
|
|
4006
|
+
return [...this.failures].map(([key2, error]) => ({ key: key2, error }));
|
|
3937
4007
|
}
|
|
3938
4008
|
/** Resolves when nothing is rendering. */
|
|
3939
4009
|
async idle() {
|
|
@@ -4556,10 +4626,10 @@ function resolveProfile(doc) {
|
|
|
4556
4626
|
let p;
|
|
4557
4627
|
if (doc.builtin) p = builtinProfile(doc.builtin);
|
|
4558
4628
|
else if (doc.icc) {
|
|
4559
|
-
const
|
|
4560
|
-
p = fromBytes.get(
|
|
4629
|
+
const key2 = bytesToBase64(doc.icc);
|
|
4630
|
+
p = fromBytes.get(key2) ?? parseProfile(doc.icc);
|
|
4561
4631
|
if (fromBytes.size > 32) fromBytes.clear();
|
|
4562
|
-
fromBytes.set(
|
|
4632
|
+
fromBytes.set(key2, p);
|
|
4563
4633
|
} else p = srgbProfile();
|
|
4564
4634
|
resolved.set(doc, p);
|
|
4565
4635
|
return p;
|
|
@@ -4649,12 +4719,12 @@ function convertHex(hex, from, to, intent = "relative", bpc = true) {
|
|
|
4649
4719
|
return `#${c(out[0])}${c(out[1])}${c(out[2])}`;
|
|
4650
4720
|
}
|
|
4651
4721
|
function convertColorFields(value, convert) {
|
|
4652
|
-
const walk = (v,
|
|
4653
|
-
if (typeof v === "string") return /color$/i.test(
|
|
4722
|
+
const walk = (v, key2) => {
|
|
4723
|
+
if (typeof v === "string") return /color$/i.test(key2) && /^#[0-9a-f]{6}$/i.test(v) ? convert(v) : v;
|
|
4654
4724
|
if (Array.isArray(v)) {
|
|
4655
4725
|
let changed = false;
|
|
4656
4726
|
const out = v.map((x) => {
|
|
4657
|
-
const y = walk(x,
|
|
4727
|
+
const y = walk(x, key2);
|
|
4658
4728
|
if (y !== x) changed = true;
|
|
4659
4729
|
return y;
|
|
4660
4730
|
});
|
|
@@ -4676,7 +4746,7 @@ function convertSurfaceColors(surface, t) {
|
|
|
4676
4746
|
if (surface.channels !== 4 || t.inputs !== 3 || t.outputs !== 3) return surface;
|
|
4677
4747
|
const tiles = /* @__PURE__ */ new Map();
|
|
4678
4748
|
const memo = /* @__PURE__ */ new Map();
|
|
4679
|
-
for (const [
|
|
4749
|
+
for (const [key2, tile] of surface.tiles) {
|
|
4680
4750
|
let next = memo.get(tile);
|
|
4681
4751
|
if (next === void 0) {
|
|
4682
4752
|
if (tile.deep instanceof Uint16Array) {
|
|
@@ -4699,7 +4769,7 @@ function convertSurfaceColors(surface, t) {
|
|
|
4699
4769
|
}
|
|
4700
4770
|
memo.set(tile, next);
|
|
4701
4771
|
}
|
|
4702
|
-
if (next) tiles.set(
|
|
4772
|
+
if (next) tiles.set(key2, next);
|
|
4703
4773
|
}
|
|
4704
4774
|
return new TiledSurface(4, tiles, surface.defaultValue);
|
|
4705
4775
|
}
|
|
@@ -4716,22 +4786,22 @@ function paintCoverageInto(base, coverage, apply, rects, depth = null) {
|
|
|
4716
4786
|
const done = /* @__PURE__ */ new Set();
|
|
4717
4787
|
for (const r of rects) {
|
|
4718
4788
|
for (const t of tilesInRect({ x: r.x - apply.offset.x, y: r.y - apply.offset.y, width: r.width, height: r.height })) {
|
|
4719
|
-
const
|
|
4720
|
-
if (done.has(
|
|
4721
|
-
done.add(
|
|
4789
|
+
const key2 = tileKey(t.tx, t.ty);
|
|
4790
|
+
if (done.has(key2)) continue;
|
|
4791
|
+
done.add(key2);
|
|
4722
4792
|
if (depth) {
|
|
4723
4793
|
const ch = base.channels;
|
|
4724
|
-
const d = applyStrokeToDeepTile(coverage, tileSamples(base.tiles.get(
|
|
4794
|
+
const d = applyStrokeToDeepTile(coverage, tileSamples(base.tiles.get(key2), ch, depth, base.defaultValue), t.tx, t.ty, apply, { channels: ch });
|
|
4725
4795
|
const tile2 = deepTile(d, ch, base.defaultValue);
|
|
4726
|
-
if (tile2) tiles.set(
|
|
4727
|
-
else tiles.delete(
|
|
4796
|
+
if (tile2) tiles.set(key2, tile2);
|
|
4797
|
+
else tiles.delete(key2);
|
|
4728
4798
|
continue;
|
|
4729
4799
|
}
|
|
4730
|
-
const src = base.tiles.get(
|
|
4800
|
+
const src = base.tiles.get(key2)?.data ?? defaultTile(base);
|
|
4731
4801
|
const data = applyStrokeToTile(coverage, src, base.channels, t.tx, t.ty, apply);
|
|
4732
4802
|
const tile = finishTile(data, base.channels, base.defaultValue);
|
|
4733
|
-
if (tile) tiles.set(
|
|
4734
|
-
else tiles.delete(
|
|
4803
|
+
if (tile) tiles.set(key2, tile);
|
|
4804
|
+
else tiles.delete(key2);
|
|
4735
4805
|
}
|
|
4736
4806
|
}
|
|
4737
4807
|
return new TiledSurface(base.channels, tiles, base.defaultValue);
|
|
@@ -4802,19 +4872,19 @@ var PaintStrokeSession = class {
|
|
|
4802
4872
|
work = /* @__PURE__ */ new Map();
|
|
4803
4873
|
workRead = /* @__PURE__ */ new Map();
|
|
4804
4874
|
/** The pre-stroke tile as a float working tile (cached; `null`: transparent). */
|
|
4805
|
-
readWork(
|
|
4806
|
-
if (!this.workRead.has(
|
|
4807
|
-
return this.workRead.get(
|
|
4875
|
+
readWork(key2) {
|
|
4876
|
+
if (!this.workRead.has(key2)) this.workRead.set(key2, workTileOf(this.base.tiles.get(key2)));
|
|
4877
|
+
return this.workRead.get(key2);
|
|
4808
4878
|
}
|
|
4809
4879
|
/** Clone, healing and the spot-heal trail at depth: `paint` through the coverage on the float working tile. */
|
|
4810
|
-
retouchDeepTile(
|
|
4880
|
+
retouchDeepTile(key2, tx, ty, paint, options) {
|
|
4811
4881
|
const r = this.retouch;
|
|
4812
|
-
const read = this.readWork(
|
|
4882
|
+
const read = this.readWork(key2);
|
|
4813
4883
|
const out = applyRetouchToTile(this.stroke.coverage, read ?? new Float32Array(TILE * TILE * 4), 4, tx, ty, paint, r.apply, options);
|
|
4814
|
-
this.work.set(
|
|
4815
|
-
const d = deepOfWorkTile(out, this.deep, read, this.base.tiles.get(
|
|
4816
|
-
this.liveDeep.set(
|
|
4817
|
-
this.live.set(
|
|
4884
|
+
this.work.set(key2, out);
|
|
4885
|
+
const d = deepOfWorkTile(out, this.deep, read, this.base.tiles.get(key2));
|
|
4886
|
+
this.liveDeep.set(key2, d);
|
|
4887
|
+
this.live.set(key2, { data: proxyOf(d, 4), uniform: null, deep: d });
|
|
4818
4888
|
return d;
|
|
4819
4889
|
}
|
|
4820
4890
|
/** The new tile of a coverage stroke over the pre-stroke tile `src`. */
|
|
@@ -4828,23 +4898,23 @@ var PaintStrokeSession = class {
|
|
|
4828
4898
|
const a = this.apply;
|
|
4829
4899
|
const depth = this.dabDeep;
|
|
4830
4900
|
if (depth) {
|
|
4831
|
-
const read = (
|
|
4901
|
+
const read = (key2) => this.readWork(key2);
|
|
4832
4902
|
return {
|
|
4833
4903
|
offset: a.offset,
|
|
4834
4904
|
lockTransparency: a.lockTransparency,
|
|
4835
4905
|
selection: a.selection,
|
|
4836
4906
|
readTile: (tx, ty) => {
|
|
4837
|
-
const
|
|
4838
|
-
return this.work.get(
|
|
4907
|
+
const key2 = tileKey(tx, ty);
|
|
4908
|
+
return this.work.get(key2) ?? read(key2);
|
|
4839
4909
|
},
|
|
4840
4910
|
writeTile: (tx, ty) => {
|
|
4841
|
-
const
|
|
4842
|
-
if (!this.touched.has(
|
|
4843
|
-
this.work.set(
|
|
4844
|
-
this.touched.add(
|
|
4911
|
+
const key2 = tileKey(tx, ty);
|
|
4912
|
+
if (!this.touched.has(key2)) {
|
|
4913
|
+
this.work.set(key2, read(key2)?.slice() ?? new Float32Array(TILE * TILE * 4));
|
|
4914
|
+
this.touched.add(key2);
|
|
4845
4915
|
}
|
|
4846
|
-
this.fresh.add(
|
|
4847
|
-
return this.work.get(
|
|
4916
|
+
this.fresh.add(key2);
|
|
4917
|
+
return this.work.get(key2);
|
|
4848
4918
|
}
|
|
4849
4919
|
};
|
|
4850
4920
|
}
|
|
@@ -4854,14 +4924,14 @@ var PaintStrokeSession = class {
|
|
|
4854
4924
|
selection: a.selection,
|
|
4855
4925
|
readTile: (tx, ty) => this.live.get(tileKey(tx, ty))?.data ?? (this.base.defaultValue ? defaultTile(this.base) : null),
|
|
4856
4926
|
writeTile: (tx, ty) => {
|
|
4857
|
-
const
|
|
4858
|
-
if (!this.touched.has(
|
|
4859
|
-
const src = this.base.tiles.get(
|
|
4860
|
-
this.live.set(
|
|
4861
|
-
this.touched.add(
|
|
4927
|
+
const key2 = tileKey(tx, ty);
|
|
4928
|
+
if (!this.touched.has(key2)) {
|
|
4929
|
+
const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
|
|
4930
|
+
this.live.set(key2, { data: new Uint8ClampedArray(src), uniform: null });
|
|
4931
|
+
this.touched.add(key2);
|
|
4862
4932
|
}
|
|
4863
|
-
this.fresh.add(
|
|
4864
|
-
return this.live.get(
|
|
4933
|
+
this.fresh.add(key2);
|
|
4934
|
+
return this.live.get(key2).data;
|
|
4865
4935
|
}
|
|
4866
4936
|
};
|
|
4867
4937
|
}
|
|
@@ -4885,12 +4955,12 @@ var PaintStrokeSession = class {
|
|
|
4885
4955
|
const ch = this.base.channels;
|
|
4886
4956
|
if (this.mixer) {
|
|
4887
4957
|
if (this.dabDeep) {
|
|
4888
|
-
for (const
|
|
4889
|
-
const d = deepOfWorkTile(this.work.get(
|
|
4890
|
-
this.liveDeep.set(
|
|
4891
|
-
this.live.set(
|
|
4958
|
+
for (const key2 of this.fresh) {
|
|
4959
|
+
const d = deepOfWorkTile(this.work.get(key2), this.dabDeep, this.workRead.get(key2) ?? null, this.base.tiles.get(key2));
|
|
4960
|
+
this.liveDeep.set(key2, d);
|
|
4961
|
+
this.live.set(key2, { data: proxyOf(d, 4), uniform: null, deep: d });
|
|
4892
4962
|
}
|
|
4893
|
-
} else for (const
|
|
4963
|
+
} else for (const key2 of this.fresh) this.live.set(key2, { data: this.live.get(key2).data, uniform: null });
|
|
4894
4964
|
this.fresh.clear();
|
|
4895
4965
|
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
4896
4966
|
return;
|
|
@@ -4899,35 +4969,35 @@ var PaintStrokeSession = class {
|
|
|
4899
4969
|
const regions = /* @__PURE__ */ new Map();
|
|
4900
4970
|
for (const r of rects) {
|
|
4901
4971
|
for (const t of tilesInRect({ x: r.x - this.apply.offset.x, y: r.y - this.apply.offset.y, width: r.width, height: r.height })) {
|
|
4902
|
-
const
|
|
4903
|
-
const cur = regions.get(
|
|
4904
|
-
regions.set(
|
|
4972
|
+
const key2 = tileKey(t.tx, t.ty);
|
|
4973
|
+
const cur = regions.get(key2);
|
|
4974
|
+
regions.set(key2, { tx: t.tx, ty: t.ty, r: cur ? unionRect(cur.r, r) : r });
|
|
4905
4975
|
}
|
|
4906
4976
|
}
|
|
4907
|
-
for (const [
|
|
4977
|
+
for (const [key2, { tx, ty, r }] of regions) {
|
|
4908
4978
|
if (this.deep) {
|
|
4909
4979
|
const rt = this.retouch;
|
|
4910
4980
|
const paint = rt?.kind === "coverage" ? rt.paint() : null;
|
|
4911
4981
|
if (paint && paint.kind !== "tone") {
|
|
4912
|
-
const prevWork = this.touched.has(
|
|
4913
|
-
this.touched.add(
|
|
4914
|
-
this.retouchDeepTile(
|
|
4982
|
+
const prevWork = this.touched.has(key2) ? this.work.get(key2) : void 0;
|
|
4983
|
+
this.touched.add(key2);
|
|
4984
|
+
this.retouchDeepTile(key2, tx, ty, paint, prevWork ? { into: prevWork, region: r } : {});
|
|
4915
4985
|
continue;
|
|
4916
4986
|
}
|
|
4917
|
-
const src2 = tileSamples(this.base.tiles.get(
|
|
4918
|
-
const prev2 = this.touched.has(
|
|
4919
|
-
this.touched.add(
|
|
4987
|
+
const src2 = tileSamples(this.base.tiles.get(key2), ch, this.deep, this.base.defaultValue);
|
|
4988
|
+
const prev2 = this.touched.has(key2) ? this.liveDeep.get(key2) : void 0;
|
|
4989
|
+
this.touched.add(key2);
|
|
4920
4990
|
const opts = prev2 ? { into: prev2, region: r, channels: ch } : { channels: ch };
|
|
4921
4991
|
const d = rt?.kind === "coverage" && paint?.kind === "tone" ? applyToneToDeepTile(stroke.coverage, src2, tx, ty, paint.tone, rt.apply, opts) : applyStrokeToDeepTile(stroke.coverage, src2, tx, ty, this.apply, opts);
|
|
4922
|
-
this.liveDeep.set(
|
|
4923
|
-
this.live.set(
|
|
4992
|
+
this.liveDeep.set(key2, d);
|
|
4993
|
+
this.live.set(key2, { data: proxyOf(d, ch), uniform: null, deep: d });
|
|
4924
4994
|
continue;
|
|
4925
4995
|
}
|
|
4926
|
-
const src = this.base.tiles.get(
|
|
4927
|
-
const prev = this.touched.has(
|
|
4928
|
-
this.touched.add(
|
|
4996
|
+
const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
|
|
4997
|
+
const prev = this.touched.has(key2) ? this.live.get(key2)?.data : void 0;
|
|
4998
|
+
this.touched.add(key2);
|
|
4929
4999
|
const data = this.compositeTile(src, tx, ty, prev ? { into: prev, region: r } : {});
|
|
4930
|
-
this.live.set(
|
|
5000
|
+
this.live.set(key2, { data, uniform: null });
|
|
4931
5001
|
}
|
|
4932
5002
|
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
4933
5003
|
}
|
|
@@ -4944,26 +5014,26 @@ var PaintStrokeSession = class {
|
|
|
4944
5014
|
if (r?.kind === "coverage" && r.finalPaint) {
|
|
4945
5015
|
const final = r.finalPaint(this.stroke.coverage);
|
|
4946
5016
|
if (final) {
|
|
4947
|
-
for (const
|
|
4948
|
-
const { tx, ty } = tileCoord(
|
|
5017
|
+
for (const key2 of this.touched) {
|
|
5018
|
+
const { tx, ty } = tileCoord(key2);
|
|
4949
5019
|
if (this.deep) {
|
|
4950
|
-
this.retouchDeepTile(
|
|
5020
|
+
this.retouchDeepTile(key2, tx, ty, final, {});
|
|
4951
5021
|
continue;
|
|
4952
5022
|
}
|
|
4953
|
-
const src = this.base.tiles.get(
|
|
4954
|
-
this.live.set(
|
|
5023
|
+
const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
|
|
5024
|
+
this.live.set(key2, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
|
|
4955
5025
|
}
|
|
4956
5026
|
}
|
|
4957
5027
|
}
|
|
4958
5028
|
const tiles = new Map(this.base.tiles);
|
|
4959
5029
|
let changed = false;
|
|
4960
|
-
for (const
|
|
4961
|
-
const live = this.live.get(
|
|
4962
|
-
const deep = this.liveDeep.get(
|
|
5030
|
+
for (const key2 of this.touched) {
|
|
5031
|
+
const live = this.live.get(key2);
|
|
5032
|
+
const deep = this.liveDeep.get(key2);
|
|
4963
5033
|
const tile = deep ? deepTile(deep, ch, this.base.defaultValue) : finishTile(live.data, ch, this.base.defaultValue);
|
|
4964
|
-
const prev = this.base.tiles.get(
|
|
4965
|
-
if (tile) tiles.set(
|
|
4966
|
-
else tiles.delete(
|
|
5034
|
+
const prev = this.base.tiles.get(key2);
|
|
5035
|
+
if (tile) tiles.set(key2, tile);
|
|
5036
|
+
else tiles.delete(key2);
|
|
4967
5037
|
if (!sameTile(prev, tile)) changed = true;
|
|
4968
5038
|
}
|
|
4969
5039
|
const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
|
|
@@ -4982,9 +5052,9 @@ var PaintStrokeSession = class {
|
|
|
4982
5052
|
this.controller._strokeCommit(this.before, this.target.id, this.target.channel, null);
|
|
4983
5053
|
}
|
|
4984
5054
|
};
|
|
4985
|
-
function workRegionOf(surface,
|
|
4986
|
-
if (!depth) return Float32Array.from(readRegion(surface,
|
|
4987
|
-
const d = readDeepRegion(surface,
|
|
5055
|
+
function workRegionOf(surface, rect2, depth) {
|
|
5056
|
+
if (!depth) return Float32Array.from(readRegion(surface, rect2));
|
|
5057
|
+
const d = readDeepRegion(surface, rect2, depth);
|
|
4988
5058
|
const out = new Float32Array(d.length);
|
|
4989
5059
|
if (d instanceof Uint16Array) {
|
|
4990
5060
|
for (let i = 0; i < d.length; i++) out[i] = d[i] / 257;
|
|
@@ -5671,9 +5741,9 @@ var ImageEditorController = class {
|
|
|
5671
5741
|
/** Recomputes the compatibility views (`adjustments`, `depthMask`, `annotations`) when the stack changed. */
|
|
5672
5742
|
syncViews() {
|
|
5673
5743
|
const s = this.state;
|
|
5674
|
-
const
|
|
5675
|
-
if (
|
|
5676
|
-
this.viewKey =
|
|
5744
|
+
const key2 = [s.layers, s.outputOpacity, s.legacyOutputBlur, s.activeLayerId, s.imageDimensions.width, s.imageDimensions.height];
|
|
5745
|
+
if (key2.length === this.viewKey.length && key2.every((v, i) => v === this.viewKey[i])) return;
|
|
5746
|
+
this.viewKey = key2;
|
|
5677
5747
|
const { width, height } = this.naturalSize;
|
|
5678
5748
|
const view = legacyView(this.stack(), width, height, s.activeLayerId);
|
|
5679
5749
|
s.adjustments = view.adjustments;
|
|
@@ -6000,7 +6070,7 @@ var ImageEditorController = class {
|
|
|
6000
6070
|
getLayerFrame(id) {
|
|
6001
6071
|
const layer = this.getLayer(id);
|
|
6002
6072
|
if (!layer) return null;
|
|
6003
|
-
const
|
|
6073
|
+
const rect2 = (b2) => [
|
|
6004
6074
|
{ x: b2.x, y: b2.y },
|
|
6005
6075
|
{ x: b2.x + b2.width, y: b2.y },
|
|
6006
6076
|
{ x: b2.x + b2.width, y: b2.y + b2.height },
|
|
@@ -6015,10 +6085,10 @@ var ImageEditorController = class {
|
|
|
6015
6085
|
}
|
|
6016
6086
|
if (layer.type === "shape") {
|
|
6017
6087
|
const g = shapeGeometryBounds(layer);
|
|
6018
|
-
return g ?
|
|
6088
|
+
return g ? rect2(g) : null;
|
|
6019
6089
|
}
|
|
6020
6090
|
const b = this.getLayerBounds(id);
|
|
6021
|
-
return b ?
|
|
6091
|
+
return b ? rect2(b) : null;
|
|
6022
6092
|
}
|
|
6023
6093
|
/**
|
|
6024
6094
|
* Hit-tests the layer stack at `point` (in document space) and returns the id of the topmost
|
|
@@ -6099,9 +6169,9 @@ var ImageEditorController = class {
|
|
|
6099
6169
|
* and are left out): `rect` (default: the canvas) as RGBA, straight alpha. Crop, rotation and
|
|
6100
6170
|
* output opacity are not applied. With `layerIds`, only those layers are composited.
|
|
6101
6171
|
*/
|
|
6102
|
-
compositePixels(
|
|
6172
|
+
compositePixels(rect2, options = {}) {
|
|
6103
6173
|
const { width, height } = this.naturalSize;
|
|
6104
|
-
const r =
|
|
6174
|
+
const r = rect2 ?? { x: 0, y: 0, width, height };
|
|
6105
6175
|
let layers = this.state.layers;
|
|
6106
6176
|
if (options.layerIds) {
|
|
6107
6177
|
const keep = new Set(options.layerIds);
|
|
@@ -6125,12 +6195,12 @@ var ImageEditorController = class {
|
|
|
6125
6195
|
nextSourceId = 1;
|
|
6126
6196
|
/** Changes whenever the Background's source pixels can have changed. */
|
|
6127
6197
|
sourceVersion() {
|
|
6128
|
-
const
|
|
6129
|
-
if (!
|
|
6130
|
-
let id = this.sourceIds.get(
|
|
6198
|
+
const key2 = this.samplePixelsOverride ? this.samplePixels : this.imageElement ?? this.samplePixels;
|
|
6199
|
+
if (!key2) return "0";
|
|
6200
|
+
let id = this.sourceIds.get(key2);
|
|
6131
6201
|
if (!id) {
|
|
6132
6202
|
id = this.nextSourceId++;
|
|
6133
|
-
this.sourceIds.set(
|
|
6203
|
+
this.sourceIds.set(key2, id);
|
|
6134
6204
|
}
|
|
6135
6205
|
return String(id);
|
|
6136
6206
|
}
|
|
@@ -6572,8 +6642,8 @@ var ImageEditorController = class {
|
|
|
6572
6642
|
this.state.activePathId = id;
|
|
6573
6643
|
this.notify();
|
|
6574
6644
|
}
|
|
6575
|
-
commitPaths(next, active) {
|
|
6576
|
-
this.recordHistory();
|
|
6645
|
+
commitPaths(next, active, commitToHistory = true) {
|
|
6646
|
+
if (commitToHistory) this.recordHistory();
|
|
6577
6647
|
if (next.length) this.state.paths = next;
|
|
6578
6648
|
else delete this.state.paths;
|
|
6579
6649
|
if (active !== void 0) this.state.activePathId = active;
|
|
@@ -6609,6 +6679,64 @@ var ImageEditorController = class {
|
|
|
6609
6679
|
this.commitPaths(this.getPaths().map((p) => p.id === id ? path : p));
|
|
6610
6680
|
return path;
|
|
6611
6681
|
}
|
|
6682
|
+
/**
|
|
6683
|
+
* What the path tools (Path Selection, Direct Selection, Pen) edit: the active path in the Paths
|
|
6684
|
+
* panel, else the active layer when it is a shape, else the active layer's vector mask; null
|
|
6685
|
+
* when there is none of these (the Pen then starts a new Work Path).
|
|
6686
|
+
*/
|
|
6687
|
+
pathEditTarget() {
|
|
6688
|
+
const id = this.state.activePathId;
|
|
6689
|
+
if (id && this.getPath(id)) return { kind: "path", id };
|
|
6690
|
+
const layer = this.getActiveLayer();
|
|
6691
|
+
if (!layer) return null;
|
|
6692
|
+
if (layer.type === "shape") return { kind: "shape", layerId: layer.id };
|
|
6693
|
+
if (layer.vectorMask) return { kind: "vector-mask", layerId: layer.id };
|
|
6694
|
+
return null;
|
|
6695
|
+
}
|
|
6696
|
+
/** The components a path tool target holds (document pixels). Throws for a target that is gone. */
|
|
6697
|
+
getPathEditComponents(target) {
|
|
6698
|
+
if (target.kind === "path") return this.requirePath(target.id).components;
|
|
6699
|
+
if (target.kind === "shape") return requireShapeLayer(this.state.layers, target.layerId).components;
|
|
6700
|
+
return this.requireVectorMask(target.layerId).vm.components;
|
|
6701
|
+
}
|
|
6702
|
+
/**
|
|
6703
|
+
* Replaces a path tool target's components: one undo step, or none with `commitToHistory` false
|
|
6704
|
+
* (the later moves of a drag whose first move recorded one). A component that still has its
|
|
6705
|
+
* `live` shape is kept as given (drop `live` from one whose anchors changed); a shape layer keeps
|
|
6706
|
+
* at least one component.
|
|
6707
|
+
*/
|
|
6708
|
+
setPathEditComponents(target, components, commitToHistory = true) {
|
|
6709
|
+
this.getPathEditComponents(target);
|
|
6710
|
+
const next = components.map(
|
|
6711
|
+
(c, i) => c.live ? { ...c } : this.componentFrom({ op: c.op, subpaths: c.subpaths.filter((sp) => sp.anchors.length) }, `components[${i}].`)
|
|
6712
|
+
);
|
|
6713
|
+
if (target.kind === "path") {
|
|
6714
|
+
this.commitPaths(this.getPaths().map((p) => p.id === target.id ? { ...p, components: next } : p), void 0, commitToHistory);
|
|
6715
|
+
} else if (target.kind === "shape") {
|
|
6716
|
+
if (!next.length) throw new LayerOperationError("A shape needs at least one component; delete the layer instead (delete_layer).");
|
|
6717
|
+
const { layers } = updateShapeLayer(this.state.layers, target.layerId, () => ({ components: next }), this.shapeEnv(), "edit its path");
|
|
6718
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
6719
|
+
} else {
|
|
6720
|
+
const { vm } = this.requireVectorMask(target.layerId);
|
|
6721
|
+
this.commitLayers(mapLayer(this.state.layers, target.layerId, (l) => ({ ...l, vectorMask: { ...vm, components: next } })), void 0, commitToHistory);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
/**
|
|
6725
|
+
* The Pen with no path to add to: a new Work Path from `components` (replacing the last one,
|
|
6726
|
+
* unless it was saved), made the active path. One undo step. Returns the path.
|
|
6727
|
+
*/
|
|
6728
|
+
startWorkPath(components) {
|
|
6729
|
+
const paths = this.getPaths();
|
|
6730
|
+
const old = paths.find((p) => p.work);
|
|
6731
|
+
const work = {
|
|
6732
|
+
id: old?.id ?? newPathId(),
|
|
6733
|
+
name: WORK_PATH_NAME,
|
|
6734
|
+
components: components.map((c, i) => this.componentFrom({ op: c.op, subpaths: c.subpaths }, `components[${i}].`)),
|
|
6735
|
+
work: true
|
|
6736
|
+
};
|
|
6737
|
+
this.commitPaths(old ? paths.map((p) => p === old ? work : p) : [...paths, work], work.id);
|
|
6738
|
+
return work;
|
|
6739
|
+
}
|
|
6612
6740
|
/** Renames a path; renaming the Work Path saves it (Photoshop's Save Path). One undo step. */
|
|
6613
6741
|
renamePath(id, name) {
|
|
6614
6742
|
const old = this.requirePath(id);
|
|
@@ -7485,7 +7613,7 @@ var ImageEditorController = class {
|
|
|
7485
7613
|
return this.getGlobalLight();
|
|
7486
7614
|
}
|
|
7487
7615
|
/** Composites `layers` (in isolation) over `rect` into a surface, loading depth maps first. */
|
|
7488
|
-
async compositeToSurface(layers,
|
|
7616
|
+
async compositeToSurface(layers, rect2) {
|
|
7489
7617
|
const { width, height } = this.naturalSize;
|
|
7490
7618
|
if (flattenLayers(layers).some((l) => l.type === "annotation" && l.visible) && !this.renderer.canRasterize()) {
|
|
7491
7619
|
throw new LayerOperationError("Merging annotation layers needs a canvas (a browser); hide or delete them first, or merge in the browser.");
|
|
@@ -7494,20 +7622,20 @@ var ImageEditorController = class {
|
|
|
7494
7622
|
const sources = this.renderer.compositorSources(width, height);
|
|
7495
7623
|
const depth = this.bitDepth;
|
|
7496
7624
|
if (depth !== 8) {
|
|
7497
|
-
const samples = workingToSamples(compositeDeep(layers,
|
|
7498
|
-
return surfaceFromDeepPixels({ width:
|
|
7625
|
+
const samples = workingToSamples(compositeDeep(layers, rect2, sources, depth), depth);
|
|
7626
|
+
return surfaceFromDeepPixels({ width: rect2.width, height: rect2.height, data: samples }, 4, rect2.x, rect2.y);
|
|
7499
7627
|
}
|
|
7500
|
-
const data = new LayerCompositor().composite(layers,
|
|
7501
|
-
return surfaceFromPixels({ width:
|
|
7628
|
+
const data = new LayerCompositor().composite(layers, rect2, sources);
|
|
7629
|
+
return surfaceFromPixels({ width: rect2.width, height: rect2.height, data }, 4, rect2.x, rect2.y);
|
|
7502
7630
|
}
|
|
7503
7631
|
/**
|
|
7504
7632
|
* The composite of the layer stack at the document's bit depth (as `compositePixels`, no
|
|
7505
7633
|
* canvas): 16-bit sRGB samples, or 32-bit linear float, over `rect` (default the canvas). At 8
|
|
7506
7634
|
* bits, the 8-bit composite widened to 16 bits.
|
|
7507
7635
|
*/
|
|
7508
|
-
compositeDeepPixels(
|
|
7636
|
+
compositeDeepPixels(rect2) {
|
|
7509
7637
|
const { width, height } = this.naturalSize;
|
|
7510
|
-
const r =
|
|
7638
|
+
const r = rect2 ?? { x: 0, y: 0, width, height };
|
|
7511
7639
|
const depth = this.bitDepth;
|
|
7512
7640
|
if (depth === 8) return { width: r.width, height: r.height, depth: 16, data: to16From8(this.compositePixels(r).data) };
|
|
7513
7641
|
const data = workingToSamples(compositeDeep(this.state.layers, r, this.compositorSources(), depth), depth);
|
|
@@ -7545,12 +7673,12 @@ var ImageEditorController = class {
|
|
|
7545
7673
|
if (!upper.visible) throw new LayerOperationError(`Layer "${upper.name || id}" is hidden; show it or delete it instead of merging.`);
|
|
7546
7674
|
requireUnlocked(upper, "all", "merge it");
|
|
7547
7675
|
requireUnlocked(lower, "pixels", "merge into it");
|
|
7548
|
-
let
|
|
7676
|
+
let rect2 = unionRect(this.paintBounds(lower), upper.type === "adjustment" ? null : this.paintBounds(upper)) ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
7549
7677
|
const reach = Math.max(effectsMargin(lower.effects), upper.type === "adjustment" ? 0 : effectsMargin(upper.effects));
|
|
7550
|
-
if (reach)
|
|
7678
|
+
if (reach) rect2 = { x: rect2.x - reach, y: rect2.y - reach, width: rect2.width + 2 * reach, height: rect2.height + 2 * reach };
|
|
7551
7679
|
const base = { ...lower, blend: "normal", opacity: 1 };
|
|
7552
7680
|
delete base.clipped;
|
|
7553
|
-
const surface = await this.compositeToSurface([base, upper],
|
|
7681
|
+
const surface = await this.compositeToSurface([base, upper], rect2);
|
|
7554
7682
|
const merged = { ...lower, surface, offset: { x: 0, y: 0 } };
|
|
7555
7683
|
delete merged.mask;
|
|
7556
7684
|
delete merged.fillOpacity;
|
|
@@ -7632,9 +7760,9 @@ var ImageEditorController = class {
|
|
|
7632
7760
|
if (!surface) return null;
|
|
7633
7761
|
const ox = layer.type === "pixel" ? layer.offset.x : 0;
|
|
7634
7762
|
const oy = layer.type === "pixel" ? layer.offset.y : 0;
|
|
7635
|
-
const
|
|
7763
|
+
const key2 = `${tw}x${th}:${ox},${oy}`;
|
|
7636
7764
|
let byKey = this.thumbCache.get(surface);
|
|
7637
|
-
const hit = byKey?.get(
|
|
7765
|
+
const hit = byKey?.get(key2);
|
|
7638
7766
|
if (hit) return hit;
|
|
7639
7767
|
const out = new Uint8ClampedArray(tw * th * 4);
|
|
7640
7768
|
for (let y = 0; y < th; y++) {
|
|
@@ -7654,7 +7782,7 @@ var ImageEditorController = class {
|
|
|
7654
7782
|
byKey = /* @__PURE__ */ new Map();
|
|
7655
7783
|
this.thumbCache.set(surface, byKey);
|
|
7656
7784
|
}
|
|
7657
|
-
byKey.set(
|
|
7785
|
+
byKey.set(key2, thumb);
|
|
7658
7786
|
return thumb;
|
|
7659
7787
|
}
|
|
7660
7788
|
/**
|
|
@@ -8139,7 +8267,9 @@ var ImageEditorController = class {
|
|
|
8139
8267
|
if (!sel?.bounds) throw new LayerOperationError("Nothing is selected; select what to remove, then fill it.");
|
|
8140
8268
|
if (this.bitDepth !== 8) throw new LayerOperationError("Content-Aware Fill works on 8-bit documents for now; set_bit_depth 8 first, or use spot_heal.");
|
|
8141
8269
|
const output = options.output ?? "current";
|
|
8142
|
-
if (output !== "current" && output !== "new-layer"
|
|
8270
|
+
if (output !== "current" && output !== "new-layer" && output !== "duplicate-layer") {
|
|
8271
|
+
throw new LayerOperationError(`Unknown output "${String(output)}"; use current, new-layer or duplicate-layer.`);
|
|
8272
|
+
}
|
|
8143
8273
|
const target = this.paintTarget(options.layerId, "pixels", "fill");
|
|
8144
8274
|
const { width, height } = this.naturalSize;
|
|
8145
8275
|
const b = sel.bounds;
|
|
@@ -8216,6 +8346,13 @@ var ImageEditorController = class {
|
|
|
8216
8346
|
}
|
|
8217
8347
|
const layerRect = { x: win.x - target.offset.x, y: win.y - target.offset.y, width: win.width, height: win.height };
|
|
8218
8348
|
const surface = writeRegion(target.surface, layerRect, out);
|
|
8349
|
+
if (output === "duplicate-layer") {
|
|
8350
|
+
return this.asOneStep(() => {
|
|
8351
|
+
const copy = this.duplicateLayer(target.id);
|
|
8352
|
+
this.commitLayers(mapLayer(this.state.layers, copy, (l) => this.withSurface(l, "pixels", surface)));
|
|
8353
|
+
return { layerId: copy, bounds: { ...b }, output };
|
|
8354
|
+
});
|
|
8355
|
+
}
|
|
8219
8356
|
this.commitLayers(mapLayer(this.state.layers, target.id, (l) => this.withSurface(l, "pixels", surface)));
|
|
8220
8357
|
return { layerId: target.id, bounds: { ...b }, output };
|
|
8221
8358
|
}
|
|
@@ -8240,27 +8377,27 @@ var ImageEditorController = class {
|
|
|
8240
8377
|
copyPixels(options = {}) {
|
|
8241
8378
|
const { width, height } = this.naturalSize;
|
|
8242
8379
|
const sel = this.state.selection?.bounds ? this.state.selection : null;
|
|
8243
|
-
const
|
|
8380
|
+
const rect2 = sel?.bounds ?? { x: 0, y: 0, width, height };
|
|
8244
8381
|
let data;
|
|
8245
8382
|
if (options.merged) {
|
|
8246
|
-
data = new Uint8ClampedArray(this.compositePixels(
|
|
8383
|
+
data = new Uint8ClampedArray(this.compositePixels(rect2).data);
|
|
8247
8384
|
} else {
|
|
8248
8385
|
const id = options.layerId ?? this.state.activeLayerId;
|
|
8249
8386
|
if (!id) throw new LayerOperationError("No layer given and none is active; pass layerId, or merged: true to copy the visible image.");
|
|
8250
8387
|
const layer = requireLayer(this.state.layers, id).layer;
|
|
8251
8388
|
if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${layer.name || id}" is not a pixel layer; copy merged, or rasterize it first.`);
|
|
8252
|
-
data = readRegion(this.surfaceOf(layer), { x:
|
|
8389
|
+
data = readRegion(this.surfaceOf(layer), { x: rect2.x - layer.offset.x, y: rect2.y - layer.offset.y, width: rect2.width, height: rect2.height });
|
|
8253
8390
|
}
|
|
8254
8391
|
if (sel) {
|
|
8255
|
-
for (let y = 0; y <
|
|
8256
|
-
for (let x = 0; x <
|
|
8257
|
-
const k = sel.data[(
|
|
8258
|
-
const p = (y *
|
|
8392
|
+
for (let y = 0; y < rect2.height; y++) {
|
|
8393
|
+
for (let x = 0; x < rect2.width; x++) {
|
|
8394
|
+
const k = sel.data[(rect2.y + y) * sel.width + rect2.x + x];
|
|
8395
|
+
const p = (y * rect2.width + x) * 4 + 3;
|
|
8259
8396
|
data[p] = Math.round(data[p] * k / 255);
|
|
8260
8397
|
}
|
|
8261
8398
|
}
|
|
8262
8399
|
}
|
|
8263
|
-
this.clipboard = { width:
|
|
8400
|
+
this.clipboard = { width: rect2.width, height: rect2.height, x: rect2.x, y: rect2.y, data };
|
|
8264
8401
|
return this.clipboard;
|
|
8265
8402
|
}
|
|
8266
8403
|
/** Edit › Cut: Copy, then clear the selected pixels, as one undo step. */
|
|
@@ -8744,8 +8881,8 @@ var ImageEditorController = class {
|
|
|
8744
8881
|
if (!Number.isFinite(position) || at < 0 || at >= max) {
|
|
8745
8882
|
throw new RangeError(`${orientation === "row" ? "y" : "x"} ${position} is outside the image (0..${max - 1}).`);
|
|
8746
8883
|
}
|
|
8747
|
-
const
|
|
8748
|
-
return this.shapeCommand(rectSelection(width, height,
|
|
8884
|
+
const rect2 = orientation === "row" ? { x: 0, y: at, width, height: 1 } : { x: at, y: 0, width: 1, height };
|
|
8885
|
+
return this.shapeCommand(rectSelection(width, height, rect2, { antiAlias: false }), options);
|
|
8749
8886
|
}
|
|
8750
8887
|
/** Select › Grow: adds connected pixels within `tolerance` of a selected colour (one undo step). */
|
|
8751
8888
|
async growSelection(options = {}) {
|
|
@@ -8801,14 +8938,14 @@ var ImageEditorController = class {
|
|
|
8801
8938
|
return this.setSelection(shaped, options.mode ?? "replace");
|
|
8802
8939
|
}
|
|
8803
8940
|
/** Rectangular marquee in natural image pixels. */
|
|
8804
|
-
selectRect(
|
|
8941
|
+
selectRect(rect2, options = {}) {
|
|
8805
8942
|
const { width, height } = this.naturalSize;
|
|
8806
|
-
return this.shapeCommand(rectSelection(width, height,
|
|
8943
|
+
return this.shapeCommand(rectSelection(width, height, rect2, options), options);
|
|
8807
8944
|
}
|
|
8808
8945
|
/** Elliptical marquee inscribed in `rect`. */
|
|
8809
|
-
selectEllipse(
|
|
8946
|
+
selectEllipse(rect2, options = {}) {
|
|
8810
8947
|
const { width, height } = this.naturalSize;
|
|
8811
|
-
return this.shapeCommand(ellipseSelection(width, height,
|
|
8948
|
+
return this.shapeCommand(ellipseSelection(width, height, rect2, options), options);
|
|
8812
8949
|
}
|
|
8813
8950
|
/** Lasso / polygonal lasso. */
|
|
8814
8951
|
selectPolygon(points, options = {}) {
|
|
@@ -10027,15 +10164,15 @@ var ImageEditorController = class {
|
|
|
10027
10164
|
let mask;
|
|
10028
10165
|
if (!b || x1 <= x0 || y1 <= y0) mask = createSelectionMask(width, height);
|
|
10029
10166
|
else {
|
|
10030
|
-
const
|
|
10031
|
-
const rgba = readRegion(layer.surface,
|
|
10032
|
-
const bytes = new Uint8Array(
|
|
10167
|
+
const rect2 = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
10168
|
+
const rgba = readRegion(layer.surface, rect2);
|
|
10169
|
+
const bytes = new Uint8Array(rect2.width * rect2.height);
|
|
10033
10170
|
const hard = init.antiAlias === false;
|
|
10034
10171
|
for (let i = 0; i < bytes.length; i++) {
|
|
10035
10172
|
const a = rgba[i * 4 + 3];
|
|
10036
10173
|
bytes[i] = hard ? a >= 128 ? 255 : 0 : a;
|
|
10037
10174
|
}
|
|
10038
|
-
mask = selectionFromRegion(width, height,
|
|
10175
|
+
mask = selectionFromRegion(width, height, rect2, bytes);
|
|
10039
10176
|
}
|
|
10040
10177
|
if ((init.feather ?? 0) > 0 && mask.bounds) mask = modifySelection(mask, "feather", init.feather);
|
|
10041
10178
|
return this.setSelection(mask.bounds ? mask : null, init.mode ?? "replace");
|
|
@@ -10394,8 +10531,8 @@ var ImageEditorController = class {
|
|
|
10394
10531
|
delete content.mask;
|
|
10395
10532
|
delete content.clipped;
|
|
10396
10533
|
delete content.fillOpacity;
|
|
10397
|
-
const
|
|
10398
|
-
const surface = await this.compositeToSurface([content],
|
|
10534
|
+
const rect2 = spatial ? canvas : this.getLayerBounds(id) ?? canvas;
|
|
10535
|
+
const surface = await this.compositeToSurface([content], rect2);
|
|
10399
10536
|
next = { ...layer, surface, offset: { x: 0, y: 0 } };
|
|
10400
10537
|
delete next.filters;
|
|
10401
10538
|
delete next.source;
|
|
@@ -12037,7 +12174,7 @@ function layersOf(args) {
|
|
|
12037
12174
|
add2(args.layerId);
|
|
12038
12175
|
add2(args.id);
|
|
12039
12176
|
add2(args.target);
|
|
12040
|
-
for (const
|
|
12177
|
+
for (const key2 of ["ids", "layerIds"]) if (Array.isArray(args[key2])) args[key2].forEach(add2);
|
|
12041
12178
|
return out.slice(0, 6);
|
|
12042
12179
|
}
|
|
12043
12180
|
function planImageToolPlayback(tool, args) {
|
|
@@ -12050,9 +12187,9 @@ function planImageToolPlayback(tool, args) {
|
|
|
12050
12187
|
else if (box) steps.push({ type: "move", to: box.from }, { type: "press" }, { type: "drag", to: box.to }, { type: "release" });
|
|
12051
12188
|
else if (num(args.x) && num(args.y)) steps.push({ type: "move", to: { x: args.x, y: args.y } }, { type: "press" }, { type: "release" });
|
|
12052
12189
|
else if (tool === "set_adjustments" && args.values && typeof args.values === "object") {
|
|
12053
|
-
for (const
|
|
12054
|
-
const m = /^layers\.([^.]+)\.(.+)$/.exec(
|
|
12055
|
-
steps.push({ type: "target", ref: m ? { kind: "control", id: m[2] } : { kind: "control", id:
|
|
12190
|
+
for (const key2 of Object.keys(args.values).slice(0, 4)) {
|
|
12191
|
+
const m = /^layers\.([^.]+)\.(.+)$/.exec(key2);
|
|
12192
|
+
steps.push({ type: "target", ref: m ? { kind: "control", id: m[2] } : { kind: "control", id: key2 } });
|
|
12056
12193
|
}
|
|
12057
12194
|
} else for (const id of layersOf(args)) steps.push({ type: "target", ref: { kind: IMAGE_PLAYBACK_KIND, id } });
|
|
12058
12195
|
if (typeof args.text === "string" && args.text.trim()) {
|
|
@@ -12228,10 +12365,10 @@ function sampleOptions(args) {
|
|
|
12228
12365
|
return { sampleMerged: args.sampleMerged === true, sample: args.sample ?? "composite" };
|
|
12229
12366
|
}
|
|
12230
12367
|
function rectOf(args) {
|
|
12231
|
-
const
|
|
12232
|
-
if (![
|
|
12233
|
-
if (
|
|
12234
|
-
return
|
|
12368
|
+
const rect2 = { x: num2(args.x, NaN), y: num2(args.y, NaN), width: num2(args.width, NaN), height: num2(args.height, NaN) };
|
|
12369
|
+
if (![rect2.x, rect2.y, rect2.width, rect2.height].every(Number.isFinite)) throw new Error("Pass numeric x, y, width and height in natural image pixels.");
|
|
12370
|
+
if (rect2.width <= 0 || rect2.height <= 0) throw new Error("width and height must be greater than 0.");
|
|
12371
|
+
return rect2;
|
|
12235
12372
|
}
|
|
12236
12373
|
function summary(controller) {
|
|
12237
12374
|
const sel = controller.getSelection();
|
|
@@ -12306,12 +12443,12 @@ function createImageSelectionTools(controller) {
|
|
|
12306
12443
|
break;
|
|
12307
12444
|
case "rect":
|
|
12308
12445
|
case "ellipse": {
|
|
12309
|
-
const
|
|
12310
|
-
if (![
|
|
12446
|
+
const rect2 = { x: num2(x, NaN), y: num2(y, NaN), width: num2(width, NaN), height: num2(height, NaN) };
|
|
12447
|
+
if (![rect2.x, rect2.y, rect2.width, rect2.height].every(Number.isFinite)) {
|
|
12311
12448
|
throw new Error("rect and ellipse need numeric x, y, width and height.");
|
|
12312
12449
|
}
|
|
12313
|
-
if (shape === "rect") controller.selectRect(
|
|
12314
|
-
else controller.selectEllipse(
|
|
12450
|
+
if (shape === "rect") controller.selectRect(rect2, opts);
|
|
12451
|
+
else controller.selectEllipse(rect2, opts);
|
|
12315
12452
|
break;
|
|
12316
12453
|
}
|
|
12317
12454
|
case "polygon": {
|
|
@@ -12682,10 +12819,10 @@ function createImageSelectionTools(controller) {
|
|
|
12682
12819
|
},
|
|
12683
12820
|
annotations: { ...writeAnnotations("Object Selection"), openWorldHint: true },
|
|
12684
12821
|
handler: async (args, context) => {
|
|
12685
|
-
const
|
|
12822
|
+
const rect2 = args.rect;
|
|
12686
12823
|
const point = args.point;
|
|
12687
|
-
if (!
|
|
12688
|
-
if (
|
|
12824
|
+
if (!rect2 && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
|
|
12825
|
+
if (rect2 && point) throw new Error("Pass rect or point, not both.");
|
|
12689
12826
|
const options = {
|
|
12690
12827
|
mode: parseSelectionMode(args.mode),
|
|
12691
12828
|
sampleAllLayers: args.sampleAllLayers,
|
|
@@ -12693,7 +12830,7 @@ function createImageSelectionTools(controller) {
|
|
|
12693
12830
|
useModel: args.useModel,
|
|
12694
12831
|
signal: context?.signal
|
|
12695
12832
|
};
|
|
12696
|
-
const { method } =
|
|
12833
|
+
const { method } = rect2 ? await controller.selectObject({ rect: { x: num2(rect2.x, NaN), y: num2(rect2.y, NaN), width: num2(rect2.width, NaN), height: num2(rect2.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
|
|
12697
12834
|
return { ...summary(controller), method };
|
|
12698
12835
|
}
|
|
12699
12836
|
};
|
|
@@ -13075,7 +13212,7 @@ function createImageSelectionTools(controller) {
|
|
|
13075
13212
|
inputSchema: {
|
|
13076
13213
|
type: "object",
|
|
13077
13214
|
properties: {
|
|
13078
|
-
output: { type: "string", enum: ["new-layer", "current"], description: "new-layer
|
|
13215
|
+
output: { type: "string", enum: ["new-layer", "current", "duplicate-layer"], description: "new-layer (the fill alone on a layer above), current (into the layer) or duplicate-layer (into a copy of the layer, above it); default current." },
|
|
13079
13216
|
sampleRadius: { type: "number", minimum: 32, maximum: 4e3, description: "Sample within this many pixels of the selection (default its larger side)." },
|
|
13080
13217
|
seed: { type: "integer", minimum: 0, description: "Random seed; another seed gives another fill to choose from (default 0)." },
|
|
13081
13218
|
sampleRects: { type: "array", minItems: 1, items: rectSchemaStrict, description: "Sample only from these rectangles (natural image pixels), instead of the ring around the selection." },
|
|
@@ -13445,7 +13582,7 @@ async function renderImageRegion(controller, request) {
|
|
|
13445
13582
|
out.height = request.height;
|
|
13446
13583
|
const context = out.getContext("2d");
|
|
13447
13584
|
if (!context) throw new Error(NO_CANVAS);
|
|
13448
|
-
const { rect } = request;
|
|
13585
|
+
const { rect: rect2 } = request;
|
|
13449
13586
|
context.imageSmoothingEnabled = request.scale < 1;
|
|
13450
13587
|
if (request.format === "jpeg") {
|
|
13451
13588
|
context.fillStyle = "#ffffff";
|
|
@@ -13453,22 +13590,22 @@ async function renderImageRegion(controller, request) {
|
|
|
13453
13590
|
}
|
|
13454
13591
|
const kx = view.width / state.imageDimensions.width;
|
|
13455
13592
|
const ky = view.height / state.imageDimensions.height;
|
|
13456
|
-
context.drawImage(view,
|
|
13593
|
+
context.drawImage(view, rect2.x * kx, rect2.y * ky, rect2.width * kx, rect2.height * ky, 0, 0, out.width, out.height);
|
|
13457
13594
|
if (request.includeOverlays) {
|
|
13458
13595
|
const s = request.scale;
|
|
13459
13596
|
const maskView = maskViewCanvas(controller, document);
|
|
13460
|
-
if (maskView) context.drawImage(maskView,
|
|
13597
|
+
if (maskView) context.drawImage(maskView, rect2.x, rect2.y, rect2.width, rect2.height, 0, 0, out.width, out.height);
|
|
13461
13598
|
const selection = state.selection;
|
|
13462
13599
|
if (selection?.bounds && selection.width === state.imageDimensions.width && selection.height === state.imageDimensions.height) {
|
|
13463
13600
|
const shade = selectionShade(selection, document);
|
|
13464
|
-
if (shade) context.drawImage(shade,
|
|
13601
|
+
if (shade) context.drawImage(shade, rect2.x, rect2.y, rect2.width, rect2.height, 0, 0, out.width, out.height);
|
|
13465
13602
|
}
|
|
13466
13603
|
if (state.crop) {
|
|
13467
13604
|
context.save();
|
|
13468
13605
|
context.strokeStyle = "#ffd400";
|
|
13469
13606
|
context.lineWidth = 2;
|
|
13470
13607
|
context.setLineDash([6, 4]);
|
|
13471
|
-
context.strokeRect((state.crop.x -
|
|
13608
|
+
context.strokeRect((state.crop.x - rect2.x) * s, (state.crop.y - rect2.y) * s, state.crop.width * s, state.crop.height * s);
|
|
13472
13609
|
context.restore();
|
|
13473
13610
|
}
|
|
13474
13611
|
}
|
|
@@ -14770,19 +14907,19 @@ var SelectionGesture = class {
|
|
|
14770
14907
|
const { tool } = this.getOptions();
|
|
14771
14908
|
if (tool === "quick") return null;
|
|
14772
14909
|
if (tool === "lasso") return this.points.length > 1 ? this.points.slice() : null;
|
|
14773
|
-
const
|
|
14910
|
+
const rect2 = normalizeRect({
|
|
14774
14911
|
x: this.start.x,
|
|
14775
14912
|
y: this.start.y,
|
|
14776
14913
|
width: this.current.x - this.start.x,
|
|
14777
14914
|
height: this.current.y - this.start.y
|
|
14778
14915
|
});
|
|
14779
|
-
if (
|
|
14780
|
-
if (tool === "ellipse") return ellipsePoints(
|
|
14916
|
+
if (rect2.width < 1 && rect2.height < 1) return null;
|
|
14917
|
+
if (tool === "ellipse") return ellipsePoints(rect2);
|
|
14781
14918
|
return [
|
|
14782
|
-
{ x:
|
|
14783
|
-
{ x:
|
|
14784
|
-
{ x:
|
|
14785
|
-
{ x:
|
|
14919
|
+
{ x: rect2.x, y: rect2.y },
|
|
14920
|
+
{ x: rect2.x + rect2.width, y: rect2.y },
|
|
14921
|
+
{ x: rect2.x + rect2.width, y: rect2.y + rect2.height },
|
|
14922
|
+
{ x: rect2.x, y: rect2.y + rect2.height }
|
|
14786
14923
|
];
|
|
14787
14924
|
}
|
|
14788
14925
|
/** Pointer down. Resolves once a wand click has been applied. */
|
|
@@ -14938,11 +15075,11 @@ var SelectionGesture = class {
|
|
|
14938
15075
|
const options = this.getOptions();
|
|
14939
15076
|
const mode = selectionModeFromModifiers(modifiers ?? this.modifiers, options.mode);
|
|
14940
15077
|
if (options.tool === "object") {
|
|
14941
|
-
const
|
|
15078
|
+
const rect3 = normalizeRect({ x: this.start.x, y: this.start.y, width: this.current.x - this.start.x, height: this.current.y - this.start.y });
|
|
14942
15079
|
this.start = null;
|
|
14943
15080
|
this.current = null;
|
|
14944
15081
|
this.points = [];
|
|
14945
|
-
const target =
|
|
15082
|
+
const target = rect3.width >= 4 && rect3.height >= 4 ? { rect: rect3 } : { point: { x: rect3.x + rect3.width / 2, y: rect3.y + rect3.height / 2 } };
|
|
14946
15083
|
this.pending = this.controller.selectObject(target, { mode, sampleAllLayers: options.objectSampleAll ?? true, enhanceEdge: options.objectEnhance ?? false }).then(
|
|
14947
15084
|
() => void 0,
|
|
14948
15085
|
(err) => {
|
|
@@ -14972,7 +15109,7 @@ var SelectionGesture = class {
|
|
|
14972
15109
|
return false;
|
|
14973
15110
|
}
|
|
14974
15111
|
const path = this.draftPath;
|
|
14975
|
-
const
|
|
15112
|
+
const rect2 = normalizeRect({
|
|
14976
15113
|
x: this.start.x,
|
|
14977
15114
|
y: this.start.y,
|
|
14978
15115
|
width: this.current.x - this.start.x,
|
|
@@ -14984,7 +15121,7 @@ var SelectionGesture = class {
|
|
|
14984
15121
|
if (!path) return mode === "replace" ? this.controller.deselect() : false;
|
|
14985
15122
|
const opts = { mode, antiAlias: options.antiAlias, feather: options.feather ?? 0 };
|
|
14986
15123
|
if (options.tool === "lasso") return path.length >= 3 ? this.controller.selectPolygon(path, opts) : false;
|
|
14987
|
-
return options.tool === "ellipse" ? this.controller.selectEllipse(
|
|
15124
|
+
return options.tool === "ellipse" ? this.controller.selectEllipse(rect2, opts) : this.controller.selectRect(rect2, opts);
|
|
14988
15125
|
}
|
|
14989
15126
|
/** Closes a polygonal lasso and selects it. Returns true when the selection changed. */
|
|
14990
15127
|
close() {
|
|
@@ -15008,11 +15145,11 @@ var SelectionGesture = class {
|
|
|
15008
15145
|
* Keys for a polygonal lasso being drawn: Enter closes it, Escape cancels, Backspace / Delete
|
|
15009
15146
|
* removes the last corner. Returns true when the key was used (the host prevents its default).
|
|
15010
15147
|
*/
|
|
15011
|
-
key(
|
|
15148
|
+
key(key2) {
|
|
15012
15149
|
if (!this.polygon && !this.magnetic) return false;
|
|
15013
|
-
if (
|
|
15014
|
-
else if (
|
|
15015
|
-
else if (
|
|
15150
|
+
if (key2 === "Enter") this.close();
|
|
15151
|
+
else if (key2 === "Escape") this.cancel();
|
|
15152
|
+
else if (key2 === "Backspace" || key2 === "Delete") {
|
|
15016
15153
|
const m = this.magnetic;
|
|
15017
15154
|
if (m) {
|
|
15018
15155
|
m.anchors.pop();
|
|
@@ -15112,7 +15249,7 @@ var SelectionGesture = class {
|
|
|
15112
15249
|
};
|
|
15113
15250
|
function handleSelectionShortcut(controller, e, onError) {
|
|
15114
15251
|
if (!(e.ctrlKey || e.metaKey)) return false;
|
|
15115
|
-
const
|
|
15252
|
+
const key2 = e.key.toLowerCase();
|
|
15116
15253
|
const run = (fn) => {
|
|
15117
15254
|
try {
|
|
15118
15255
|
fn();
|
|
@@ -15121,11 +15258,11 @@ function handleSelectionShortcut(controller, e, onError) {
|
|
|
15121
15258
|
}
|
|
15122
15259
|
return true;
|
|
15123
15260
|
};
|
|
15124
|
-
if (
|
|
15125
|
-
if (
|
|
15126
|
-
if (
|
|
15127
|
-
if (
|
|
15128
|
-
if (
|
|
15261
|
+
if (key2 === "c") return run(() => controller.copyPixels({ merged: Boolean(e.shiftKey) }));
|
|
15262
|
+
if (key2 === "x" && !e.shiftKey) return run(() => controller.cutPixels());
|
|
15263
|
+
if (key2 === "v") return run(() => controller.pastePixels(e.shiftKey && e.altKey ? "into" : e.shiftKey ? "in-place" : "new-layer"));
|
|
15264
|
+
if (key2 === "d") return run(() => e.shiftKey ? controller.reselect() : controller.deselect());
|
|
15265
|
+
if (key2 === "i" && e.shiftKey) return run(() => controller.invertSelection());
|
|
15129
15266
|
return false;
|
|
15130
15267
|
}
|
|
15131
15268
|
function handleMaskViewKey(controller, e) {
|
|
@@ -15275,14 +15412,14 @@ function handlePaintShortcut(controller, e) {
|
|
|
15275
15412
|
if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
|
|
15276
15413
|
const brush = controller.getState().paint.brush;
|
|
15277
15414
|
const set = (b) => controller.setPaintOptions({ brush: b });
|
|
15278
|
-
const
|
|
15415
|
+
const key2 = e.key;
|
|
15279
15416
|
if (handleMaskViewKey(controller, e)) return true;
|
|
15280
|
-
switch (
|
|
15417
|
+
switch (key2.toLowerCase()) {
|
|
15281
15418
|
case "q":
|
|
15282
15419
|
controller.setQuickMaskMode(!controller.quickMaskEditing);
|
|
15283
15420
|
return true;
|
|
15284
15421
|
case "b":
|
|
15285
|
-
controller.setTool(e.shiftKey ||
|
|
15422
|
+
controller.setTool(e.shiftKey || key2 === "B" ? "mixer" : "brush");
|
|
15286
15423
|
return true;
|
|
15287
15424
|
case "e":
|
|
15288
15425
|
controller.setTool("eraser");
|
|
@@ -15294,11 +15431,11 @@ function handlePaintShortcut(controller, e) {
|
|
|
15294
15431
|
controller.setTool("clone");
|
|
15295
15432
|
return true;
|
|
15296
15433
|
case "j":
|
|
15297
|
-
controller.setTool(e.shiftKey ||
|
|
15434
|
+
controller.setTool(e.shiftKey || key2 === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
|
|
15298
15435
|
return true;
|
|
15299
15436
|
case "o": {
|
|
15300
15437
|
const cur = controller.getState().activeTool;
|
|
15301
|
-
const next = e.shiftKey ||
|
|
15438
|
+
const next = e.shiftKey || key2 === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
|
|
15302
15439
|
controller.setTool(next);
|
|
15303
15440
|
return true;
|
|
15304
15441
|
}
|
|
@@ -15312,18 +15449,18 @@ function handlePaintShortcut(controller, e) {
|
|
|
15312
15449
|
controller.swapPaintColors();
|
|
15313
15450
|
return true;
|
|
15314
15451
|
}
|
|
15315
|
-
if (
|
|
15316
|
-
if (e.shiftKey ||
|
|
15452
|
+
if (key2 === "[" || key2 === "{") {
|
|
15453
|
+
if (e.shiftKey || key2 === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
|
|
15317
15454
|
else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
|
|
15318
15455
|
return true;
|
|
15319
15456
|
}
|
|
15320
|
-
if (
|
|
15321
|
-
if (e.shiftKey ||
|
|
15457
|
+
if (key2 === "]" || key2 === "}") {
|
|
15458
|
+
if (e.shiftKey || key2 === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
|
|
15322
15459
|
else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
|
|
15323
15460
|
return true;
|
|
15324
15461
|
}
|
|
15325
|
-
if (/^[0-9]$/.test(
|
|
15326
|
-
set({ opacity:
|
|
15462
|
+
if (/^[0-9]$/.test(key2) && isPaintTool(controller.getState().activeTool)) {
|
|
15463
|
+
set({ opacity: key2 === "0" ? 1 : Number(key2) / 10 });
|
|
15327
15464
|
return true;
|
|
15328
15465
|
}
|
|
15329
15466
|
return false;
|
|
@@ -18127,11 +18264,11 @@ function scalePixels(pixels, w, h) {
|
|
|
18127
18264
|
return { width: w, height: h, data: out };
|
|
18128
18265
|
}
|
|
18129
18266
|
function scaleEdit(edit, s) {
|
|
18130
|
-
const
|
|
18267
|
+
const pt2 = (p) => ({ x: p.x * s, y: p.y * s });
|
|
18131
18268
|
if (edit.kind === "object") {
|
|
18132
|
-
return { ...edit, rect: edit.rect && { x: edit.rect.x * s, y: edit.rect.y * s, width: edit.rect.width * s, height: edit.rect.height * s }, point: edit.point &&
|
|
18269
|
+
return { ...edit, rect: edit.rect && { x: edit.rect.x * s, y: edit.rect.y * s, width: edit.rect.width * s, height: edit.rect.height * s }, point: edit.point && pt2(edit.point) };
|
|
18133
18270
|
}
|
|
18134
|
-
const scaled = { ...edit, points: edit.points.map(
|
|
18271
|
+
const scaled = { ...edit, points: edit.points.map(pt2) };
|
|
18135
18272
|
if ("size" in edit && edit.size !== void 0) scaled.size = Math.max(1, edit.size * s);
|
|
18136
18273
|
return scaled;
|
|
18137
18274
|
}
|
|
@@ -18423,8 +18560,8 @@ var SelectAndMaskSession = class {
|
|
|
18423
18560
|
if (!live || !this.dragStart) return;
|
|
18424
18561
|
if (live.kind === "object") {
|
|
18425
18562
|
const a = this.dragStart;
|
|
18426
|
-
const
|
|
18427
|
-
this.live =
|
|
18563
|
+
const rect2 = { x: Math.min(a.x, point.x), y: Math.min(a.y, point.y), width: Math.abs(point.x - a.x), height: Math.abs(point.y - a.y) };
|
|
18564
|
+
this.live = rect2.width >= 4 && rect2.height >= 4 ? { kind: "object", rect: rect2, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
|
|
18428
18565
|
return;
|
|
18429
18566
|
}
|
|
18430
18567
|
const last = live.points[live.points.length - 1];
|
|
@@ -18461,8 +18598,8 @@ var SelectAndMaskSession = class {
|
|
|
18461
18598
|
* Show Original), J (Show Edge), P (Show Original), a tool letter, [ and ] (brush size).
|
|
18462
18599
|
* Returns true when the key was used.
|
|
18463
18600
|
*/
|
|
18464
|
-
handleKey(
|
|
18465
|
-
const k =
|
|
18601
|
+
handleKey(key2) {
|
|
18602
|
+
const k = key2.toLowerCase();
|
|
18466
18603
|
const view = SELECT_AND_MASK_VIEWS.find((v) => v.key === k);
|
|
18467
18604
|
if (view) {
|
|
18468
18605
|
this.update({ view: view.id });
|
|
@@ -19139,9 +19276,9 @@ function gradientControls(prefix, g) {
|
|
|
19139
19276
|
{ key: `${prefix}.align`, label: "Align with layer", type: "checkbox", value: g.align }
|
|
19140
19277
|
];
|
|
19141
19278
|
}
|
|
19142
|
-
var px = (
|
|
19143
|
-
var pct = (
|
|
19144
|
-
var deg = (
|
|
19279
|
+
var px = (key2, label, value, max = 250) => ({ key: key2, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
|
|
19280
|
+
var pct = (key2, label, value, min = 0, max = 1) => ({ key: key2, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
|
|
19281
|
+
var deg = (key2, label, value, min = -180, max = 180) => ({ key: key2, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
|
|
19145
19282
|
function layerEffectControls(e, patterns = []) {
|
|
19146
19283
|
const blended = e.kind === "bevel-emboss" ? [] : [
|
|
19147
19284
|
{ key: "blend", label: "Blend mode", type: "select", value: e.blend, options: BLENDS },
|
|
@@ -19226,8 +19363,8 @@ function fillOfType(current, type, patterns) {
|
|
|
19226
19363
|
if (type === "pattern") return { type: "pattern", patternId: patterns[0]?.id ?? "checker", scale: 1 };
|
|
19227
19364
|
return { type: "color", color: current.type === "color" ? current.color : "#000000" };
|
|
19228
19365
|
}
|
|
19229
|
-
function effectControlChange(effect,
|
|
19230
|
-
const [top, ...rest] =
|
|
19366
|
+
function effectControlChange(effect, key2, value, patterns = []) {
|
|
19367
|
+
const [top, ...rest] = key2.split(".");
|
|
19231
19368
|
if (top === "fill" && rest[0] === "type") return { fill: fillOfType(effect.fill, String(value), patterns) };
|
|
19232
19369
|
if (top === "patternId" && !rest.length) {
|
|
19233
19370
|
const name = patternOptions(patterns).find((o) => o.value === value)?.label;
|
|
@@ -20079,7 +20216,9 @@ var IMAGE_TOOL_GROUPS = [
|
|
|
20079
20216
|
{ id: "focus", label: "Blur & Sharpen", tools: ["blur", "sharpen", "smudge"] },
|
|
20080
20217
|
{ id: "tone", label: "Dodge & Burn", tools: ["dodge", "burn", "sponge"] },
|
|
20081
20218
|
{ id: "shape", label: "Shape", tools: ["shape"] },
|
|
20219
|
+
{ id: "pen", label: "Pen", tools: ["path-pen"] },
|
|
20082
20220
|
{ id: "type", label: "Type", tools: ["type"] },
|
|
20221
|
+
{ id: "path-select", label: "Path Selection", tools: ["path-select", "direct-select"] },
|
|
20083
20222
|
{ id: "annotate", label: "Annotate", tools: ["pen", "line", "arrow", "rect", "circle"] }
|
|
20084
20223
|
];
|
|
20085
20224
|
function imageToolGroup(tool) {
|
|
@@ -20090,7 +20229,10 @@ var OTHER_TOOL_LABELS = {
|
|
|
20090
20229
|
crop: "Crop",
|
|
20091
20230
|
shape: "Shape",
|
|
20092
20231
|
type: "Type",
|
|
20093
|
-
pen: "Pen",
|
|
20232
|
+
"path-pen": "Pen",
|
|
20233
|
+
"path-select": "Path Selection",
|
|
20234
|
+
"direct-select": "Direct Selection",
|
|
20235
|
+
pen: "Freehand",
|
|
20094
20236
|
line: "Line",
|
|
20095
20237
|
arrow: "Arrow",
|
|
20096
20238
|
rect: "Rectangle",
|
|
@@ -20106,10 +20248,13 @@ var IMAGE_TOOL_SHORTCUTS = {
|
|
|
20106
20248
|
select: "M",
|
|
20107
20249
|
crop: "C",
|
|
20108
20250
|
shape: "U",
|
|
20109
|
-
type: "T"
|
|
20251
|
+
type: "T",
|
|
20252
|
+
"path-pen": "P",
|
|
20253
|
+
"path-select": "A",
|
|
20254
|
+
"direct-select": "Shift+A"
|
|
20110
20255
|
};
|
|
20111
|
-
function imageToolForShortcut(
|
|
20112
|
-
const shortcut = `${shift ? "Shift+" : ""}${
|
|
20256
|
+
function imageToolForShortcut(key2, shift = false) {
|
|
20257
|
+
const shortcut = `${shift ? "Shift+" : ""}${key2.toUpperCase()}`;
|
|
20113
20258
|
for (const group of IMAGE_TOOL_GROUPS) {
|
|
20114
20259
|
for (const tool of group.tools) if (IMAGE_TOOL_SHORTCUTS[tool] === shortcut) return tool;
|
|
20115
20260
|
}
|
|
@@ -20166,6 +20311,422 @@ function createImageEditorDocument(controller, options = {}) {
|
|
|
20166
20311
|
});
|
|
20167
20312
|
}
|
|
20168
20313
|
|
|
20314
|
+
// src/image-editor/path-edit.ts
|
|
20315
|
+
var PATH_EDIT_TOOLS = ["path-select", "direct-select", "path-pen"];
|
|
20316
|
+
function isPathEditTool(tool) {
|
|
20317
|
+
return PATH_EDIT_TOOLS.includes(tool);
|
|
20318
|
+
}
|
|
20319
|
+
var key = (r) => `${r.component}:${r.subpath}:${r.anchor}`;
|
|
20320
|
+
var parseKey = (k) => {
|
|
20321
|
+
const [component, subpath, anchor] = k.split(":").map(Number);
|
|
20322
|
+
return { component, subpath, anchor };
|
|
20323
|
+
};
|
|
20324
|
+
var pt = (p) => ({ x: p.x, y: p.y });
|
|
20325
|
+
var cloneComponents = (cs) => cs.map((c) => ({ op: c.op, subpaths: c.subpaths.map((sp) => ({ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed })), ...c.live ? { live: c.live } : {} }));
|
|
20326
|
+
var sameTarget = (a, b) => !!a && !!b && a.kind === b.kind && (a.kind === "path" ? a.id === b.id : a.layerId === b.layerId);
|
|
20327
|
+
var PathEditGesture = class {
|
|
20328
|
+
constructor(controller) {
|
|
20329
|
+
this.controller = controller;
|
|
20330
|
+
}
|
|
20331
|
+
controller;
|
|
20332
|
+
/** Selected anchors, as `component:subpath:anchor` keys. */
|
|
20333
|
+
selected = /* @__PURE__ */ new Set();
|
|
20334
|
+
target = null;
|
|
20335
|
+
drag = null;
|
|
20336
|
+
/** The Pen's open subpath being drawn. */
|
|
20337
|
+
drawing = null;
|
|
20338
|
+
pointer = null;
|
|
20339
|
+
scale = 1;
|
|
20340
|
+
error = null;
|
|
20341
|
+
/** Screen pixels per document pixel (the viewport projection's scale). */
|
|
20342
|
+
setView(scale) {
|
|
20343
|
+
if (scale > 0 && Number.isFinite(scale)) this.scale = scale;
|
|
20344
|
+
}
|
|
20345
|
+
get active() {
|
|
20346
|
+
return this.drag !== null;
|
|
20347
|
+
}
|
|
20348
|
+
/** The anchors selected now (on the current target). */
|
|
20349
|
+
get selection() {
|
|
20350
|
+
return [...this.selected].map(parseKey);
|
|
20351
|
+
}
|
|
20352
|
+
/** True while the Pen has an open subpath it adds to. */
|
|
20353
|
+
get isDrawing() {
|
|
20354
|
+
return this.drawing !== null;
|
|
20355
|
+
}
|
|
20356
|
+
get tolerance() {
|
|
20357
|
+
return 6 / this.scale;
|
|
20358
|
+
}
|
|
20359
|
+
/** The target's components, or null (and the selection dropped) when the target changed or is gone. */
|
|
20360
|
+
components() {
|
|
20361
|
+
const target = this.controller.pathEditTarget();
|
|
20362
|
+
if (!sameTarget(target, this.target)) {
|
|
20363
|
+
this.target = target;
|
|
20364
|
+
this.selected.clear();
|
|
20365
|
+
this.drawing = null;
|
|
20366
|
+
}
|
|
20367
|
+
if (!target) return null;
|
|
20368
|
+
let cs;
|
|
20369
|
+
try {
|
|
20370
|
+
cs = this.controller.getPathEditComponents(target);
|
|
20371
|
+
} catch {
|
|
20372
|
+
return null;
|
|
20373
|
+
}
|
|
20374
|
+
for (const k of [...this.selected]) {
|
|
20375
|
+
const r = parseKey(k);
|
|
20376
|
+
if (!cs[r.component]?.subpaths[r.subpath]?.anchors[r.anchor]) this.selected.delete(k);
|
|
20377
|
+
}
|
|
20378
|
+
const d = this.drawing;
|
|
20379
|
+
if (d && (!cs[d.component]?.subpaths[d.subpath] || cs[d.component].subpaths[d.subpath].closed)) this.drawing = null;
|
|
20380
|
+
return cs;
|
|
20381
|
+
}
|
|
20382
|
+
anchorAt(cs, p) {
|
|
20383
|
+
const tol = this.tolerance;
|
|
20384
|
+
let best = null;
|
|
20385
|
+
let bestD = tol;
|
|
20386
|
+
cs.forEach(
|
|
20387
|
+
(c, ci) => c.subpaths.forEach(
|
|
20388
|
+
(sp, si) => sp.anchors.forEach((a, ai) => {
|
|
20389
|
+
const d = Math.hypot(a.x - p.x, a.y - p.y);
|
|
20390
|
+
if (d <= bestD) {
|
|
20391
|
+
bestD = d;
|
|
20392
|
+
best = { component: ci, subpath: si, anchor: ai };
|
|
20393
|
+
}
|
|
20394
|
+
})
|
|
20395
|
+
)
|
|
20396
|
+
);
|
|
20397
|
+
return best;
|
|
20398
|
+
}
|
|
20399
|
+
/** A handle of a selected anchor near `p`. */
|
|
20400
|
+
handleAt(cs, p) {
|
|
20401
|
+
const tol = this.tolerance;
|
|
20402
|
+
for (const r of this.selection) {
|
|
20403
|
+
const a = cs[r.component].subpaths[r.subpath].anchors[r.anchor];
|
|
20404
|
+
for (const side of ["out", "in"]) {
|
|
20405
|
+
const h = a[side];
|
|
20406
|
+
if (h && Math.hypot(h.x - p.x, h.y - p.y) <= tol) return { ref: r, side };
|
|
20407
|
+
}
|
|
20408
|
+
}
|
|
20409
|
+
return null;
|
|
20410
|
+
}
|
|
20411
|
+
/** The segment near `p`: its first anchor (as `anchor`) and the parameter on it. */
|
|
20412
|
+
segmentAt(cs, p) {
|
|
20413
|
+
let best = null;
|
|
20414
|
+
let bestD = this.tolerance;
|
|
20415
|
+
cs.forEach(
|
|
20416
|
+
(c, ci) => c.subpaths.forEach((sp, si) => {
|
|
20417
|
+
for (let i = 0; i < subpathSegmentCount(sp); i++) {
|
|
20418
|
+
const { t, distance } = segmentNearest(subpathSegment(sp, i), [p.x, p.y]);
|
|
20419
|
+
if (distance <= bestD) {
|
|
20420
|
+
bestD = distance;
|
|
20421
|
+
best = { ref: { component: ci, subpath: si, anchor: i }, t };
|
|
20422
|
+
}
|
|
20423
|
+
}
|
|
20424
|
+
})
|
|
20425
|
+
);
|
|
20426
|
+
return best;
|
|
20427
|
+
}
|
|
20428
|
+
/** The component under `p`: on its outline, or inside its fill. */
|
|
20429
|
+
componentAt(cs, p) {
|
|
20430
|
+
const seg = this.segmentAt(cs, p) ?? (this.anchorAt(cs, p) ? { ref: this.anchorAt(cs, p) } : null);
|
|
20431
|
+
if (seg) return seg.ref.component;
|
|
20432
|
+
for (let i = cs.length - 1; i >= 0; i--) {
|
|
20433
|
+
const closed = cs[i].subpaths.filter((sp) => sp.anchors.length > 2);
|
|
20434
|
+
if (closed.length && pointInBezierPath(subpathsToBezier(closed), [p.x, p.y])) return i;
|
|
20435
|
+
}
|
|
20436
|
+
return null;
|
|
20437
|
+
}
|
|
20438
|
+
selectComponent(cs, ci) {
|
|
20439
|
+
cs[ci].subpaths.forEach((sp, si) => sp.anchors.forEach((_, ai) => this.selected.add(key({ component: ci, subpath: si, anchor: ai }))));
|
|
20440
|
+
}
|
|
20441
|
+
commit(cs, record) {
|
|
20442
|
+
if (!this.target) return false;
|
|
20443
|
+
try {
|
|
20444
|
+
this.controller.setPathEditComponents(this.target, cs, record);
|
|
20445
|
+
return true;
|
|
20446
|
+
} catch (e) {
|
|
20447
|
+
this.error = e instanceof Error ? e.message : String(e);
|
|
20448
|
+
return false;
|
|
20449
|
+
}
|
|
20450
|
+
}
|
|
20451
|
+
down(p, tool, mods = {}) {
|
|
20452
|
+
this.error = null;
|
|
20453
|
+
this.pointer = pt(p);
|
|
20454
|
+
const cs = this.components();
|
|
20455
|
+
if (tool === "path-pen") return this.penDown(cs, p, mods);
|
|
20456
|
+
if (!cs) {
|
|
20457
|
+
this.error = "There is no path to edit: pick a path in the Paths panel, a shape layer, or a layer with a vector mask (or draw one with the Pen).";
|
|
20458
|
+
return;
|
|
20459
|
+
}
|
|
20460
|
+
if (tool === "path-select") {
|
|
20461
|
+
const ci = this.componentAt(cs, p);
|
|
20462
|
+
if (ci === null) {
|
|
20463
|
+
this.drag = { kind: "marquee", start: pt(p), end: pt(p), keep: mods.shift ? new Set(this.selected) : /* @__PURE__ */ new Set() };
|
|
20464
|
+
if (!mods.shift) this.selected.clear();
|
|
20465
|
+
return;
|
|
20466
|
+
}
|
|
20467
|
+
const ref = { component: ci, subpath: 0, anchor: 0 };
|
|
20468
|
+
if (!mods.shift && !this.selected.has(key(ref))) this.selected.clear();
|
|
20469
|
+
this.selectComponent(cs, ci);
|
|
20470
|
+
this.drag = { kind: "anchors", start: pt(p), base: cloneComponents(cs), moved: false };
|
|
20471
|
+
return;
|
|
20472
|
+
}
|
|
20473
|
+
const handle = this.handleAt(cs, p);
|
|
20474
|
+
if (handle) {
|
|
20475
|
+
this.drag = { kind: "handle", ...handle, base: cloneComponents(cs), breakHandles: !!mods.alt, moved: false };
|
|
20476
|
+
return;
|
|
20477
|
+
}
|
|
20478
|
+
const anchor = this.anchorAt(cs, p);
|
|
20479
|
+
if (anchor) {
|
|
20480
|
+
if (mods.alt) return this.convert(cs, anchor);
|
|
20481
|
+
const k = key(anchor);
|
|
20482
|
+
if (mods.shift) {
|
|
20483
|
+
if (this.selected.has(k)) this.selected.delete(k);
|
|
20484
|
+
else this.selected.add(k);
|
|
20485
|
+
} else if (!this.selected.has(k)) {
|
|
20486
|
+
this.selected.clear();
|
|
20487
|
+
this.selected.add(k);
|
|
20488
|
+
}
|
|
20489
|
+
this.drag = { kind: "anchors", start: pt(p), base: cloneComponents(cs), moved: false };
|
|
20490
|
+
return;
|
|
20491
|
+
}
|
|
20492
|
+
const seg = this.segmentAt(cs, p);
|
|
20493
|
+
if (seg) {
|
|
20494
|
+
if (!mods.shift) this.selected.clear();
|
|
20495
|
+
this.drag = { kind: "segment", ...seg, base: cloneComponents(cs), moved: false };
|
|
20496
|
+
return;
|
|
20497
|
+
}
|
|
20498
|
+
this.drag = { kind: "marquee", start: pt(p), end: pt(p), keep: mods.shift ? new Set(this.selected) : /* @__PURE__ */ new Set() };
|
|
20499
|
+
if (!mods.shift) this.selected.clear();
|
|
20500
|
+
}
|
|
20501
|
+
/** Alt-click: a corner anchor becomes smooth, a smooth one a corner (one undo step). */
|
|
20502
|
+
convert(cs, r) {
|
|
20503
|
+
const next = cloneComponents(cs);
|
|
20504
|
+
const c = next[r.component];
|
|
20505
|
+
const sp = c.subpaths[r.subpath];
|
|
20506
|
+
const a = sp.anchors[r.anchor];
|
|
20507
|
+
const type = inferAnchorType(a) === "corner" && !a.in && !a.out ? "smooth" : "corner";
|
|
20508
|
+
c.subpaths[r.subpath] = convertAnchor(sp, r.anchor, type);
|
|
20509
|
+
delete c.live;
|
|
20510
|
+
this.selected.clear();
|
|
20511
|
+
this.selected.add(key(r));
|
|
20512
|
+
this.commit(next, true);
|
|
20513
|
+
}
|
|
20514
|
+
penDown(cs, p, mods) {
|
|
20515
|
+
const d = this.drawing;
|
|
20516
|
+
if (cs && d) {
|
|
20517
|
+
const next = cloneComponents(cs);
|
|
20518
|
+
const c = next[d.component];
|
|
20519
|
+
const sp = c.subpaths[d.subpath];
|
|
20520
|
+
const first = sp.anchors[0];
|
|
20521
|
+
delete c.live;
|
|
20522
|
+
if (sp.anchors.length > 1 && Math.hypot(first.x - p.x, first.y - p.y) <= this.tolerance) {
|
|
20523
|
+
sp.closed = true;
|
|
20524
|
+
this.drawing = null;
|
|
20525
|
+
const ref3 = { component: d.component, subpath: d.subpath, anchor: 0 };
|
|
20526
|
+
if (this.commit(next, true)) this.drag = { kind: "pen", ref: ref3, base: next };
|
|
20527
|
+
return;
|
|
20528
|
+
}
|
|
20529
|
+
sp.anchors.push({ x: p.x, y: p.y });
|
|
20530
|
+
const ref2 = { component: d.component, subpath: d.subpath, anchor: sp.anchors.length - 1 };
|
|
20531
|
+
this.selected = /* @__PURE__ */ new Set([key(ref2)]);
|
|
20532
|
+
if (this.commit(next, true)) this.drag = { kind: "pen", ref: ref2, base: next };
|
|
20533
|
+
return;
|
|
20534
|
+
}
|
|
20535
|
+
if (cs) {
|
|
20536
|
+
const anchor = this.anchorAt(cs, p);
|
|
20537
|
+
if (anchor && mods.alt) return this.convert(cs, anchor);
|
|
20538
|
+
if (anchor) {
|
|
20539
|
+
const next = cloneComponents(cs);
|
|
20540
|
+
const c = next[anchor.component];
|
|
20541
|
+
const sp = c.subpaths[anchor.subpath];
|
|
20542
|
+
delete c.live;
|
|
20543
|
+
c.subpaths[anchor.subpath] = sp.anchors.length > 2 ? removeAnchorsKeepingShape(sp, [anchor.anchor]) : removeAnchors(sp, [anchor.anchor]);
|
|
20544
|
+
this.selected.clear();
|
|
20545
|
+
this.commit(prune(next), true);
|
|
20546
|
+
return;
|
|
20547
|
+
}
|
|
20548
|
+
const seg = this.segmentAt(cs, p);
|
|
20549
|
+
if (seg) {
|
|
20550
|
+
const next = cloneComponents(cs);
|
|
20551
|
+
const c = next[seg.ref.component];
|
|
20552
|
+
const { subpath, index } = insertAnchor(c.subpaths[seg.ref.subpath], seg.ref.anchor, seg.t);
|
|
20553
|
+
c.subpaths[seg.ref.subpath] = subpath;
|
|
20554
|
+
delete c.live;
|
|
20555
|
+
this.selected = /* @__PURE__ */ new Set([key({ ...seg.ref, anchor: index })]);
|
|
20556
|
+
this.commit(next, true);
|
|
20557
|
+
return;
|
|
20558
|
+
}
|
|
20559
|
+
}
|
|
20560
|
+
const start = { anchors: [{ x: p.x, y: p.y }], closed: false };
|
|
20561
|
+
if (!this.target || !cs) {
|
|
20562
|
+
try {
|
|
20563
|
+
const path = this.controller.startWorkPath([{ op: "combine", subpaths: [start] }]);
|
|
20564
|
+
this.target = { kind: "path", id: path.id };
|
|
20565
|
+
} catch (e) {
|
|
20566
|
+
this.error = e instanceof Error ? e.message : String(e);
|
|
20567
|
+
return;
|
|
20568
|
+
}
|
|
20569
|
+
this.drawing = { component: 0, subpath: 0 };
|
|
20570
|
+
} else {
|
|
20571
|
+
const next = cloneComponents(cs);
|
|
20572
|
+
if (!next.length) next.push({ op: "combine", subpaths: [] });
|
|
20573
|
+
const ci = next.length - 1;
|
|
20574
|
+
delete next[ci].live;
|
|
20575
|
+
next[ci].subpaths.push(start);
|
|
20576
|
+
if (!this.commit(next, true)) return;
|
|
20577
|
+
this.drawing = { component: ci, subpath: next[ci].subpaths.length - 1 };
|
|
20578
|
+
}
|
|
20579
|
+
const ref = { component: this.drawing.component, subpath: this.drawing.subpath, anchor: 0 };
|
|
20580
|
+
this.selected = /* @__PURE__ */ new Set([key(ref)]);
|
|
20581
|
+
this.drag = { kind: "pen", ref, base: cloneComponents(this.controller.getPathEditComponents(this.target)) };
|
|
20582
|
+
}
|
|
20583
|
+
move(p, mods = {}) {
|
|
20584
|
+
this.pointer = pt(p);
|
|
20585
|
+
const g = this.drag;
|
|
20586
|
+
if (!g) return;
|
|
20587
|
+
if (g.kind === "marquee") {
|
|
20588
|
+
g.end = pt(p);
|
|
20589
|
+
const box = rect(g.start, g.end);
|
|
20590
|
+
const cs = this.target ? this.controller.getPathEditComponents(this.target) : [];
|
|
20591
|
+
this.selected = new Set(g.keep);
|
|
20592
|
+
cs.forEach(
|
|
20593
|
+
(c, ci) => c.subpaths.forEach(
|
|
20594
|
+
(sp, si) => sp.anchors.forEach((a, ai) => {
|
|
20595
|
+
if (a.x >= box.x && a.x <= box.x + box.width && a.y >= box.y && a.y <= box.y + box.height) this.selected.add(key({ component: ci, subpath: si, anchor: ai }));
|
|
20596
|
+
})
|
|
20597
|
+
)
|
|
20598
|
+
);
|
|
20599
|
+
return;
|
|
20600
|
+
}
|
|
20601
|
+
const next = cloneComponents(g.base);
|
|
20602
|
+
if (g.kind === "anchors") {
|
|
20603
|
+
let dx = p.x - g.start.x;
|
|
20604
|
+
let dy = p.y - g.start.y;
|
|
20605
|
+
if (!g.moved && Math.hypot(dx, dy) < 1 / this.scale) return;
|
|
20606
|
+
if (mods.shift) {
|
|
20607
|
+
if (Math.abs(dx) > Math.abs(dy)) dy = 0;
|
|
20608
|
+
else dx = 0;
|
|
20609
|
+
}
|
|
20610
|
+
for (const r of this.selection) {
|
|
20611
|
+
const c = next[r.component];
|
|
20612
|
+
c.subpaths[r.subpath].anchors[r.anchor] = translateAnchor(c.subpaths[r.subpath].anchors[r.anchor], dx, dy);
|
|
20613
|
+
delete c.live;
|
|
20614
|
+
}
|
|
20615
|
+
} else if (g.kind === "handle") {
|
|
20616
|
+
const c = next[g.ref.component];
|
|
20617
|
+
const a = c.subpaths[g.ref.subpath].anchors[g.ref.anchor];
|
|
20618
|
+
const typed = { ...a, type: a.type ?? inferAnchorType(a) };
|
|
20619
|
+
const moved = moveAnchorHandle(typed, g.side, p, g.breakHandles);
|
|
20620
|
+
delete moved.type;
|
|
20621
|
+
c.subpaths[g.ref.subpath].anchors[g.ref.anchor] = moved;
|
|
20622
|
+
delete c.live;
|
|
20623
|
+
} else if (g.kind === "segment") {
|
|
20624
|
+
const c = next[g.ref.component];
|
|
20625
|
+
c.subpaths[g.ref.subpath] = bendSegment(c.subpaths[g.ref.subpath], g.ref.anchor, g.t, p);
|
|
20626
|
+
delete c.live;
|
|
20627
|
+
} else {
|
|
20628
|
+
const c = next[g.ref.component];
|
|
20629
|
+
const a = c.subpaths[g.ref.subpath].anchors[g.ref.anchor];
|
|
20630
|
+
if (Math.hypot(p.x - a.x, p.y - a.y) < 1 / this.scale) return;
|
|
20631
|
+
a.out = { x: p.x, y: p.y };
|
|
20632
|
+
a.in = { x: 2 * a.x - p.x, y: 2 * a.y - p.y };
|
|
20633
|
+
delete c.live;
|
|
20634
|
+
this.commit(next, false);
|
|
20635
|
+
return;
|
|
20636
|
+
}
|
|
20637
|
+
if (this.commit(next, !g.moved)) g.moved = true;
|
|
20638
|
+
}
|
|
20639
|
+
/** The pointer moving with no button down (the Pen's preview of its next segment). */
|
|
20640
|
+
hover(p) {
|
|
20641
|
+
this.pointer = pt(p);
|
|
20642
|
+
}
|
|
20643
|
+
up() {
|
|
20644
|
+
this.drag = null;
|
|
20645
|
+
}
|
|
20646
|
+
/** Ends the Pen's subpath (left open). */
|
|
20647
|
+
finish() {
|
|
20648
|
+
this.drawing = null;
|
|
20649
|
+
this.drag = null;
|
|
20650
|
+
}
|
|
20651
|
+
cancel() {
|
|
20652
|
+
this.drag = null;
|
|
20653
|
+
this.drawing = null;
|
|
20654
|
+
this.selected.clear();
|
|
20655
|
+
}
|
|
20656
|
+
/**
|
|
20657
|
+
* Keys: Delete / Backspace remove the selected anchors (one undo step), Enter and Escape end the
|
|
20658
|
+
* Pen's subpath, Escape also deselects. Returns true when the key was handled.
|
|
20659
|
+
*/
|
|
20660
|
+
key(k) {
|
|
20661
|
+
if (k === "Enter" && this.drawing) {
|
|
20662
|
+
this.finish();
|
|
20663
|
+
return true;
|
|
20664
|
+
}
|
|
20665
|
+
if (k === "Escape") {
|
|
20666
|
+
if (!this.drawing && !this.selected.size) return false;
|
|
20667
|
+
this.drawing = null;
|
|
20668
|
+
this.selected.clear();
|
|
20669
|
+
return true;
|
|
20670
|
+
}
|
|
20671
|
+
if ((k === "Delete" || k === "Backspace") && this.selected.size && !this.drawing) {
|
|
20672
|
+
const cs = this.components();
|
|
20673
|
+
if (!cs) return false;
|
|
20674
|
+
const next = cloneComponents(cs);
|
|
20675
|
+
const bySub = /* @__PURE__ */ new Map();
|
|
20676
|
+
for (const r of this.selection) {
|
|
20677
|
+
const sk = `${r.component}:${r.subpath}`;
|
|
20678
|
+
bySub.set(sk, [...bySub.get(sk) ?? [], r.anchor]);
|
|
20679
|
+
}
|
|
20680
|
+
for (const [sk, indices] of bySub) {
|
|
20681
|
+
const [ci, si] = sk.split(":").map(Number);
|
|
20682
|
+
next[ci].subpaths[si] = removeAnchors(next[ci].subpaths[si], indices);
|
|
20683
|
+
delete next[ci].live;
|
|
20684
|
+
}
|
|
20685
|
+
this.selected.clear();
|
|
20686
|
+
this.commit(prune(next), true);
|
|
20687
|
+
return true;
|
|
20688
|
+
}
|
|
20689
|
+
return false;
|
|
20690
|
+
}
|
|
20691
|
+
/** What to draw (`RenderOptions.pathEdit`) for `tool`, or null when there is no target. */
|
|
20692
|
+
overlay(tool) {
|
|
20693
|
+
const cs = this.components();
|
|
20694
|
+
if (!cs) return null;
|
|
20695
|
+
const curves = [];
|
|
20696
|
+
const anchors = [];
|
|
20697
|
+
const handles = [];
|
|
20698
|
+
const selectedComponents = new Set(this.selection.map((r) => r.component));
|
|
20699
|
+
cs.forEach(
|
|
20700
|
+
(c, ci) => c.subpaths.forEach((sp, si) => {
|
|
20701
|
+
for (let i = 0; i < subpathSegmentCount(sp); i++) curves.push(subpathSegment(sp, i).map(([x, y]) => ({ x, y })));
|
|
20702
|
+
sp.anchors.forEach((a, ai) => {
|
|
20703
|
+
const selected = this.selected.has(key({ component: ci, subpath: si, anchor: ai }));
|
|
20704
|
+
if (tool === "path-select" && !selectedComponents.has(ci)) return;
|
|
20705
|
+
anchors.push({ point: pt(a), selected });
|
|
20706
|
+
if (!selected || tool === "path-select") return;
|
|
20707
|
+
for (const side of ["in", "out"]) if (a[side]) handles.push({ point: pt(a[side]), anchor: pt(a) });
|
|
20708
|
+
});
|
|
20709
|
+
})
|
|
20710
|
+
);
|
|
20711
|
+
let preview = null;
|
|
20712
|
+
const d = this.drawing;
|
|
20713
|
+
if (tool === "path-pen" && d && this.pointer && !this.drag) {
|
|
20714
|
+
const sp = cs[d.component]?.subpaths[d.subpath];
|
|
20715
|
+
const last = sp?.anchors[sp.anchors.length - 1];
|
|
20716
|
+
if (last) preview = last.out ? [pt(last), pt(last.out), this.pointer, this.pointer] : [pt(last), this.pointer];
|
|
20717
|
+
}
|
|
20718
|
+
const marquee = this.drag?.kind === "marquee" ? rect(this.drag.start, this.drag.end) : null;
|
|
20719
|
+
return { curves, anchors, handles, marquee, preview };
|
|
20720
|
+
}
|
|
20721
|
+
};
|
|
20722
|
+
function rect(a, b) {
|
|
20723
|
+
return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(b.x - a.x), height: Math.abs(b.y - a.y) };
|
|
20724
|
+
}
|
|
20725
|
+
function prune(cs) {
|
|
20726
|
+
for (const c of cs) c.subpaths = c.subpaths.filter((sp) => sp.anchors.length);
|
|
20727
|
+
return cs.filter((c) => c.subpaths.length);
|
|
20728
|
+
}
|
|
20729
|
+
|
|
20169
20730
|
// src/image-editor/content-aware-fill-session.ts
|
|
20170
20731
|
var CONTENT_AWARE_FILL_TOOLS = [
|
|
20171
20732
|
{ id: "sampling-brush", label: "Sampling Brush", key: "b" },
|
|
@@ -20188,7 +20749,7 @@ var DEFAULT_CONTENT_AWARE_FILL_SETTINGS = {
|
|
|
20188
20749
|
output: "new-layer",
|
|
20189
20750
|
livePreview: true
|
|
20190
20751
|
};
|
|
20191
|
-
var ContentAwareFillSession = class {
|
|
20752
|
+
var ContentAwareFillSession = class _ContentAwareFillSession {
|
|
20192
20753
|
constructor(controller) {
|
|
20193
20754
|
this.controller = controller;
|
|
20194
20755
|
}
|
|
@@ -20224,16 +20785,21 @@ var ContentAwareFillSession = class {
|
|
|
20224
20785
|
this.revision++;
|
|
20225
20786
|
for (const l of this.listeners) l();
|
|
20226
20787
|
}
|
|
20788
|
+
/** Images up to this many pixels show (and can be sampled) whole in the workspace. */
|
|
20789
|
+
static WHOLE_IMAGE_PIXELS = 4e6;
|
|
20790
|
+
/** Room around the selection the window holds, in px each way. */
|
|
20791
|
+
margin = 0;
|
|
20227
20792
|
/**
|
|
20228
|
-
* Reads the layer
|
|
20229
|
-
*
|
|
20793
|
+
* Reads the layer and makes the first preview. The window is the whole image up to
|
|
20794
|
+
* {@link WHOLE_IMAGE_PIXELS}; on a larger one it holds the selection and room around it (twice
|
|
20795
|
+
* its larger side, at least 256 px, each way), and {@link expandView} widens it.
|
|
20230
20796
|
*/
|
|
20231
20797
|
async open() {
|
|
20232
20798
|
try {
|
|
20233
|
-
const
|
|
20234
|
-
const b =
|
|
20235
|
-
|
|
20236
|
-
this.plan = this.controller.prepareContentAwareFill({ sampleRadius: margin });
|
|
20799
|
+
const { width, height } = this.controller.getState().imageDimensions;
|
|
20800
|
+
const b = this.controller.getSelection()?.bounds;
|
|
20801
|
+
this.margin = width * height <= _ContentAwareFillSession.WHOLE_IMAGE_PIXELS ? Math.max(width, height) : Math.max(256, 2 * Math.max(b?.width ?? 0, b?.height ?? 0));
|
|
20802
|
+
this.plan = this.controller.prepareContentAwareFill({ sampleRadius: this.margin });
|
|
20237
20803
|
} catch (err) {
|
|
20238
20804
|
this.error = err instanceof Error ? err.message : String(err);
|
|
20239
20805
|
this.emit();
|
|
@@ -20247,6 +20813,42 @@ var ContentAwareFillSession = class {
|
|
|
20247
20813
|
await this.refresh();
|
|
20248
20814
|
return true;
|
|
20249
20815
|
}
|
|
20816
|
+
/** Whether the window shows the whole image (else {@link expandView} can widen it). */
|
|
20817
|
+
get showsWholeImage() {
|
|
20818
|
+
const { width, height } = this.controller.getState().imageDimensions;
|
|
20819
|
+
const w = this.win;
|
|
20820
|
+
return !w || w.x === 0 && w.y === 0 && w.width >= width && w.height >= height;
|
|
20821
|
+
}
|
|
20822
|
+
/**
|
|
20823
|
+
* Doubles the room around the selection (up to the whole image), keeping what was painted; a
|
|
20824
|
+
* preset fills the new room as it would. Returns false when the whole image already shows.
|
|
20825
|
+
*/
|
|
20826
|
+
expandView() {
|
|
20827
|
+
if (!this.win || this.showsWholeImage) return false;
|
|
20828
|
+
const { width, height } = this.controller.getState().imageDimensions;
|
|
20829
|
+
const old = { win: this.win, area: this.area };
|
|
20830
|
+
this.margin = Math.min(Math.max(width, height), this.margin * 2);
|
|
20831
|
+
const plan = this.controller.prepareContentAwareFill({ layerId: this.plan.layerId, sampleRadius: this.margin });
|
|
20832
|
+
this.plan = plan;
|
|
20833
|
+
this.win = plan.window;
|
|
20834
|
+
this.before = plan.before;
|
|
20835
|
+
this.hole = plan.hole;
|
|
20836
|
+
const mode = this.settingsValue.sampling;
|
|
20837
|
+
this.area = null;
|
|
20838
|
+
this.applySampling(mode === "custom" ? "auto" : mode);
|
|
20839
|
+
if (mode === "custom") {
|
|
20840
|
+
const area = new Uint8Array(plan.window.width * plan.window.height);
|
|
20841
|
+
for (let y = 0; y < old.win.height; y++) {
|
|
20842
|
+
const src = y * old.win.width;
|
|
20843
|
+
const dst = (old.win.y - plan.window.y + y) * plan.window.width + (old.win.x - plan.window.x);
|
|
20844
|
+
area.set(old.area.subarray(src, src + old.win.width), dst);
|
|
20845
|
+
}
|
|
20846
|
+
this.area = area;
|
|
20847
|
+
}
|
|
20848
|
+
this.emit();
|
|
20849
|
+
if (this.settingsValue.livePreview) void this.refresh();
|
|
20850
|
+
return true;
|
|
20851
|
+
}
|
|
20250
20852
|
/** Sets the area to a preset: Auto (a band around the selection), Rectangular (the whole window). */
|
|
20251
20853
|
applySampling(mode) {
|
|
20252
20854
|
const win = this.win;
|
|
@@ -20416,8 +21018,8 @@ var ContentAwareFillSession = class {
|
|
|
20416
21018
|
/**
|
|
20417
21019
|
* Keys: B, H, Z for the tools, [ and ] for the brush size. Returns true when the key was used.
|
|
20418
21020
|
*/
|
|
20419
|
-
handleKey(
|
|
20420
|
-
const k =
|
|
21021
|
+
handleKey(key2) {
|
|
21022
|
+
const k = key2.toLowerCase();
|
|
20421
21023
|
const tool = CONTENT_AWARE_FILL_TOOLS.find((t) => t.key === k);
|
|
20422
21024
|
if (tool) {
|
|
20423
21025
|
this.update({ tool: tool.id });
|
|
@@ -20596,6 +21198,7 @@ export {
|
|
|
20596
21198
|
PARAM,
|
|
20597
21199
|
PARAM_FLOATS,
|
|
20598
21200
|
PASTE_MODES,
|
|
21201
|
+
PATH_EDIT_TOOLS,
|
|
20599
21202
|
PATTERN_BLOCK_KEYS,
|
|
20600
21203
|
PIGMENT_BANDS,
|
|
20601
21204
|
PIGMENT_CHANNELS,
|
|
@@ -20625,6 +21228,7 @@ export {
|
|
|
20625
21228
|
PaintSourceError,
|
|
20626
21229
|
PaintStrokeSession,
|
|
20627
21230
|
PaperGrain,
|
|
21231
|
+
PathEditGesture,
|
|
20628
21232
|
PatternPlacement,
|
|
20629
21233
|
PatternTile,
|
|
20630
21234
|
PsdError,
|
|
@@ -21072,6 +21676,7 @@ export {
|
|
|
21072
21676
|
isPaintTool,
|
|
21073
21677
|
isPaintableLayer,
|
|
21074
21678
|
isPainterlyBlendMode,
|
|
21679
|
+
isPathEditTool,
|
|
21075
21680
|
isPng,
|
|
21076
21681
|
isQuarterTurn,
|
|
21077
21682
|
isQuarterTurnSwapped,
|