@pixerate/editor 0.179.0 → 0.181.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 (56) hide show
  1. package/dist/agent/index.cjs +34 -34
  2. package/dist/agent/index.js +5 -5
  3. package/dist/canvas/index.cjs +6 -6
  4. package/dist/canvas/index.js +2 -2
  5. package/dist/chart/index.cjs +162 -157
  6. package/dist/chart/index.d.cts +1 -0
  7. package/dist/chart/index.d.ts +1 -0
  8. package/dist/chart/index.js +12 -7
  9. package/dist/chart/layout-streamgraph.d.cts +15 -0
  10. package/dist/chart/layout-streamgraph.d.ts +15 -0
  11. package/dist/chart/spec.d.cts +1 -1
  12. package/dist/chart/spec.d.ts +1 -1
  13. package/dist/chart/types.d.cts +2 -1
  14. package/dist/chart/types.d.ts +2 -1
  15. package/dist/{chunk-KPWG3PSW.js → chunk-23E5GI2U.js} +8 -2
  16. package/dist/{chunk-IMHRN4ZZ.js → chunk-3MO3ZS7E.js} +3 -3
  17. package/dist/{chunk-GXEC4FS6.cjs → chunk-5DQPDXOM.cjs} +82 -76
  18. package/dist/{chunk-ORZF4JOO.js → chunk-6ZZKRH7R.js} +3 -1
  19. package/dist/{chunk-FHNGNL3V.cjs → chunk-CZBRY4SW.cjs} +51 -51
  20. package/dist/{chunk-34UCPHYO.js → chunk-GOBOCZZI.js} +2 -2
  21. package/dist/{chunk-VKHJJUBK.cjs → chunk-HUAISCUX.cjs} +22 -18
  22. package/dist/{chunk-OLCHKGGO.js → chunk-L6Y7TSQ4.js} +5 -1
  23. package/dist/{chunk-TUXKLM5W.cjs → chunk-N76AMOKE.cjs} +224 -42
  24. package/dist/{chunk-RLOIEDA3.cjs → chunk-O7QXVGMP.cjs} +16 -16
  25. package/dist/{chunk-R2VHVQVY.js → chunk-PI2UYMZY.js} +3 -3
  26. package/dist/{chunk-E45AIIWY.cjs → chunk-RUONPPMC.cjs} +8 -8
  27. package/dist/{chunk-GTF4RYXJ.cjs → chunk-SOSBOS5T.cjs} +22 -22
  28. package/dist/{chunk-FKYGM35R.js → chunk-UFEDHACQ.js} +2 -2
  29. package/dist/{chunk-OJQVKJYD.js → chunk-WKMUBATU.js} +183 -1
  30. package/dist/{chunk-OUO3DSRT.cjs → chunk-WRZ6C4TU.cjs} +3 -1
  31. package/dist/export/index.cjs +4 -4
  32. package/dist/export/index.js +3 -3
  33. package/dist/extensions/index.cjs +7 -7
  34. package/dist/extensions/index.js +6 -6
  35. package/dist/image-editor/controller.d.cts +25 -0
  36. package/dist/image-editor/controller.d.ts +25 -0
  37. package/dist/image-editor/index.cjs +466 -303
  38. package/dist/image-editor/index.js +188 -25
  39. package/dist/image-editor/layers/panel.d.cts +2 -1
  40. package/dist/image-editor/layers/panel.d.ts +2 -1
  41. package/dist/image-editor/renderer.d.cts +4 -1
  42. package/dist/image-editor/renderer.d.ts +4 -1
  43. package/dist/image-editor/selection/index.d.cts +1 -0
  44. package/dist/image-editor/selection/index.d.ts +1 -0
  45. package/dist/image-editor/selection/overlay.d.cts +8 -0
  46. package/dist/image-editor/selection/overlay.d.ts +8 -0
  47. package/dist/image-editor/selection/segmentation.d.cts +49 -0
  48. package/dist/image-editor/selection/segmentation.d.ts +49 -0
  49. package/dist/image-editor/selection-tools.d.cts +18 -1
  50. package/dist/image-editor/selection-tools.d.ts +18 -1
  51. package/dist/image-editor/types.d.cts +7 -0
  52. package/dist/image-editor/types.d.ts +7 -0
  53. package/dist/index.cjs +13 -13
  54. package/dist/index.js +7 -7
  55. package/package.json +1 -1
  56. package/skills/pixerate-image-editor/SKILL.md +5 -3
@@ -948,6 +948,24 @@ function quickMaskPixels(mask, style = {}) {
948
948
  }
949
949
  return out;
950
950
  }
951
+ var MARCHING_ANTS_STEP_MS = 100;
952
+ function prefersReducedMotion() {
953
+ try {
954
+ return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
955
+ } catch {
956
+ return false;
957
+ }
958
+ }
959
+ function startMarchingAnts(tick, stepMs = MARCHING_ANTS_STEP_MS) {
960
+ if (prefersReducedMotion()) return () => {
961
+ };
962
+ let offset = 0;
963
+ const id = setInterval(() => {
964
+ offset = (offset + 1) % 8;
965
+ tick(offset);
966
+ }, stepMs);
967
+ return () => clearInterval(id);
968
+ }
951
969
 
