@pixerate/editor 0.212.0 → 0.214.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.
@@ -1,8 +1,10 @@
1
1
  import {
2
+ bendSegment,
2
3
  loadOpenTypeFont,
3
4
  parseOpenTypeFont,
5
+ removeAnchorsKeepingShape,
4
6
  traceField
5
- } from "../chunk-5OC22IEO.js";
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, rect) {
1179
- for (let y = rect.y; y < rect.y + rect.height; y++) {
1180
- let i = (y * stride + rect.x) * 4;
1181
- for (let x = 0; x < rect.width; x++, i += 4) {
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, rect) => this.rasterizeAnnotation(annotation, rect),
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 key = `${source.maskSource}|${width}x${height}`;
1758
- if (this.depthMaps.has(key)) continue;
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(key, { width, height, data: data.data });
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, rect) {
1787
- if (!this.scratch) this.scratch = this.createCanvas(rect.width, rect.height);
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 !== rect.width || canvas.height !== rect.height) {
1790
- canvas.width = rect.width;
1791
- canvas.height = rect.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, rect.width, rect.height);
1798
- ctx.setTransform(1, 0, 0, 1, -rect.x, -rect.y);
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, rect.width, rect.height).data);
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, rect) {
1808
- const ox = tx * TILE - rect.x;
1809
- const oy = ty * TILE - rect.y;
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, rect.width - ox);
1813
- const y1 = Math.min(TILE, rect.height - oy);
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 rect = coveringRect(region);
1895
+ const rect2 = coveringRect(region);
1826
1896
  let comp = this.comp;
1827
1897
  let fresh = false;
1828
- if (!comp || comp.rect.x !== rect.x || comp.rect.y !== rect.y || comp.rect.width !== rect.width || comp.rect.height !== rect.height) {
1829
- const canvas = this.createCanvas(rect.width, rect.height);
1830
- comp = this.comp = new CompositeCanvas(canvas, rect);
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, rect, this.compositorSources(width, height), (tx, ty, data) => {
1837
- const key = tileKey(tx, ty);
1838
- if (comp.tiles.get(key) === data) return;
1839
- comp.tiles.set(key, data);
1840
- comp.histograms.delete(key);
1841
- this.putTile(ctx, data, tx, ty, rect);
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 key = `${this.displayKey}:${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
1861
- if (this.cachedPostCanvas && this.cachedPostKey === key) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
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 = key;
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 key = `${width}x${height}:${layer.type === "pixel" ? `${layer.offset.x},${layer.offset.y}` : ""}`;
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 === key) return cached.mask;
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 key = `${style.color?.join(",")}|${style.opacity}|${style.indicates}`;
2004
+ const key2 = `${style.color?.join(",")}|${style.opacity}|${style.indicates}`;
1935
2005
  let cached = this.overlayCache.get(mask);
1936
- if (!cached || cached.key !== 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 key = img;
2005
- let id = this.sourceIds.get(key);
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(key, id);
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 rect = ann;
2108
- ctx.strokeStyle = rect.strokeColor;
2109
- ctx.lineWidth = rect.strokeWidth;
2110
- if (rect.fillColor) {
2111
- ctx.fillStyle = rect.fillColor;
2112
- ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
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 (rect.strokeWidth > 0) {
2115
- ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
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 rect = coveringRect(region);
2307
- const comp = this.createCanvas(rect.width, rect.height);
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, rect, this.compositorSources(naturalWidth, naturalHeight), (tx, ty, data) => {
2311
- this.putTile(cctx, data, tx, ty, rect);
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, rect.x, rect.y);
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, rect) {
2520
+ constructor(canvas, rect2) {
2451
2521
  this.canvas = canvas;
2452
- this.rect = rect;
2453
- this.region = rect;
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 [key, data] of this.tiles) {
2467
- let h = this.histograms.get(key);
2536
+ for (const [key2, data] of this.tiles) {
2537
+ let h = this.histograms.get(key2);
2468
2538
  if (!h) {
2469
- const tx = key % 65536 - 32768;
2470
- const ty = Math.floor(key / 65536) - 32768;
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(key, h);
2547
+ this.histograms.set(key2, h);
2478
2548
  }
2479
2549
  parts.push(h);
2480
2550
  }
@@ -2496,6 +2566,423 @@ function hexRgb(hex) {
2496
2566
  return [v >> 16 & 255, v >> 8 & 255, v & 255];
2497
2567
  }
2498
2568
 
2569
+ // src/image-editor/selection/inspect.ts
2570
+ var MASK_EDGE_THRESHOLDS = { halo: 0.35, alignment: 0.5, jaggedness: 15, soft: 0.9 };
2571
+ var STRONG_EDGE = 0.1;
2572
+ function toLab(r, g, b) {
2573
+ const lin = (v) => {
2574
+ v /= 255;
2575
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
2576
+ };
2577
+ const R = lin(r);
2578
+ const G = lin(g);
2579
+ const B = lin(b);
2580
+ const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
2581
+ const x = f((0.4124 * R + 0.3576 * G + 0.1805 * B) / 0.95047);
2582
+ const y = f(0.2126 * R + 0.7152 * G + 0.0722 * B);
2583
+ const z = f((0.0193 * R + 0.1192 * G + 0.9505 * B) / 1.08883);
2584
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
2585
+ }
2586
+ var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
2587
+ var r3 = (v) => Math.round(v * 1e3) / 1e3;
2588
+ function inspectMaskEdge(mask, image, options = {}) {
2589
+ const b = mask.bounds;
2590
+ if (!b) throw new Error("The mask is empty: there is no edge to inspect.");
2591
+ const minArea = Math.max(1, options.minArea ?? 16);
2592
+ const W = mask.width;
2593
+ const H = mask.height;
2594
+ const a = mask.data;
2595
+ const alpha = (x, y) => x < 0 || y < 0 || x >= W || y >= H ? 0 : a[y * W + x];
2596
+ const img = image.data;
2597
+ const rgb = (x, y) => {
2598
+ const ix = x - image.x;
2599
+ const iy = y - image.y;
2600
+ if (ix < 0 || iy < 0 || ix >= image.width || iy >= image.height) return null;
2601
+ const i = (iy * image.width + ix) * 4;
2602
+ if (img[i + 3] < 128) return null;
2603
+ return [img[i], img[i + 1], img[i + 2]];
2604
+ };
2605
+ const premul = (x, y, c) => {
2606
+ const ix = Math.min(image.width - 1, Math.max(0, x - image.x));
2607
+ const iy = Math.min(image.height - 1, Math.max(0, y - image.y));
2608
+ const i = (iy * image.width + ix) * 4;
2609
+ return img[i + c] * img[i + 3] / 255;
2610
+ };
2611
+ const gradCache = /* @__PURE__ */ new Map();
2612
+ const gradient = (x, y) => {
2613
+ const k = y * W + x;
2614
+ const hit = gradCache.get(k);
2615
+ if (hit !== void 0) return hit;
2616
+ let best = 0;
2617
+ for (let c = 0; c < 3; c++) {
2618
+ const p = (dx, dy) => premul(x + dx, y + dy, c);
2619
+ const gx = p(1, -1) + 2 * p(1, 0) + p(1, 1) - p(-1, -1) - 2 * p(-1, 0) - p(-1, 1);
2620
+ const gy = p(-1, 1) + 2 * p(0, 1) + p(1, 1) - p(-1, -1) - 2 * p(0, -1) - p(1, -1);
2621
+ best = Math.max(best, Math.hypot(gx, gy) / 1020);
2622
+ }
2623
+ gradCache.set(k, best);
2624
+ return best;
2625
+ };
2626
+ const B = Math.min(256, Math.max(24, Math.round(Math.max(W, H) / 12)));
2627
+ const BW = Math.ceil(W / B);
2628
+ const blocks = /* @__PURE__ */ new Map();
2629
+ const block = (x, y) => {
2630
+ const k = Math.floor(y / B) * BW + Math.floor(x / B);
2631
+ let blk = blocks.get(k);
2632
+ if (!blk) blocks.set(k, blk = { edge: 0, aligned: 0, halo: 0, haloN: 0, turn: 0, len: 0, x0: Infinity, y0: Infinity, x1: -Infinity, y1: -Infinity });
2633
+ return blk;
2634
+ };
2635
+ const grow = (blk, x, y) => {
2636
+ blk.x0 = Math.min(blk.x0, x);
2637
+ blk.y0 = Math.min(blk.y0, y);
2638
+ blk.x1 = Math.max(blk.x1, x);
2639
+ blk.y1 = Math.max(blk.y1, y);
2640
+ };
2641
+ const x0 = Math.max(0, b.x - 1);
2642
+ const y0 = Math.max(0, b.y - 1);
2643
+ const x1 = Math.min(W, b.x + b.width + 1);
2644
+ const y1 = Math.min(H, b.y + b.height + 1);
2645
+ let area = 0;
2646
+ let edgeLength = 0;
2647
+ let aligned = 0;
2648
+ let haloSum = 0;
2649
+ let haloN = 0;
2650
+ let band = 0;
2651
+ let soft = 0;
2652
+ for (let y = y0; y < y1; y++) {
2653
+ for (let x = x0; x < x1; x++) {
2654
+ const v = a[y * W + x];
2655
+ if (v >= 128) area++;
2656
+ let lo = false;
2657
+ let hi = false;
2658
+ for (let dy = -1; dy <= 1; dy++) {
2659
+ for (let dx = -1; dx <= 1; dx++) {
2660
+ const nx = x + dx;
2661
+ const ny = y + dy;
2662
+ if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
2663
+ if (a[ny * W + nx] >= 128) hi = true;
2664
+ else lo = true;
2665
+ }
2666
+ }
2667
+ const partial = v > 0 && v < 255;
2668
+ if (hi && lo || partial) {
2669
+ band++;
2670
+ if (partial) soft++;
2671
+ }
2672
+ if (v < 128) continue;
2673
+ const inCanvasLow = (nx, ny) => nx >= 0 && ny >= 0 && nx < W && ny < H && a[ny * W + nx] < 128;
2674
+ if (!(inCanvasLow(x - 1, y) || inCanvasLow(x + 1, y) || inCanvasLow(x, y - 1) || inCanvasLow(x, y + 1))) continue;
2675
+ edgeLength++;
2676
+ const blk = block(x, y);
2677
+ blk.edge++;
2678
+ grow(blk, x, y);
2679
+ let strong = false;
2680
+ for (let dy = -2; dy <= 2 && !strong; dy++) {
2681
+ for (let dx = -2; dx <= 2 && !strong; dx++) {
2682
+ const nx = x + dx;
2683
+ const ny = y + dy;
2684
+ if (nx >= 0 && ny >= 0 && nx < W && ny < H && gradient(nx, ny) >= STRONG_EDGE) strong = true;
2685
+ }
2686
+ }
2687
+ if (strong) {
2688
+ aligned++;
2689
+ blk.aligned++;
2690
+ }
2691
+ const s = (dx, dy) => alpha(Math.min(W - 1, Math.max(0, x + dx)), Math.min(H - 1, Math.max(0, y + dy)));
2692
+ const gx = s(1, -1) + 2 * s(1, 0) + s(1, 1) - s(-1, -1) - 2 * s(-1, 0) - s(-1, 1);
2693
+ const gy = s(-1, 1) + 2 * s(0, 1) + s(1, 1) - s(-1, -1) - 2 * s(0, -1) - s(1, -1);
2694
+ const len = Math.hypot(gx, gy);
2695
+ if (len < 1e-6) continue;
2696
+ const ux = gx / len;
2697
+ const uy = gy / len;
2698
+ const at = (d) => rgb(Math.round(x + ux * d), Math.round(y + uy * d));
2699
+ const ring = at(1.5);
2700
+ const deep = at(5);
2701
+ const out = at(-3);
2702
+ if (!ring || !deep || !out) continue;
2703
+ const labOut = toLab(...out);
2704
+ const span = dist(toLab(...deep), labOut);
2705
+ if (span < 10) continue;
2706
+ const h = Math.min(1, Math.max(0, 1 - dist(toLab(...ring), labOut) / span));
2707
+ haloSum += h;
2708
+ haloN++;
2709
+ blk.halo += h;
2710
+ blk.haloN++;
2711
+ }
2712
+ }
2713
+ let turnExcess = 0;
2714
+ let outline = 0;
2715
+ for (const loop of traceLoops(a, W, H, x0 - 1, y0 - 1, x1 + 1, y1 + 1)) {
2716
+ const pts = resample(loop, 2);
2717
+ const n = pts.length;
2718
+ if (n < 6) continue;
2719
+ const L = n * 2;
2720
+ let total = 0;
2721
+ let net = 0;
2722
+ const turns = [];
2723
+ for (let i = 0; i < n; i++) {
2724
+ const p0 = pts[(i + n - 1) % n];
2725
+ const p1 = pts[i];
2726
+ const p2 = pts[(i + 1) % n];
2727
+ let d = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) - Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
2728
+ while (d > Math.PI) d -= 2 * Math.PI;
2729
+ while (d <= -Math.PI) d += 2 * Math.PI;
2730
+ turns.push(d);
2731
+ total += Math.abs(d);
2732
+ net += d;
2733
+ }
2734
+ const closure = Math.abs(net);
2735
+ turnExcess += Math.max(0, total - closure);
2736
+ outline += L;
2737
+ for (let i = 0; i < n; i++) {
2738
+ const [px2, py] = pts[i];
2739
+ if (px2 < 0 || py < 0 || px2 >= W || py >= H) continue;
2740
+ const blk = block(Math.floor(px2), Math.floor(py));
2741
+ blk.turn += Math.abs(turns[i]) - closure / n;
2742
+ blk.len += 2;
2743
+ }
2744
+ }
2745
+ const jaggedness = outline ? 100 * turnExcess / outline : 0;
2746
+ const { holes, islands } = regions(a, W, H, b, minArea);
2747
+ const haloScore = haloN ? haloSum / haloN : 0;
2748
+ const edgeAlignment = edgeLength ? aligned / edgeLength : 0;
2749
+ const softFraction = band ? soft / band : 0;
2750
+ const T2 = MASK_EDGE_THRESHOLDS;
2751
+ const candidates = [];
2752
+ const pad = (r) => {
2753
+ const px0 = Math.max(0, r.x - 8);
2754
+ const py0 = Math.max(0, r.y - 8);
2755
+ return { x: px0, y: py0, width: Math.min(W, r.x + r.width + 8) - px0, height: Math.min(H, r.y + r.height + 8) - py0 };
2756
+ };
2757
+ for (const blk of blocks.values()) {
2758
+ if (blk.edge < 8) continue;
2759
+ const bounds = pad({ x: blk.x0, y: blk.y0, width: blk.x1 - blk.x0 + 1, height: blk.y1 - blk.y0 + 1 });
2760
+ const opts = [];
2761
+ if (blk.haloN >= 8) {
2762
+ const h = blk.halo / blk.haloN;
2763
+ if (h >= T2.halo) opts.push({ severity: h, reason: `halo ${r3(h)}: the colour just inside the edge matches the background` });
2764
+ }
2765
+ const al = blk.aligned / blk.edge;
2766
+ if (al < T2.alignment) opts.push({ severity: (1 - al) * 0.9, reason: `alignment ${r3(al)}: the edge is off the image's edges` });
2767
+ if (blk.len >= 16) {
2768
+ const j = 100 * Math.max(0, blk.turn) / blk.len;
2769
+ if (j >= T2.jaggedness) opts.push({ severity: Math.min(1, j / (4 * T2.jaggedness)), reason: `jaggedness ${r3(j)}: the outline zigzags` });
2770
+ }
2771
+ const top = opts.sort((p, q) => q.severity - p.severity)[0];
2772
+ if (top) candidates.push({ ...top, bounds });
2773
+ }
2774
+ const imageArea = W * H;
2775
+ for (const r of holes.largest) candidates.push({ severity: Math.min(0.95, 0.5 + r.area / (0.01 * imageArea)), bounds: pad(r.bounds), reason: `hole of ${r.area} px inside the selection` });
2776
+ for (const r of islands.largest) candidates.push({ severity: Math.min(0.95, 0.5 + r.area / (0.01 * imageArea)), bounds: pad(r.bounds), reason: `island of ${r.area} px apart from the main selection` });
2777
+ const worst = candidates.sort((p, q) => q.severity - p.severity).slice(0, 5).map(({ bounds, reason }) => ({ bounds, reason }));
2778
+ const issues = [];
2779
+ if (haloScore >= T2.halo)
2780
+ issues.push(`Halo (haloScore ${r3(haloScore)} \u2265 ${T2.halo}): background colour is left inside the edge. Contract the edge (modify_selection contract 1\u20132 px, or refine_selection shiftEdge -20 to -40), or decontaminate on a new-layer output.`);
2781
+ if (edgeLength >= 16 && edgeAlignment < T2.alignment)
2782
+ issues.push(`Off the image's edges (edgeAlignment ${r3(edgeAlignment)} < ${T2.alignment}): much of the edge does not follow edges in the image. Rebuild it with select_subject, select_object or quick_select, or refine_selection with a larger radius.`);
2783
+ if (jaggedness >= T2.jaggedness) issues.push(`Jagged (jaggedness ${r3(jaggedness)} \u2265 ${T2.jaggedness}): the outline zigzags. Smooth it (refine_selection smooth 20\u201340, or modify_selection smooth).`);
2784
+ if (softFraction >= T2.soft && edgeAlignment >= T2.alignment)
2785
+ issues.push(`Very soft (softFraction ${r3(softFraction)} \u2265 ${T2.soft}): almost the whole edge is partly selected. If the subject has hard edges, raise refine_selection contrast or lower feather.`);
2786
+ if (holes.count) issues.push(`${holes.count} hole(s) (${holes.totalArea} px) inside the selection. If they belong to the subject, fill them (quick_select or select_polygon with mode add).`);
2787
+ if (islands.count) issues.push(`${islands.count} island(s) (${islands.totalArea} px) apart from the main selection. If they are stray, remove them (select_polygon or quick_select with mode subtract).`);
2788
+ return {
2789
+ area,
2790
+ bounds: { ...b },
2791
+ edgeLength,
2792
+ haloScore: r3(haloScore),
2793
+ edgeAlignment: r3(edgeAlignment),
2794
+ jaggedness: r3(jaggedness),
2795
+ softFraction: r3(softFraction),
2796
+ holes,
2797
+ islands,
2798
+ worst,
2799
+ issues
2800
+ };
2801
+ }
2802
+ function traceLoops(a, W, H, rx0, ry0, rx1, ry1) {
2803
+ const w = rx1 - rx0;
2804
+ const h = ry1 - ry0;
2805
+ const f = (i, j) => {
2806
+ const x = rx0 + i;
2807
+ const y = ry0 + j;
2808
+ return x < 0 || y < 0 || x >= W || y >= H ? 0 : a[y * W + x];
2809
+ };
2810
+ const on = (i, j) => f(i, j) >= 128;
2811
+ const V = w * h;
2812
+ const point = (id) => {
2813
+ const vertical = id >= V;
2814
+ const k = vertical ? id - V : id;
2815
+ const i = k % w;
2816
+ const j = (k - i) / w;
2817
+ const p = f(i, j);
2818
+ const q = vertical ? f(i, j + 1) : f(i + 1, j);
2819
+ const t = q === p ? 0.5 : Math.min(1, Math.max(0, (127.5 - p) / (q - p)));
2820
+ return vertical ? [rx0 + i, ry0 + j + t] : [rx0 + i + t, ry0 + j];
2821
+ };
2822
+ const segs = [];
2823
+ const touch = /* @__PURE__ */ new Map();
2824
+ const add2 = (e1, e2) => {
2825
+ const s = segs.length / 2;
2826
+ segs.push(e1, e2);
2827
+ for (const e of [e1, e2]) {
2828
+ const list = touch.get(e);
2829
+ if (list) list.push(s);
2830
+ else touch.set(e, [s]);
2831
+ }
2832
+ };
2833
+ for (let j = 0; j < h - 1; j++) {
2834
+ for (let i = 0; i < w - 1; i++) {
2835
+ const c = (on(i, j) ? 1 : 0) | (on(i + 1, j) ? 2 : 0) | (on(i + 1, j + 1) ? 4 : 0) | (on(i, j + 1) ? 8 : 0);
2836
+ if (c === 0 || c === 15) continue;
2837
+ const top = j * w + i;
2838
+ const bottom = (j + 1) * w + i;
2839
+ const left = V + j * w + i;
2840
+ const right = V + j * w + i + 1;
2841
+ const centre = (f(i, j) + f(i + 1, j) + f(i + 1, j + 1) + f(i, j + 1)) / 4 >= 128;
2842
+ switch (c) {
2843
+ case 1:
2844
+ case 14:
2845
+ add2(left, top);
2846
+ break;
2847
+ case 2:
2848
+ case 13:
2849
+ add2(top, right);
2850
+ break;
2851
+ case 3:
2852
+ case 12:
2853
+ add2(left, right);
2854
+ break;
2855
+ case 4:
2856
+ case 11:
2857
+ add2(right, bottom);
2858
+ break;
2859
+ case 6:
2860
+ case 9:
2861
+ add2(top, bottom);
2862
+ break;
2863
+ case 7:
2864
+ case 8:
2865
+ add2(left, bottom);
2866
+ break;
2867
+ case 5:
2868
+ if (centre) {
2869
+ add2(top, right);
2870
+ add2(left, bottom);
2871
+ } else {
2872
+ add2(left, top);
2873
+ add2(right, bottom);
2874
+ }
2875
+ break;
2876
+ case 10:
2877
+ if (centre) {
2878
+ add2(left, top);
2879
+ add2(right, bottom);
2880
+ } else {
2881
+ add2(top, right);
2882
+ add2(left, bottom);
2883
+ }
2884
+ break;
2885
+ }
2886
+ }
2887
+ }
2888
+ const used = new Uint8Array(segs.length / 2);
2889
+ const loops = [];
2890
+ for (let s0 = 0; s0 < used.length; s0++) {
2891
+ if (used[s0]) continue;
2892
+ const loop = [];
2893
+ let s = s0;
2894
+ let from = segs[s * 2];
2895
+ while (!used[s]) {
2896
+ used[s] = 1;
2897
+ loop.push(point(from));
2898
+ const to = segs[s * 2] === from ? segs[s * 2 + 1] : segs[s * 2];
2899
+ const next = touch.get(to).find((t) => !used[t]);
2900
+ from = to;
2901
+ if (next === void 0) break;
2902
+ s = next;
2903
+ }
2904
+ loops.push(loop);
2905
+ }
2906
+ return loops;
2907
+ }
2908
+ function resample(loop, step) {
2909
+ const n = loop.length;
2910
+ if (n < 2) return [];
2911
+ const out = [loop[0]];
2912
+ let carry = 0;
2913
+ for (let i = 0; i < n; i++) {
2914
+ const [ax, ay] = loop[i];
2915
+ const [bx, by] = loop[(i + 1) % n];
2916
+ const seg = Math.hypot(bx - ax, by - ay);
2917
+ let t = step - carry;
2918
+ while (t <= seg) {
2919
+ out.push([ax + (bx - ax) * t / seg, ay + (by - ay) * t / seg]);
2920
+ t += step;
2921
+ }
2922
+ carry = seg - (t - step);
2923
+ }
2924
+ if (out.length > 1 && Math.hypot(out[out.length - 1][0] - out[0][0], out[out.length - 1][1] - out[0][1]) < step / 2) out.pop();
2925
+ return out;
2926
+ }
2927
+ function regions(a, W, H, b, minArea) {
2928
+ const x0 = b.x - 1;
2929
+ const y0 = b.y - 1;
2930
+ const w = b.width + 2;
2931
+ const h = b.height + 2;
2932
+ const on = (i, j) => {
2933
+ const x = x0 + i;
2934
+ const y = y0 + j;
2935
+ return x >= 0 && y >= 0 && x < W && y < H && a[y * W + x] >= 128;
2936
+ };
2937
+ const label = new Int32Array(w * h);
2938
+ const stack = new Int32Array(w * h);
2939
+ const found = [];
2940
+ const N4 = [[1, 0], [-1, 0], [0, 1], [0, -1]];
2941
+ const N8 = [...N4, [1, 1], [1, -1], [-1, 1], [-1, -1]];
2942
+ for (let start = 0; start < w * h; start++) {
2943
+ if (label[start]) continue;
2944
+ const si = start % w;
2945
+ const sj = (start - si) / w;
2946
+ const fg = on(si, sj);
2947
+ const id = found.length + 1;
2948
+ const r = { fg, area: 0, bx0: si, by0: sj, bx1: si, by1: sj, outer: false };
2949
+ let top = 0;
2950
+ stack[top++] = start;
2951
+ label[start] = id;
2952
+ while (top) {
2953
+ const k = stack[--top];
2954
+ const i = k % w;
2955
+ const j = (k - i) / w;
2956
+ r.area++;
2957
+ if (i < r.bx0) r.bx0 = i;
2958
+ if (i > r.bx1) r.bx1 = i;
2959
+ if (j < r.by0) r.by0 = j;
2960
+ if (j > r.by1) r.by1 = j;
2961
+ if (i === 0 || j === 0 || i === w - 1 || j === h - 1) r.outer = true;
2962
+ for (const [dx, dy] of fg ? N8 : N4) {
2963
+ const ni = i + dx;
2964
+ const nj = j + dy;
2965
+ if (ni < 0 || nj < 0 || ni >= w || nj >= h) continue;
2966
+ const nk = nj * w + ni;
2967
+ if (label[nk] || on(ni, nj) !== fg) continue;
2968
+ label[nk] = id;
2969
+ stack[top++] = nk;
2970
+ }
2971
+ }
2972
+ found.push(r);
2973
+ }
2974
+ const box = (r) => ({ x: x0 + r.bx0, y: y0 + r.by0, width: r.bx1 - r.bx0 + 1, height: r.by1 - r.by0 + 1 });
2975
+ const summarize = (list) => {
2976
+ const kept = list.filter((r) => r.area >= minArea).sort((p, q) => q.area - p.area);
2977
+ return { count: kept.length, totalArea: kept.reduce((s, r) => s + r.area, 0), largest: kept.slice(0, 10).map((r) => ({ area: r.area, bounds: box(r) })) };
2978
+ };
2979
+ const pieces = found.filter((r) => r.fg).sort((p, q) => q.area - p.area);
2980
+ return {
2981
+ holes: summarize(found.filter((r) => !r.fg && !r.outer)),
2982
+ islands: summarize(pieces.slice(1))
2983
+ };
2984
+ }
2985
+
2499
2986
  // src/image-editor/selection/path.ts
