@pixerate/editor 0.210.0 → 0.212.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/{chunk-QE7D7TZR.cjs → chunk-H65IBXFI.cjs} +74 -25
  2. package/dist/{chunk-KFIOEOCQ.js → chunk-W3TOZTL7.js} +55 -6
  3. package/dist/image-editor/content-aware-fill-session.d.cts +17 -5
  4. package/dist/image-editor/content-aware-fill-session.d.ts +17 -5
  5. package/dist/image-editor/controller.d.cts +60 -4
  6. package/dist/image-editor/controller.d.ts +60 -4
  7. package/dist/image-editor/index.cjs +1522 -1013
  8. package/dist/image-editor/index.d.cts +1 -0
  9. package/dist/image-editor/index.d.ts +1 -0
  10. package/dist/image-editor/index.js +532 -23
  11. package/dist/image-editor/layers/serialize.d.cts +5 -8
  12. package/dist/image-editor/layers/serialize.d.ts +5 -8
  13. package/dist/image-editor/paths.d.cts +41 -0
  14. package/dist/image-editor/paths.d.ts +41 -0
  15. package/dist/image-editor/psd/export.d.cts +8 -1
  16. package/dist/image-editor/psd/export.d.ts +8 -1
  17. package/dist/image-editor/psd/import.d.cts +7 -0
  18. package/dist/image-editor/psd/import.d.ts +7 -0
  19. package/dist/image-editor/psd/paths.d.cts +11 -0
  20. package/dist/image-editor/psd/paths.d.ts +11 -0
  21. package/dist/image-editor/selection/gesture.d.cts +10 -2
  22. package/dist/image-editor/selection/gesture.d.ts +10 -2
  23. package/dist/image-editor/selection/select-and-mask.d.cts +5 -0
  24. package/dist/image-editor/selection/select-and-mask.d.ts +5 -0
  25. package/dist/image-editor/types.d.cts +12 -0
  26. package/dist/image-editor/types.d.ts +12 -0
  27. package/dist/paint-editor/index.cjs +6 -6
  28. package/dist/paint-editor/index.js +1 -1
  29. package/package.json +1 -1
  30. package/skills/pixerate-image-editor/SKILL.md +1 -1
@@ -301,6 +301,7 @@ import {
301
301
  rasterizeCoverage,
302
302
  rasterizeSmartObject,
303
303
  readPsd,
304
+ readShapeComponents,
304
305
  referencePoint,
305
306
  removeAlpha,
306
307
  removeLayer,
@@ -401,7 +402,7 @@ import {
401
402
  writePsd,
402
403
  xyToTempTint,
403
404
  xyToUv
404
- } from "../chunk-KFIOEOCQ.js";
405
+ } from "../chunk-W3TOZTL7.js";
405
406
  import {
406
407
  parseCssColor
407
408
  } from "../chunk-3CI7FRMK.js";
@@ -1477,8 +1478,9 @@ var ImageEditorRenderer = class {
1477
1478
  ctx.restore();
1478
1479
  }
1479
1480
  const { width: iw, height: ih } = state.imageDimensions;