952
970
  // src/image-editor/layers/render.ts
953
971
  function isQuarterTurn(rotate) {
@@ -1353,8 +1371,8 @@ var ImageEditorRenderer = class {
1353
1371
  ctx.rect(region.x, region.y, region.width, region.height);
1354
1372
  ctx.clip();
1355
1373
  if (state.selection?.bounds && display === "quickMask") this.drawQuickMask(ctx, state.selection);
1356
- else if (state.selection?.bounds && display === "ants") this.drawAnts(ctx, state.selection, projection.scale, options.antsOffset ?? 0);
1357
- if (options.draftSelectionPath?.length) this.drawDraftPath(ctx, options.draftSelectionPath, projection.scale);
1374
+ else if (state.selection?.bounds && display === "ants") this.drawAnts(ctx, state.selection, projection.scale / u, options.antsOffset ?? 0);
1375
+ if (options.draftSelectionPath?.length) this.drawDraftPath(ctx, options.draftSelectionPath, projection.scale / u);
1358
1376
  } catch {
1359
1377
  }
1360
1378
  ctx.restore();
@@ -1772,7 +1790,10 @@ var ImageEditorRenderer = class {
1772
1790
  }
1773
1791
  ctx.drawImage(canvas, 0, 0, selection.width, selection.height);
1774
1792
  }