2500
2987
  function selectionToSubpaths(mask, options = {}) {
2501
2988
  const b = mask.bounds;
@@ -2547,12 +3034,12 @@ function pathToSelection(components, width, height, options = {}) {
2547
3034
  const rx1 = Math.min(width, Math.ceil(x1));
2548
3035
  const ry1 = Math.min(height, Math.ceil(y1));
2549
3036
  if (!(rx1 > rx0 && ry1 > ry0)) return createSelectionMask(width, height);
2550
- const rect = { x: rx0, y: ry0, width: rx1 - rx0, height: ry1 - ry0 };
2551
- const cov = componentsCoverage(components, rect, options.fillRule ?? "nonzero");
3037
+ const rect2 = { x: rx0, y: ry0, width: rx1 - rx0, height: ry1 - ry0 };
3038
+ const cov = componentsCoverage(components, rect2, options.fillRule ?? "nonzero");
2552
3039
  const hard = options.antiAlias === false;
2553
3040
  const bytes = new Uint8Array(cov.length);
2554
3041
  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, rect, bytes);
3042
+ const mask = selectionFromRegion(width, height, rect2, bytes);
2556
3043
  const feather = options.feather ?? 0;
2557
3044
  return feather > 0 && mask.bounds ? modifySelection(mask, "feather", feather) : mask;
2558
3045
  }