1480
- const quickMaskSel = display === "quickMask" && state.quickMaskEditing && !state.selection?.bounds ? this.emptyMask(Math.round(iw), Math.round(ih)) : null;
1481
- if (state.selection?.bounds && display !== "none" || quickMaskSel || options.draftSelectionPath?.length) {
1481
+ const shownSel = state.selectionPreview !== void 0 ? state.selectionPreview : state.selection;
1482
+ const quickMaskSel = display === "quickMask" && state.quickMaskEditing && !shownSel?.bounds ? this.emptyMask(Math.round(iw), Math.round(ih)) : null;
1483
+ if (shownSel?.bounds && display !== "none" || quickMaskSel || options.draftSelectionPath?.length) {
1482
1484
  ctx.save();
1483
1485
  try {
1484
1486
  applyImageProjectionToContext(ctx, projection);
@@ -1486,8 +1488,8 @@ var ImageEditorRenderer = class {
1486
1488
  ctx.rect(region.x, region.y, region.width, region.height);
1487
1489
  ctx.clip();
1488
1490
  if (quickMaskSel) this.drawQuickMask(ctx, quickMaskSel);
1489
- else if (state.selection?.bounds && display === "quickMask") this.drawQuickMask(ctx, state.selection);
1490
- else if (state.selection?.bounds && display === "ants") this.drawAnts(ctx, state.selection, projection.scale / u, options.antsOffset ?? 0);
1491
+ else if (shownSel?.bounds && display === "quickMask") this.drawQuickMask(ctx, shownSel);
1492
+ else if (shownSel?.bounds && display === "ants") this.drawAnts(ctx, shownSel, projection.scale / u, options.antsOffset ?? 0);
1491
1493
  if (options.draftSelectionPath?.length) this.drawDraftPath(ctx, options.draftSelectionPath, projection.scale / u);
1492
1494
  } catch {
1493
1495
  }
@@ -2861,6 +2863,58 @@ function magneticAnchorSpacing(frequency = 57) {
2861
2863
  return 8 + (100 - f);
2862
2864
  }
2863
2865
 
2866
+ // src/image-editor/paths.ts
2867
+ var WORK_PATH_NAME = "Work Path";
2868
+ var counter = 0;
2869
+ function newPathId() {
2870
+ counter = (counter + 1) % 1e6;
2871
+ return `path-${Date.now().toString(36)}-${counter.toString(36)}`;
2872
+ }
2873
+ function pathSvgData(path) {
2874
+ return subpathsToSvgPath(path.components.flatMap((c) => c.subpaths));
2875
+ }
2876
+ function pathBounds(path) {
2877
+ return vectorMaskBounds({ components: path.components });
2878
+ }
2879
+ function pathAnchorCount(path) {
2880
+ return path.components.reduce((n, c) => n + c.subpaths.reduce((m, sp) => m + sp.anchors.length, 0), 0);
2881
+ }
2882
+ function serializePaths(paths) {
2883
+ return paths.map((p) => ({
2884
+ id: p.id,
2885
+ name: p.name,
2886
+ components: JSON.parse(JSON.stringify(p.components)),
2887
+ ...p.fillRule === "evenodd" ? { fillRule: "evenodd" } : {},
2888
+ ...p.work ? { work: true } : {}
2889
+ }));
2890
+ }
2891
+ function readPaths(stored, problems = []) {
2892
+ if (!Array.isArray(stored)) return [];
2893
+ const out = [];
2894
+ const ids = /* @__PURE__ */ new Set();
2895
+ let work = false;
2896
+ for (const [i, raw] of stored.entries()) {
2897
+ const r = raw;
2898
+ const name = typeof r?.name === "string" && r.name.trim() ? r.name.trim() : `Path ${i + 1}`;
2899
+ try {
2900
+ const components = readShapeComponents(r?.components);
2901
+ let id = typeof r?.id === "string" && r.id ? r.id : newPathId();
2902
+ if (ids.has(id)) id = newPathId();
2903
+ ids.add(id);
2904
+ const path = { id, name, components };
2905
+ if (r?.fillRule === "evenodd") path.fillRule = "evenodd";
2906
+ if (r?.work === true && !work) {
2907
+ path.work = true;
2908
+ work = true;
2909
+ }
2910
+ out.push(path);
2911
+ } catch (err) {
2912
+ problems.push(`Path "${name}" could not be read (${err instanceof Error ? err.message : String(err)}) and was left out.`);
2913
+ }
2914
+ }
2915
+ return out;
2916
+ }
2917
+
2864
2918
  // src/image-editor/layers/shape-type-ui.ts
2865
2919
  var DEFAULT_SHAPE_TOOL_OPTIONS = {
2866
2920
  kind: "rect",
@@ -5643,6 +5697,7 @@ var ImageEditorController = class {
5643
5697
  outputOpacity: s.outputOpacity,
5644
5698
  selection: s.selection ?? null,
5645
5699
  ...s.channels?.length ? { channels: s.channels } : {},
5700
+ ...s.paths?.length ? { paths: s.paths } : {},
5646
5701
  source: {
5647
5702
  sourceUrl: s.sourceUrl,
5648
5703
  element: this.imageElement,
@@ -5680,6 +5735,9 @@ var ImageEditorController = class {
5680
5735
  s.selection = entry.selection;
5681
5736
  if (entry.channels?.length) s.channels = entry.channels;
5682
5737
  else delete s.channels;
5738
+ if (entry.paths?.length) s.paths = entry.paths;
5739
+ else delete s.paths;
5740
+ if (s.activePathId && !s.paths?.some((p) => p.id === s.activePathId)) s.activePathId = null;
5683
5741
  if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
5684
5742
  this.imageElement = entry.source.element;
5685
5743
  s.sourceUrl = entry.source.sourceUrl;
@@ -6491,6 +6549,138 @@ var ImageEditorController = class {
6491
6549
  const mask = pathToSelection(components, width, height, { ...options, fillRule: path.fillRule ?? options.fillRule });
6492
6550
  return this.setSelection(mask, options.mode ?? "replace");
6493
6551
  }
6552
+ // --- Document paths (the Paths panel) ---
6553
+ /** The document's paths: saved paths and the Work Path, in panel order. */
6554
+ getPaths() {
6555
+ return this.state.paths ?? [];
6556
+ }
6557
+ getPath(id) {
6558
+ return this.state.paths?.find((p) => p.id === id) ?? null;
6559
+ }
6560
+ requirePath(id) {
6561
+ const path = this.getPath(id);
6562
+ if (!path) {
6563
+ const ids = this.getPaths().map((p) => `${p.id} (${p.name})`);
6564
+ throw new LayerOperationError(`There is no path "${id}"; the paths are ${ids.join(", ") || "none (make a work path from a selection, or add one)"}.`);
6565
+ }
6566
+ return path;
6567
+ }
6568
+ /** Selects a path in the Paths panel (or none). View state: no undo step. */
6569
+ setActivePath(id) {
6570
+ if (id !== null) this.requirePath(id);
6571
+ if ((this.state.activePathId ?? null) === id) return;
6572
+ this.state.activePathId = id;
6573
+ this.notify();
6574
+ }
6575
+ commitPaths(next, active) {
6576
+ this.recordHistory();
6577
+ if (next.length) this.state.paths = next;
6578
+ else delete this.state.paths;
6579
+ if (active !== void 0) this.state.activePathId = active;
6580
+ else if (this.state.activePathId && !next.some((p) => p.id === this.state.activePathId)) this.state.activePathId = null;
6581
+ this.notify();
6582
+ }
6583
+ /**
6584
+ * Paths › Make Work Path: the selection's outline, traced, becomes the Work Path (replacing the
6585
+ * last one, unless it was saved). One undo step. Returns the path.
6586
+ */
6587
+ makeWorkPath(options = {}) {
6588
+ const subpaths = this.traceSelection(options);
6589
+ const paths = this.getPaths();
6590
+ const old = paths.find((p) => p.work);
6591
+ const work = { id: old?.id ?? newPathId(), name: WORK_PATH_NAME, components: [{ op: "combine", subpaths }], fillRule: "evenodd", work: true };
6592
+ this.commitPaths(old ? paths.map((p) => p === old ? work : p) : [...paths, work], work.id);
6593
+ return work;
6594
+ }
6595
+ /** A new saved path from geometry (`shape`, `subpaths` or `components`). One undo step. */
6596
+ addPath(input) {
6597
+ const paths = this.getPaths();
6598
+ const path = { id: newPathId(), name: input.name?.trim() || `Path ${paths.filter((p) => !p.work).length + 1}`, components: this.vectorMaskComponents(input) };
6599
+ if (input.fillRule === "evenodd") path.fillRule = "evenodd";
6600
+ this.commitPaths([...paths.filter((p) => !p.work), path, ...paths.filter((p) => p.work)], path.id);
6601
+ return path;
6602
+ }
6603
+ /** Replaces a path's geometry (its name is kept). One undo step. */
6604
+ setPathGeometry(id, input) {
6605
+ const old = this.requirePath(id);
6606
+ const path = { ...old, components: this.vectorMaskComponents(input) };
6607
+ if (input.fillRule === "evenodd") path.fillRule = "evenodd";
6608
+ else if (input.fillRule === "nonzero") delete path.fillRule;
6609
+ this.commitPaths(this.getPaths().map((p) => p.id === id ? path : p));
6610
+ return path;
6611
+ }
6612
+ /** Renames a path; renaming the Work Path saves it (Photoshop's Save Path). One undo step. */
6613
+ renamePath(id, name) {
6614
+ const old = this.requirePath(id);
6615
+ const trimmed = name.trim();
6616
+ if (!trimmed) throw new LayerOperationError("A path needs a name.");
6617
+ const path = { ...old, name: trimmed };
6618
+ delete path.work;
6619
+ this.commitPaths(this.getPaths().map((p) => p.id === id ? path : p));
6620
+ return path;
6621
+ }
6622
+ /** Duplicates a path as a saved path. One undo step. */
6623
+ duplicatePath(id, name) {
6624
+ const old = this.requirePath(id);
6625
+ const copy = { ...old, id: newPathId(), name: name?.trim() || `${old.name} copy`, components: JSON.parse(JSON.stringify(old.components)) };
6626
+ delete copy.work;
6627
+ const paths = this.getPaths();
6628
+ this.commitPaths([...paths.filter((p) => !p.work), copy, ...paths.filter((p) => p.work)], copy.id);
6629
+ return copy;
6630
+ }
6631
+ /** Deletes a path. One undo step. */
6632
+ deletePath(id) {
6633
+ this.requirePath(id);
6634
+ this.commitPaths(this.getPaths().filter((p) => p.id !== id));
6635
+ }
6636
+ /** The area a path fills over the image, as a selection mask. */
6637
+ pathArea(path, options = {}) {
6638
+ const { width, height } = this.naturalSize;
6639
+ return pathToSelection(path.components, width, height, { ...options, fillRule: path.fillRule ?? options.fillRule });
6640
+ }
6641
+ /** Paths › Make Selection: loads a path as the selection, combined by `mode`. One undo step. */
6642
+ loadPathAsSelection(id, options = {}) {
6643
+ if (options.feather !== void 0 && !(options.feather >= 0 && options.feather <= 1e3)) throw new LayerOperationError("feather must be from 0 to 1000 px.");
6644
+ return this.setSelection(this.pathArea(this.requirePath(id), options), options.mode ?? "replace");
6645
+ }
6646
+ /**
6647
+ * Paths › Fill Path: fills the path's area on a pixel layer (default the active one) with a
6648
+ * colour (default the foreground). The selection is not changed. One undo step.
6649
+ */
6650
+ fillPath(id, options = {}) {
6651
+ const area = this.pathArea(this.requirePath(id));
6652
+ if (!area.bounds) throw new LayerOperationError("The path encloses no area to fill.");
6653
+ return this.withSelection(area, () => this.fillPixels({ ...options, useSelection: true }));
6654
+ }
6655
+ /**
6656
+ * Paths › Stroke Path: strokes along the path's outline on a pixel layer (default the active
6657
+ * one), `width` px wide and centred on it. The selection is not changed. One undo step.
6658
+ */
6659
+ strokePath(id, options) {
6660
+ const area = this.pathArea(this.requirePath(id), { antiAlias: true });
6661
+ if (!area.bounds) throw new LayerOperationError("The path encloses no area; stroke a closed path.");
6662
+ return this.withSelection(area, () => this.strokeSelection({ ...options, location: "center" }));
6663
+ }
6664
+ /** Paths › Add Vector Mask: masks a layer (default the active one) with a document path. One undo step. */
6665
+ addVectorMaskFromPath(pathId, layerId) {
6666
+ const path = this.requirePath(pathId);
6667
+ const id = layerId ?? this.state.activeLayerId;
6668
+ if (!id) throw new LayerOperationError("No layer given and none is active; pass layerId (see list_layers).");
6669
+ return this.addVectorMask(id, "path", { components: path.components.map((c) => ({ op: c.op, subpaths: c.subpaths })), ...path.fillRule ? { fillRule: path.fillRule } : {} });
6670
+ }
6671
+ /** Runs `fn` with `mask` as the selection, then puts the selection back, all as one undo step. */
6672
+ withSelection(mask, fn) {
6673
+ return this.asOneStep(() => {
6674
+ const before = this.state.selection ?? null;
6675
+ this.state.selection = mask;
6676
+ try {
6677
+ return fn();
6678
+ } finally {
6679
+ this.state.selection = before;
6680
+ this.notify();
6681
+ }
6682
+ });
6683
+ }
6494
6684
  /** Replaces a vector mask's path (its properties are kept). One undo step. Returns the vector mask. */
6495
6685
  setVectorMaskPath(id, path) {
6496
6686
  const { vm } = this.requireVectorMask(id);
@@ -7949,7 +8139,9 @@ var ImageEditorController = class {
7949
8139
  if (!sel?.bounds) throw new LayerOperationError("Nothing is selected; select what to remove, then fill it.");
7950
8140
  if (this.bitDepth !== 8) throw new LayerOperationError("Content-Aware Fill works on 8-bit documents for now; set_bit_depth 8 first, or use spot_heal.");
7951
8141
  const output = options.output ?? "current";
7952
- if (output !== "current" && output !== "new-layer") throw new LayerOperationError(`Unknown output "${String(output)}"; use current or new-layer.`);
8142
+ if (output !== "current" && output !== "new-layer" && output !== "duplicate-layer") {
8143
+ throw new LayerOperationError(`Unknown output "${String(output)}"; use current, new-layer or duplicate-layer.`);
8144
+ }
7953
8145
  const target = this.paintTarget(options.layerId, "pixels", "fill");
7954
8146
  const { width, height } = this.naturalSize;
7955
8147
  const b = sel.bounds;
@@ -8026,6 +8218,13 @@ var ImageEditorController = class {
8026
8218
  }
8027
8219
  const layerRect = { x: win.x - target.offset.x, y: win.y - target.offset.y, width: win.width, height: win.height };
8028
8220
  const surface = writeRegion(target.surface, layerRect, out);
8221
+ if (output === "duplicate-layer") {
8222
+ return this.asOneStep(() => {
8223
+ const copy = this.duplicateLayer(target.id);
8224
+ this.commitLayers(mapLayer(this.state.layers, copy, (l) => this.withSurface(l, "pixels", surface)));
8225
+ return { layerId: copy, bounds: { ...b }, output };
8226
+ });
8227
+ }
8029
8228
  this.commitLayers(mapLayer(this.state.layers, target.id, (l) => this.withSurface(l, "pixels", surface)));
8030
8229
  return { layerId: target.id, bounds: { ...b }, output };
8031
8230
  }
@@ -8511,6 +8710,17 @@ var ImageEditorController = class {
8511
8710
  this.notify();
8512
8711
  return true;
8513
8712
  }
8713
+ /**
8714
+ * Shows `mask` in place of the selection while a gesture runs (Quick Selection as the brush
8715
+ * moves; `null` shows nothing selected), or with `undefined` stops. View state only: no undo
8716
+ * step, not saved.
8717
+ */
8718
+ setSelectionPreview(mask) {
8719
+ if (this.state.selectionPreview === mask) return;
8720
+ if (mask === void 0) delete this.state.selectionPreview;
8721
+ else this.state.selectionPreview = mask;
8722
+ this.notify();
8723
+ }
8514
8724
  /** Select › All. */
8515
8725
  selectAll() {
8516
8726
  const { width, height } = this.naturalSize;
@@ -10482,6 +10692,10 @@ var ImageEditorController = class {
10482
10692
  }));
10483
10693
  if (psdChannels.length) s.channels = psdChannels;
10484
10694
  else delete s.channels;
10695
+ const psdPaths = (result.paths ?? []).map((p) => ({ id: newPathId(), name: p.name, components: p.components, ...p.work ? { work: true } : {} }));
10696
+ if (psdPaths.length) s.paths = psdPaths;
10697
+ else delete s.paths;
10698
+ s.activePathId = null;
10485
10699
  s.selectedAnnotationId = null;
10486
10700
  s.activeLayerId = defaultActiveLayerId(s.layers);
10487
10701
  s.activeTarget = "pixels";
@@ -10679,7 +10893,7 @@ var ImageEditorController = class {
10679
10893
  opacity: c.opacity,
10680
10894
  indicates: c.indicates
10681
10895
  }));
10682
- const result = exportPsd({ width, height, layers: prepared, merged, mergedDeep, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight, depth, alphaChannels, ...color });
10896
+ const result = exportPsd({ width, height, layers: prepared, merged, mergedDeep, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight, depth, alphaChannels, paths: this.getPaths(), ...color });
10683
10897
  warnings.push(...result.warnings);
10684
10898
  return { bytes: result.bytes, warnings, psb: result.psb, width, height };
10685
10899
  }
@@ -10753,6 +10967,7 @@ var ImageEditorController = class {
10753
10967
  out = this.serializeState();
10754
10968
  if (this.state.selection?.bounds) out.selection = serializeSelection(this.state.selection);
10755
10969
  if (this.state.channels?.length) out.channels = serializeChannels(this.state.channels);
10970
+ if (this.state.paths?.length) out.paths = serializePaths(this.state.paths);
10756
10971
  } finally {
10757
10972
  if (root) {
10758
10973
  this.state = current;
@@ -10797,6 +11012,10 @@ var ImageEditorController = class {
10797
11012
  const channels = deserializeChannels(data.channels, this.naturalSize, problems);
10798
11013
  if (channels.length) this.state.channels = channels;
10799
11014
  else delete this.state.channels;
11015
+ const paths = readPaths(data.paths, problems);
11016
+ if (paths.length) this.state.paths = paths;
11017
+ else delete this.state.paths;
11018
+ this.state.activePathId = null;
10800
11019
  if (problems.length) this.lastLoadProblems = [...this.lastLoadProblems, ...problems];
10801
11020
  this.notify();
10802
11021
  }
@@ -11773,7 +11992,7 @@ var DEPTH_MASK_CONTROLS = [
11773
11992
  // src/image-editor/playback.ts
11774
11993
  var IMAGE_PLAYBACK_KIND = "layer";
11775
11994
  var PAINT = /^(paint_stroke|clone_stamp|heal_brush|spot_heal|dodge|burn|sponge|smudge|blur_stroke|sharpen_stroke|history_brush|set_clone_source|set_clone_transform|fill_pixels|clear_pixels|set_paint|set_retouch_options|load_mixer_brush|clean_mixer_brush)$/;
11776
- var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|quick_select|select_object|select_focus_area|select_sky|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask|path_to_selection|select_text_shape)$/;
11995
+ var SELECT = /^(make_work_path|add_path|set_path_geometry|rename_path|duplicate_path|delete_path|fill_path|stroke_path|select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|quick_select|select_object|select_focus_area|select_sky|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask|path_to_selection|select_text_shape)$/;
11777
11996
  var ANNOTATE = /^(add_shape_layer|update_shape|add_shape_component|set_shape_component|remove_shape_component|merge_shape_components|add_text_layer|set_text|set_text_style|set_paragraph_style|set_text_frame|set_text_orientation|convert_text_to_shape)$/;
11778
11997
  var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
11779
11998
  var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
@@ -12865,7 +13084,7 @@ function createImageSelectionTools(controller) {
12865
13084
  inputSchema: {
12866
13085
  type: "object",
12867
13086
  properties: {
12868
- output: { type: "string", enum: ["new-layer", "current"], description: "new-layer or current (default current)." },
13087
+ output: { type: "string", enum: ["new-layer", "current", "duplicate-layer"], description: "new-layer (the fill alone on a layer above), current (into the layer) or duplicate-layer (into a copy of the layer, above it); default current." },
12869
13088
  sampleRadius: { type: "number", minimum: 32, maximum: 4e3, description: "Sample within this many pixels of the selection (default its larger side)." },
12870
13089
  seed: { type: "integer", minimum: 0, description: "Random seed; another seed gives another fill to choose from (default 0)." },
12871
13090
  sampleRects: { type: "array", minItems: 1, items: rectSchemaStrict, description: "Sample only from these rectangles (natural image pixels), instead of the ring around the selection." },
@@ -14497,6 +14716,9 @@ var SelectionGesture = class {
14497
14716
  /** The last error (e.g. pixels unavailable for the wand), for the UI to show. */
14498
14717
  error = null;
14499
14718
  pending = Promise.resolve();
14719
+ /** Quick Selection while dragging: the selection before the stroke, how the stroke combines, the pixels. */
14720
+ quick = null;
14721
+ livePreviews = Promise.resolve();
14500
14722
  get active() {
14501
14723
  return this.start !== null || this.polygon !== null || this.magnetic !== null;
14502
14724
  }
@@ -14504,6 +14726,46 @@ var SelectionGesture = class {
14504
14726
  get settled() {
14505
14727
  return this.pending;
14506
14728
  }
14729
+ /** Resolves once no live Quick Selection preview is being computed. */
14730
+ get previewed() {
14731
+ return this.livePreviews;
14732
+ }
14733
+ /** Grows the Quick Selection preview to the stroke so far: one at a time, the newest points following. */
14734
+ growPreview() {
14735
+ const q = this.quick;
14736
+ if (!q) return;
14737
+ if (q.running) {
14738
+ q.dirty = true;
14739
+ return;
14740
+ }
14741
+ q.running = true;
14742
+ this.livePreviews = (async () => {
14743
+ do {
14744
+ q.dirty = false;
14745
+ const stroke = this.points.slice();
14746
+ const options = this.getOptions();
14747
+ try {
14748
+ const pixels = await q.pixels;
14749
+ if (!pixels || this.quick !== q) break;
14750
+ const { mask } = await this.controller.runSelectionJob(
14751
+ {
14752
+ op: "quick",
14753
+ stroke,
14754
+ size: options.quickSize ?? 30,
14755
+ grow: q.mode === "add",
14756
+ enhanceEdge: options.quickEnhance ?? false,
14757
+ selection: q.mode === "add" && q.base?.bounds ? serializeMask(q.base) : null
14758
+ },
14759
+ pixels
14760
+ );
14761
+ if (this.quick === q) this.controller.setSelectionPreview(combineSelections(q.base, mask, q.mode));
14762
+ } catch {
14763
+ break;
14764
+ }
14765
+ } while (q.dirty && this.quick === q);
14766
+ q.running = false;
14767
+ })();
14768
+ }
14507
14769
  /** The anchors of a Magnetic Lasso being drawn (empty otherwise). */
14508
14770
  get magneticAnchors() {
14509
14771
  const m = this.magnetic;
@@ -14515,7 +14777,8 @@ var SelectionGesture = class {
14515
14777
  if (this.polygon) return this.current ? [...this.polygon, this.current] : this.polygon.slice();
14516
14778
  if (!this.start || !this.current) return null;
14517
14779
  const { tool } = this.getOptions();
14518
- if (tool === "lasso" || tool === "quick") return this.points.length > 1 ? this.points.slice() : null;
14780
+ if (tool === "quick") return null;
14781
+ if (tool === "lasso") return this.points.length > 1 ? this.points.slice() : null;
14519
14782
  const rect = normalizeRect({
14520
14783
  x: this.start.x,
14521
14784
  y: this.start.y,
@@ -14628,6 +14891,18 @@ var SelectionGesture = class {
14628
14891
  this.start = point;
14629
14892
  this.current = point;
14630
14893
  this.points = [point];
14894
+ if (options.tool === "quick") {
14895
+ const picked = selectionModeFromModifiers(modifiers, options.mode);
14896
+ const base = this.controller.getSelection();
14897
+ this.quick = {
14898
+ base,
14899
+ mode: picked === "replace" && base ? "add" : picked,
14900
+ pixels: this.controller.getSamplePixels({ sample: options.quickSampleAll === false ? "active-layer" : "composite" }).catch(() => null),
14901
+ running: false,
14902
+ dirty: false
14903
+ };
14904
+ this.growPreview();
14905
+ }
14631
14906
  }
14632
14907
  /** Pointer move; returns true when the draft changed (re-render). */
14633
14908
  move(point, modifiers = {}) {
@@ -14654,6 +14929,7 @@ var SelectionGesture = class {
14654
14929
  const last = this.points[this.points.length - 1];
14655
14930
  if (Math.hypot(point.x - last.x, point.y - last.y) < 0.5) return false;
14656
14931
  this.points.push(point);
14932
+ if (drawn === "quick") this.growPreview();
14657
14933
  }
14658
14934
  return true;
14659
14935
  }
@@ -14689,7 +14965,8 @@ var SelectionGesture = class {
14689
14965
  this.start = null;
14690
14966
  this.current = null;
14691
14967
  this.points = [];
14692
- const strokeMode = mode === "replace" && this.controller.getSelection() ? "add" : mode;
14968
+ const strokeMode = this.quick?.mode ?? (mode === "replace" && this.controller.getSelection() ? "add" : mode);
14969
+ this.quick = null;
14693
14970
  this.pending = this.controller.quickSelect([stroke], {
14694
14971
  size: options.quickSize ?? 30,
14695
14972
  mode: strokeMode,
@@ -14700,7 +14977,7 @@ var SelectionGesture = class {
14700
14977
  (err) => {
14701
14978
  this.error = err instanceof Error ? err.message : String(err);
14702
14979
  }
14703
- );
14980
+ ).finally(() => this.controller.setSelectionPreview(void 0));
14704
14981
  return false;
14705
14982
  }
14706
14983
  const path = this.draftPath;
@@ -14829,6 +15106,10 @@ var SelectionGesture = class {
14829
15106
  }
14830
15107
  /** Abandons a drag (or a polygonal lasso) without changing the selection. */
14831
15108
  cancel() {
15109
+ if (this.quick) {
15110
+ this.quick = null;
15111
+ this.controller.setSelectionPreview(void 0);
15112
+ }
14832
15113
  this.start = null;
14833
15114
  this.current = null;
14834
15115
  this.points = [];
@@ -16945,6 +17226,19 @@ function createImageShapeTools(controller) {
16945
17226
  fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component (default nonzero)." }
16946
17227
  };
16947
17228
  const LAYER = { type: "string", minLength: 1, description: "The layer (see list_layers). Not a shape layer: its path already is its vector mask." };
17229
+ const PATH_ID = { type: "string", minLength: 1, description: "A document path id from list_paths." };
17230
+ const fromPathId = (args) => {
17231
+ if (args.pathId === void 0) return null;
17232
+ const own = Object.keys(PATH_PROPS).filter((k) => args[k] !== void 0);
17233
+ if (own.length) throw new Error(`Give pathId or ${own.join(" / ")}, not both.`);
17234
+ const path = controller.getPath(String(args.pathId));
17235
+ if (!path) throw new Error(`No path "${String(args.pathId)}". Call list_paths for ids.`);
17236
+ return { components: path.components.map((c) => ({ op: c.op, subpaths: c.subpaths })), ...path.fillRule ? { fillRule: path.fillRule } : {} };
17237
+ };
17238
+ const pathSummary = (id) => {
17239
+ const p = controller.getPath(id);
17240
+ return { id: p.id, name: p.name, work: Boolean(p.work), fillRule: p.fillRule ?? "nonzero", anchors: pathAnchorCount(p), bounds: pathBounds(p) };
17241
+ };
16948
17242
  const TRACE_PROPS = {
16949
17243
  tolerance: { type: "number", minimum: 0.1, maximum: 10, description: "How far (px) the path may stray from the selection edge (default 0.5; lower is tighter, more anchors)." },
16950
17244
  cornerRoundness: { type: "number", minimum: 0, maximum: 1, description: "0: straight sides and sharp corners; 1: as smooth as the outline allows (default 0.5)." },
@@ -16976,6 +17270,7 @@ function createImageShapeTools(controller) {
16976
17270
  type: "object",
16977
17271
  $defs: DEFS2,
16978
17272
  properties: {
17273
+ pathId: { ...PATH_ID, description: "A document path from list_paths, instead of shape / subpaths / components." },
16979
17274
  ...PATH_PROPS,
16980
17275
  feather: { type: "number", minimum: 0, maximum: 1e3, description: "Feather radius in px (default 0)." },
16981
17276
  antiAlias: { type: "boolean", description: "Soft edges (default true); false makes every pixel fully in or out." },
@@ -16986,7 +17281,7 @@ function createImageShapeTools(controller) {
16986
17281
  annotations: { title: "Make a selection from a path", ...WRITE8 },
16987
17282
  handler: (args) => {
16988
17283
  const mode = args.mode;
16989
- controller.pathToSelection(pathSpec(args), { feather: args.feather, antiAlias: args.antiAlias, mode });
17284
+ controller.pathToSelection(fromPathId(args) ?? pathSpec(args), { feather: args.feather, antiAlias: args.antiAlias, mode });
16990
17285
  const sel = controller.getSelection();
16991
17286
  return { empty: !sel, bounds: sel?.bounds ?? null, area: Math.round(sel ? selectionArea(sel) : 0) };
16992
17287
  }
@@ -17008,7 +17303,8 @@ function createImageShapeTools(controller) {
17008
17303
  $defs: DEFS2,
17009
17304
  properties: {
17010
17305
  layerId: LAYER,
17011
- from: { type: "string", enum: ["path", "selection", "reveal-all", "hide-all"], description: "path (default when a shape, subpaths or components are given), selection (trace the current selection), reveal-all or hide-all (an empty path)." },
17306
+ from: { type: "string", enum: ["path", "selection", "reveal-all", "hide-all"], description: "path (default when a shape, subpaths, components or pathId are given), selection (trace the current selection), reveal-all or hide-all (an empty path)." },
17307
+ pathId: { ...PATH_ID, description: "A document path from list_paths to use as the mask (Paths \u203A Add Vector Mask), instead of shape / subpaths / components." },
17012
17308
  ...PATH_PROPS,
17013
17309
  ...TRACE_PROPS
17014
17310
  },
@@ -17018,7 +17314,7 @@ function createImageShapeTools(controller) {
17018
17314
  annotations: { title: "Add a vector mask", ...WRITE8 },
17019
17315
  handler: (args) => {
17020
17316
  const id = layerArg(args.layerId);
17021
- const spec = pathSpec(args);
17317
+ const spec = fromPathId(args) ?? pathSpec(args);
17022
17318
  const hasPath = spec.components?.length || spec.shape !== void 0 || spec.subpaths !== void 0;
17023
17319
  const from = args.from ?? (hasPath ? "path" : "reveal-all");
17024
17320
  if (from !== "selection" && TRACE_KEYS.some((k) => args[k] !== void 0)) throw new Error("tolerance, cornerRoundness and minArea apply only to from selection.");
@@ -17086,7 +17382,139 @@ function createImageShapeTools(controller) {
17086
17382
  return { layerId: id, vectorMask: null };
17087
17383
  }
17088
17384
  };
17089
- return [get, add2, update, setComponent, addComponent, removeComponent, merge, getVectorMask, addVectorMask, setVectorMaskPath, setVectorMaskProps, rasterizeVectorMask, removeVectorMask, selectionToPath, pathToSelection2];
17385
+ const listPaths = {
17386
+ name: "list_paths",
17387
+ title: "List document paths",
17388
+ description: "Paths panel: the document's saved paths and its Work Path (paths kept with the document, on no layer), each with id, name, work (true for the Work Path, which the next make_work_path replaces), fill rule, anchor count and bounds, and which is active. Use the ids with path_to_selection, add_vector_mask, fill_path, stroke_path and the other path tools.",
17389
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
17390
+ annotations: { title: "List document paths", ...READ10 },
17391
+ handler: () => ({ paths: controller.getPaths().map((p) => pathSummary(p.id)), activePathId: controller.getState().activePathId ?? null })
17392
+ };
17393
+ const getPath2 = {
17394
+ name: "get_path",
17395
+ title: "Read a document path",
17396
+ description: "Reads a document path: its summary, svg (compact SVG path data) and components (op, subpaths of anchors with handles, in document pixels), ready to edit and pass back to set_path_geometry.",
17397
+ inputSchema: { type: "object", properties: { pathId: PATH_ID }, required: ["pathId"], additionalProperties: false },
17398
+ annotations: { title: "Read a document path", ...READ10 },
17399
+ handler: ({ pathId }) => {
17400
+ const p = controller.getPath(String(pathId));
17401
+ if (!p) throw new Error(`No path "${String(pathId)}". Call list_paths for ids.`);
17402
+ return { path: { ...pathSummary(p.id), svg: pathSvgData(p), components: p.components } };
17403
+ }
17404
+ };
17405
+ const makeWorkPath = {
17406
+ name: "make_work_path",
17407
+ title: "Make a work path from the selection",
17408
+ description: "Paths \u203A Make Work Path: traces the selection's outline (within tolerance px) into the Work Path, replacing the last Work Path unless it was saved (rename_path saves it). Use it to keep a selection as an editable, resolution-independent outline. One undo step. Returns the path.",
17409
+ inputSchema: { type: "object", properties: { ...TRACE_PROPS }, additionalProperties: false },
17410
+ annotations: { title: "Make a work path from the selection", ...WRITE8 },
17411
+ handler: (args) => ({ path: pathSummary(controller.makeWorkPath(traceOptions(args)).id) })
17412
+ };
17413
+ const addPath = {
17414
+ name: "add_path",
17415
+ title: "Add a document path",
17416
+ description: "Adds a saved document path from a live shape, free subpaths or SVG path data, or components (document pixels), as add_shape_layer takes them. One undo step. Returns the path.",
17417
+ inputSchema: {
17418
+ type: "object",
17419
+ $defs: DEFS2,
17420
+ properties: { name: { type: "string", description: 'The path name (default "Path n").' }, ...PATH_PROPS },
17421
+ additionalProperties: false
17422
+ },
17423
+ annotations: { title: "Add a document path", ...WRITE8 },
17424
+ handler: (args) => {
17425
+ const spec = pathSpec(args);
17426
+ if (!spec.components?.length && spec.shape === void 0 && spec.subpaths === void 0) throw new Error("Give the path as shape, subpaths or components.");
17427
+ return { path: pathSummary(controller.addPath({ ...spec, name: args.name }).id) };
17428
+ }
17429
+ };
17430
+ const setPathGeometry = {
17431
+ name: "set_path_geometry",
17432
+ title: "Replace a document path",
17433
+ description: "Replaces a document path's geometry (shape, subpaths or components, document pixels); its name is kept. Read it with get_path first to change part of it. One undo step. Returns the path.",
17434
+ inputSchema: { type: "object", $defs: DEFS2, properties: { pathId: PATH_ID, ...PATH_PROPS }, required: ["pathId"], additionalProperties: false },
17435
+ annotations: { title: "Replace a document path", ...WRITE_IDEMPOTENT5 },
17436
+ handler: (args) => ({ path: pathSummary(controller.setPathGeometry(String(args.pathId), pathSpec(args)).id) })
17437
+ };
17438
+ const renamePath = {
17439
+ name: "rename_path",
17440
+ title: "Rename or save a path",
17441
+ description: "Renames a document path. Renaming the Work Path saves it (Paths \u203A Save Path), so make_work_path no longer replaces it. One undo step. Returns the path.",
17442
+ inputSchema: { type: "object", properties: { pathId: PATH_ID, name: { type: "string", minLength: 1, description: "The new name." } }, required: ["pathId", "name"], additionalProperties: false },
17443
+ annotations: { title: "Rename or save a path", ...WRITE_IDEMPOTENT5 },
17444
+ handler: ({ pathId, name }) => ({ path: pathSummary(controller.renamePath(String(pathId), String(name)).id) })
17445
+ };
17446
+ const duplicatePath = {
17447
+ name: "duplicate_path",
17448
+ title: "Duplicate a path",
17449
+ description: 'Duplicates a document path as a saved path (default name "<name> copy"). One undo step. Returns the new path.',
17450
+ inputSchema: { type: "object", properties: { pathId: PATH_ID, name: { type: "string", description: "The copy's name." } }, required: ["pathId"], additionalProperties: false },
17451
+ annotations: { title: "Duplicate a path", ...WRITE8 },
17452
+ handler: ({ pathId, name }) => ({ path: pathSummary(controller.duplicatePath(String(pathId), name).id) })
17453
+ };
17454
+ const deletePath = {
17455
+ name: "delete_path",
17456
+ title: "Delete a path",
17457
+ description: "Deletes a document path (undo_image brings it back). Returns the remaining paths.",
17458
+ inputSchema: { type: "object", properties: { pathId: PATH_ID }, required: ["pathId"], additionalProperties: false },
17459
+ annotations: { title: "Delete a path", ...DESTRUCTIVE6 },
17460
+ handler: ({ pathId }) => {
17461
+ controller.deletePath(String(pathId));
17462
+ return { paths: controller.getPaths().map((p) => pathSummary(p.id)) };
17463
+ }
17464
+ };
17465
+ const PAINT_PROPS = {
17466
+ layerId: { type: "string", description: "The pixel layer to paint on (default the active layer)." },
17467
+ color: { type: "string", description: "A CSS colour such as #ff0000 (default the foreground colour)." },
17468
+ opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 1)." }
17469
+ };
17470
+ const fillPath = {
17471
+ name: "fill_path",
17472
+ title: "Fill a path",
17473
+ description: "Paths \u203A Fill Path: fills the area a document path encloses on a pixel layer with a colour. The selection is not changed. One undo step. Returns what was painted.",
17474
+ inputSchema: { type: "object", properties: { pathId: PATH_ID, ...PAINT_PROPS }, required: ["pathId"], additionalProperties: false },
17475
+ annotations: { title: "Fill a path", ...WRITE8 },
17476
+ handler: (args) => controller.fillPath(String(args.pathId), { layerId: args.layerId, color: args.color, opacity: args.opacity })
17477
+ };
17478
+ const strokePath = {
17479
+ name: "stroke_path",
17480
+ title: "Stroke a path",
17481
+ description: "Paths \u203A Stroke Path: strokes along a document path's outline on a pixel layer, width px wide and centred on it. The selection is not changed. One undo step. Returns what was painted.",
17482
+ inputSchema: {
17483
+ type: "object",
17484
+ properties: { pathId: PATH_ID, width: { type: "number", exclusiveMinimum: 0, maximum: 250, description: "Stroke width in px." }, ...PAINT_PROPS },
17485
+ required: ["pathId", "width"],
17486
+ additionalProperties: false
17487
+ },
17488
+ annotations: { title: "Stroke a path", ...WRITE8 },
17489
+ handler: (args) => controller.strokePath(String(args.pathId), { width: args.width, layerId: args.layerId, color: args.color, opacity: args.opacity })
17490
+ };
17491
+ return [
17492
+ get,
17493
+ add2,
17494
+ update,
17495
+ setComponent,
17496
+ addComponent,
17497
+ removeComponent,
17498
+ merge,
17499
+ getVectorMask,
17500
+ addVectorMask,
17501
+ setVectorMaskPath,
17502
+ setVectorMaskProps,
17503
+ rasterizeVectorMask,
17504
+ removeVectorMask,
17505
+ selectionToPath,
17506
+ pathToSelection2,
17507
+ listPaths,
17508
+ getPath2,
17509
+ makeWorkPath,
17510
+ addPath,
17511
+ setPathGeometry,
17512
+ renamePath,
17513
+ duplicatePath,
17514
+ deletePath,
17515
+ fillPath,
17516
+ strokePath
17517
+ ];
17090
17518
  }
17091
17519
 
17092
17520
  // src/image-editor/type-tools.ts
@@ -17733,6 +18161,9 @@ var SelectAndMaskSession = class {
17733
18161
  dragStart = null;
17734
18162
  pixels = null;
17735
18163
  previewPixels = null;
18164
+ /** The layers below the active one, composited (On Layers shows them where nothing is selected). */
18165
+ below = null;
18166
+ belowPreview = null;
17736
18167
  base;
17737
18168
  previewMask = null;
17738
18169
  baseMask = null;
@@ -17764,10 +18195,24 @@ var SelectAndMaskSession = class {
17764
18195
  this.emit();
17765
18196
  return false;
17766
18197
  }
18198
+ this.below = this.layersBelow();
17767
18199
  this.rescale();
17768
18200
  this.recompute();
17769
18201
  return true;
17770
18202
  }
18203
+ /** The visible layers under the active one, composited at full size, or null when there are none. */
18204
+ layersBelow() {
18205
+ const active = this.controller.getState().activeLayerId;
18206
+ const flat = flattenLayers(this.controller.getLayers()).filter((l) => l.type !== "group");
18207
+ const at = flat.findIndex((l) => l.id === active);
18208
+ const ids = (at > 0 ? flat.slice(0, at) : []).filter((l) => l.visible).map((l) => l.id);
18209
+ if (!ids.length) return null;
18210
+ try {
18211
+ return this.controller.compositePixels(void 0, { layerIds: ids });
18212
+ } catch {
18213
+ return null;
18214
+ }
18215
+ }
17771
18216
  subscribe(listener) {
17772
18217
  this.listeners.add(listener);
17773
18218
  return () => this.listeners.delete(listener);
@@ -17800,6 +18245,7 @@ var SelectAndMaskSession = class {
17800
18245
  const w = Math.max(1, Math.round(px2.width * s));
17801
18246
  const h = Math.max(1, Math.round(px2.height * s));
17802
18247
  this.previewPixels = scalePixels(px2, w, h);
18248
+ this.belowPreview = this.below ? scalePixels(this.below, w, h) : null;
17803
18249
  this.baseMask = this.base?.bounds ? s === 1 ? this.base : resizeSelection(this.base, w, h) : null;
17804
18250
  }
17805
18251
  /**
@@ -17923,6 +18369,20 @@ var SelectAndMaskSession = class {
17923
18369
  }
17924
18370
  break;
17925
18371
  }
18372
+ case "on-layers": {
18373
+ const under = this.belowPreview?.data;
18374
+ let ur = checker;
18375
+ let ug = checker;
18376
+ let ub = checker;
18377
+ if (under) {
18378
+ const ua = under[p + 3] / 255;
18379
+ ur = under[p] * ua + checker * (1 - ua);
18380
+ ug = under[p + 1] * ua + checker * (1 - ua);
18381
+ ub = under[p + 2] * ua + checker * (1 - ua);
18382
+ }
18383
+ o = [r + (ur - r) * hide, g + (ug - g) * hide, b + (ub - b) * hide];
18384
+ break;
18385
+ }
17926
18386
  default:
17927
18387
  o = [r + (checker - r) * hide, g + (checker - g) * hide, b + (checker - b) * hide];
17928
18388
  }
@@ -19737,7 +20197,7 @@ var DEFAULT_CONTENT_AWARE_FILL_SETTINGS = {
19737
20197
  output: "new-layer",
19738
20198
  livePreview: true
19739
20199
  };
19740
- var ContentAwareFillSession = class {
20200
+ var ContentAwareFillSession = class _ContentAwareFillSession {
19741
20201
  constructor(controller) {
19742
20202
  this.controller = controller;
19743
20203
  }
@@ -19773,16 +20233,21 @@ var ContentAwareFillSession = class {
19773
20233
  this.revision++;
19774
20234
  for (const l of this.listeners) l();
19775
20235
  }
20236
+ /** Images up to this many pixels show (and can be sampled) whole in the workspace. */
20237
+ static WHOLE_IMAGE_PIXELS = 4e6;
20238
+ /** Room around the selection the window holds, in px each way. */
20239
+ margin = 0;
19776
20240
  /**
19777
- * Reads the layer around the selection and makes the first preview. The window holds the
19778
- * selection and room around it to sample from (its larger side, at least 64 px, each way).
20241
+ * Reads the layer and makes the first preview. The window is the whole image up to
20242
+ * {@link WHOLE_IMAGE_PIXELS}; on a larger one it holds the selection and room around it (twice
20243
+ * its larger side, at least 256 px, each way), and {@link expandView} widens it.
19779
20244
  */
19780
20245
  async open() {
19781
20246
  try {
19782
- const sel = this.controller.getSelection();
19783
- const b = sel?.bounds;
19784
- const margin = b ? Math.max(64, Math.max(b.width, b.height)) : 64;
19785
- this.plan = this.controller.prepareContentAwareFill({ sampleRadius: margin });
20247
+ const { width, height } = this.controller.getState().imageDimensions;
20248
+ const b = this.controller.getSelection()?.bounds;
20249
+ this.margin = width * height <= _ContentAwareFillSession.WHOLE_IMAGE_PIXELS ? Math.max(width, height) : Math.max(256, 2 * Math.max(b?.width ?? 0, b?.height ?? 0));
20250
+ this.plan = this.controller.prepareContentAwareFill({ sampleRadius: this.margin });
19786
20251
  } catch (err) {
19787
20252
  this.error = err instanceof Error ? err.message : String(err);
19788
20253
  this.emit();
@@ -19796,6 +20261,42 @@ var ContentAwareFillSession = class {
19796
20261
  await this.refresh();
19797
20262
  return true;
19798
20263
  }
20264
+ /** Whether the window shows the whole image (else {@link expandView} can widen it). */
20265
+ get showsWholeImage() {
20266
+ const { width, height } = this.controller.getState().imageDimensions;
20267
+ const w = this.win;
20268
+ return !w || w.x === 0 && w.y === 0 && w.width >= width && w.height >= height;
20269
+ }
20270
+ /**
20271
+ * Doubles the room around the selection (up to the whole image), keeping what was painted; a
20272
+ * preset fills the new room as it would. Returns false when the whole image already shows.
20273
+ */
20274
+ expandView() {
20275
+ if (!this.win || this.showsWholeImage) return false;
20276
+ const { width, height } = this.controller.getState().imageDimensions;
20277
+ const old = { win: this.win, area: this.area };
20278
+ this.margin = Math.min(Math.max(width, height), this.margin * 2);
20279
+ const plan = this.controller.prepareContentAwareFill({ layerId: this.plan.layerId, sampleRadius: this.margin });
20280
+ this.plan = plan;
20281
+ this.win = plan.window;
20282
+ this.before = plan.before;
20283
+ this.hole = plan.hole;
20284
+ const mode = this.settingsValue.sampling;
20285
+ this.area = null;
20286
+ this.applySampling(mode === "custom" ? "auto" : mode);
20287
+ if (mode === "custom") {
20288
+ const area = new Uint8Array(plan.window.width * plan.window.height);
20289
+ for (let y = 0; y < old.win.height; y++) {
20290
+ const src = y * old.win.width;
20291
+ const dst = (old.win.y - plan.window.y + y) * plan.window.width + (old.win.x - plan.window.x);
20292
+ area.set(old.area.subarray(src, src + old.win.width), dst);
20293
+ }
20294
+ this.area = area;
20295
+ }
20296
+ this.emit();
20297
+ if (this.settingsValue.livePreview) void this.refresh();
20298
+ return true;
20299
+ }
19799
20300
  /** Sets the area to a preset: Auto (a band around the selection), Rectangular (the whole window). */
19800
20301
  applySampling(mode) {
19801
20302
  const win = this.win;
@@ -20250,6 +20751,7 @@ export {
20250
20751
  WGSL_SHADE,
20251
20752
  WGSL_STEP,
20252
20753
  WORKING_SPACE_OPTIONS,
20754
+ WORK_PATH_NAME,
20253
20755
  WORK_SIZE,
20254
20756
  WetGpuEngine,
20255
20757
  WetMedium,
@@ -20715,6 +21217,7 @@ export {
20715
21217
  newEffectId,
20716
21218
  newLayerId,
20717
21219
  newLayerOptions,
21220
+ newPathId,
20718
21221
  normalizeAdjustmentLayer,
20719
21222
  normalizeAdjustmentLayers,
20720
21223
  normalizeAngle,
@@ -20771,8 +21274,11 @@ export {
20771
21274
  parseVersionedDescriptor,
20772
21275
  patchParagraphStyle,
20773
21276
  patchTextStyle,
21277
+ pathAnchorCount,
21278
+ pathBounds,
20774
21279
  pathStrokeCoverage,
20775
21280
  pathStrokeDabs,
21281
+ pathSvgData,
20776
21282
  pathToSelection,
20777
21283
  perDabStrength,
20778
21284
  photoFilterMatrix,
@@ -20825,9 +21331,11 @@ export {
20825
21331
  readAdjustmentControls,
20826
21332
  readDeepRegion,
20827
21333
  readPaintSource,
21334
+ readPaths,
20828
21335
  readPixel,
20829
21336
  readPsd,
20830
21337
  readRegion,
21338
+ readShapeComponents,
20831
21339
  readSourceRegion,
20832
21340
  rectSelection,
20833
21341
  rectToQuad,
@@ -20916,6 +21424,7 @@ export {
20916
21424
  serializeLayers,
20917
21425
  serializeLayersExternal,
20918
21426
  serializeMask,
21427
+ serializePaths,
20919
21428
  serializePatterns,
20920
21429
  serializeSelection,
20921
21430
  setCloneTransformValue,