1775
- /** Marching ants: a white line under a black dashed one, one screen pixel wide. */
1793
+ /**
1794
+ * Marching ants: a white line under a black dashed one, one screen pixel wide. `scale` is
1795
+ * screen pixels per natural pixel (the projection scale times the host's CSS scale).
1796
+ */
1776
1797
  drawAnts(ctx, selection, scale, offset) {
1777
1798
  let segs = this.outlineCache.get(selection);
1778
1799
  if (!segs) {
@@ -3082,6 +3103,35 @@ function hueRange(pixels, center) {
3082
3103
  return createSelectionMask(pixels.width, pixels.height, out);
3083
3104
  }
3084
3105
 
3106
+ // src/image-editor/selection/segmentation.ts
3107
+ var isPoint = (p) => !!p && typeof p === "object" && Number.isFinite(p.x) && Number.isFinite(p.y);
3108
+ function polygonsOf(result) {
3109
+ const p = result.polygon;
3110
+ if (!Array.isArray(p) || !p.length) return [];
3111
+ const rings = Array.isArray(p[0]) ? p : [p];
3112
+ return rings.map((ring) => Array.isArray(ring) ? ring.filter(isPoint) : []).filter((ring) => ring.length >= 3);
3113
+ }
3114
+ function segmentToSelection(width, height, result) {
3115
+ let out = null;
3116
+ for (const ring of polygonsOf(result)) {
3117
+ out = combineSelections(out, polygonSelection(width, height, ring), "add");
3118
+ }
3119
+ if (result.mask && result.mask.width > 0 && result.mask.height > 0) {
3120
+ const raw = selectionFromPixels(result.mask, { channel: "luminance" });
3121
+ const sized = raw.width === width && raw.height === height ? raw : resizeSelection(raw, width, height);
3122
+ out = combineSelections(out, sized, "add");
3123
+ }
3124
+ return out?.bounds ? out : null;
3125
+ }
3126
+ function segmentsToSelection(width, height, results) {
3127
+ let out = null;
3128
+ for (const r of results) {
3129
+ const sel = segmentToSelection(width, height, r);
3130
+ if (sel) out = combineSelections(out, sel, "add");
3131
+ }
3132
+ return out;
3133
+ }
3134
+
3085
3135
  // src/image-editor/selection/refine.ts
3086
3136
  var DEFAULT_REFINE_EDGE_PARAMS = {
3087
3137
  radius: 0,
@@ -4669,6 +4719,8 @@ var ImageEditorController = class {
4669
4719
  viewKey = [];
4670
4720
  /** For `compositePixels` and mask evaluation (the renderer keeps its own for the viewport). */
4671
4721
  compositor = new LayerCompositor();
4722
+ /** The segmentation provider behind `selectSubject` (see `setSegmentationProvider`). */
4723
+ segmentation = null;
4672
4724
  /** The effects provider slot (see `setEffectsProvider`). */
4673
4725
  effects = new EffectsSlot();
4674
4726
  /** Renders provider effects: effect smart filter results and effect layers. */
@@ -4678,6 +4730,7 @@ var ImageEditorController = class {
4678
4730
  this.effects.subscribe(() => this.queueEffectRefresh("all"));
4679
4731
  this.effectsEngine.subscribe(() => this.queueEffectRefresh("incomplete"));
4680
4732
  if (options.effects) this.effects.set(options.effects);
4733
+ if (options.segmentation) this.segmentation = options.segmentation;
4681
4734
  this.maxHistoryDepth = options.maxHistoryDepth ?? 50;
4682
4735
  if (options.textShaper !== void 0) this.textShaperValue = options.textShaper;
4683
4736
  this.maxHistoryBytes = options.maxHistoryBytes ?? 512 * 1024 * 1024;
@@ -4859,6 +4912,16 @@ var ImageEditorController = class {
4859
4912
  this.state.layers = next;
4860
4913
  this.notify();
4861
4914
  }
4915
+ /** The registered segmentation provider, or `null` (then `selectSubject` is unavailable). */
4916
+ get segmentationProvider() {
4917
+ return this.segmentation;
4918
+ }
4919
+ /** Registers (or with `null` removes) the segmentation provider. Not an edit: no undo step. */
4920
+ setSegmentationProvider(provider) {
4921
+ if (this.segmentation === provider) return;
4922
+ this.segmentation = provider;
4923
+ this.notify();
4924
+ }
4862
4925
  /** The registered effects provider, or `null`. */
4863
4926
  get effectsProvider() {
4864
4927
  return this.effects.provider;
@@ -7615,23 +7678,50 @@ var ImageEditorController = class {
7615
7678
  * RGBA pixels, by luminance (white = selected) or a channel. Any size; it is resampled.
7616
7679
  */
7617
7680
  async loadSelectionFromImage(source, options = {}) {
7618
- let pixels;
7619
- if (typeof source === "string") {
7620
- const img = await this.renderer.imageCache.get(source);
7621
- const w = img.naturalWidth || img.width;
7622
- const h = img.naturalHeight || img.height;
7623
- const canvas = this.renderer.createCanvas(w, h);
7624
- const ctx = canvas.getContext("2d");
7625
- if (ctx) ctx.drawImage(img, 0, 0, w, h);
7626
- const data = ctx?.getImageData?.(0, 0, w, h);
7627
- if (!data) throw new Error("The mask image pixels are not available (no canvas access); pass pixels.");
7628
- pixels = { width: w, height: h, data: data.data };
7629
- } else {
7630
- pixels = source;
7631
- }
7681
+ const pixels = await this.decodeMaskImage(source);
7632
7682
  this.setSelection(selectionFromPixels(pixels, options), options.mode ?? "replace");
7633
7683
  return this.getSelection();
7634
7684
  }
7685
+ /** A mask image (data URL / URL, or pixels as they are) as RGBA pixels. */
7686
+ async decodeMaskImage(source) {
7687
+ if (typeof source !== "string") return source;
7688
+ const img = await this.renderer.imageCache.get(source);
7689
+ const w = img.naturalWidth || img.width;
7690
+ const h = img.naturalHeight || img.height;
7691
+ const canvas = this.renderer.createCanvas(w, h);
7692
+ const ctx = canvas.getContext("2d");
7693
+ if (ctx) ctx.drawImage(img, 0, 0, w, h);
7694
+ const data = ctx?.getImageData?.(0, 0, w, h);
7695
+ if (!data) throw new Error("The mask image pixels are not available (no canvas access); pass pixels.");
7696
+ return { width: w, height: h, data: data.data };
7697
+ }
7698
+ /**
7699
+ * Select › Subject: asks the segmentation provider for the objects matching `target` ("the
7700
+ * dog", "the sky") in the flattened image and selects their shapes, combined with the
7701
+ * selection by `mode`, as one undo step. Returns what was found (labels and bounds in natural
7702
+ * pixels); an empty list leaves the selection as it was. Throws without a provider.
7703
+ */
7704
+ async selectSubject(target, options = {}) {
7705
+ const provider = this.segmentation;
7706
+ if (!provider) throw new Error("Select Subject needs a segmentation provider (setSegmentationProvider).");
7707
+ const description = target.trim();
7708
+ if (!description) throw new Error("Describe the subject to select.");
7709
+ const { width, height } = this.naturalSize;
7710
+ const image = await this.toDataURL({ format: "image/png" });
7711
+ const results = await provider.segment({ image, width, height, target: description, maxSegments: options.maxSegments, signal: options.signal });
7712
+ options.signal?.throwIfAborted();
7713
+ const decoded = [];
7714
+ for (const r of results ?? []) {
7715
+ decoded.push({ ...r, mask: r.mask !== void 0 ? await this.decodeMaskImage(r.mask) : void 0 });
7716
+ }
7717
+ const segments = decoded.map((r) => {
7718
+ const one = segmentsToSelection(width, height, [r]);
7719
+ return { label: r.label || description, bounds: one?.bounds ?? null, ...r.score !== void 0 ? { score: r.score } : {} };
7720
+ });
7721
+ const mask = segmentsToSelection(width, height, decoded);
7722
+ if (mask) this.shapeCommand(mask, options);
7723
+ return { segments: segments.filter((s) => s.bounds) };
7724
+ }
7635
7725
  /** Quick Mask view (tints the unselected area). View state: no undo step. */
7636
7726
  setQuickMask(on) {
7637
7727
  if (Boolean(this.state.quickMask) === on) return;
@@ -10022,7 +10112,7 @@ var DEPTH_MASK_CONTROLS = [
10022
10112
  // src/image-editor/playback.ts
10023
10113
  var IMAGE_PLAYBACK_KIND = "layer";
10024
10114
  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)$/;
10025
- var SELECT = /^(select_region|select_by_color|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy)$/;
10115
+ var SELECT = /^(select_region|select_subject|select_by_color|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy)$/;
10026
10116
  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|convert_text_to_shape)$/;
10027
10117
  var CROP = /^(set_crop|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
10028
10118
  var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
@@ -10043,7 +10133,7 @@ function createImagePlaybackLocator(controller) {
10043
10133
  };
10044
10134
  }
10045
10135
  var num = (v) => typeof v === "number" && Number.isFinite(v);
10046
- var isPoint = (p) => !!p && typeof p === "object" && num(p.x) && num(p.y);
10136
+ var isPoint2 = (p) => !!p && typeof p === "object" && num(p.x) && num(p.y);
10047
10137
  function trace(points, max = 32) {
10048
10138
  const list = points.length <= max ? points : Array.from({ length: max }, (_, i) => points[Math.round(i * (points.length - 1) / (max - 1))]);
10049
10139
  if (!list.length) return [];
@@ -10079,7 +10169,7 @@ function planImageToolPlayback(tool, args) {
10079
10169
  const steps = [];
10080
10170
  const panel = imagePlaybackPanel(tool);
10081
10171
  if (panel) steps.push({ type: "open", ref: { kind: "panel", id: panel } });
10082
- const points = Array.isArray(args.points) ? args.points.filter(isPoint) : [];
10172
+ const points = Array.isArray(args.points) ? args.points.filter(isPoint2) : [];
10083
10173
  const box = boxOf(args);
10084
10174
  if (points.length) steps.push(...trace(points));
10085
10175
  else if (box) steps.push({ type: "move", to: box.from }, { type: "press" }, { type: "drag", to: box.to }, { type: "release" });
@@ -10212,15 +10302,26 @@ function createImageSelectionTools(controller) {
10212
10302
  const selectRegion = {
10213
10303
  name: "select_region",
10214
10304
  title: "Select a region",
10215
- description: 'Selects a rectangle or ellipse (x, y, width, height in natural image pixels), everything ("all"), nothing ("none") or the inverse of the current selection ("invert"). One undoable step.',
10305
+ description: 'Selects a rectangle or ellipse (x, y, width, height in natural image pixels), a polygon (the lasso: points along an outline, closed automatically), everything ("all"), nothing ("none") or the inverse of the current selection ("invert"). To cut out a subject, trace its contour with polygon (then refine_selection for soft edges such as fur or foliage) rather than selecting its bounding box. One undoable step.',
10216
10306
  inputSchema: {
10217
10307
  type: "object",
10218
10308
  properties: {
10219
- shape: { type: "string", enum: ["rect", "ellipse", "all", "none", "invert"], description: "What to select." },
10309
+ shape: { type: "string", enum: ["rect", "ellipse", "polygon", "all", "none", "invert"], description: "What to select." },
10220
10310
  x: { type: "number", description: "rect / ellipse: left edge in natural image pixels." },
10221
10311
  y: { type: "number", description: "rect / ellipse: top edge in natural image pixels." },
10222
10312
  width: { type: "number", description: "rect / ellipse: width in natural image pixels." },
10223
10313
  height: { type: "number", description: "rect / ellipse: height in natural image pixels." },
10314
+ points: {
10315
+ type: "array",
10316
+ minItems: 3,
10317
+ items: {
10318
+ type: "object",
10319
+ properties: { x: { type: "number" }, y: { type: "number" } },
10320
+ required: ["x", "y"],
10321
+ additionalProperties: false
10322
+ },
10323
+ description: "polygon: at least 3 vertices along the outline, in order, in natural image pixels. Follow the edge closely; more points give a tighter contour."
10324
+ },
10224
10325
  mode: modeSchema,
10225
10326
  feather: { type: "number", minimum: 0, description: "Feather radius in pixels (default 0)." }
10226
10327
  },
@@ -10228,7 +10329,7 @@ function createImageSelectionTools(controller) {
10228
10329
  additionalProperties: false
10229
10330
  },
10230
10331
  annotations: writeAnnotations("Select a region", true),
10231
- handler: ({ shape, x, y, width, height, mode, feather }) => {
10332
+ handler: ({ shape, x, y, width, height, points, mode, feather }) => {
10232
10333
  const opts = { mode: parseSelectionMode(mode), feather: num2(feather, 0) };
10233
10334
  switch (shape) {
10234
10335
  case "all":
@@ -10250,8 +10351,16 @@ function createImageSelectionTools(controller) {
10250
10351
  else controller.selectEllipse(rect, opts);
10251
10352
  break;
10252
10353
  }
10354
+ case "polygon": {
10355
+ const list = Array.isArray(points) ? points.map((p) => ({ x: num2(p?.x, NaN), y: num2(p?.y, NaN) })) : [];
10356
+ if (list.length < 3 || !list.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) {
10357
+ throw new Error("polygon needs points: at least 3 { x, y } vertices.");
10358
+ }
10359
+ controller.selectPolygon(list, opts);
10360
+ break;
10361
+ }
10253
10362
  default:
10254
- throw new Error(`Unknown shape "${String(shape)}". Use rect, ellipse, all, none or invert.`);
10363
+ throw new Error(`Unknown shape "${String(shape)}". Use rect, ellipse, polygon, all, none or invert.`);
10255
10364
  }
10256
10365
  return summary(controller);
10257
10366
  }
@@ -10391,6 +10500,53 @@ function createImageSelectionTools(controller) {
10391
10500
  };
10392
10501
  return [getSelection, selectRegion, selectByColor, modify, refine, applyMask];
10393
10502
  }
10503
+ function createImageSubjectSelectionTool(controller) {
10504
+ return {
10505
+ name: "select_subject",
10506
+ title: "Select a subject",
10507
+ description: 'Select \u203A Subject: an AI segmentation model finds the objects matching target (a description such as "the dog", "the house", "the sky") in the flattened image and selects their outlines, combined with the selection by mode. Use it to cut a subject out by its contour (then layer_via_copy for a layer of just that subject); describe one object precisely to get only that one. One undoable step. Returns the selection and the objects found (label, bounds); none found leaves the selection unchanged.',
10508
+ inputSchema: {
10509
+ type: "object",
10510
+ properties: {
10511
+ target: { type: "string", minLength: 1, description: "What to select, in words." },
10512
+ maxSegments: {
10513
+ type: "integer",
10514
+ minimum: 1,
10515
+ maximum: 32,
10516
+ description: "Select at most this many matching objects (default the model\u2019s; 1 for a single subject)."
10517
+ },
10518
+ mode: modeSchema,
10519
+ feather: { type: "number", minimum: 0, description: "Feather radius in pixels (default 0)." }
10520
+ },
10521
+ required: ["target"],
10522
+ additionalProperties: false
10523
+ },
10524
+ annotations: { ...writeAnnotations("Select a subject"), openWorldHint: true },
10525
+ handler: async ({ target, maxSegments, mode, feather }) => {
10526
+ if (typeof target !== "string" || !target.trim()) throw new Error("target must describe what to select.");
10527
+ const { segments } = await controller.selectSubject(target, {
10528
+ mode: parseSelectionMode(mode),
10529
+ feather: num2(feather, 0),
10530
+ maxSegments: typeof maxSegments === "number" ? Math.max(1, Math.round(maxSegments)) : void 0
10531
+ });
10532
+ return { ...summary(controller), found: segments };
10533
+ }
10534
+ };
10535
+ }
10536
+ function createImageSegmentationToolProvider(controller, options = {}) {
10537
+ const tool = createImageSubjectSelectionTool(controller);
10538
+ return withImagePlayback(
10539
+ {
10540
+ id: options.id ?? "image-segmentation",
10541
+ namespace: options.namespace,
10542
+ title: "Image segmentation",
10543
+ description: "Select \u203A Subject: select objects in the image by description through an AI segmentation model.",
10544
+ skill: "pixerate-image-editor",
10545
+ tools: () => controller.segmentationProvider ? [tool] : []
10546
+ },
10547
+ options.playbackSurface
10548
+ );
10549
+ }
10394
10550
 
10395
10551
  // src/image-editor/capture.ts
10396
10552
  var NO_CANVAS = "Rendering is not available in this environment.";
@@ -11176,7 +11332,7 @@ function createImageLayerTools(controller) {
11176
11332
  const viaCopy = {
11177
11333
  name: "layer_via_copy",
11178
11334
  title: "New layer from the selection",
11179
- description: "Layer via Copy: copies the selected pixels of a pixel layer (layerId, default the active layer) to a new layer directly above it and makes it active. cut: true also removes them from the source (Layer via Cut). Needs a selection. One undo step. Returns the new layer.",
11335
+ description: "Layer via Copy: copies the selected pixels of a pixel layer (layerId, default the active layer) to a new layer directly above it and makes it active. cut: true also removes them from the source (Layer via Cut). Needs a selection; the new layer takes the selection\u2019s shape, so select the subject\u2019s outline (select_region polygon, select_by_color) to isolate it, not its bounding box. One undo step. Returns the new layer.",
11180
11336
  inputSchema: {
11181
11337
  type: "object",
11182
11338
  properties: {
@@ -14746,6 +14902,7 @@ function layerPanels(layer, options = {}) {
14746
14902
  if (layer.mask) {
14747
14903
  panels.push({ id: "mask", title: "Mask Options" });
14748
14904
  }
14905
+ panels.push({ id: "selection", title: "Selection" });
14749
14906
  return panels;
14750
14907
  }
14751
14908
 
@@ -15880,6 +16037,7 @@ export {
15880
16037
  LayerCompositor,
15881
16038
  LayerEffectError,
15882
16039
  LayerOperationError,
16040
+ MARCHING_ANTS_STEP_MS,
15883
16041
  MAX_BATCH,
15884
16042
  MAX_BRUSH_SIZE,
15885
16043
  MAX_CANVAS_BACKING_SIDE,
@@ -16159,9 +16317,11 @@ export {
16159
16317
  createImagePlaybackLocator,
16160
16318
  createImagePsdTools,
16161
16319
  createImageRetouchTools,
16320
+ createImageSegmentationToolProvider,
16162
16321
  createImageSelectionTools,
16163
16322
  createImageShapeTools,
16164
16323
  createImageSmartObjectTools,
16324
+ createImageSubjectSelectionTool,
16165
16325
  createImageTransformTools,
16166
16326
  createImageTypeTools,
16167
16327
  createMetricTextShaper,
@@ -16602,6 +16762,8 @@ export {
16602
16762
  scalePaintGradient,
16603
16763
  seamlessClone,
16604
16764
  seedFromString,
16765
+ segmentToSelection,
16766
+ segmentsToSelection,
16605
16767
  selectionArea,
16606
16768
  selectionFromCoverage,
16607
16769
  selectionFromPixels,
@@ -16659,6 +16821,7 @@ export {
16659
16821
  srgbEncode,
16660
16822
  srgbToLinear,
16661
16823
  standardBlendMode,
16824
+ startMarchingAnts,
16662
16825
  stepSlotTs,
16663
16826
  strokeGrainCatch,
16664
16827
  styleAt,
@@ -123,6 +123,7 @@ export interface LayerPanelsOptions {
123
123
  *
124
124
  * `properties` ("Layer Properties") is first for every layer and includes the layer's Layer Style
125
125
  * (drop shadow, stroke, glow…: `layerEffectsPanel`) for layers that can take one, with or without a
126
- * provider. There is no separate `effects` (Layer Effects) or `smart-filters` panel.
126
+ * provider. There is no separate `effects` (Layer Effects) or `smart-filters` panel. `selection`
127
+ * is last for every layer, and with no active layer follows `canvas-size`.
127
128
  */
128
129
  export declare function layerPanels(layer: ImageLayer | null, options?: LayerPanelsOptions): LayerPanelDescriptor[];
@@ -123,6 +123,7 @@ export interface LayerPanelsOptions {
123
123
  *
124
124
  * `properties` ("Layer Properties") is first for every layer and includes the layer's Layer Style
125
125
  * (drop shadow, stroke, glow…: `layerEffectsPanel`) for layers that can take one, with or without a
126
- * provider. There is no separate `effects` (Layer Effects) or `smart-filters` panel.
126
+ * provider. There is no separate `effects` (Layer Effects) or `smart-filters` panel. `selection`
127
+ * is last for every layer, and with no active layer follows `canvas-size`.
127
128
  */
128
129
  export declare function layerPanels(layer: ImageLayer | null, options?: LayerPanelsOptions): LayerPanelDescriptor[];
@@ -176,7 +176,10 @@ export declare class ImageEditorRenderer {
176
176
  private outlineCache;
177
177
  /** Rubylith over the unselected area, drawn in natural image space. */
178
178
  private drawQuickMask;
179
- /** Marching ants: a white line under a black dashed one, one screen pixel wide. */
179
+ /**
180
+ * Marching ants: a white line under a black dashed one, one screen pixel wide. `scale` is
181
+ * screen pixels per natural pixel (the projection scale times the host's CSS scale).
182
+ */
180
183
  private drawAnts;
181
184
  private drawDraftPath;
182
185
  private getSourceId;
@@ -176,7 +176,10 @@ export declare class ImageEditorRenderer {
176
176
  private outlineCache;
177
177
  /** Rubylith over the unselected area, drawn in natural image space. */
178
178
  private drawQuickMask;
179
- /** Marching ants: a white line under a black dashed one, one screen pixel wide. */
179
+ /**
180
+ * Marching ants: a white line under a black dashed one, one screen pixel wide. `scale` is
181
+ * screen pixels per natural pixel (the projection scale times the host's CSS scale).
182
+ */
180
183
  private drawAnts;
181
184
  private drawDraftPath;
182
185
  private getSourceId;
@@ -7,3 +7,4 @@ export * from './layer-mask.cjs';
7
7
  export * from './overlay.cjs';
8
8
  export * from './tool-options.cjs';
9
9
  export * from './gesture.cjs';
10
+ export * from './segmentation.cjs';
@@ -7,3 +7,4 @@ export * from './layer-mask.js';
7
7
  export * from './overlay.js';
8
8
  export * from './tool-options.js';
9
9
  export * from './gesture.js';
10
+ export * from './segmentation.js';
@@ -16,3 +16,11 @@ export interface QuickMaskStyle {
16
16
  * selection shows through clear, as Photoshop's rubylith overlay.
17
17
  */
18
18
  export declare function quickMaskPixels(mask: SelectionMask, style?: QuickMaskStyle): Uint8ClampedArray;
19
+ /** How often marching ants step (ms); each step moves the dashes one screen pixel. */
20
+ export declare const MARCHING_ANTS_STEP_MS = 100;
21
+ /**
22
+ * Marches the ants: calls `tick` with the next `RenderOptions.antsOffset` every `stepMs` until the
23
+ * returned stop function is called. Hosts run it while a selection is shown and render on each
24
+ * tick. With reduced motion requested the ants stand still and `tick` is never called.
25
+ */
26
+ export declare function startMarchingAnts(tick: (offset: number) => void, stepMs?: number): () => void;
@@ -16,3 +16,11 @@ export interface QuickMaskStyle {
16
16
  * selection shows through clear, as Photoshop's rubylith overlay.
17
17
  */
18
18
  export declare function quickMaskPixels(mask: SelectionMask, style?: QuickMaskStyle): Uint8ClampedArray;
19
+ /** How often marching ants step (ms); each step moves the dashes one screen pixel. */
20
+ export declare const MARCHING_ANTS_STEP_MS = 100;
21
+ /**
22
+ * Marches the ants: calls `tick` with the next `RenderOptions.antsOffset` every `stepMs` until the
23
+ * returned stop function is called. Hosts run it while a selection is shown and render on each
24
+ * tick. With reduced motion requested the ants stand still and `tick` is never called.
25
+ */
26
+ export declare function startMarchingAnts(tick: (offset: number) => void, stepMs?: number): () => void;
@@ -0,0 +1,49 @@
1
+ import type { CropBox, ImagePoint } from '../types.cjs';
2
+ import { type PixelBuffer, type SelectionMask } from './mask.cjs';
3
+ /** What a segmentation provider is asked for (see `SegmentationProvider`). */
4
+ export interface SegmentationRequest {
5
+ /** The flattened image at its natural size, as a PNG data URL. */
6
+ image: string;
7
+ /** The image's natural size in pixels. */
8
+ width: number;
9
+ height: number;
10
+ /** What to find, in words ("the dog", "the sky", "every separate object"). */
11
+ target: string;
12
+ /** Return at most this many objects (the provider's default when absent). */
13
+ maxSegments?: number;
14
+ signal?: AbortSignal;
15
+ }
16
+ /**
17
+ * One object a provider found, in natural image pixels: an outline `polygon` (or several, for
18
+ * an object in pieces), or a `mask` covering the whole image (white = the object, any size;
19
+ * it is resampled), or both, which are combined.
20
+ */
21
+ export interface SegmentationResult {
22
+ label?: string;
23
+ polygon?: ImagePoint[] | ImagePoint[][];
24
+ mask?: string | PixelBuffer;
25
+ /** The object's bounding box, when the provider knows it. */
26
+ box?: CropBox;
27
+ /** The provider's confidence, 0..1. */
28
+ score?: number;
29
+ }
30
+ /**
31
+ * Finds objects in an image by a description and returns their shapes: an AI segmentation
32
+ * model (SAM, Gemini, a background remover…) behind `ImageEditorController.selectSubject`.
33
+ */
34
+ export interface SegmentationProvider {
35
+ /** Shown in the UI and in tool results (e.g. "Gemini"). */
36
+ readonly name?: string;
37
+ segment(request: SegmentationRequest): Promise<SegmentationResult[]>;
38
+ }
39
+ /** A segmentation result with its `mask` already decoded to pixels (strings are decoded by the controller). */
40
+ export type DecodedSegmentationResult = Omit<SegmentationResult, 'mask'> & {
41
+ mask?: PixelBuffer;
42
+ };
43
+ /**
44
+ * One segmentation result as a selection over a `width × height` image, or null when it has no
45
+ * usable shape. Polygons are anti-aliased; several polygons and a mask are added together.
46
+ */
47
+ export declare function segmentToSelection(width: number, height: number, result: DecodedSegmentationResult): SelectionMask | null;
48
+ /** Every usable result added into one selection (null when none has a shape). */
49
+ export declare function segmentsToSelection(width: number, height: number, results: readonly DecodedSegmentationResult[]): SelectionMask | null;
@@ -0,0 +1,49 @@
1
+ import type { CropBox, ImagePoint } from '../types.js';
2
+ import { type PixelBuffer, type SelectionMask } from './mask.js';
3
+ /** What a segmentation provider is asked for (see `SegmentationProvider`). */
4
+ export interface SegmentationRequest {
5
+ /** The flattened image at its natural size, as a PNG data URL. */
6
+ image: string;
7
+ /** The image's natural size in pixels. */
8
+ width: number;
9
+ height: number;
10
+ /** What to find, in words ("the dog", "the sky", "every separate object"). */
11
+ target: string;
12
+ /** Return at most this many objects (the provider's default when absent). */
13
+ maxSegments?: number;
14
+ signal?: AbortSignal;
15
+ }
16
+ /**
17
+ * One object a provider found, in natural image pixels: an outline `polygon` (or several, for
18
+ * an object in pieces), or a `mask` covering the whole image (white = the object, any size;
19
+ * it is resampled), or both, which are combined.
20
+ */
21
+ export interface SegmentationResult {
22
+ label?: string;
23
+ polygon?: ImagePoint[] | ImagePoint[][];
24
+ mask?: string | PixelBuffer;
25
+ /** The object's bounding box, when the provider knows it. */
26
+ box?: CropBox;
27
+ /** The provider's confidence, 0..1. */
28
+ score?: number;
29
+ }
30
+ /**
31
+ * Finds objects in an image by a description and returns their shapes: an AI segmentation
32
+ * model (SAM, Gemini, a background remover…) behind `ImageEditorController.selectSubject`.
33
+ */
34
+ export interface SegmentationProvider {
35
+ /** Shown in the UI and in tool results (e.g. "Gemini"). */
36
+ readonly name?: string;
37
+ segment(request: SegmentationRequest): Promise<SegmentationResult[]>;
38
+ }
39
+ /** A segmentation result with its `mask` already decoded to pixels (strings are decoded by the controller). */
40
+ export type DecodedSegmentationResult = Omit<SegmentationResult, 'mask'> & {
41
+ mask?: PixelBuffer;
42
+ };
43
+ /**
44
+ * One segmentation result as a selection over a `width × height` image, or null when it has no
45
+ * usable shape. Polygons are anti-aliased; several polygons and a mask are added together.
46
+ */
47
+ export declare function segmentToSelection(width: number, height: number, result: DecodedSegmentationResult): SelectionMask | null;
48
+ /** Every usable result added into one selection (null when none has a shape). */
49
+ export declare function segmentsToSelection(width: number, height: number, results: readonly DecodedSegmentationResult[]): SelectionMask | null;
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from '../tools/registry.cjs';
1
+ import type { AgentTool, AgentToolProvider } from '../tools/registry.cjs';
2
2
  import type { ImageEditorController } from './controller.cjs';
3
3
  /**
4
4
  * Agent tools for selections and masks (part of `createImageEditorToolProvider`):
@@ -6,3 +6,20 @@ import type { ImageEditorController } from './controller.cjs';
6
6
  * and `apply_selection_as_mask`. Coordinates are natural pixels of the source image.
7
7
  */
8
8
  export declare function createImageSelectionTools(controller: ImageEditorController): AgentTool[];
9
+ /** Options for `createImageSegmentationToolProvider`. */
10
+ export interface ImageSegmentationToolProviderOptions {
11
+ /** Provider id (default `image-segmentation`). */
12
+ id?: string;
13
+ /** Tool-name prefix (`<namespace>__<name>`). */
14
+ namespace?: string;
15
+ /** Agent playback surface (default the image editor's). */
16
+ playbackSurface?: string;
17
+ }
18
+ /** The `select_subject` tool: Select › Subject through the controller's segmentation provider. */
19
+ export declare function createImageSubjectSelectionTool(controller: ImageEditorController): AgentTool;
20
+ /**
21
+ * Agent tools that need the controller's segmentation provider (`select_subject`). The list is
22
+ * empty while no provider is set, so a host can register this once and the tool appears when a
23
+ * model is available.
24
+ */
25
+ export declare function createImageSegmentationToolProvider(controller: ImageEditorController, options?: ImageSegmentationToolProviderOptions): AgentToolProvider;
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from '../tools/registry.js';
1
+ import type { AgentTool, AgentToolProvider } from '../tools/registry.js';
2
2
  import type { ImageEditorController } from './controller.js';
3
3
  /**
4
4
  * Agent tools for selections and masks (part of `createImageEditorToolProvider`):
@@ -6,3 +6,20 @@ import type { ImageEditorController } from './controller.js';
6
6
  * and `apply_selection_as_mask`. Coordinates are natural pixels of the source image.
7
7
  */
8
8
  export declare function createImageSelectionTools(controller: ImageEditorController): AgentTool[];
9
+ /** Options for `createImageSegmentationToolProvider`. */
10
+ export interface ImageSegmentationToolProviderOptions {
11
+ /** Provider id (default `image-segmentation`). */
12
+ id?: string;
13
+ /** Tool-name prefix (`<namespace>__<name>`). */
14
+ namespace?: string;
15
+ /** Agent playback surface (default the image editor's). */
16
+ playbackSurface?: string;
17
+ }
18
+ /** The `select_subject` tool: Select › Subject through the controller's segmentation provider. */
19
+ export declare function createImageSubjectSelectionTool(controller: ImageEditorController): AgentTool;
20
+ /**
21
+ * Agent tools that need the controller's segmentation provider (`select_subject`). The list is
22
+ * empty while no provider is set, so a host can register this once and the tool appears when a
23
+ * model is available.
24
+ */
25
+ export declare function createImageSegmentationToolProvider(controller: ImageEditorController, options?: ImageSegmentationToolProviderOptions): AgentToolProvider;
@@ -395,6 +395,7 @@ export interface ExportOptions {
395
395
  layerIds?: string[];
396
396
  }
397
397
  import type { EffectProvider } from '../effects/types.cjs';
398
+ import type { SegmentationProvider } from './selection/segmentation.cjs';
398
399
  export interface ImageEditorOptions {
399
400
  /**
400
401
  * What lays out and draws type layers. Default: the browser's fonts through Canvas 2D
@@ -421,6 +422,12 @@ export interface ImageEditorOptions {
421
422
  * with `setEffectsProvider`.
422
423
  */
423
424
  effects?: EffectProvider | null;
425
+ /**
426
+ * A segmentation provider (an AI model that finds objects by description) for Select ›
427
+ * Subject (`selectSubject`, the `select_subject` tool). Optional; change it later with
428
+ * `setSegmentationProvider`.
429
+ */
430
+ segmentation?: SegmentationProvider | null;
424
431
  }
425
432
  export interface InpaintOptions {
426
433
  prompt: string;
@@ -395,6 +395,7 @@ export interface ExportOptions {
395
395
  layerIds?: string[];
396
396
  }
397
397
  import type { EffectProvider } from '../effects/types.js';
398
+ import type { SegmentationProvider } from './selection/segmentation.js';
398
399
  export interface ImageEditorOptions {
399
400
  /**
400
401
  * What lays out and draws type layers. Default: the browser's fonts through Canvas 2D
@@ -421,6 +422,12 @@ export interface ImageEditorOptions {
421
422
  * with `setEffectsProvider`.
422
423
  */
423
424
  effects?: EffectProvider | null;
425
+ /**
426
+ * A segmentation provider (an AI model that finds objects by description) for Select ›
427
+ * Subject (`selectSubject`, the `select_subject` tool). Optional; change it later with
428
+ * `setSegmentationProvider`.
429
+ */
430
+ segmentation?: SegmentationProvider | null;
424
431
  }
425
432
  export interface InpaintOptions {
426
433
  prompt: string;