@@ -2591,11 +3078,11 @@ var EdgeMap = class {
2591
3078
  block(x, y) {
2592
3079
  const bx = Math.floor(x / BLOCK);
2593
3080
  const by = Math.floor(y / BLOCK);
2594
- const key = by * 65536 + bx;
2595
- let b = this.blocks.get(key);
3081
+ const key2 = by * 65536 + bx;
3082
+ let b = this.blocks.get(key2);
2596
3083
  if (!b) {
2597
3084
  b = this.computeBlock(bx * BLOCK, by * BLOCK);
2598
- this.blocks.set(key, b);
3085
+ this.blocks.set(key2, b);
2599
3086
  }
2600
3087
  return b;
2601
3088
  }
@@ -2689,7 +3176,7 @@ var Heap = class {
2689
3176
  ids = new Int32Array(256);
2690
3177
  keys = new Float64Array(256);
2691
3178
  size = 0;
2692
- push(id, key) {
3179
+ push(id, key2) {
2693
3180
  if (this.size === this.ids.length) {
2694
3181
  const ids = new Int32Array(this.size * 2);
2695
3182
  ids.set(this.ids);
@@ -2701,31 +3188,31 @@ var Heap = class {
2701
3188
  let i = this.size++;
2702
3189
  while (i > 0) {
2703
3190
  const p = i - 1 >> 1;
2704
- if (this.keys[p] <= key) break;
3191
+ if (this.keys[p] <= key2) break;
2705
3192
  this.ids[i] = this.ids[p];
2706
3193
  this.keys[i] = this.keys[p];
2707
3194
  i = p;
2708
3195
  }
2709
3196
  this.ids[i] = id;
2710
- this.keys[i] = key;
3197
+ this.keys[i] = key2;
2711
3198
  }
2712
3199
  /** Removes the cheapest entry; read it from `topId` / `topKey` first. */
2713
3200
  pop() {
2714
3201
  const n = --this.size;
2715
3202
  const id = this.ids[n];
2716
- const key = this.keys[n];
3203
+ const key2 = this.keys[n];
2717
3204
  let i = 0;
2718
3205
  for (; ; ) {
2719
3206
  let c = 2 * i + 1;
2720
3207
  if (c >= n) break;
2721
3208
  if (c + 1 < n && this.keys[c + 1] < this.keys[c]) c++;
2722
- if (this.keys[c] >= key) break;
3209
+ if (this.keys[c] >= key2) break;
2723
3210
  this.ids[i] = this.ids[c];
2724
3211
  this.keys[i] = this.keys[c];
2725
3212
  i = c;
2726
3213
  }
2727
3214
  this.ids[i] = id;
2728
- this.keys[i] = key;
3215
+ this.keys[i] = key2;
2729
3216
  }
2730
3217
  get topId() {
2731
3218
  return this.ids[0];
@@ -2784,19 +3271,19 @@ function liveWire(map, from, to, band) {
2784
3271
  }
2785
3272
  }
2786
3273
  const scale = maxStrength > 0 ? 1 / maxStrength : 0;
2787
- const dist = new Float64Array(n).fill(Infinity);
3274
+ const dist2 = new Float64Array(n).fill(Infinity);
2788
3275
  const prev = new Int32Array(n).fill(-1);
2789
3276
  const done = new Uint8Array(n);
2790
3277
  const start = (ay - ry0) * w + (ax - rx0);
2791
3278
  const goal = (by - ry0) * w + (bx - rx0);
2792
- dist[start] = 0;
3279
+ dist2[start] = 0;
2793
3280
  const heap = new Heap();
2794
3281
  heap.push(start, 0);
2795
3282
  while (heap.size) {
2796
3283
  const p = heap.topId;
2797
3284
  const d = heap.topKey;
2798
3285
  heap.pop();
2799
- if (done[p] || d > dist[p]) continue;
3286
+ if (done[p] || d > dist2[p]) continue;
2800
3287
  done[p] = 1;
2801
3288
  if (p === goal) break;
2802
3289
  const x = p % w;
@@ -2817,8 +3304,8 @@ function liveWire(map, from, to, band) {
2817
3304
  const fZ = 1 - zero[q];
2818
3305
  const cost = (W_ZERO * fZ + W_GRADIENT * fG + W_DIRECTION * fD + 0.01) * step;
2819
3306
  const nd = d + cost;
2820
- if (nd < dist[q]) {
2821
- dist[q] = nd;
3307
+ if (nd < dist2[q]) {
3308
+ dist2[q] = nd;
2822
3309
  prev[q] = p;
2823
3310
  heap.push(q, nd);
2824
3311
  }
@@ -3534,8 +4021,8 @@ function colorRange(pixels, samples, options = {}) {
3534
4021
  const d = Math.max(Math.abs(data[p] - s.c[0]), Math.abs(data[p + 1] - s.c[1]), Math.abs(data[p + 2] - s.c[2]));
3535
4022
  let k = Math.min(1, Math.max(0, 1 - d / f));
3536
4023
  if (k > 0 && radius !== void 0 && s.at) {
3537
- const dist = Math.hypot(x - s.at.x, y - s.at.y);
3538
- k *= Math.min(1, Math.max(0, 1 - dist / radius));
4024
+ const dist2 = Math.hypot(x - s.at.x, y - s.at.y);
4025
+ k *= Math.min(1, Math.max(0, 1 - dist2 / radius));
3539
4026
  }
3540
4027
  if (k > best) best = k;
3541
4028
  }
@@ -3885,29 +4372,29 @@ var ImageEffectsEngine = class {
3885
4372
  return this.pending.size > 0;
3886
4373
  }
3887
4374
  /** See `SmartEffectResolver`. */
3888
- resolve(filter, input, width, height, key) {
3889
- const hit = this.cache.get(key);
4375
+ resolve(filter, input, width, height, key2) {
4376
+ const hit = this.cache.get(key2);
3890
4377
  if (hit) {
3891
- this.cache.delete(key);
3892
- this.cache.set(key, hit);
4378
+ this.cache.delete(key2);
4379
+ this.cache.set(key2, hit);
3893
4380
  return { result: hit, pending: false };
3894
4381
  }
3895
- if (this.pending.has(key)) return { result: null, pending: true };
4382
+ if (this.pending.has(key2)) return { result: null, pending: true };
3896
4383
  const provider = this.slot.provider;
3897
- if (!provider || this.failures.has(key)) return { result: null, pending: false };
4384
+ if (!provider || this.failures.has(key2)) return { result: null, pending: false };
3898
4385
  const generation = this.generation;
3899
4386
  const source = { width, height, data: new Uint8ClampedArray(input) };
3900
4387
  const run = this.filter(provider, filter.effect, filter.values, source).then((out) => {
3901
4388
  if (generation !== this.generation) return;
3902
- this.cache.set(key, out);
4389
+ this.cache.set(key2, out);
3903
4390
  while (this.cache.size > this.maxEntries) this.cache.delete(this.cache.keys().next().value);
3904
4391
  }).catch((error) => {
3905
- if (generation === this.generation) this.failures.set(key, error instanceof Error ? error : new Error(String(error)));
4392
+ if (generation === this.generation) this.failures.set(key2, error instanceof Error ? error : new Error(String(error)));
3906
4393
  }).finally(() => {
3907
- if (this.pending.get(key) === run) this.pending.delete(key);
4394
+ if (this.pending.get(key2) === run) this.pending.delete(key2);
3908
4395
  if (generation === this.generation) this.notify();
3909
4396
  });
3910
- this.pending.set(key, run);
4397
+ this.pending.set(key2, run);
3911
4398
  return { result: null, pending: true };
3912
4399
  }
3913
4400
  /** A filter effect over `source`: its result, or (for an overlay) the effect drawn over it. */
@@ -3928,12 +4415,12 @@ var ImageEffectsEngine = class {
3928
4415
  return out.width === size.width && out.height === size.height ? out : createEffectPixels(size.width, size.height);
3929
4416
  }
3930
4417
  /** Why the result for `key` failed, if it did. */
3931
- failure(key) {
3932
- return this.failures.get(key);
4418
+ failure(key2) {
4419
+ return this.failures.get(key2);
3933
4420
  }
3934
4421
  /** Every failure so far. */
3935
4422
  failuresList() {
3936
- return [...this.failures].map(([key, error]) => ({ key, error }));
4423
+ return [...this.failures].map(([key2, error]) => ({ key: key2, error }));
3937
4424
  }
3938
4425
  /** Resolves when nothing is rendering. */
3939
4426
  async idle() {
@@ -4556,10 +5043,10 @@ function resolveProfile(doc) {
4556
5043
  let p;
4557
5044
  if (doc.builtin) p = builtinProfile(doc.builtin);
4558
5045
  else if (doc.icc) {
4559
- const key = bytesToBase64(doc.icc);
4560
- p = fromBytes.get(key) ?? parseProfile(doc.icc);
5046
+ const key2 = bytesToBase64(doc.icc);
5047
+ p = fromBytes.get(key2) ?? parseProfile(doc.icc);
4561
5048
  if (fromBytes.size > 32) fromBytes.clear();
4562
- fromBytes.set(key, p);
5049
+ fromBytes.set(key2, p);
4563
5050
  } else p = srgbProfile();
4564
5051
  resolved.set(doc, p);
4565
5052
  return p;
@@ -4649,12 +5136,12 @@ function convertHex(hex, from, to, intent = "relative", bpc = true) {
4649
5136
  return `#${c(out[0])}${c(out[1])}${c(out[2])}`;
4650
5137
  }
4651
5138
  function convertColorFields(value, convert) {
4652
- const walk = (v, key) => {
4653
- if (typeof v === "string") return /color$/i.test(key) && /^#[0-9a-f]{6}$/i.test(v) ? convert(v) : v;
5139
+ const walk = (v, key2) => {
5140
+ if (typeof v === "string") return /color$/i.test(key2) && /^#[0-9a-f]{6}$/i.test(v) ? convert(v) : v;
4654
5141
  if (Array.isArray(v)) {
4655
5142
  let changed = false;
4656
5143
  const out = v.map((x) => {
4657
- const y = walk(x, key);
5144
+ const y = walk(x, key2);
4658
5145
  if (y !== x) changed = true;
4659
5146
  return y;
4660
5147
  });
@@ -4676,7 +5163,7 @@ function convertSurfaceColors(surface, t) {
4676
5163
  if (surface.channels !== 4 || t.inputs !== 3 || t.outputs !== 3) return surface;
4677
5164
  const tiles = /* @__PURE__ */ new Map();
4678
5165
  const memo = /* @__PURE__ */ new Map();
4679
- for (const [key, tile] of surface.tiles) {
5166
+ for (const [key2, tile] of surface.tiles) {
4680
5167
  let next = memo.get(tile);
4681
5168
  if (next === void 0) {
4682
5169
  if (tile.deep instanceof Uint16Array) {
@@ -4699,7 +5186,7 @@ function convertSurfaceColors(surface, t) {
4699
5186
  }
4700
5187
  memo.set(tile, next);
4701
5188
  }
4702
- if (next) tiles.set(key, next);
5189
+ if (next) tiles.set(key2, next);
4703
5190
  }
4704
5191
  return new TiledSurface(4, tiles, surface.defaultValue);
4705
5192
  }
@@ -4716,22 +5203,22 @@ function paintCoverageInto(base, coverage, apply, rects, depth = null) {
4716
5203
  const done = /* @__PURE__ */ new Set();
4717
5204
  for (const r of rects) {
4718
5205
  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 key = tileKey(t.tx, t.ty);
4720
- if (done.has(key)) continue;
4721
- done.add(key);
5206
+ const key2 = tileKey(t.tx, t.ty);
5207
+ if (done.has(key2)) continue;
5208
+ done.add(key2);
4722
5209
  if (depth) {
4723
5210
  const ch = base.channels;
4724
- const d = applyStrokeToDeepTile(coverage, tileSamples(base.tiles.get(key), ch, depth, base.defaultValue), t.tx, t.ty, apply, { channels: ch });
5211
+ const d = applyStrokeToDeepTile(coverage, tileSamples(base.tiles.get(key2), ch, depth, base.defaultValue), t.tx, t.ty, apply, { channels: ch });
4725
5212
  const tile2 = deepTile(d, ch, base.defaultValue);
4726
- if (tile2) tiles.set(key, tile2);
4727
- else tiles.delete(key);
5213
+ if (tile2) tiles.set(key2, tile2);
5214
+ else tiles.delete(key2);
4728
5215
  continue;
4729
5216
  }
4730
- const src = base.tiles.get(key)?.data ?? defaultTile(base);
5217
+ const src = base.tiles.get(key2)?.data ?? defaultTile(base);
4731
5218
  const data = applyStrokeToTile(coverage, src, base.channels, t.tx, t.ty, apply);
4732
5219
  const tile = finishTile(data, base.channels, base.defaultValue);
4733
- if (tile) tiles.set(key, tile);
4734
- else tiles.delete(key);
5220
+ if (tile) tiles.set(key2, tile);
5221
+ else tiles.delete(key2);
4735
5222
  }
4736
5223
  }
4737
5224
  return new TiledSurface(base.channels, tiles, base.defaultValue);
@@ -4802,19 +5289,19 @@ var PaintStrokeSession = class {
4802
5289
  work = /* @__PURE__ */ new Map();
4803
5290
  workRead = /* @__PURE__ */ new Map();
4804
5291
  /** The pre-stroke tile as a float working tile (cached; `null`: transparent). */
4805
- readWork(key) {
4806
- if (!this.workRead.has(key)) this.workRead.set(key, workTileOf(this.base.tiles.get(key)));
4807
- return this.workRead.get(key);
5292
+ readWork(key2) {
5293
+ if (!this.workRead.has(key2)) this.workRead.set(key2, workTileOf(this.base.tiles.get(key2)));
5294
+ return this.workRead.get(key2);
4808
5295
  }
4809
5296
  /** Clone, healing and the spot-heal trail at depth: `paint` through the coverage on the float working tile. */
4810
- retouchDeepTile(key, tx, ty, paint, options) {
5297
+ retouchDeepTile(key2, tx, ty, paint, options) {
4811
5298
  const r = this.retouch;
4812
- const read = this.readWork(key);
5299
+ const read = this.readWork(key2);
4813
5300
  const out = applyRetouchToTile(this.stroke.coverage, read ?? new Float32Array(TILE * TILE * 4), 4, tx, ty, paint, r.apply, options);
4814
- this.work.set(key, out);
4815
- const d = deepOfWorkTile(out, this.deep, read, this.base.tiles.get(key));
4816
- this.liveDeep.set(key, d);
4817
- this.live.set(key, { data: proxyOf(d, 4), uniform: null, deep: d });
5301
+ this.work.set(key2, out);
5302
+ const d = deepOfWorkTile(out, this.deep, read, this.base.tiles.get(key2));
5303
+ this.liveDeep.set(key2, d);
5304
+ this.live.set(key2, { data: proxyOf(d, 4), uniform: null, deep: d });
4818
5305
  return d;
4819
5306
  }
4820
5307
  /** The new tile of a coverage stroke over the pre-stroke tile `src`. */
@@ -4828,23 +5315,23 @@ var PaintStrokeSession = class {
4828
5315
  const a = this.apply;
4829
5316
  const depth = this.dabDeep;
4830
5317
  if (depth) {
4831
- const read = (key) => this.readWork(key);
5318
+ const read = (key2) => this.readWork(key2);
4832
5319
  return {
4833
5320
  offset: a.offset,
4834
5321
  lockTransparency: a.lockTransparency,
4835
5322
  selection: a.selection,
4836
5323
  readTile: (tx, ty) => {
4837
- const key = tileKey(tx, ty);
4838
- return this.work.get(key) ?? read(key);
5324
+ const key2 = tileKey(tx, ty);
5325
+ return this.work.get(key2) ?? read(key2);
4839
5326
  },
4840
5327
  writeTile: (tx, ty) => {
4841
- const key = tileKey(tx, ty);
4842
- if (!this.touched.has(key)) {
4843
- this.work.set(key, read(key)?.slice() ?? new Float32Array(TILE * TILE * 4));
4844
- this.touched.add(key);
5328
+ const key2 = tileKey(tx, ty);
5329
+ if (!this.touched.has(key2)) {
5330
+ this.work.set(key2, read(key2)?.slice() ?? new Float32Array(TILE * TILE * 4));
5331
+ this.touched.add(key2);
4845
5332
  }
4846
- this.fresh.add(key);
4847
- return this.work.get(key);
5333
+ this.fresh.add(key2);
5334
+ return this.work.get(key2);
4848
5335
  }
4849
5336
  };
4850
5337
  }
@@ -4854,14 +5341,14 @@ var PaintStrokeSession = class {
4854
5341
  selection: a.selection,
4855
5342
  readTile: (tx, ty) => this.live.get(tileKey(tx, ty))?.data ?? (this.base.defaultValue ? defaultTile(this.base) : null),
4856
5343
  writeTile: (tx, ty) => {
4857
- const key = tileKey(tx, ty);
4858
- if (!this.touched.has(key)) {
4859
- const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
4860
- this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
4861
- this.touched.add(key);
5344
+ const key2 = tileKey(tx, ty);
5345
+ if (!this.touched.has(key2)) {
5346
+ const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
5347
+ this.live.set(key2, { data: new Uint8ClampedArray(src), uniform: null });
5348
+ this.touched.add(key2);
4862
5349
  }
4863
- this.fresh.add(key);
4864
- return this.live.get(key).data;
5350
+ this.fresh.add(key2);
5351
+ return this.live.get(key2).data;
4865
5352
  }
4866
5353
  };
4867
5354
  }
@@ -4885,49 +5372,49 @@ var PaintStrokeSession = class {
4885
5372
  const ch = this.base.channels;
4886
5373
  if (this.mixer) {
4887
5374
  if (this.dabDeep) {
4888
- for (const key of this.fresh) {
4889
- const d = deepOfWorkTile(this.work.get(key), this.dabDeep, this.workRead.get(key) ?? null, this.base.tiles.get(key));
4890
- this.liveDeep.set(key, d);
4891
- this.live.set(key, { data: proxyOf(d, 4), uniform: null, deep: d });
5375
+ for (const key2 of this.fresh) {
5376
+ const d = deepOfWorkTile(this.work.get(key2), this.dabDeep, this.workRead.get(key2) ?? null, this.base.tiles.get(key2));
5377
+ this.liveDeep.set(key2, d);
5378
+ this.live.set(key2, { data: proxyOf(d, 4), uniform: null, deep: d });
4892
5379
  }
4893
- } else for (const key of this.fresh) this.live.set(key, { data: this.live.get(key).data, uniform: null });
5380
+ } else for (const key2 of this.fresh) this.live.set(key2, { data: this.live.get(key2).data, uniform: null });
4894
5381
  this.fresh.clear();
4895
5382
  this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
4896
5383
  return;
4897
5384
  }
4898
5385
  const stroke = this.stroke;
4899
- const regions = /* @__PURE__ */ new Map();
5386
+ const regions2 = /* @__PURE__ */ new Map();
4900
5387
  for (const r of rects) {
4901
5388
  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 key = tileKey(t.tx, t.ty);
4903
- const cur = regions.get(key);
4904
- regions.set(key, { tx: t.tx, ty: t.ty, r: cur ? unionRect(cur.r, r) : r });
5389
+ const key2 = tileKey(t.tx, t.ty);
5390
+ const cur = regions2.get(key2);
5391
+ regions2.set(key2, { tx: t.tx, ty: t.ty, r: cur ? unionRect(cur.r, r) : r });
4905
5392
  }
4906
5393
  }
4907
- for (const [key, { tx, ty, r }] of regions) {
5394
+ for (const [key2, { tx, ty, r }] of regions2) {
4908
5395
  if (this.deep) {
4909
5396
  const rt = this.retouch;
4910
5397
  const paint = rt?.kind === "coverage" ? rt.paint() : null;
4911
5398
  if (paint && paint.kind !== "tone") {
4912
- const prevWork = this.touched.has(key) ? this.work.get(key) : void 0;
4913
- this.touched.add(key);
4914
- this.retouchDeepTile(key, tx, ty, paint, prevWork ? { into: prevWork, region: r } : {});
5399
+ const prevWork = this.touched.has(key2) ? this.work.get(key2) : void 0;
5400
+ this.touched.add(key2);
5401
+ this.retouchDeepTile(key2, tx, ty, paint, prevWork ? { into: prevWork, region: r } : {});
4915
5402
  continue;
4916
5403
  }
4917
- const src2 = tileSamples(this.base.tiles.get(key), ch, this.deep, this.base.defaultValue);
4918
- const prev2 = this.touched.has(key) ? this.liveDeep.get(key) : void 0;
4919
- this.touched.add(key);
5404
+ const src2 = tileSamples(this.base.tiles.get(key2), ch, this.deep, this.base.defaultValue);
5405
+ const prev2 = this.touched.has(key2) ? this.liveDeep.get(key2) : void 0;
5406
+ this.touched.add(key2);
4920
5407
  const opts = prev2 ? { into: prev2, region: r, channels: ch } : { channels: ch };
4921
5408
  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(key, d);
4923
- this.live.set(key, { data: proxyOf(d, ch), uniform: null, deep: d });
5409
+ this.liveDeep.set(key2, d);
5410
+ this.live.set(key2, { data: proxyOf(d, ch), uniform: null, deep: d });
4924
5411
  continue;
4925
5412
  }
4926
- const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
4927
- const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
4928
- this.touched.add(key);
5413
+ const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
5414
+ const prev = this.touched.has(key2) ? this.live.get(key2)?.data : void 0;
5415
+ this.touched.add(key2);
4929
5416
  const data = this.compositeTile(src, tx, ty, prev ? { into: prev, region: r } : {});
4930
- this.live.set(key, { data, uniform: null });
5417
+ this.live.set(key2, { data, uniform: null });
4931
5418
  }
4932
5419
  this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
4933
5420
  }
@@ -4944,26 +5431,26 @@ var PaintStrokeSession = class {
4944
5431
  if (r?.kind === "coverage" && r.finalPaint) {
4945
5432
  const final = r.finalPaint(this.stroke.coverage);
4946
5433
  if (final) {
4947
- for (const key of this.touched) {
4948
- const { tx, ty } = tileCoord(key);
5434
+ for (const key2 of this.touched) {
5435
+ const { tx, ty } = tileCoord(key2);
4949
5436
  if (this.deep) {
4950
- this.retouchDeepTile(key, tx, ty, final, {});
5437
+ this.retouchDeepTile(key2, tx, ty, final, {});
4951
5438
  continue;
4952
5439
  }
4953
- const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
4954
- this.live.set(key, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
5440
+ const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
5441
+ this.live.set(key2, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
4955
5442
  }
4956
5443
  }
4957
5444
  }
4958
5445
  const tiles = new Map(this.base.tiles);
4959
5446
  let changed = false;
4960
- for (const key of this.touched) {
4961
- const live = this.live.get(key);
4962
- const deep = this.liveDeep.get(key);
5447
+ for (const key2 of this.touched) {
5448
+ const live = this.live.get(key2);
5449
+ const deep = this.liveDeep.get(key2);
4963
5450
  const tile = deep ? deepTile(deep, ch, this.base.defaultValue) : finishTile(live.data, ch, this.base.defaultValue);
4964
- const prev = this.base.tiles.get(key);
4965
- if (tile) tiles.set(key, tile);
4966
- else tiles.delete(key);
5451
+ const prev = this.base.tiles.get(key2);
5452
+ if (tile) tiles.set(key2, tile);
5453
+ else tiles.delete(key2);
4967
5454
  if (!sameTile(prev, tile)) changed = true;
4968
5455
  }
4969
5456
  const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
@@ -4982,9 +5469,9 @@ var PaintStrokeSession = class {
4982
5469
  this.controller._strokeCommit(this.before, this.target.id, this.target.channel, null);
4983
5470
  }
4984
5471
  };
4985
- function workRegionOf(surface, rect, depth) {
4986
- if (!depth) return Float32Array.from(readRegion(surface, rect));
4987
- const d = readDeepRegion(surface, rect, depth);
5472
+ function workRegionOf(surface, rect2, depth) {
5473
+ if (!depth) return Float32Array.from(readRegion(surface, rect2));
5474
+ const d = readDeepRegion(surface, rect2, depth);
4988
5475
  const out = new Float32Array(d.length);
4989
5476
  if (d instanceof Uint16Array) {
4990
5477
  for (let i = 0; i < d.length; i++) out[i] = d[i] / 257;
@@ -5671,9 +6158,9 @@ var ImageEditorController = class {
5671
6158
  /** Recomputes the compatibility views (`adjustments`, `depthMask`, `annotations`) when the stack changed. */
5672
6159
  syncViews() {
5673
6160
  const s = this.state;
5674
- const key = [s.layers, s.outputOpacity, s.legacyOutputBlur, s.activeLayerId, s.imageDimensions.width, s.imageDimensions.height];
5675
- if (key.length === this.viewKey.length && key.every((v, i) => v === this.viewKey[i])) return;
5676
- this.viewKey = key;
6161
+ const key2 = [s.layers, s.outputOpacity, s.legacyOutputBlur, s.activeLayerId, s.imageDimensions.width, s.imageDimensions.height];
6162
+ if (key2.length === this.viewKey.length && key2.every((v, i) => v === this.viewKey[i])) return;
6163
+ this.viewKey = key2;
5677
6164
  const { width, height } = this.naturalSize;
5678
6165
  const view = legacyView(this.stack(), width, height, s.activeLayerId);
5679
6166
  s.adjustments = view.adjustments;
@@ -6000,7 +6487,7 @@ var ImageEditorController = class {
6000
6487
  getLayerFrame(id) {
6001
6488
  const layer = this.getLayer(id);
6002
6489
  if (!layer) return null;
6003
- const rect = (b2) => [
6490
+ const rect2 = (b2) => [
6004
6491
  { x: b2.x, y: b2.y },
6005
6492
  { x: b2.x + b2.width, y: b2.y },
6006
6493
  { x: b2.x + b2.width, y: b2.y + b2.height },
@@ -6015,10 +6502,10 @@ var ImageEditorController = class {
6015
6502
  }
6016
6503
  if (layer.type === "shape") {
6017
6504
  const g = shapeGeometryBounds(layer);
6018
- return g ? rect(g) : null;
6505
+ return g ? rect2(g) : null;
6019
6506
  }
6020
6507
  const b = this.getLayerBounds(id);
6021
- return b ? rect(b) : null;
6508
+ return b ? rect2(b) : null;
6022
6509
  }
6023
6510
  /**
6024
6511
  * Hit-tests the layer stack at `point` (in document space) and returns the id of the topmost
@@ -6099,9 +6586,9 @@ var ImageEditorController = class {
6099
6586
  * and are left out): `rect` (default: the canvas) as RGBA, straight alpha. Crop, rotation and
6100
6587
  * output opacity are not applied. With `layerIds`, only those layers are composited.
6101
6588
  */
6102
- compositePixels(rect, options = {}) {
6589
+ compositePixels(rect2, options = {}) {
6103
6590
  const { width, height } = this.naturalSize;
6104
- const r = rect ?? { x: 0, y: 0, width, height };
6591
+ const r = rect2 ?? { x: 0, y: 0, width, height };
6105
6592
  let layers = this.state.layers;
6106
6593
  if (options.layerIds) {
6107
6594
  const keep = new Set(options.layerIds);
@@ -6125,12 +6612,12 @@ var ImageEditorController = class {
6125
6612
  nextSourceId = 1;
6126
6613
  /** Changes whenever the Background's source pixels can have changed. */
6127
6614
  sourceVersion() {
6128
- const key = this.samplePixelsOverride ? this.samplePixels : this.imageElement ?? this.samplePixels;
6129
- if (!key) return "0";
6130
- let id = this.sourceIds.get(key);
6615
+ const key2 = this.samplePixelsOverride ? this.samplePixels : this.imageElement ?? this.samplePixels;
6616
+ if (!key2) return "0";
6617
+ let id = this.sourceIds.get(key2);
6131
6618
  if (!id) {
6132
6619
  id = this.nextSourceId++;
6133
- this.sourceIds.set(key, id);
6620
+ this.sourceIds.set(key2, id);
6134
6621
  }
6135
6622
  return String(id);
6136
6623
  }
@@ -6572,8 +7059,8 @@ var ImageEditorController = class {
6572
7059
  this.state.activePathId = id;
6573
7060
  this.notify();
6574
7061
  }
6575
- commitPaths(next, active) {
6576
- this.recordHistory();
7062
+ commitPaths(next, active, commitToHistory = true) {
7063
+ if (commitToHistory) this.recordHistory();
6577
7064
  if (next.length) this.state.paths = next;
6578
7065
  else delete this.state.paths;
6579
7066
  if (active !== void 0) this.state.activePathId = active;
@@ -6609,6 +7096,64 @@ var ImageEditorController = class {
6609
7096
  this.commitPaths(this.getPaths().map((p) => p.id === id ? path : p));
6610
7097
  return path;
6611
7098
  }
7099
+ /**
7100
+ * What the path tools (Path Selection, Direct Selection, Pen) edit: the active path in the Paths
7101
+ * panel, else the active layer when it is a shape, else the active layer's vector mask; null
7102
+ * when there is none of these (the Pen then starts a new Work Path).
7103
+ */
7104
+ pathEditTarget() {
7105
+ const id = this.state.activePathId;
7106
+ if (id && this.getPath(id)) return { kind: "path", id };
7107
+ const layer = this.getActiveLayer();
7108
+ if (!layer) return null;
7109
+ if (layer.type === "shape") return { kind: "shape", layerId: layer.id };
7110
+ if (layer.vectorMask) return { kind: "vector-mask", layerId: layer.id };
7111
+ return null;
7112
+ }
7113
+ /** The components a path tool target holds (document pixels). Throws for a target that is gone. */
7114
+ getPathEditComponents(target) {
7115
+ if (target.kind === "path") return this.requirePath(target.id).components;
7116
+ if (target.kind === "shape") return requireShapeLayer(this.state.layers, target.layerId).components;
7117
+ return this.requireVectorMask(target.layerId).vm.components;
7118
+ }
7119
+ /**
7120
+ * Replaces a path tool target's components: one undo step, or none with `commitToHistory` false
7121
+ * (the later moves of a drag whose first move recorded one). A component that still has its
7122
+ * `live` shape is kept as given (drop `live` from one whose anchors changed); a shape layer keeps
7123
+ * at least one component.
7124
+ */
7125
+ setPathEditComponents(target, components, commitToHistory = true) {
7126
+ this.getPathEditComponents(target);
7127
+ const next = components.map(
7128
+ (c, i) => c.live ? { ...c } : this.componentFrom({ op: c.op, subpaths: c.subpaths.filter((sp) => sp.anchors.length) }, `components[${i}].`)
7129
+ );
7130
+ if (target.kind === "path") {
7131
+ this.commitPaths(this.getPaths().map((p) => p.id === target.id ? { ...p, components: next } : p), void 0, commitToHistory);
7132
+ } else if (target.kind === "shape") {
7133
+ if (!next.length) throw new LayerOperationError("A shape needs at least one component; delete the layer instead (delete_layer).");
7134
+ const { layers } = updateShapeLayer(this.state.layers, target.layerId, () => ({ components: next }), this.shapeEnv(), "edit its path");
7135
+ this.commitLayers(layers, void 0, commitToHistory);
7136
+ } else {
7137
+ const { vm } = this.requireVectorMask(target.layerId);
7138
+ this.commitLayers(mapLayer(this.state.layers, target.layerId, (l) => ({ ...l, vectorMask: { ...vm, components: next } })), void 0, commitToHistory);
7139
+ }
7140
+ }
7141
+ /**
7142
+ * The Pen with no path to add to: a new Work Path from `components` (replacing the last one,
7143
+ * unless it was saved), made the active path. One undo step. Returns the path.
7144
+ */
7145
+ startWorkPath(components) {
7146
+ const paths = this.getPaths();
7147
+ const old = paths.find((p) => p.work);
7148
+ const work = {
7149
+ id: old?.id ?? newPathId(),
7150
+ name: WORK_PATH_NAME,
7151
+ components: components.map((c, i) => this.componentFrom({ op: c.op, subpaths: c.subpaths }, `components[${i}].`)),
7152
+ work: true
7153
+ };
7154
+ this.commitPaths(old ? paths.map((p) => p === old ? work : p) : [...paths, work], work.id);
7155
+ return work;
7156
+ }
6612
7157
  /** Renames a path; renaming the Work Path saves it (Photoshop's Save Path). One undo step. */
6613
7158
  renamePath(id, name) {
6614
7159
  const old = this.requirePath(id);
@@ -7485,7 +8030,7 @@ var ImageEditorController = class {
7485
8030
  return this.getGlobalLight();
7486
8031
  }
7487
8032
  /** Composites `layers` (in isolation) over `rect` into a surface, loading depth maps first. */
7488
- async compositeToSurface(layers, rect) {
8033
+ async compositeToSurface(layers, rect2) {
7489
8034
  const { width, height } = this.naturalSize;
7490
8035
  if (flattenLayers(layers).some((l) => l.type === "annotation" && l.visible) && !this.renderer.canRasterize()) {
7491
8036
  throw new LayerOperationError("Merging annotation layers needs a canvas (a browser); hide or delete them first, or merge in the browser.");
@@ -7494,20 +8039,20 @@ var ImageEditorController = class {
7494
8039
  const sources = this.renderer.compositorSources(width, height);
7495
8040
  const depth = this.bitDepth;
7496
8041
  if (depth !== 8) {
7497
- const samples = workingToSamples(compositeDeep(layers, rect, sources, depth), depth);
7498
- return surfaceFromDeepPixels({ width: rect.width, height: rect.height, data: samples }, 4, rect.x, rect.y);
8042
+ const samples = workingToSamples(compositeDeep(layers, rect2, sources, depth), depth);
8043
+ return surfaceFromDeepPixels({ width: rect2.width, height: rect2.height, data: samples }, 4, rect2.x, rect2.y);
7499
8044
  }
7500
- const data = new LayerCompositor().composite(layers, rect, sources);
7501
- return surfaceFromPixels({ width: rect.width, height: rect.height, data }, 4, rect.x, rect.y);
8045
+ const data = new LayerCompositor().composite(layers, rect2, sources);
8046
+ return surfaceFromPixels({ width: rect2.width, height: rect2.height, data }, 4, rect2.x, rect2.y);
7502
8047
  }
7503
8048
  /**
7504
8049
  * The composite of the layer stack at the document's bit depth (as `compositePixels`, no
7505
8050
  * canvas): 16-bit sRGB samples, or 32-bit linear float, over `rect` (default the canvas). At 8
7506
8051
  * bits, the 8-bit composite widened to 16 bits.
7507
8052
  */
7508
- compositeDeepPixels(rect) {
8053
+ compositeDeepPixels(rect2) {
7509
8054
  const { width, height } = this.naturalSize;
7510
- const r = rect ?? { x: 0, y: 0, width, height };
8055
+ const r = rect2 ?? { x: 0, y: 0, width, height };
7511
8056
  const depth = this.bitDepth;
7512
8057
  if (depth === 8) return { width: r.width, height: r.height, depth: 16, data: to16From8(this.compositePixels(r).data) };
7513
8058
  const data = workingToSamples(compositeDeep(this.state.layers, r, this.compositorSources(), depth), depth);
@@ -7545,12 +8090,12 @@ var ImageEditorController = class {
7545
8090
  if (!upper.visible) throw new LayerOperationError(`Layer "${upper.name || id}" is hidden; show it or delete it instead of merging.`);
7546
8091
  requireUnlocked(upper, "all", "merge it");
7547
8092
  requireUnlocked(lower, "pixels", "merge into it");
7548
- let rect = unionRect(this.paintBounds(lower), upper.type === "adjustment" ? null : this.paintBounds(upper)) ?? { x: 0, y: 0, width: 1, height: 1 };
8093
+ let rect2 = unionRect(this.paintBounds(lower), upper.type === "adjustment" ? null : this.paintBounds(upper)) ?? { x: 0, y: 0, width: 1, height: 1 };
7549
8094
  const reach = Math.max(effectsMargin(lower.effects), upper.type === "adjustment" ? 0 : effectsMargin(upper.effects));
7550
- if (reach) rect = { x: rect.x - reach, y: rect.y - reach, width: rect.width + 2 * reach, height: rect.height + 2 * reach };
8095
+ if (reach) rect2 = { x: rect2.x - reach, y: rect2.y - reach, width: rect2.width + 2 * reach, height: rect2.height + 2 * reach };
7551
8096
  const base = { ...lower, blend: "normal", opacity: 1 };
7552
8097
  delete base.clipped;
7553
- const surface = await this.compositeToSurface([base, upper], rect);
8098
+ const surface = await this.compositeToSurface([base, upper], rect2);
7554
8099
  const merged = { ...lower, surface, offset: { x: 0, y: 0 } };
7555
8100
  delete merged.mask;
7556
8101
  delete merged.fillOpacity;
@@ -7632,9 +8177,9 @@ var ImageEditorController = class {
7632
8177
  if (!surface) return null;
7633
8178
  const ox = layer.type === "pixel" ? layer.offset.x : 0;
7634
8179
  const oy = layer.type === "pixel" ? layer.offset.y : 0;
7635
- const key = `${tw}x${th}:${ox},${oy}`;
8180
+ const key2 = `${tw}x${th}:${ox},${oy}`;
7636
8181
  let byKey = this.thumbCache.get(surface);
7637
- const hit = byKey?.get(key);
8182
+ const hit = byKey?.get(key2);
7638
8183
  if (hit) return hit;
7639
8184
  const out = new Uint8ClampedArray(tw * th * 4);
7640
8185
  for (let y = 0; y < th; y++) {
@@ -7654,7 +8199,7 @@ var ImageEditorController = class {
7654
8199
  byKey = /* @__PURE__ */ new Map();
7655
8200
  this.thumbCache.set(surface, byKey);
7656
8201
  }
7657
- byKey.set(key, thumb);
8202
+ byKey.set(key2, thumb);
7658
8203
  return thumb;
7659
8204
  }
7660
8205
  /**
@@ -8249,27 +8794,27 @@ var ImageEditorController = class {
8249
8794
  copyPixels(options = {}) {
8250
8795
  const { width, height } = this.naturalSize;
8251
8796
  const sel = this.state.selection?.bounds ? this.state.selection : null;
8252
- const rect = sel?.bounds ?? { x: 0, y: 0, width, height };
8797
+ const rect2 = sel?.bounds ?? { x: 0, y: 0, width, height };
8253
8798
  let data;
8254
8799
  if (options.merged) {
8255
- data = new Uint8ClampedArray(this.compositePixels(rect).data);
8800
+ data = new Uint8ClampedArray(this.compositePixels(rect2).data);
8256
8801
  } else {
8257
8802
  const id = options.layerId ?? this.state.activeLayerId;
8258
8803
  if (!id) throw new LayerOperationError("No layer given and none is active; pass layerId, or merged: true to copy the visible image.");
8259
8804
  const layer = requireLayer(this.state.layers, id).layer;
8260
8805
  if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${layer.name || id}" is not a pixel layer; copy merged, or rasterize it first.`);
8261
- data = readRegion(this.surfaceOf(layer), { x: rect.x - layer.offset.x, y: rect.y - layer.offset.y, width: rect.width, height: rect.height });
8806
+ data = readRegion(this.surfaceOf(layer), { x: rect2.x - layer.offset.x, y: rect2.y - layer.offset.y, width: rect2.width, height: rect2.height });
8262
8807
  }
8263
8808
  if (sel) {
8264
- for (let y = 0; y < rect.height; y++) {
8265
- for (let x = 0; x < rect.width; x++) {
8266
- const k = sel.data[(rect.y + y) * sel.width + rect.x + x];
8267
- const p = (y * rect.width + x) * 4 + 3;
8809
+ for (let y = 0; y < rect2.height; y++) {
8810
+ for (let x = 0; x < rect2.width; x++) {
8811
+ const k = sel.data[(rect2.y + y) * sel.width + rect2.x + x];
8812
+ const p = (y * rect2.width + x) * 4 + 3;
8268
8813
  data[p] = Math.round(data[p] * k / 255);
8269
8814
  }
8270
8815
  }
8271
8816
  }
8272
- this.clipboard = { width: rect.width, height: rect.height, x: rect.x, y: rect.y, data };
8817
+ this.clipboard = { width: rect2.width, height: rect2.height, x: rect2.x, y: rect2.y, data };
8273
8818
  return this.clipboard;
8274
8819
  }
8275
8820
  /** Edit › Cut: Copy, then clear the selected pixels, as one undo step. */
@@ -8753,8 +9298,8 @@ var ImageEditorController = class {
8753
9298
  if (!Number.isFinite(position) || at < 0 || at >= max) {
8754
9299
  throw new RangeError(`${orientation === "row" ? "y" : "x"} ${position} is outside the image (0..${max - 1}).`);
8755
9300
  }
8756
- const rect = orientation === "row" ? { x: 0, y: at, width, height: 1 } : { x: at, y: 0, width: 1, height };
8757
- return this.shapeCommand(rectSelection(width, height, rect, { antiAlias: false }), options);
9301
+ const rect2 = orientation === "row" ? { x: 0, y: at, width, height: 1 } : { x: at, y: 0, width: 1, height };
9302
+ return this.shapeCommand(rectSelection(width, height, rect2, { antiAlias: false }), options);
8758
9303
  }
8759
9304
  /** Select › Grow: adds connected pixels within `tolerance` of a selected colour (one undo step). */
8760
9305
  async growSelection(options = {}) {
@@ -8810,14 +9355,14 @@ var ImageEditorController = class {
8810
9355
  return this.setSelection(shaped, options.mode ?? "replace");
8811
9356
  }
8812
9357
  /** Rectangular marquee in natural image pixels. */
8813
- selectRect(rect, options = {}) {
9358
+ selectRect(rect2, options = {}) {
8814
9359
  const { width, height } = this.naturalSize;
8815
- return this.shapeCommand(rectSelection(width, height, rect, options), options);
9360
+ return this.shapeCommand(rectSelection(width, height, rect2, options), options);
8816
9361
  }
8817
9362
  /** Elliptical marquee inscribed in `rect`. */
8818
- selectEllipse(rect, options = {}) {
9363
+ selectEllipse(rect2, options = {}) {
8819
9364
  const { width, height } = this.naturalSize;
8820
- return this.shapeCommand(ellipseSelection(width, height, rect, options), options);
9365
+ return this.shapeCommand(ellipseSelection(width, height, rect2, options), options);
8821
9366
  }
8822
9367
  /** Lasso / polygonal lasso. */
8823
9368
  selectPolygon(points, options = {}) {
@@ -9540,6 +10085,44 @@ var ImageEditorController = class {
9540
10085
  if (!sel) return false;
9541
10086
  return this.setSelection(invert2 ? invertSelection(sel) : sel, mode);
9542
10087
  }
10088
+ /**
10089
+ * A measured report on a mask's edge (`inspect_mask_edge`): the selection (default), a layer's
10090
+ * painted mask or its vector mask (its path, without density or feather). Halo and alignment are
10091
+ * measured against the layer's own pixels for a pixel layer's masks, else the composite.
10092
+ * Read-only. Throws for an empty selection or a layer without that mask.
10093
+ */
10094
+ inspectMaskEdge(target = "selection", options = {}) {
10095
+ const { width, height } = this.naturalSize;
10096
+ let mask;
10097
+ let layer;
10098
+ if (target === "selection") {
10099
+ mask = this.getSelection();
10100
+ if (!mask?.bounds) throw new LayerOperationError("Nothing is selected: make a selection first, or inspect a layer-mask or vector-mask with layerId.");
10101
+ } else if (target === "layer-mask" || target === "vector-mask") {
10102
+ if (!options.layerId) throw new LayerOperationError(`A ${target} needs layerId (see list_layers).`);
10103
+ layer = this.getLayer(options.layerId);
10104
+ if (!layer) throw new LayerOperationError(`There is no layer "${options.layerId}" (see list_layers).`);
10105
+ if (target === "layer-mask") {
10106
+ mask = this.layerMaskCoverage(layer.id);
10107
+ if (!mask) throw new LayerOperationError(`Layer "${layer.name || layer.id}" has no painted mask; add one with add_layer_mask, or inspect its vector-mask.`);
10108
+ } else {
10109
+ const { vm } = this.requireVectorMask(layer.id);
10110
+ mask = pathToSelection(vm.components, width, height, { fillRule: vm.fillRule });
10111
+ if (vm.inverted) mask = invertSelection(mask);
10112
+ }
10113
+ if (!mask.bounds) throw new LayerOperationError(`The ${target} of "${layer.name || layer.id}" hides everything: there is no edge to inspect.`);
10114
+ } else {
10115
+ throw new LayerOperationError(`Unknown target "${String(target)}". Use selection, layer-mask or vector-mask.`);
10116
+ }
10117
+ const b = mask.bounds;
10118
+ const x = Math.max(0, b.x - 8);
10119
+ const y = Math.max(0, b.y - 8);
10120
+ const region = { x, y, width: Math.min(width, b.x + b.width + 8) - x, height: Math.min(height, b.y + b.height + 8) - y };
10121
+ const own = layer?.type === "pixel" ? this.getLayerPixels(layer.id) : null;
10122
+ const image = own ?? { ...this.compositePixels(region), x: region.x, y: region.y };
10123
+ const report = inspectMaskEdge(mask, image, options);
10124
+ return { target, ...layer ? { layerId: layer.id } : {}, ...report };
10125
+ }
9543
10126
  /** A layer's painted mask over the canvas as a selection mask; null without one (or for a depth mask). */
9544
10127
  layerMaskCoverage(layerId) {
9545
10128
  const layer = this.getLayer(layerId);
@@ -10036,15 +10619,15 @@ var ImageEditorController = class {
10036
10619
  let mask;
10037
10620
  if (!b || x1 <= x0 || y1 <= y0) mask = createSelectionMask(width, height);
10038
10621
  else {
10039
- const rect = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
10040
- const rgba = readRegion(layer.surface, rect);
10041
- const bytes = new Uint8Array(rect.width * rect.height);
10622
+ const rect2 = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
10623
+ const rgba = readRegion(layer.surface, rect2);
10624
+ const bytes = new Uint8Array(rect2.width * rect2.height);
10042
10625
  const hard = init.antiAlias === false;
10043
10626
  for (let i = 0; i < bytes.length; i++) {
10044
10627
  const a = rgba[i * 4 + 3];
10045
10628
  bytes[i] = hard ? a >= 128 ? 255 : 0 : a;
10046
10629
  }
10047
- mask = selectionFromRegion(width, height, rect, bytes);
10630
+ mask = selectionFromRegion(width, height, rect2, bytes);
10048
10631
  }
10049
10632
  if ((init.feather ?? 0) > 0 && mask.bounds) mask = modifySelection(mask, "feather", init.feather);
10050
10633
  return this.setSelection(mask.bounds ? mask : null, init.mode ?? "replace");
@@ -10403,8 +10986,8 @@ var ImageEditorController = class {
10403
10986
  delete content.mask;
10404
10987
  delete content.clipped;
10405
10988
  delete content.fillOpacity;
10406
- const rect = spatial ? canvas : this.getLayerBounds(id) ?? canvas;
10407
- const surface = await this.compositeToSurface([content], rect);
10989
+ const rect2 = spatial ? canvas : this.getLayerBounds(id) ?? canvas;
10990
+ const surface = await this.compositeToSurface([content], rect2);
10408
10991
  next = { ...layer, surface, offset: { x: 0, y: 0 } };
10409
10992
  delete next.filters;
10410
10993
  delete next.source;
@@ -10589,7 +11172,7 @@ var ImageEditorController = class {
10589
11172
  }
10590
11173
  if (src.colorSpace !== "rgb") throw new LayerOperationError("Colours are given as #rrggbb, so the source profile must be RGB.");
10591
11174
  const t = Transform.create(src, dst, { intent: options.intent ?? "relative", bpc: options.bpc ?? true });
10592
- const toLab = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
11175
+ const toLab2 = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
10593
11176
  return colors.map((input) => {
10594
11177
  const m = /^#?([0-9a-f]{6})$/i.exec(input.trim());
10595
11178
  if (!m) throw new LayerOperationError(`Invalid colour "${input}"; use #rrggbb.`);
@@ -10599,7 +11182,7 @@ var ImageEditorController = class {
10599
11182
  t.eval(rgb, out);
10600
11183
  const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
10601
11184
  const l = new Float32Array(3);
10602
- toLab.eval(rgb, l);
11185
+ toLab2.eval(rgb, l);
10603
11186
  const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
10604
11187
  const res = { input, values, lab };
10605
11188
  if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
@@ -12046,7 +12629,7 @@ function layersOf(args) {
12046
12629
  add2(args.layerId);
12047
12630
  add2(args.id);
12048
12631
  add2(args.target);
12049
- for (const key of ["ids", "layerIds"]) if (Array.isArray(args[key])) args[key].forEach(add2);
12632
+ for (const key2 of ["ids", "layerIds"]) if (Array.isArray(args[key2])) args[key2].forEach(add2);
12050
12633
  return out.slice(0, 6);
12051
12634
  }
12052
12635
  function planImageToolPlayback(tool, args) {
@@ -12059,9 +12642,9 @@ function planImageToolPlayback(tool, args) {
12059
12642
  else if (box) steps.push({ type: "move", to: box.from }, { type: "press" }, { type: "drag", to: box.to }, { type: "release" });
12060
12643
  else if (num(args.x) && num(args.y)) steps.push({ type: "move", to: { x: args.x, y: args.y } }, { type: "press" }, { type: "release" });
12061
12644
  else if (tool === "set_adjustments" && args.values && typeof args.values === "object") {
12062
- for (const key of Object.keys(args.values).slice(0, 4)) {
12063
- const m = /^layers\.([^.]+)\.(.+)$/.exec(key);
12064
- steps.push({ type: "target", ref: m ? { kind: "control", id: m[2] } : { kind: "control", id: key } });
12645
+ for (const key2 of Object.keys(args.values).slice(0, 4)) {
12646
+ const m = /^layers\.([^.]+)\.(.+)$/.exec(key2);
12647
+ steps.push({ type: "target", ref: m ? { kind: "control", id: m[2] } : { kind: "control", id: key2 } });
12065
12648
  }
12066
12649
  } else for (const id of layersOf(args)) steps.push({ type: "target", ref: { kind: IMAGE_PLAYBACK_KIND, id } });
12067
12650
  if (typeof args.text === "string" && args.text.trim()) {
@@ -12237,10 +12820,10 @@ function sampleOptions(args) {
12237
12820
  return { sampleMerged: args.sampleMerged === true, sample: args.sample ?? "composite" };
12238
12821
  }
12239
12822
  function rectOf(args) {
12240
- const rect = { x: num2(args.x, NaN), y: num2(args.y, NaN), width: num2(args.width, NaN), height: num2(args.height, NaN) };
12241
- if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) throw new Error("Pass numeric x, y, width and height in natural image pixels.");
12242
- if (rect.width <= 0 || rect.height <= 0) throw new Error("width and height must be greater than 0.");
12243
- return rect;
12823
+ const rect2 = { x: num2(args.x, NaN), y: num2(args.y, NaN), width: num2(args.width, NaN), height: num2(args.height, NaN) };
12824
+ 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.");
12825
+ if (rect2.width <= 0 || rect2.height <= 0) throw new Error("width and height must be greater than 0.");
12826
+ return rect2;
12244
12827
  }
12245
12828
  function summary(controller) {
12246
12829
  const sel = controller.getSelection();
@@ -12271,6 +12854,26 @@ function createImageSelectionTools(controller) {
12271
12854
  annotations: { title: "Read the selection", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12272
12855
  handler: () => summary(controller)
12273
12856
  };
12857
+ const inspectEdge = {
12858
+ name: "inspect_mask_edge",
12859
+ title: "Inspect a mask edge",
12860
+ description: "Measures the edge of the selection (default), a layer's painted mask (layer-mask) or its vector mask (vector-mask), so you can check it without looking. Returns haloScore 0-1 (background colour left just inside the edge; worse above 0.35), edgeAlignment 0-1 (share of the edge on strong image edges; worse below 0.5), jaggedness (outline turning in radians per 100 px beyond what closing it needs; smooth outlines score under 3, worse above 15), softFraction 0-1 (share of edge pixels partly selected), holes and islands (count, total area, the 10 largest with bounds), worst (up to 5 regions with a reason, ready for capture_view rect) and issues (findings with a suggested fix; empty when clean). Measures a pixel layer's masks against its own pixels, otherwise the composite. Read-only: inspect, fix the issues, inspect again.",
12861
+ inputSchema: {
12862
+ type: "object",
12863
+ properties: {
12864
+ target: { type: "string", enum: ["selection", "layer-mask", "vector-mask"], description: "What to inspect (default selection). layer-mask and vector-mask need layerId." },
12865
+ layerId: { type: "string", description: "The layer whose mask to inspect (see list_layers); only with layer-mask or vector-mask." },
12866
+ minArea: { type: "number", minimum: 1, maximum: 1e6, description: "Holes and islands smaller than this many pixels are left out (default 16)." }
12867
+ },
12868
+ additionalProperties: false
12869
+ },
12870
+ annotations: { title: "Inspect a mask edge", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12871
+ handler: ({ target, layerId, minArea }) => {
12872
+ const t = target ?? "selection";
12873
+ if (t === "selection" && layerId !== void 0) throw new Error("layerId is only for target layer-mask or vector-mask; leave it out to inspect the selection.");
12874
+ return controller.inspectMaskEdge(t, { layerId, minArea });
12875
+ }
12876
+ };
12274
12877
  const selectRegion = {
12275
12878
  name: "select_region",
12276
12879
  title: "Select a region",
@@ -12315,12 +12918,12 @@ function createImageSelectionTools(controller) {
12315
12918
  break;
12316
12919
  case "rect":
12317
12920
  case "ellipse": {
12318
- const rect = { x: num2(x, NaN), y: num2(y, NaN), width: num2(width, NaN), height: num2(height, NaN) };
12319
- if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) {
12921
+ const rect2 = { x: num2(x, NaN), y: num2(y, NaN), width: num2(width, NaN), height: num2(height, NaN) };
12922
+ if (![rect2.x, rect2.y, rect2.width, rect2.height].every(Number.isFinite)) {
12320
12923
  throw new Error("rect and ellipse need numeric x, y, width and height.");
12321
12924
  }
12322
- if (shape === "rect") controller.selectRect(rect, opts);
12323
- else controller.selectEllipse(rect, opts);
12925
+ if (shape === "rect") controller.selectRect(rect2, opts);
12926
+ else controller.selectEllipse(rect2, opts);
12324
12927
  break;
12325
12928
  }
12326
12929
  case "polygon": {
@@ -12691,10 +13294,10 @@ function createImageSelectionTools(controller) {
12691
13294
  },
12692
13295
  annotations: { ...writeAnnotations("Object Selection"), openWorldHint: true },
12693
13296
  handler: async (args, context) => {
12694
- const rect = args.rect;
13297
+ const rect2 = args.rect;
12695
13298
  const point = args.point;
12696
- if (!rect && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
12697
- if (rect && point) throw new Error("Pass rect or point, not both.");
13299
+ if (!rect2 && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
13300
+ if (rect2 && point) throw new Error("Pass rect or point, not both.");
12698
13301
  const options = {
12699
13302
  mode: parseSelectionMode(args.mode),
12700
13303
  sampleAllLayers: args.sampleAllLayers,
@@ -12702,7 +13305,7 @@ function createImageSelectionTools(controller) {
12702
13305
  useModel: args.useModel,
12703
13306
  signal: context?.signal
12704
13307
  };
12705
- const { method } = rect ? await controller.selectObject({ rect: { x: num2(rect.x, NaN), y: num2(rect.y, NaN), width: num2(rect.width, NaN), height: num2(rect.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
13308
+ 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);
12706
13309
  return { ...summary(controller), method };
12707
13310
  }
12708
13311
  };
@@ -13316,6 +13919,7 @@ function createImageSelectionTools(controller) {
13316
13919
  };
13317
13920
  return [
13318
13921
  getSelection,
13922
+ inspectEdge,
13319
13923
  selectAll,
13320
13924
  deselect,
13321
13925
  invert2,
@@ -13454,7 +14058,7 @@ async function renderImageRegion(controller, request) {
13454
14058
  out.height = request.height;
13455
14059
  const context = out.getContext("2d");
13456
14060
  if (!context) throw new Error(NO_CANVAS);
13457
- const { rect } = request;
14061
+ const { rect: rect2 } = request;
13458
14062
  context.imageSmoothingEnabled = request.scale < 1;
13459
14063
  if (request.format === "jpeg") {
13460
14064
  context.fillStyle = "#ffffff";
@@ -13462,22 +14066,22 @@ async function renderImageRegion(controller, request) {
13462
14066
  }
13463
14067
  const kx = view.width / state.imageDimensions.width;
13464
14068
  const ky = view.height / state.imageDimensions.height;
13465
- context.drawImage(view, rect.x * kx, rect.y * ky, rect.width * kx, rect.height * ky, 0, 0, out.width, out.height);
14069
+ context.drawImage(view, rect2.x * kx, rect2.y * ky, rect2.width * kx, rect2.height * ky, 0, 0, out.width, out.height);
13466
14070
  if (request.includeOverlays) {
13467
14071
  const s = request.scale;
13468
14072
  const maskView = maskViewCanvas(controller, document);
13469
- if (maskView) context.drawImage(maskView, rect.x, rect.y, rect.width, rect.height, 0, 0, out.width, out.height);
14073
+ if (maskView) context.drawImage(maskView, rect2.x, rect2.y, rect2.width, rect2.height, 0, 0, out.width, out.height);
13470
14074
  const selection = state.selection;
13471
14075
  if (selection?.bounds && selection.width === state.imageDimensions.width && selection.height === state.imageDimensions.height) {
13472
14076
  const shade = selectionShade(selection, document);
13473
- if (shade) context.drawImage(shade, rect.x, rect.y, rect.width, rect.height, 0, 0, out.width, out.height);
14077
+ if (shade) context.drawImage(shade, rect2.x, rect2.y, rect2.width, rect2.height, 0, 0, out.width, out.height);
13474
14078
  }
13475
14079
  if (state.crop) {
13476
14080
  context.save();
13477
14081
  context.strokeStyle = "#ffd400";
13478
14082
  context.lineWidth = 2;
13479
14083
  context.setLineDash([6, 4]);
13480
- context.strokeRect((state.crop.x - rect.x) * s, (state.crop.y - rect.y) * s, state.crop.width * s, state.crop.height * s);
14084
+ context.strokeRect((state.crop.x - rect2.x) * s, (state.crop.y - rect2.y) * s, state.crop.width * s, state.crop.height * s);
13481
14085
  context.restore();
13482
14086
  }
13483
14087
  }
@@ -14779,19 +15383,19 @@ var SelectionGesture = class {
14779
15383
  const { tool } = this.getOptions();
14780
15384
  if (tool === "quick") return null;
14781
15385
  if (tool === "lasso") return this.points.length > 1 ? this.points.slice() : null;
14782
- const rect = normalizeRect({
15386
+ const rect2 = normalizeRect({
14783
15387
  x: this.start.x,
14784
15388
  y: this.start.y,
14785
15389
  width: this.current.x - this.start.x,
14786
15390
  height: this.current.y - this.start.y
14787
15391
  });
14788
- if (rect.width < 1 && rect.height < 1) return null;
14789
- if (tool === "ellipse") return ellipsePoints(rect);
15392
+ if (rect2.width < 1 && rect2.height < 1) return null;
15393
+ if (tool === "ellipse") return ellipsePoints(rect2);
14790
15394
  return [
14791
- { x: rect.x, y: rect.y },
14792
- { x: rect.x + rect.width, y: rect.y },
14793
- { x: rect.x + rect.width, y: rect.y + rect.height },
14794
- { x: rect.x, y: rect.y + rect.height }
15395
+ { x: rect2.x, y: rect2.y },
15396
+ { x: rect2.x + rect2.width, y: rect2.y },
15397
+ { x: rect2.x + rect2.width, y: rect2.y + rect2.height },
15398
+ { x: rect2.x, y: rect2.y + rect2.height }
14795
15399
  ];
14796
15400
  }
14797
15401
  /** Pointer down. Resolves once a wand click has been applied. */
@@ -14947,11 +15551,11 @@ var SelectionGesture = class {
14947
15551
  const options = this.getOptions();
14948
15552
  const mode = selectionModeFromModifiers(modifiers ?? this.modifiers, options.mode);
14949
15553
  if (options.tool === "object") {
14950
- const rect2 = normalizeRect({ x: this.start.x, y: this.start.y, width: this.current.x - this.start.x, height: this.current.y - this.start.y });
15554
+ 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 });
14951
15555
  this.start = null;
14952
15556
  this.current = null;
14953
15557
  this.points = [];
14954
- const target = rect2.width >= 4 && rect2.height >= 4 ? { rect: rect2 } : { point: { x: rect2.x + rect2.width / 2, y: rect2.y + rect2.height / 2 } };
15558
+ const target = rect3.width >= 4 && rect3.height >= 4 ? { rect: rect3 } : { point: { x: rect3.x + rect3.width / 2, y: rect3.y + rect3.height / 2 } };
14955
15559
  this.pending = this.controller.selectObject(target, { mode, sampleAllLayers: options.objectSampleAll ?? true, enhanceEdge: options.objectEnhance ?? false }).then(
14956
15560
  () => void 0,
14957
15561
  (err) => {
@@ -14981,7 +15585,7 @@ var SelectionGesture = class {
14981
15585
  return false;
14982
15586
  }
14983
15587
  const path = this.draftPath;
14984
- const rect = normalizeRect({
15588
+ const rect2 = normalizeRect({
14985
15589
  x: this.start.x,
14986
15590
  y: this.start.y,
14987
15591
  width: this.current.x - this.start.x,
@@ -14993,7 +15597,7 @@ var SelectionGesture = class {
14993
15597
  if (!path) return mode === "replace" ? this.controller.deselect() : false;
14994
15598
  const opts = { mode, antiAlias: options.antiAlias, feather: options.feather ?? 0 };
14995
15599
  if (options.tool === "lasso") return path.length >= 3 ? this.controller.selectPolygon(path, opts) : false;
14996
- return options.tool === "ellipse" ? this.controller.selectEllipse(rect, opts) : this.controller.selectRect(rect, opts);
15600
+ return options.tool === "ellipse" ? this.controller.selectEllipse(rect2, opts) : this.controller.selectRect(rect2, opts);
14997
15601
  }
14998
15602
  /** Closes a polygonal lasso and selects it. Returns true when the selection changed. */
14999
15603
  close() {
@@ -15017,11 +15621,11 @@ var SelectionGesture = class {
15017
15621
  * Keys for a polygonal lasso being drawn: Enter closes it, Escape cancels, Backspace / Delete
15018
15622
  * removes the last corner. Returns true when the key was used (the host prevents its default).
15019
15623
  */
15020
- key(key) {
15624
+ key(key2) {
15021
15625
  if (!this.polygon && !this.magnetic) return false;
15022
- if (key === "Enter") this.close();
15023
- else if (key === "Escape") this.cancel();
15024
- else if (key === "Backspace" || key === "Delete") {
15626
+ if (key2 === "Enter") this.close();
15627
+ else if (key2 === "Escape") this.cancel();
15628
+ else if (key2 === "Backspace" || key2 === "Delete") {
15025
15629
  const m = this.magnetic;
15026
15630
  if (m) {
15027
15631
  m.anchors.pop();
@@ -15121,7 +15725,7 @@ var SelectionGesture = class {
15121
15725
  };
15122
15726
  function handleSelectionShortcut(controller, e, onError) {
15123
15727
  if (!(e.ctrlKey || e.metaKey)) return false;
15124
- const key = e.key.toLowerCase();
15728
+ const key2 = e.key.toLowerCase();
15125
15729
  const run = (fn) => {
15126
15730
  try {
15127
15731
  fn();
@@ -15130,11 +15734,11 @@ function handleSelectionShortcut(controller, e, onError) {
15130
15734
  }
15131
15735
  return true;
15132
15736
  };
15133
- if (key === "c") return run(() => controller.copyPixels({ merged: Boolean(e.shiftKey) }));
15134
- if (key === "x" && !e.shiftKey) return run(() => controller.cutPixels());
15135
- if (key === "v") return run(() => controller.pastePixels(e.shiftKey && e.altKey ? "into" : e.shiftKey ? "in-place" : "new-layer"));
15136
- if (key === "d") return run(() => e.shiftKey ? controller.reselect() : controller.deselect());
15137
- if (key === "i" && e.shiftKey) return run(() => controller.invertSelection());
15737
+ if (key2 === "c") return run(() => controller.copyPixels({ merged: Boolean(e.shiftKey) }));
15738
+ if (key2 === "x" && !e.shiftKey) return run(() => controller.cutPixels());
15739
+ if (key2 === "v") return run(() => controller.pastePixels(e.shiftKey && e.altKey ? "into" : e.shiftKey ? "in-place" : "new-layer"));
15740
+ if (key2 === "d") return run(() => e.shiftKey ? controller.reselect() : controller.deselect());
15741
+ if (key2 === "i" && e.shiftKey) return run(() => controller.invertSelection());
15138
15742
  return false;
15139
15743
  }
15140
15744
  function handleMaskViewKey(controller, e) {
@@ -15284,14 +15888,14 @@ function handlePaintShortcut(controller, e) {
15284
15888
  if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
15285
15889
  const brush = controller.getState().paint.brush;
15286
15890
  const set = (b) => controller.setPaintOptions({ brush: b });
15287
- const key = e.key;
15891
+ const key2 = e.key;
15288
15892
  if (handleMaskViewKey(controller, e)) return true;
15289
- switch (key.toLowerCase()) {
15893
+ switch (key2.toLowerCase()) {
15290
15894
  case "q":
15291
15895
  controller.setQuickMaskMode(!controller.quickMaskEditing);
15292
15896
  return true;
15293
15897
  case "b":
15294
- controller.setTool(e.shiftKey || key === "B" ? "mixer" : "brush");
15898
+ controller.setTool(e.shiftKey || key2 === "B" ? "mixer" : "brush");
15295
15899
  return true;
15296
15900
  case "e":
15297
15901
  controller.setTool("eraser");
@@ -15303,11 +15907,11 @@ function handlePaintShortcut(controller, e) {
15303
15907
  controller.setTool("clone");
15304
15908
  return true;
15305
15909
  case "j":
15306
- controller.setTool(e.shiftKey || key === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
15910
+ controller.setTool(e.shiftKey || key2 === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
15307
15911
  return true;
15308
15912
  case "o": {
15309
15913
  const cur = controller.getState().activeTool;
15310
- const next = e.shiftKey || key === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
15914
+ const next = e.shiftKey || key2 === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
15311
15915
  controller.setTool(next);
15312
15916
  return true;
15313
15917
  }
@@ -15321,18 +15925,18 @@ function handlePaintShortcut(controller, e) {
15321
15925
  controller.swapPaintColors();
15322
15926
  return true;
15323
15927
  }
15324
- if (key === "[" || key === "{") {
15325
- if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
15928
+ if (key2 === "[" || key2 === "{") {
15929
+ if (e.shiftKey || key2 === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
15326
15930
  else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
15327
15931
  return true;
15328
15932
  }
15329
- if (key === "]" || key === "}") {
15330
- if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
15933
+ if (key2 === "]" || key2 === "}") {
15934
+ if (e.shiftKey || key2 === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
15331
15935
  else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
15332
15936
  return true;
15333
15937
  }
15334
- if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
15335
- set({ opacity: key === "0" ? 1 : Number(key) / 10 });
15938
+ if (/^[0-9]$/.test(key2) && isPaintTool(controller.getState().activeTool)) {
15939
+ set({ opacity: key2 === "0" ? 1 : Number(key2) / 10 });
15336
15940
  return true;
15337
15941
  }
15338
15942
  return false;
@@ -18136,11 +18740,11 @@ function scalePixels(pixels, w, h) {
18136
18740
  return { width: w, height: h, data: out };
18137
18741
  }
18138
18742
  function scaleEdit(edit, s) {
18139
- const pt = (p) => ({ x: p.x * s, y: p.y * s });
18743
+ const pt2 = (p) => ({ x: p.x * s, y: p.y * s });
18140
18744
  if (edit.kind === "object") {
18141
- 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 && pt(edit.point) };
18745
+ 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) };
18142
18746
  }
18143
- const scaled = { ...edit, points: edit.points.map(pt) };
18747
+ const scaled = { ...edit, points: edit.points.map(pt2) };
18144
18748
  if ("size" in edit && edit.size !== void 0) scaled.size = Math.max(1, edit.size * s);
18145
18749
  return scaled;
18146
18750
  }
@@ -18432,8 +19036,8 @@ var SelectAndMaskSession = class {
18432
19036
  if (!live || !this.dragStart) return;
18433
19037
  if (live.kind === "object") {
18434
19038
  const a = this.dragStart;
18435
- const rect = { 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) };
18436
- this.live = rect.width >= 4 && rect.height >= 4 ? { kind: "object", rect, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
19039
+ 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) };
19040
+ this.live = rect2.width >= 4 && rect2.height >= 4 ? { kind: "object", rect: rect2, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
18437
19041
  return;
18438
19042
  }
18439
19043
  const last = live.points[live.points.length - 1];
@@ -18470,8 +19074,8 @@ var SelectAndMaskSession = class {
18470
19074
  * Show Original), J (Show Edge), P (Show Original), a tool letter, [ and ] (brush size).
18471
19075
  * Returns true when the key was used.
18472
19076
  */
18473
- handleKey(key) {
18474
- const k = key.toLowerCase();
19077
+ handleKey(key2) {
19078
+ const k = key2.toLowerCase();
18475
19079
  const view = SELECT_AND_MASK_VIEWS.find((v) => v.key === k);
18476
19080
  if (view) {
18477
19081
  this.update({ view: view.id });
@@ -18549,10 +19153,10 @@ function resolveMosaicRegionBounds(region, imgWidth, imgHeight) {
18549
19153
  if (x1 <= x0 || y1 <= y0) return null;
18550
19154
  return { x0, y0, x1, y1 };
18551
19155
  }
18552
- function applyRegionMosaic(imageData, regions, blockSize = 12) {
19156
+ function applyRegionMosaic(imageData, regions2, blockSize = 12) {
18553
19157
  const { data, width, height } = imageData;
18554
19158
  const step = Math.max(2, Math.round(blockSize));
18555
- for (const region of regions) {
19159
+ for (const region of regions2) {
18556
19160
  const bounds = resolveMosaicRegionBounds(region, width, height);
18557
19161
  if (!bounds) continue;
18558
19162
  const { x0, y0, x1, y1 } = bounds;
@@ -18594,10 +19198,10 @@ function applyRegionMosaic(imageData, regions, blockSize = 12) {
18594
19198
  }
18595
19199
  return imageData;
18596
19200
  }
18597
- function applyRegionBlur(imageData, regions, radius = 10) {
19201
+ function applyRegionBlur(imageData, regions2, radius = 10) {
18598
19202
  const { data, width, height } = imageData;
18599
19203
  const rad = Math.max(1, Math.min(64, Math.round(radius)));
18600
- for (const region of regions) {
19204
+ for (const region of regions2) {
18601
19205
  const bounds = resolveMosaicRegionBounds(region, width, height);
18602
19206
  if (!bounds) continue;
18603
19207
  const { x0, y0, x1, y1 } = bounds;
@@ -19148,9 +19752,9 @@ function gradientControls(prefix, g) {
19148
19752
  { key: `${prefix}.align`, label: "Align with layer", type: "checkbox", value: g.align }
19149
19753
  ];
19150
19754
  }
19151
- var px = (key, label, value, max = 250) => ({ key, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
19152
- var pct = (key, label, value, min = 0, max = 1) => ({ key, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
19153
- var deg = (key, label, value, min = -180, max = 180) => ({ key, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
19755
+ var px = (key2, label, value, max = 250) => ({ key: key2, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
19756
+ var pct = (key2, label, value, min = 0, max = 1) => ({ key: key2, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
19757
+ var deg = (key2, label, value, min = -180, max = 180) => ({ key: key2, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
19154
19758
  function layerEffectControls(e, patterns = []) {
19155
19759
  const blended = e.kind === "bevel-emboss" ? [] : [
19156
19760
  { key: "blend", label: "Blend mode", type: "select", value: e.blend, options: BLENDS },
@@ -19235,8 +19839,8 @@ function fillOfType(current, type, patterns) {
19235
19839
  if (type === "pattern") return { type: "pattern", patternId: patterns[0]?.id ?? "checker", scale: 1 };
19236
19840
  return { type: "color", color: current.type === "color" ? current.color : "#000000" };
19237
19841
  }
19238
- function effectControlChange(effect, key, value, patterns = []) {
19239
- const [top, ...rest] = key.split(".");
19842
+ function effectControlChange(effect, key2, value, patterns = []) {
19843
+ const [top, ...rest] = key2.split(".");
19240
19844
  if (top === "fill" && rest[0] === "type") return { fill: fillOfType(effect.fill, String(value), patterns) };
19241
19845
  if (top === "patternId" && !rest.length) {
19242
19846
  const name = patternOptions(patterns).find((o) => o.value === value)?.label;
@@ -20088,7 +20692,9 @@ var IMAGE_TOOL_GROUPS = [
20088
20692
  { id: "focus", label: "Blur & Sharpen", tools: ["blur", "sharpen", "smudge"] },
20089
20693
  { id: "tone", label: "Dodge & Burn", tools: ["dodge", "burn", "sponge"] },
20090
20694
  { id: "shape", label: "Shape", tools: ["shape"] },
20695
+ { id: "pen", label: "Pen", tools: ["path-pen"] },
20091
20696
  { id: "type", label: "Type", tools: ["type"] },
20697
+ { id: "path-select", label: "Path Selection", tools: ["path-select", "direct-select"] },
20092
20698
  { id: "annotate", label: "Annotate", tools: ["pen", "line", "arrow", "rect", "circle"] }
20093
20699
  ];
20094
20700
  function imageToolGroup(tool) {
@@ -20099,7 +20705,10 @@ var OTHER_TOOL_LABELS = {
20099
20705
  crop: "Crop",
20100
20706
  shape: "Shape",
20101
20707
  type: "Type",
20102
- pen: "Pen",
20708
+ "path-pen": "Pen",
20709
+ "path-select": "Path Selection",
20710
+ "direct-select": "Direct Selection",
20711
+ pen: "Freehand",
20103
20712
  line: "Line",
20104
20713
  arrow: "Arrow",
20105
20714
  rect: "Rectangle",
@@ -20115,10 +20724,13 @@ var IMAGE_TOOL_SHORTCUTS = {
20115
20724
  select: "M",
20116
20725
  crop: "C",
20117
20726
  shape: "U",
20118
- type: "T"
20727
+ type: "T",
20728
+ "path-pen": "P",
20729
+ "path-select": "A",
20730
+ "direct-select": "Shift+A"
20119
20731
  };
20120
- function imageToolForShortcut(key, shift = false) {
20121
- const shortcut = `${shift ? "Shift+" : ""}${key.toUpperCase()}`;
20732
+ function imageToolForShortcut(key2, shift = false) {
20733
+ const shortcut = `${shift ? "Shift+" : ""}${key2.toUpperCase()}`;
20122
20734
  for (const group of IMAGE_TOOL_GROUPS) {
20123
20735
  for (const tool of group.tools) if (IMAGE_TOOL_SHORTCUTS[tool] === shortcut) return tool;
20124
20736
  }
@@ -20175,6 +20787,422 @@ function createImageEditorDocument(controller, options = {}) {
20175
20787
  });
20176
20788
  }
20177
20789
 
20790
+ // src/image-editor/path-edit.ts
20791
+ var PATH_EDIT_TOOLS = ["path-select", "direct-select", "path-pen"];
20792
+ function isPathEditTool(tool) {
20793
+ return PATH_EDIT_TOOLS.includes(tool);
20794
+ }
20795
+ var key = (r) => `${r.component}:${r.subpath}:${r.anchor}`;
20796
+ var parseKey = (k) => {
20797
+ const [component, subpath, anchor] = k.split(":").map(Number);
20798
+ return { component, subpath, anchor };
20799
+ };
20800
+ var pt = (p) => ({ x: p.x, y: p.y });
20801
+ 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 } : {} }));
20802
+ var sameTarget = (a, b) => !!a && !!b && a.kind === b.kind && (a.kind === "path" ? a.id === b.id : a.layerId === b.layerId);
20803
+ var PathEditGesture = class {
20804
+ constructor(controller) {
20805
+ this.controller = controller;
20806
+ }
20807
+ controller;
20808
+ /** Selected anchors, as `component:subpath:anchor` keys. */
20809
+ selected = /* @__PURE__ */ new Set();
20810
+ target = null;
20811
+ drag = null;
20812
+ /** The Pen's open subpath being drawn. */
20813
+ drawing = null;
20814
+ pointer = null;
20815
+ scale = 1;
20816
+ error = null;
20817
+ /** Screen pixels per document pixel (the viewport projection's scale). */
20818
+ setView(scale) {
20819
+ if (scale > 0 && Number.isFinite(scale)) this.scale = scale;
20820
+ }
20821
+ get active() {
20822
+ return this.drag !== null;
20823
+ }
20824
+ /** The anchors selected now (on the current target). */
20825
+ get selection() {
20826
+ return [...this.selected].map(parseKey);
20827
+ }
20828
+ /** True while the Pen has an open subpath it adds to. */
20829
+ get isDrawing() {
20830
+ return this.drawing !== null;
20831
+ }
20832
+ get tolerance() {
20833
+ return 6 / this.scale;
20834
+ }
20835
+ /** The target's components, or null (and the selection dropped) when the target changed or is gone. */
20836
+ components() {
20837
+ const target = this.controller.pathEditTarget();
20838
+ if (!sameTarget(target, this.target)) {
20839
+ this.target = target;
20840
+ this.selected.clear();
20841
+ this.drawing = null;
20842
+ }
20843
+ if (!target) return null;
20844
+ let cs;
20845
+ try {
20846
+ cs = this.controller.getPathEditComponents(target);
20847
+ } catch {
20848
+ return null;
20849
+ }
20850
+ for (const k of [...this.selected]) {
20851
+ const r = parseKey(k);
20852
+ if (!cs[r.component]?.subpaths[r.subpath]?.anchors[r.anchor]) this.selected.delete(k);
20853
+ }
20854
+ const d = this.drawing;
20855
+ if (d && (!cs[d.component]?.subpaths[d.subpath] || cs[d.component].subpaths[d.subpath].closed)) this.drawing = null;
20856
+ return cs;
20857
+ }
20858
+ anchorAt(cs, p) {
20859
+ const tol = this.tolerance;
20860
+ let best = null;
20861
+ let bestD = tol;
20862
+ cs.forEach(
20863
+ (c, ci) => c.subpaths.forEach(
20864
+ (sp, si) => sp.anchors.forEach((a, ai) => {
20865
+ const d = Math.hypot(a.x - p.x, a.y - p.y);
20866
+ if (d <= bestD) {
20867
+ bestD = d;
20868
+ best = { component: ci, subpath: si, anchor: ai };
20869
+ }
20870
+ })
20871
+ )
20872
+ );
20873
+ return best;
20874
+ }
20875
+ /** A handle of a selected anchor near `p`. */
20876
+ handleAt(cs, p) {
20877
+ const tol = this.tolerance;
20878
+ for (const r of this.selection) {
20879
+ const a = cs[r.component].subpaths[r.subpath].anchors[r.anchor];
20880
+ for (const side of ["out", "in"]) {
20881
+ const h = a[side];
20882
+ if (h && Math.hypot(h.x - p.x, h.y - p.y) <= tol) return { ref: r, side };
20883
+ }
20884
+ }
20885
+ return null;
20886
+ }
20887
+ /** The segment near `p`: its first anchor (as `anchor`) and the parameter on it. */
20888
+ segmentAt(cs, p) {
20889
+ let best = null;
20890
+ let bestD = this.tolerance;
20891
+ cs.forEach(
20892
+ (c, ci) => c.subpaths.forEach((sp, si) => {
20893
+ for (let i = 0; i < subpathSegmentCount(sp); i++) {
20894
+ const { t, distance } = segmentNearest(subpathSegment(sp, i), [p.x, p.y]);
20895
+ if (distance <= bestD) {
20896
+ bestD = distance;
20897
+ best = { ref: { component: ci, subpath: si, anchor: i }, t };
20898
+ }
20899
+ }
20900
+ })
20901
+ );
20902
+ return best;
20903
+ }
20904
+ /** The component under `p`: on its outline, or inside its fill. */
20905
+ componentAt(cs, p) {
20906
+ const seg = this.segmentAt(cs, p) ?? (this.anchorAt(cs, p) ? { ref: this.anchorAt(cs, p) } : null);
20907
+ if (seg) return seg.ref.component;
20908
+ for (let i = cs.length - 1; i >= 0; i--) {
20909
+ const closed = cs[i].subpaths.filter((sp) => sp.anchors.length > 2);
20910
+ if (closed.length && pointInBezierPath(subpathsToBezier(closed), [p.x, p.y])) return i;
20911
+ }
20912
+ return null;
20913
+ }
20914
+ selectComponent(cs, ci) {
20915
+ cs[ci].subpaths.forEach((sp, si) => sp.anchors.forEach((_, ai) => this.selected.add(key({ component: ci, subpath: si, anchor: ai }))));
20916
+ }
20917
+ commit(cs, record) {
20918
+ if (!this.target) return false;
20919
+ try {
20920
+ this.controller.setPathEditComponents(this.target, cs, record);
20921
+ return true;
20922
+ } catch (e) {
20923
+ this.error = e instanceof Error ? e.message : String(e);
20924
+ return false;
20925
+ }
20926
+ }
20927
+ down(p, tool, mods = {}) {
20928
+ this.error = null;
20929
+ this.pointer = pt(p);
20930
+ const cs = this.components();
20931
+ if (tool === "path-pen") return this.penDown(cs, p, mods);
20932
+ if (!cs) {
20933
+ 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).";
20934
+ return;
20935
+ }
20936
+ if (tool === "path-select") {
20937
+ const ci = this.componentAt(cs, p);
20938
+ if (ci === null) {
20939
+ this.drag = { kind: "marquee", start: pt(p), end: pt(p), keep: mods.shift ? new Set(this.selected) : /* @__PURE__ */ new Set() };
20940
+ if (!mods.shift) this.selected.clear();
20941
+ return;
20942
+ }
20943
+ const ref = { component: ci, subpath: 0, anchor: 0 };
20944
+ if (!mods.shift && !this.selected.has(key(ref))) this.selected.clear();
20945
+ this.selectComponent(cs, ci);
20946
+ this.drag = { kind: "anchors", start: pt(p), base: cloneComponents(cs), moved: false };
20947
+ return;
20948
+ }
20949
+ const handle = this.handleAt(cs, p);
20950
+ if (handle) {
20951
+ this.drag = { kind: "handle", ...handle, base: cloneComponents(cs), breakHandles: !!mods.alt, moved: false };
20952
+ return;
20953
+ }
20954
+ const anchor = this.anchorAt(cs, p);
20955
+ if (anchor) {
20956
+ if (mods.alt) return this.convert(cs, anchor);
20957
+ const k = key(anchor);
20958
+ if (mods.shift) {
20959
+ if (this.selected.has(k)) this.selected.delete(k);
20960
+ else this.selected.add(k);
20961
+ } else if (!this.selected.has(k)) {
20962
+ this.selected.clear();
20963
+ this.selected.add(k);
20964
+ }
20965
+ this.drag = { kind: "anchors", start: pt(p), base: cloneComponents(cs), moved: false };
20966
+ return;
20967
+ }
20968
+ const seg = this.segmentAt(cs, p);
20969
+ if (seg) {
20970
+ if (!mods.shift) this.selected.clear();
20971
+ this.drag = { kind: "segment", ...seg, base: cloneComponents(cs), moved: false };
20972
+ return;
20973
+ }
20974
+ this.drag = { kind: "marquee", start: pt(p), end: pt(p), keep: mods.shift ? new Set(this.selected) : /* @__PURE__ */ new Set() };
20975
+ if (!mods.shift) this.selected.clear();
20976
+ }
20977
+ /** Alt-click: a corner anchor becomes smooth, a smooth one a corner (one undo step). */
20978
+ convert(cs, r) {
20979
+ const next = cloneComponents(cs);
20980
+ const c = next[r.component];
20981
+ const sp = c.subpaths[r.subpath];
20982
+ const a = sp.anchors[r.anchor];
20983
+ const type = inferAnchorType(a) === "corner" && !a.in && !a.out ? "smooth" : "corner";
20984
+ c.subpaths[r.subpath] = convertAnchor(sp, r.anchor, type);
20985
+ delete c.live;
20986
+ this.selected.clear();
20987
+ this.selected.add(key(r));
20988
+ this.commit(next, true);
20989
+ }
20990
+ penDown(cs, p, mods) {
20991
+ const d = this.drawing;
20992
+ if (cs && d) {
20993
+ const next = cloneComponents(cs);
20994
+ const c = next[d.component];
20995
+ const sp = c.subpaths[d.subpath];
20996
+ const first = sp.anchors[0];
20997
+ delete c.live;
20998
+ if (sp.anchors.length > 1 && Math.hypot(first.x - p.x, first.y - p.y) <= this.tolerance) {
20999
+ sp.closed = true;
21000
+ this.drawing = null;
21001
+ const ref3 = { component: d.component, subpath: d.subpath, anchor: 0 };
21002
+ if (this.commit(next, true)) this.drag = { kind: "pen", ref: ref3, base: next };
21003
+ return;
21004
+ }
21005
+ sp.anchors.push({ x: p.x, y: p.y });
21006
+ const ref2 = { component: d.component, subpath: d.subpath, anchor: sp.anchors.length - 1 };
21007
+ this.selected = /* @__PURE__ */ new Set([key(ref2)]);
21008
+ if (this.commit(next, true)) this.drag = { kind: "pen", ref: ref2, base: next };
21009
+ return;
21010
+ }
21011
+ if (cs) {
21012
+ const anchor = this.anchorAt(cs, p);
21013
+ if (anchor && mods.alt) return this.convert(cs, anchor);
21014
+ if (anchor) {
21015
+ const next = cloneComponents(cs);
21016
+ const c = next[anchor.component];
21017
+ const sp = c.subpaths[anchor.subpath];
21018
+ delete c.live;
21019
+ c.subpaths[anchor.subpath] = sp.anchors.length > 2 ? removeAnchorsKeepingShape(sp, [anchor.anchor]) : removeAnchors(sp, [anchor.anchor]);
21020
+ this.selected.clear();
21021
+ this.commit(prune(next), true);
21022
+ return;
21023
+ }
21024
+ const seg = this.segmentAt(cs, p);
21025
+ if (seg) {
21026
+ const next = cloneComponents(cs);
21027
+ const c = next[seg.ref.component];
21028
+ const { subpath, index } = insertAnchor(c.subpaths[seg.ref.subpath], seg.ref.anchor, seg.t);
21029
+ c.subpaths[seg.ref.subpath] = subpath;
21030
+ delete c.live;
21031
+ this.selected = /* @__PURE__ */ new Set([key({ ...seg.ref, anchor: index })]);
21032
+ this.commit(next, true);
21033
+ return;
21034
+ }
21035
+ }
21036
+ const start = { anchors: [{ x: p.x, y: p.y }], closed: false };
21037
+ if (!this.target || !cs) {
21038
+ try {
21039
+ const path = this.controller.startWorkPath([{ op: "combine", subpaths: [start] }]);
21040
+ this.target = { kind: "path", id: path.id };
21041
+ } catch (e) {
21042
+ this.error = e instanceof Error ? e.message : String(e);
21043
+ return;
21044
+ }
21045
+ this.drawing = { component: 0, subpath: 0 };
21046
+ } else {
21047
+ const next = cloneComponents(cs);
21048
+ if (!next.length) next.push({ op: "combine", subpaths: [] });
21049
+ const ci = next.length - 1;
21050
+ delete next[ci].live;
21051
+ next[ci].subpaths.push(start);
21052
+ if (!this.commit(next, true)) return;
21053
+ this.drawing = { component: ci, subpath: next[ci].subpaths.length - 1 };
21054
+ }
21055
+ const ref = { component: this.drawing.component, subpath: this.drawing.subpath, anchor: 0 };
21056
+ this.selected = /* @__PURE__ */ new Set([key(ref)]);
21057
+ this.drag = { kind: "pen", ref, base: cloneComponents(this.controller.getPathEditComponents(this.target)) };
21058
+ }
21059
+ move(p, mods = {}) {
21060
+ this.pointer = pt(p);
21061
+ const g = this.drag;
21062
+ if (!g) return;
21063
+ if (g.kind === "marquee") {
21064
+ g.end = pt(p);
21065
+ const box = rect(g.start, g.end);
21066
+ const cs = this.target ? this.controller.getPathEditComponents(this.target) : [];
21067
+ this.selected = new Set(g.keep);
21068
+ cs.forEach(
21069
+ (c, ci) => c.subpaths.forEach(
21070
+ (sp, si) => sp.anchors.forEach((a, ai) => {
21071
+ 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 }));
21072
+ })
21073
+ )
21074
+ );
21075
+ return;
21076
+ }
21077
+ const next = cloneComponents(g.base);
21078
+ if (g.kind === "anchors") {
21079
+ let dx = p.x - g.start.x;
21080
+ let dy = p.y - g.start.y;
21081
+ if (!g.moved && Math.hypot(dx, dy) < 1 / this.scale) return;
21082
+ if (mods.shift) {
21083
+ if (Math.abs(dx) > Math.abs(dy)) dy = 0;
21084
+ else dx = 0;
21085
+ }
21086
+ for (const r of this.selection) {
21087
+ const c = next[r.component];
21088
+ c.subpaths[r.subpath].anchors[r.anchor] = translateAnchor(c.subpaths[r.subpath].anchors[r.anchor], dx, dy);
21089
+ delete c.live;
21090
+ }
21091
+ } else if (g.kind === "handle") {
21092
+ const c = next[g.ref.component];
21093
+ const a = c.subpaths[g.ref.subpath].anchors[g.ref.anchor];
21094
+ const typed = { ...a, type: a.type ?? inferAnchorType(a) };
21095
+ const moved = moveAnchorHandle(typed, g.side, p, g.breakHandles);
21096
+ delete moved.type;
21097
+ c.subpaths[g.ref.subpath].anchors[g.ref.anchor] = moved;
21098
+ delete c.live;
21099
+ } else if (g.kind === "segment") {
21100
+ const c = next[g.ref.component];
21101
+ c.subpaths[g.ref.subpath] = bendSegment(c.subpaths[g.ref.subpath], g.ref.anchor, g.t, p);
21102
+ delete c.live;
21103
+ } else {
21104
+ const c = next[g.ref.component];
21105
+ const a = c.subpaths[g.ref.subpath].anchors[g.ref.anchor];
21106
+ if (Math.hypot(p.x - a.x, p.y - a.y) < 1 / this.scale) return;
21107
+ a.out = { x: p.x, y: p.y };
21108
+ a.in = { x: 2 * a.x - p.x, y: 2 * a.y - p.y };
21109
+ delete c.live;
21110
+ this.commit(next, false);
21111
+ return;
21112
+ }
21113
+ if (this.commit(next, !g.moved)) g.moved = true;
21114
+ }
21115
+ /** The pointer moving with no button down (the Pen's preview of its next segment). */
21116
+ hover(p) {
21117
+ this.pointer = pt(p);
21118
+ }
21119
+ up() {
21120
+ this.drag = null;
21121
+ }
21122
+ /** Ends the Pen's subpath (left open). */
21123
+ finish() {
21124
+ this.drawing = null;
21125
+ this.drag = null;
21126
+ }
21127
+ cancel() {
21128
+ this.drag = null;
21129
+ this.drawing = null;
21130
+ this.selected.clear();
21131
+ }
21132
+ /**
21133
+ * Keys: Delete / Backspace remove the selected anchors (one undo step), Enter and Escape end the
21134
+ * Pen's subpath, Escape also deselects. Returns true when the key was handled.
21135
+ */
21136
+ key(k) {
21137
+ if (k === "Enter" && this.drawing) {
21138
+ this.finish();
21139
+ return true;
21140
+ }
21141
+ if (k === "Escape") {
21142
+ if (!this.drawing && !this.selected.size) return false;
21143
+ this.drawing = null;
21144
+ this.selected.clear();
21145
+ return true;
21146
+ }
21147
+ if ((k === "Delete" || k === "Backspace") && this.selected.size && !this.drawing) {
21148
+ const cs = this.components();
21149
+ if (!cs) return false;
21150
+ const next = cloneComponents(cs);
21151
+ const bySub = /* @__PURE__ */ new Map();
21152
+ for (const r of this.selection) {
21153
+ const sk = `${r.component}:${r.subpath}`;
21154
+ bySub.set(sk, [...bySub.get(sk) ?? [], r.anchor]);
21155
+ }
21156
+ for (const [sk, indices] of bySub) {
21157
+ const [ci, si] = sk.split(":").map(Number);
21158
+ next[ci].subpaths[si] = removeAnchors(next[ci].subpaths[si], indices);
21159
+ delete next[ci].live;
21160
+ }
21161
+ this.selected.clear();
21162
+ this.commit(prune(next), true);
21163
+ return true;
21164
+ }
21165
+ return false;
21166
+ }
21167
+ /** What to draw (`RenderOptions.pathEdit`) for `tool`, or null when there is no target. */
21168
+ overlay(tool) {
21169
+ const cs = this.components();
21170
+ if (!cs) return null;
21171
+ const curves = [];
21172
+ const anchors = [];
21173
+ const handles = [];
21174
+ const selectedComponents = new Set(this.selection.map((r) => r.component));
21175
+ cs.forEach(
21176
+ (c, ci) => c.subpaths.forEach((sp, si) => {
21177
+ for (let i = 0; i < subpathSegmentCount(sp); i++) curves.push(subpathSegment(sp, i).map(([x, y]) => ({ x, y })));
21178
+ sp.anchors.forEach((a, ai) => {
21179
+ const selected = this.selected.has(key({ component: ci, subpath: si, anchor: ai }));
21180
+ if (tool === "path-select" && !selectedComponents.has(ci)) return;
21181
+ anchors.push({ point: pt(a), selected });
21182
+ if (!selected || tool === "path-select") return;
21183
+ for (const side of ["in", "out"]) if (a[side]) handles.push({ point: pt(a[side]), anchor: pt(a) });
21184
+ });
21185
+ })
21186
+ );
21187
+ let preview = null;
21188
+ const d = this.drawing;
21189
+ if (tool === "path-pen" && d && this.pointer && !this.drag) {
21190
+ const sp = cs[d.component]?.subpaths[d.subpath];
21191
+ const last = sp?.anchors[sp.anchors.length - 1];
21192
+ if (last) preview = last.out ? [pt(last), pt(last.out), this.pointer, this.pointer] : [pt(last), this.pointer];
21193
+ }
21194
+ const marquee = this.drag?.kind === "marquee" ? rect(this.drag.start, this.drag.end) : null;
21195
+ return { curves, anchors, handles, marquee, preview };
21196
+ }
21197
+ };
21198
+ function rect(a, b) {
21199
+ 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) };
21200
+ }
21201
+ function prune(cs) {
21202
+ for (const c of cs) c.subpaths = c.subpaths.filter((sp) => sp.anchors.length);
21203
+ return cs.filter((c) => c.subpaths.length);
21204
+ }
21205
+
20178
21206
  // src/image-editor/content-aware-fill-session.ts
20179
21207
  var CONTENT_AWARE_FILL_TOOLS = [
20180
21208
  { id: "sampling-brush", label: "Sampling Brush", key: "b" },
@@ -20466,8 +21494,8 @@ var ContentAwareFillSession = class _ContentAwareFillSession {
20466
21494
  /**
20467
21495
  * Keys: B, H, Z for the tools, [ and ] for the brush size. Returns true when the key was used.
20468
21496
  */
20469
- handleKey(key) {
20470
- const k = key.toLowerCase();
21497
+ handleKey(key2) {
21498
+ const k = key2.toLowerCase();
20471
21499
  const tool = CONTENT_AWARE_FILL_TOOLS.find((t) => t.key === k);
20472
21500
  if (tool) {
20473
21501
  this.update({ tool: tool.id });
@@ -20607,6 +21635,7 @@ export {
20607
21635
  LayerEffectError,
20608
21636
  LayerOperationError,
20609
21637
  MARCHING_ANTS_STEP_MS,
21638
+ MASK_EDGE_THRESHOLDS,
20610
21639
  MASK_FILE_ACCEPT,
20611
21640
  MAX_BATCH,
20612
21641
  MAX_BRUSH_SIZE,
@@ -20646,6 +21675,7 @@ export {
20646
21675
  PARAM,
20647
21676
  PARAM_FLOATS,
20648
21677
  PASTE_MODES,
21678
+ PATH_EDIT_TOOLS,
20649
21679
  PATTERN_BLOCK_KEYS,
20650
21680
  PIGMENT_BANDS,
20651
21681
  PIGMENT_CHANNELS,
@@ -20675,6 +21705,7 @@ export {
20675
21705
  PaintSourceError,
20676
21706
  PaintStrokeSession,
20677
21707
  PaperGrain,
21708
+ PathEditGesture,
20678
21709
  PatternPlacement,
20679
21710
  PatternTile,
20680
21711
  PsdError,
@@ -21099,6 +22130,7 @@ export {
21099
22130
  inflateRaw,
21100
22131
  insertCurvePoint,
21101
22132
  insertLayer,
22133
+ inspectMaskEdge,
21102
22134
  instancesOf,
21103
22135
  invertHomography,
21104
22136
  invertMatrix3,
@@ -21122,6 +22154,7 @@ export {
21122
22154
  isPaintTool,
21123
22155
  isPaintableLayer,
21124
22156
  isPainterlyBlendMode,
22157
+ isPathEditTool,
21125
22158
  isPng,
21126
22159
  isQuarterTurn,
21127
22160
  isQuarterTurnSwapped,