@pixerate/editor 0.201.0 → 0.203.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 (33) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/{chunk-GLQQ2VIL.cjs → chunk-53GOGBTM.cjs} +23 -1
  4. package/dist/{chunk-3TNA7JKI.js → chunk-ZPJEH456.js} +22 -0
  5. package/dist/commands/builtins.d.cts +6 -0
  6. package/dist/commands/builtins.d.ts +6 -0
  7. package/dist/commands/index.cjs +4 -2
  8. package/dist/commands/index.js +4 -2
  9. package/dist/image-editor/controller.d.cts +24 -1
  10. package/dist/image-editor/controller.d.ts +24 -1
  11. package/dist/image-editor/index.cjs +226 -171
  12. package/dist/image-editor/index.js +78 -23
  13. package/dist/image-editor/selection/segmentation.d.cts +7 -0
  14. package/dist/image-editor/selection/segmentation.d.ts +7 -0
  15. package/dist/paint-editor/guide-tools.d.cts +2 -2
  16. package/dist/paint-editor/guide-tools.d.ts +2 -2
  17. package/dist/paint-editor/hosted.d.cts +34 -0
  18. package/dist/paint-editor/hosted.d.ts +34 -0
  19. package/dist/paint-editor/index.cjs +101 -10
  20. package/dist/paint-editor/index.d.cts +1 -0
  21. package/dist/paint-editor/index.d.ts +1 -0
  22. package/dist/paint-editor/index.js +96 -5
  23. package/dist/paint-editor/sticker-tools.d.cts +2 -2
  24. package/dist/paint-editor/sticker-tools.d.ts +2 -2
  25. package/dist/paint-editor/thick-tools.d.cts +2 -2
  26. package/dist/paint-editor/thick-tools.d.ts +2 -2
  27. package/dist/paint-editor/tools.d.cts +19 -2
  28. package/dist/paint-editor/tools.d.ts +19 -2
  29. package/dist/paint-editor/wet-tools.d.cts +2 -2
  30. package/dist/paint-editor/wet-tools.d.ts +2 -2
  31. package/package.json +1 -1
  32. package/skills/pixerate-image-editor/SKILL.md +2 -2
  33. package/skills/pixerate-paint-editor/SKILL.md +9 -0
@@ -6213,6 +6213,49 @@ function withPaintPlayback(provider, options) {
6213
6213
  };
6214
6214
  }
6215
6215
 
6216
+ // src/paint-editor/hosted.ts
6217
+ function shiftAt(value, keys, dx, dy) {
6218
+ if (value === null || typeof value !== "object") return value;
6219
+ if (keys.length === 0) {
6220
+ if (Array.isArray(value)) return value;
6221
+ const o = { ...value };
6222
+ if (typeof o.x === "number") o.x += dx;
6223
+ if (typeof o.y === "number") o.y += dy;
6224
+ return o;
6225
+ }
6226
+ const [head, ...rest] = keys;
6227
+ const each = head.endsWith("[]");
6228
+ const key = each ? head.slice(0, -2) : head;
6229
+ const inner = key === "" ? value : value[key];
6230
+ if (inner === void 0) return value;
6231
+ const next = each ? Array.isArray(inner) ? inner.map((v) => shiftAt(v, rest, dx, dy)) : inner : shiftAt(inner, rest, dx, dy);
6232
+ if (key === "") return next;
6233
+ return Array.isArray(value) ? value : { ...value, [key]: next };
6234
+ }
6235
+ var shiftAll = (value, paths, dx, dy) => paths.reduce((v, p) => shiftAt(v, p === "" ? [] : p.split("."), dx, dy), value);
6236
+ var toDocument = (value, paths, origin) => shiftAll(value, paths, -origin.x, -origin.y);
6237
+ var fromDocument = (value, paths, origin) => shiftAll(value, paths, origin.x, origin.y);
6238
+ function hostedText(text) {
6239
+ return text.replace(/\ba document pixel\b/g, "a canvas point").replace(/\bdocument pixels\b/g, "canvas coordinates").replace(/\bDocument ([xy])\b/g, "Canvas $1").replace(/\bdocument ([xy])\b/g, "canvas $1");
6240
+ }
6241
+ function hostedSchema(schema) {
6242
+ if (Array.isArray(schema)) return schema.map(hostedSchema);
6243
+ if (schema === null || typeof schema !== "object") return schema;
6244
+ const out = {};
6245
+ for (const [k, v] of Object.entries(schema)) out[k] = k === "description" && typeof v === "string" ? hostedText(v) : hostedSchema(v);
6246
+ return out;
6247
+ }
6248
+ function lazyController(get) {
6249
+ return new Proxy({}, {
6250
+ get(_, key) {
6251
+ const target = get();
6252
+ const value = Reflect.get(target, key, target);
6253
+ return typeof value === "function" ? value.bind(target) : value;
6254
+ },
6255
+ has: (_, key) => Reflect.has(get(), key)
6256
+ });
6257
+ }
6258
+
6216
6259
  // src/paint-editor/sticker-tools.ts
6217
6260
  var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
6218
6261
  var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
@@ -6226,6 +6269,7 @@ function createPaintStickerTools(c) {
6226
6269
  description: "Lists a layer\u2019s stickers bottom first: id, stamp, x, y (centre, document pixels), size (diameter px), rotation (degrees clockwise), color, opacity. Empty for a layer without stickers. Changes nothing.",
6227
6270
  inputSchema: { type: "object", properties: { layerId: LAYER_ID }, additionalProperties: false },
6228
6271
  annotations: { title: "List stickers", ...READ },
6272
+ coordinates: { out: ["stickers[]"] },
6229
6273
  handler: ({ layerId }) => ({ layerId: layerId ?? c.document.activeLayerId, stickers: c.listStickers(layerId) })
6230
6274
  },
6231
6275
  {
@@ -6249,6 +6293,7 @@ function createPaintStickerTools(c) {
6249
6293
  additionalProperties: false
6250
6294
  },
6251
6295
  annotations: { title: "Change a sticker", ...WRITE },
6296
+ coordinates: { in: [""], out: [""] },
6252
6297
  handler: ({ layerId, id, ...update }) => c.updateSticker(layerId, id, update)
6253
6298
  },
6254
6299
  {
@@ -6304,6 +6349,7 @@ var RECT = {
6304
6349
  required: ["x", "y", "width", "height"],
6305
6350
  additionalProperties: false
6306
6351
  };
6352
+ var STENCIL_POSITIONS = ["from", "to", "center", "rect", "points[]"];
6307
6353
  var PNG = { type: "string", minLength: 1, description: "The image as a PNG, base64 or a data:image/png URL." };
6308
6354
  var tracingInfo = (t) => t ? { width: t.width, height: t.height, rect: t.rect, opacity: t.opacity, visible: t.visible, sampleColors: t.sampleColors } : null;
6309
6355
  var refInfo = ({ src: _s, ...r }) => r;
@@ -6332,6 +6378,7 @@ function createPaintGuideTools(c) {
6332
6378
  description: "Reads the painting guides: symmetry (mode, centre, rotation, segments, mirror, and how many copies each stroke paints), the perspective guide (vanishing points, snap), the tracing image (size, placement, opacity, visibility, colour sampling), the reference images and the stencils (id, kind, mode, geometry).",
6333
6379
  inputSchema: EMPTY,
6334
6380
  annotations: { title: "Get the painting guides", ...READ2 },
6381
+ coordinates: { out: ["symmetry.center", "perspective.points[]", "tracing.rect", ...STENCIL_POSITIONS.map((p) => `stencils[].${p}`)] },
6335
6382
  handler: () => {
6336
6383
  const g = guides();
6337
6384
  return { symmetry: { ...g.symmetry, copies: symmetryCopies(g.symmetry).length }, perspective: g.perspective, tracing: tracingInfo(g.tracing), references: g.references.map(refInfo), stencils: g.stencils };
@@ -6353,6 +6400,7 @@ function createPaintGuideTools(c) {
6353
6400
  additionalProperties: false
6354
6401
  },
6355
6402
  annotations: { title: "Set mirror or kaleidoscope painting", ...SET },
6403
+ coordinates: { in: ["center"], out: ["center"] },
6356
6404
  handler: (a) => {
6357
6405
  const s = c.setSymmetry(a);
6358
6406
  return { ...s, copies: symmetryCopies(s).length };
@@ -6372,6 +6420,7 @@ function createPaintGuideTools(c) {
6372
6420
  additionalProperties: false
6373
6421
  },
6374
6422
  annotations: { title: "Set the perspective guide", ...SET },
6423
+ coordinates: { in: ["points[]"], out: ["points[]"] },
6375
6424
  handler: (a) => c.setPerspective(a)
6376
6425
  },
6377
6426
  {
@@ -6390,6 +6439,7 @@ function createPaintGuideTools(c) {
6390
6439
  additionalProperties: false
6391
6440
  },
6392
6441
  annotations: { title: "Set a tracing image", ...WRITE2 },
6442
+ coordinates: { in: ["rect"], out: ["rect"] },
6393
6443
  handler: (a) => tracingInfo(c.setTracingImage(a))
6394
6444
  },
6395
6445
  {
@@ -6407,6 +6457,7 @@ function createPaintGuideTools(c) {
6407
6457
  additionalProperties: false
6408
6458
  },
6409
6459
  annotations: { title: "Change the tracing image", ...SET },
6460
+ coordinates: { in: ["rect"], out: ["rect"] },
6410
6461
  handler: (a) => tracingInfo(c.updateTracing(a))
6411
6462
  },
6412
6463
  {
@@ -6700,6 +6751,7 @@ function createPaintGuideTools(c) {
6700
6751
  description: "Lays a stencil on the canvas: a ruler (straight edge to draw along) or an arc (circle or part of one) that pointer and agent strokes starting near it follow exactly, or a line (straight masking edge) or a shape (rect, ellipse or polygon template) that paint lands only inside of (outside with invert). Editor state saved with the document, not an undo step; replays keep the mask they were painted through. Returns the stencil with its id.",
6701
6752
  inputSchema: { type: "object", properties: { kind: { type: "string", enum: ["ruler", "arc", "line", "shape"], description: "ruler, arc, line or shape." }, ...STENCIL_GEOMETRY }, required: ["kind"], additionalProperties: false },
6702
6753
  annotations: { title: "Lay a stencil", ...WRITE2 },
6754
+ coordinates: { in: STENCIL_POSITIONS, out: STENCIL_POSITIONS },
6703
6755
  handler: (a) => c.addStencil(a)
6704
6756
  },
6705
6757
  {
@@ -6708,6 +6760,7 @@ function createPaintGuideTools(c) {
6708
6760
  description: "Moves, reshapes, switches (guide or mask, invert) or hides (enabled false) a stencil by id from get_guides. Returns the stencil.",
6709
6761
  inputSchema: { type: "object", properties: { id: { type: "string", description: "Stencil id from get_guides." }, ...STENCIL_GEOMETRY }, required: ["id"], additionalProperties: false },
6710
6762
  annotations: { title: "Change a stencil", ...SET },
6763
+ coordinates: { in: STENCIL_POSITIONS, out: STENCIL_POSITIONS },
6711
6764
  handler: ({ id, ...update }) => c.updateStencil(id, update)
6712
6765
  },
6713
6766
  {
@@ -6760,6 +6813,7 @@ function createPaintWetTools(c) {
6760
6813
  description: "Reads the wet media: whether any paint is still wet, the simulation clock (steps), whether time is paused, its speed (steps per second), the canvas tilt, the kernel running it (typescript, wasm, wasm-threads with the thread count, and a message when it runs slower than it could, e.g. without cross-origin isolation), and each wet layer: wet cells, where the water is (wetBounds), live tiles, its settings and flow map. Also engine: Engine B, the WebGPU accelerator (mode auto/off, state, reason, adapter, minTiles, activeTiles). Call it after wet strokes to see what is still wet.",
6761
6814
  inputSchema: EMPTY2,
6762
6815
  annotations: { title: "Get the wet paint", ...READ3 },
6816
+ coordinates: { out: ["layers[].wetBounds", "layers[].flow.center"] },
6763
6817
  handler: () => c.wetState()
6764
6818
  },
6765
6819
  {
@@ -6786,6 +6840,7 @@ function createPaintWetTools(c) {
6786
6840
  description: "Dries wet paint at once: everything settles where it is and the water goes, as a hair dryer at full blast would (the same deterministic bake that saving uses). On one layer or every wet layer, optionally only inside rect. Dried washes stay put: later washes glaze over them instead of mixing in. One undo step. Returns dried (each layer id with the rectangle that dried) and stillWet.",
6787
6841
  inputSchema: { type: "object", properties: { layerId: { type: "string", description: "Only this layer (default every wet layer)." }, rect: RECT2 }, additionalProperties: false },
6788
6842
  annotations: { title: "Dry the wet paint", ...WRITE3 },
6843
+ coordinates: { in: ["rect"], out: ["dried[].bounds"] },
6789
6844
  handler: async ({ layerId, rect }) => {
6790
6845
  await c.wetIdle();
6791
6846
  return c.dryPaint({ layerId, rect });
@@ -6801,6 +6856,7 @@ function createPaintWetTools(c) {
6801
6856
  additionalProperties: false
6802
6857
  },
6803
6858
  annotations: { title: "Pre-wet the paper", ...WRITE3 },
6859
+ coordinates: { in: ["rect"], out: ["rect"] },
6804
6860
  handler: async ({ layerId, rect, amount }) => {
6805
6861
  await c.wetIdle();
6806
6862
  return c.wetPaper({ layerId, rect, amount });
@@ -6843,6 +6899,7 @@ function createPaintWetTools(c) {
6843
6899
  additionalProperties: false
6844
6900
  },
6845
6901
  annotations: { title: "Set a flow map", ...SET2 },
6902
+ coordinates: { in: ["center"], out: ["flow.center"] },
6846
6903
  handler: async ({ layerId, ...flow }) => {
6847
6904
  await c.wetIdle();
6848
6905
  return { layerId: layerId ?? c.document.activeLayerId, flow: c.setFlowMap(layerId, flow), stillWet: stillWet() };
@@ -6943,6 +7000,7 @@ function createPaintThickTools(c) {
6943
7000
  description: "Reads a layer\u2019s thick paint: where the relief is (bounds, layer pixels), its highest point (0..255) and the lighting. relief is null when the layer is flat. Changes nothing.",
6944
7001
  inputSchema: { type: "object", properties: { layerId: LAYER_ID3 }, additionalProperties: false },
6945
7002
  annotations: { title: "Read a layer\u2019s relief", ...READ4 },
7003
+ coordinates: { out: ["relief.bounds"] },
6946
7004
  handler: ({ layerId }) => ({ relief: c.reliefOf(layerId), lighting: c.getLighting() })
6947
7005
  },
6948
7006
  {
@@ -7039,7 +7097,7 @@ var TIP = {
7039
7097
  type: "object",
7040
7098
  description: "The tip (the shared brush engine). Missing fields keep their current values.",
7041
7099
  properties: {
7042
- size: { type: "number", minimum: 1, maximum: MAX_BRUSH_SIZE, description: "Diameter in document pixels." },
7100
+ size: { type: "number", minimum: 1, maximum: MAX_BRUSH_SIZE, description: "Diameter in pixels." },
7043
7101
  hardness: { type: "number", minimum: 0, maximum: 1, description: "Edge hardness, 0 (soft) .. 1 (hard)." },
7044
7102
  spacing: { type: "number", minimum: 0.01, maximum: 10, description: "Dab spacing as a fraction of the diameter (0.05 is smooth, 1 is dotted)." },
7045
7103
  angle: { type: "number", minimum: -360, maximum: 360, description: "Tip angle in degrees, counter-clockwise; with rotation stroke or barrel it is added to the stroke direction or pen rotation." },
@@ -7289,7 +7347,7 @@ function brushSummary(b) {
7289
7347
  };
7290
7348
  }
7291
7349
  function createPaintEditorToolProvider(controller, options = {}) {
7292
- const c = controller;
7350
+ const c = typeof controller === "function" ? lazyController(controller) : controller;
7293
7351
  const tools = [];
7294
7352
  const add = (t) => tools.push(t);
7295
7353
  const act = async (command, direct) => {
@@ -7301,9 +7359,10 @@ function createPaintEditorToolProvider(controller, options = {}) {
7301
7359
  add({
7302
7360
  name: "get_canvas_info",
7303
7361
  title: "Get the canvas",
7304
- description: "Reads the canvas: size in pixels and dpi, its origin (where (0, 0) is relative to the canvas it was extended from), the paper (preset, colour, grain depth), the active layer, the active brush id, the paint colour, the view (zoom, rotation, mirror), the hand, how many strokes are recorded, the undo / redo labels, and the blend modes. Call it first.",
7362
+ description: "Reads the canvas: size in pixels and dpi, its origin (where (0, 0) is relative to the canvas it was extended from), bounds (the rectangle the painting covers, in the coordinates every other tool takes), the paper (preset, colour, grain depth), the active layer, the active brush id, the paint colour, the view (zoom, rotation, mirror), the hand, how many strokes are recorded, the undo / redo labels, and the blend modes. Call it first.",
7305
7363
  inputSchema: EMPTY3,
7306
7364
  annotations: { title: "Get the canvas", ...READ6 },
7365
+ coordinates: { out: ["bounds"] },
7307
7366
  handler: () => {
7308
7367
  const s = c.getState();
7309
7368
  const d = s.document;
@@ -7312,6 +7371,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7312
7371
  height: d.canvas.height,
7313
7372
  dpi: d.canvas.dpi,
7314
7373
  origin: { ...d.origin ?? { x: 0, y: 0 } },
7374
+ bounds: { x: 0, y: 0, width: d.canvas.width, height: d.canvas.height },
7315
7375
  paper: { preset: d.paper.preset, name: d.paper.name, color: d.paper.color, depth: d.paper.depth },
7316
7376
  activeLayerId: d.activeLayerId,
7317
7377
  layerCount: c.listLayers().length,
@@ -7440,6 +7500,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7440
7500
  description: "Lists the layers top first: id, name, type (pixel or group), visibility, opacity, blend, clipping, locks, whether it is active, the painted bounds in document pixels (null when empty), parent and depth.",
7441
7501
  inputSchema: EMPTY3,
7442
7502
  annotations: { title: "List layers", ...READ6 },
7503
+ coordinates: { out: ["layers[].bounds"] },
7443
7504
  handler: () => ({ layers: c.listLayers(), activeLayerId: c.document.activeLayerId })
7444
7505
  });
7445
7506
  add({
@@ -7457,6 +7518,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7457
7518
  additionalProperties: false
7458
7519
  },
7459
7520
  annotations: { title: "Add a layer", ...WRITE5 },
7521
+ coordinates: { out: ["bounds"] },
7460
7522
  handler: (a) => c.addLayer({ name: a.name, type: a.type, aboveId: a.aboveId, parentId: a.parentId })
7461
7523
  });
7462
7524
  add({
@@ -7492,6 +7554,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7492
7554
  additionalProperties: false
7493
7555
  },
7494
7556
  annotations: { title: "Change a layer", ...SET4 },
7557
+ coordinates: { out: ["bounds"] },
7495
7558
  handler: ({ layerId, ...u }) => c.updateLayer(layerId, u)
7496
7559
  });
7497
7560
  add({
@@ -7516,6 +7579,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7516
7579
  description: "Makes a layer active: strokes, fills and clears without a layerId go to it. Editor state, not an undo step. Returns the layer.",
7517
7580
  inputSchema: { type: "object", properties: { layerId: { type: "string", description: "Layer id from list_layers." } }, required: ["layerId"], additionalProperties: false },
7518
7581
  annotations: { title: "Choose the active layer", ...SET4 },
7582
+ coordinates: { out: ["bounds"] },
7519
7583
  handler: ({ layerId }) => {
7520
7584
  c.setActiveLayer(layerId);
7521
7585
  return c.describeLayer(c.getLayer(layerId));
@@ -7535,6 +7599,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7535
7599
  description: "Copies a layer (a group with its children) directly above it; the copy becomes active. One undo step. Returns the copy.",
7536
7600
  inputSchema: { type: "object", properties: { layerId: { type: "string", description: "Layer id from list_layers." } }, required: ["layerId"], additionalProperties: false },
7537
7601
  annotations: { title: "Duplicate a layer", ...WRITE5 },
7602
+ coordinates: { out: ["bounds"] },
7538
7603
  handler: ({ layerId }) => c.duplicateLayer(layerId)
7539
7604
  });
7540
7605
  add({
@@ -7552,6 +7617,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7552
7617
  additionalProperties: false
7553
7618
  },
7554
7619
  annotations: { title: "Reorder a layer", ...WRITE5 },
7620
+ coordinates: { out: ["bounds"] },
7555
7621
  handler: ({ layerId, index, parentId }) => c.moveLayer(layerId, index, parentId)
7556
7622
  });
7557
7623
  add({
@@ -7560,6 +7626,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7560
7626
  description: "Merges a pixel layer into the pixel layer directly below it, which keeps its name, opacity and blend. One undo step. Returns the merged layer.",
7561
7627
  inputSchema: { type: "object", properties: { layerId: { type: "string", description: "The upper layer, from list_layers." } }, required: ["layerId"], additionalProperties: false },
7562
7628
  annotations: { title: "Merge a layer down", ...DESTRUCTIVE3 },
7629
+ coordinates: { out: ["bounds"] },
7563
7630
  handler: ({ layerId }) => c.mergeDown(layerId)
7564
7631
  });
7565
7632
  add({
@@ -7579,6 +7646,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7579
7646
  additionalProperties: false
7580
7647
  },
7581
7648
  annotations: { title: "Fill a layer", ...WRITE5 },
7649
+ coordinates: { out: ["bounds"] },
7582
7650
  handler: (a) => act({ kind: "fill", fill: a }, () => c.fillLayer(a))
7583
7651
  });
7584
7652
  add({
@@ -7587,6 +7655,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7587
7655
  description: "Removes every pixel of a pixel layer. One undo step. Returns the layer id and the bounds that were cleared (null if it was empty).",
7588
7656
  inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, additionalProperties: false },
7589
7657
  annotations: { title: "Clear a layer", ...DESTRUCTIVE3 },
7658
+ coordinates: { out: ["bounds"] },
7590
7659
  handler: ({ layerId }) => c.clearLayer(layerId)
7591
7660
  });
7592
7661
  add({
@@ -7746,6 +7815,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7746
7815
  additionalProperties: false
7747
7816
  },
7748
7817
  annotations: { title: "Pick a colour", ...READ6 },
7818
+ coordinates: { in: [""] },
7749
7819
  handler: ({ x, y, layerId }) => c.pickColor(x, y, { layerId })
7750
7820
  });
7751
7821
  add({
@@ -7792,6 +7862,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7792
7862
  additionalProperties: false
7793
7863
  },
7794
7864
  annotations: { title: "Paint a brush stroke", ...WRITE5 },
7865
+ coordinates: { in: ["points[]"], out: ["bounds"] },
7795
7866
  handler: async (a) => {
7796
7867
  await c.wetIdle();
7797
7868
  const stroke = { points: a.points, layerId: a.layerId, brushId: a.brushId, brush: a.brush, color: a.color, paint: a.paint, seed: a.seed, snapToPerspective: a.snapToPerspective };
@@ -7812,6 +7883,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7812
7883
  additionalProperties: false
7813
7884
  },
7814
7885
  annotations: { title: "List recorded strokes", ...READ6 },
7886
+ coordinates: { out: ["strokes[].bounds", "strokes[].points[]"] },
7815
7887
  handler: ({ ids, last }) => {
7816
7888
  const all = c.getStrokes();
7817
7889
  const picked = ids ? all.filter((s) => ids.includes(s.id)) : all.slice(-(last ?? 100));
@@ -7869,6 +7941,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7869
7941
  additionalProperties: false
7870
7942
  },
7871
7943
  annotations: { title: "Replay strokes, varied", ...WRITE5 },
7944
+ coordinates: { in: ["origin"], out: ["bounds"] },
7872
7945
  handler: (a) => {
7873
7946
  const replay = a;
7874
7947
  return act({ kind: "replay", replay }, () => c.replayStrokes(replay));
@@ -7985,15 +8058,33 @@ function createPaintEditorToolProvider(controller, options = {}) {
7985
8058
  tools.push(...createPaintReplayTools(c));
7986
8059
  tools.push(...createPaintStickerTools(c));
7987
8060
  tools.push(...createPigmentTools());
8061
+ const exclude = new Set(options.exclude ?? []);
8062
+ const description = "Natural-media painting: paper and grain, dry and wet brushes, the brush library, strokes as data, pigment mixing, stencils, guides, layers and export.";
7988
8063
  return withPaintPlayback({
7989
8064
  id: options.id ?? "paint-editor",
7990
8065
  namespace: options.namespace,
7991
8066
  title: "Paint editor",
7992
- description: "Natural-media painting: paper and grain, dry and wet brushes, the brush library, strokes as data, pigment mixing, stencils, guides, layers and export.",
8067
+ description: options.origin ? `${description} It is a layer of a larger canvas: every position is in canvas coordinates.` : description,
7993
8068
  skill: "pixerate-paint-editor",
7994
- tools
8069
+ tools: tools.filter((t) => !exclude.has(t.name)).map((t) => hostTool(t, options))
7995
8070
  }, { director: !!options.director, surface: options.playbackSurface });
7996
8071
  }
8072
+ function hostTool({ coordinates, ...tool }, { origin, onChange }) {
8073
+ if (!origin && !onChange) return tool;
8074
+ const write = !tool.annotations?.readOnlyHint;
8075
+ const done = (result) => {
8076
+ if (write) onChange?.({ tool: tool.name });
8077
+ return origin && coordinates?.out ? fromDocument(result, coordinates.out, origin) : result;
8078
+ };
8079
+ return {
8080
+ ...tool,
8081
+ ...origin ? { description: hostedText(tool.description ?? ""), inputSchema: hostedSchema(tool.inputSchema) } : {},
8082
+ handler: (args, context) => {
8083
+ const r = tool.handler(origin && coordinates?.in ? toDocument(args, coordinates.in, origin) : args, context);
8084
+ return r instanceof Promise ? r.then(done) : done(r);
8085
+ }
8086
+ };
8087
+ }
7997
8088
 
7998
8089
  // src/paint-editor/ui.ts
7999
8090
  var PAINT_TOOLS = [
@@ -3,6 +3,6 @@
3
3
  * category sticker) stay objects on their layer, which these tools list, move, turn, resize,
4
4
  * recolour, reorder and delete. Each change is one undo step and redraws the layer.
5
5
  */
6
- import type { AgentTool } from '../tools/registry.cjs';
6
+ import type { PaintAgentTool } from './hosted.cjs';
7
7
  import type { PaintEditorController } from './controller.cjs';
8
- export declare function createPaintStickerTools(c: PaintEditorController): AgentTool[];
8
+ export declare function createPaintStickerTools(c: PaintEditorController): PaintAgentTool[];
@@ -3,6 +3,6 @@
3
3
  * category sticker) stay objects on their layer, which these tools list, move, turn, resize,
4
4
  * recolour, reorder and delete. Each change is one undo step and redraws the layer.
5
5
  */
6
- import type { AgentTool } from '../tools/registry.js';
6
+ import type { PaintAgentTool } from './hosted.js';
7
7
  import type { PaintEditorController } from './controller.js';
8
- export declare function createPaintStickerTools(c: PaintEditorController): AgentTool[];
8
+ export declare function createPaintStickerTools(c: PaintEditorController): PaintAgentTool[];
@@ -3,6 +3,6 @@
3
3
  * impasto brush; these tools move the light, read and flatten a layer's relief, and switch instant
4
4
  * drying of wet strokes.
5
5
  */
6
- import type { AgentTool } from '../tools/registry.cjs';
6
+ import type { PaintAgentTool } from './hosted.cjs';
7
7
  import type { PaintEditorController } from './controller.cjs';
8
- export declare function createPaintThickTools(c: PaintEditorController): AgentTool[];
8
+ export declare function createPaintThickTools(c: PaintEditorController): PaintAgentTool[];
@@ -3,6 +3,6 @@
3
3
  * impasto brush; these tools move the light, read and flatten a layer's relief, and switch instant
4
4
  * drying of wet strokes.
5
5
  */
6
- import type { AgentTool } from '../tools/registry.js';
6
+ import type { PaintAgentTool } from './hosted.js';
7
7
  import type { PaintEditorController } from './controller.js';
8
- export declare function createPaintThickTools(c: PaintEditorController): AgentTool[];
8
+ export declare function createPaintThickTools(c: PaintEditorController): PaintAgentTool[];
@@ -5,6 +5,7 @@
5
5
  * `list_papers`, `list_strokes`).
6
6
  */
7
7
  import type { AgentToolProvider } from '../tools/registry.cjs';
8
+ import { type PaintOrigin } from './hosted.cjs';
8
9
  import type { PaintEditorController } from './controller.cjs';
9
10
  import type { PaintDirector } from './presence.cjs';
10
11
  import type { PresenceActorInfo } from '../animation/presence.cjs';
@@ -25,6 +26,22 @@ export interface PaintEditorToolProviderOptions {
25
26
  * Set it when one registry holds several editors of this kind, each attached under its own surface id.
26
27
  */
27
28
  playbackSurface?: string;
29
+ /**
30
+ * Hosts the document as a layer on a larger canvas: where document (0, 0) sits in host
31
+ * coordinates. Every position the tools take is then a host coordinate and every position they
32
+ * return is one too (strokes, picks, bounds, guides, stencils, stickers, wet areas); the
33
+ * descriptions say canvas coordinates. Directors receive document pixels.
34
+ */
35
+ origin?: PaintOrigin;
36
+ /** Bare tool names to leave out (e.g. `new_canvas`, `export_psd` when the host owns the canvas). */
37
+ exclude?: readonly string[];
38
+ /** Called after each successful write (every tool not marked read-only), e.g. so the host can save. */
39
+ onChange?: (info: {
40
+ tool: string;
41
+ }) => void;
28
42
  }
29
- /** Agent tools for one paint editor. */
30
- export declare function createPaintEditorToolProvider(controller: PaintEditorController, options?: PaintEditorToolProviderOptions): AgentToolProvider;
43
+ /**
44
+ * Agent tools for one paint editor. Pass a getter to create the document lazily: it is first
45
+ * called when a tool runs, never on registration.
46
+ */
47
+ export declare function createPaintEditorToolProvider(controller: PaintEditorController | (() => PaintEditorController), options?: PaintEditorToolProviderOptions): AgentToolProvider;
@@ -5,6 +5,7 @@
5
5
  * `list_papers`, `list_strokes`).
6
6
  */
7
7
  import type { AgentToolProvider } from '../tools/registry.js';
8
+ import { type PaintOrigin } from './hosted.js';
8
9
  import type { PaintEditorController } from './controller.js';
9
10
  import type { PaintDirector } from './presence.js';
10
11
  import type { PresenceActorInfo } from '../animation/presence.js';
@@ -25,6 +26,22 @@ export interface PaintEditorToolProviderOptions {
25
26
  * Set it when one registry holds several editors of this kind, each attached under its own surface id.
26
27
  */
27
28
  playbackSurface?: string;
29
+ /**
30
+ * Hosts the document as a layer on a larger canvas: where document (0, 0) sits in host
31
+ * coordinates. Every position the tools take is then a host coordinate and every position they
32
+ * return is one too (strokes, picks, bounds, guides, stencils, stickers, wet areas); the
33
+ * descriptions say canvas coordinates. Directors receive document pixels.
34
+ */
35
+ origin?: PaintOrigin;
36
+ /** Bare tool names to leave out (e.g. `new_canvas`, `export_psd` when the host owns the canvas). */
37
+ exclude?: readonly string[];
38
+ /** Called after each successful write (every tool not marked read-only), e.g. so the host can save. */
39
+ onChange?: (info: {
40
+ tool: string;
41
+ }) => void;
28
42
  }
29
- /** Agent tools for one paint editor. */
30
- export declare function createPaintEditorToolProvider(controller: PaintEditorController, options?: PaintEditorToolProviderOptions): AgentToolProvider;
43
+ /**
44
+ * Agent tools for one paint editor. Pass a getter to create the document lazily: it is first
45
+ * called when a tool runs, never on registration.
46
+ */
47
+ export declare function createPaintEditorToolProvider(controller: PaintEditorController | (() => PaintEditorController), options?: PaintEditorToolProviderOptions): AgentToolProvider;
@@ -5,7 +5,7 @@
5
5
  * wet. Settings (wet parameters, flow maps, tilt) are saved with the document but are not undo
6
6
  * steps; drying and wetting the paper are.
7
7
  */
8
- import type { AgentTool } from '../tools/registry.cjs';
8
+ import type { PaintAgentTool } from './hosted.cjs';
9
9
  import type { PaintEditorController } from './controller.cjs';
10
10
  export declare const WET_PARAM_SCHEMA: {
11
11
  readonly viscosity: {
@@ -63,4 +63,4 @@ export declare const WET_PARAM_SCHEMA: {
63
63
  readonly description: string;
64
64
  };
65
65
  };
66
- export declare function createPaintWetTools(c: PaintEditorController): AgentTool[];
66
+ export declare function createPaintWetTools(c: PaintEditorController): PaintAgentTool[];
@@ -5,7 +5,7 @@
5
5
  * wet. Settings (wet parameters, flow maps, tilt) are saved with the document but are not undo
6
6
  * steps; drying and wetting the paper are.
7
7
  */
8
- import type { AgentTool } from '../tools/registry.js';
8
+ import type { PaintAgentTool } from './hosted.js';
9
9
  import type { PaintEditorController } from './controller.js';
10
10
  export declare const WET_PARAM_SCHEMA: {
11
11
  readonly viscosity: {
@@ -63,4 +63,4 @@ export declare const WET_PARAM_SCHEMA: {
63
63
  readonly description: string;
64
64
  };
65
65
  };
66
- export declare function createPaintWetTools(c: PaintEditorController): AgentTool[];
66
+ export declare function createPaintWetTools(c: PaintEditorController): PaintAgentTool[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.201.0",
3
+ "version": "0.203.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -253,9 +253,9 @@ in the source frame at the document's bit depth, colour mode and profile. Read
253
253
  | `select_ellipse` | Elliptical marquee inscribed in `x`, `y`, `width`, `height`; same options. A feathered ellipse makes a vignette. Returns the summary. |
254
254
  | `select_line` | Single row or column marquee: `orientation` `row` (at y) or `column` (at x), `position` in natural px, `mode`. A 1 px line across the image. Returns the summary. |
255
255
  | `quick_select` | Quick Selection: `strokes` (brush paths of `{x, y}`), `size` (diameter, default 30), `mode`, `sampleAllLayers`, `enhanceEdge`. Each stroke grows out to its region's edges; with `replace` the first replaces and the rest add. Paint inside the area, then check and fix with `add` / `subtract` strokes. Returns the summary. |
256
- | `select_object` | Object Selection (GrabCut): `rect` (a box around the object) or `point` (on it), `mode`, `sampleAllLayers`, `enhanceEdge`. Outside the box is background. Make the box a little larger than the object. Returns the summary. |
256
+ | `select_object` | Object Selection: `rect` (a box around the object) or `point` (on it), `mode`, `sampleAllLayers`, `enhanceEdge`, `useModel`. Uses the host model when set (else, or when nothing fits, GrabCut; outside the box is background). Make the box a little larger than the object. Returns the summary and `method`. |
257
257
  | `select_focus_area` | Select › Focus Area: the in-focus (sharp) parts, edge fitted to the image. `range` 0..100 (higher takes in softer areas), `noise` 0..100 (raise when grain is selected), `softness` px, `mode`. Returns the summary and `noiseLevel`. |
258
- | `select_sky` | Select › Sky: the sky (blue or overcast, from the top down, with patches through gaps), edge fitted to the skyline. `threshold` 0..100 (higher takes in haze), `softness` px, `mode`. Returns the summary, and `note` when none was found. |
258
+ | `select_sky` | Select › Sky: the sky, from the host model when set, else classically (blue or overcast, from the top down, with patches through gaps). `threshold` 0..100 (higher takes in haze) and `softness` px for classical, `mode`, `useModel`. Returns the summary, `method`, and `note` when none was found. |
259
259
  | `select_polygon` | The lasso: `points` (at least 3 `{x, y}` along the outline, closed automatically), `mode`, `feather`, `antiAlias`; `snapToEdges: {width, contrast}` makes it the Magnetic Lasso (points snap to edges, the outline follows them). Trace a subject's contour, not its box. Returns the summary (plus `outlinePoints` when snapped). |
260
260
  | `magic_wand` | Select similar colour from `x`, `y`: `tolerance` 0..255 (default 32), `contiguous` (default true), `distance` `rgb` or `perceptual`, `sampleMerged`, `sample` (`composite` or `active-layer`), `mode`, `feather`. For flat backdrops and solid colours. Returns the summary. |
261
261
  | `select_color_range` | Color Range, soft: `samples` (`[{color, x?, y?}]`) or a `preset` (reds … magentas, highlights, midtones, shadows, skinTones); `fuzziness` (samples 0..200, default 40; tone presets' falloff, default 20), `localized` px radius around positioned samples, `invert`, `sample`, `mode`, `feather`. Returns the summary. |
@@ -265,6 +265,15 @@ for the controls and presets.
265
265
  and baked in, at `opacity` strength. Transparent areas stay transparent, and thick paint keeps
266
266
  its relief and stays lit by the editor's light. Dry wet paint first (`dry_paint`).
267
267
 
268
+ ## Canvas-hosted layer
269
+
270
+ A host can mount the painting as a layer of a larger canvas (`createPaintEditorToolProvider` with
271
+ `origin`). Then every position you pass or read back is a canvas coordinate, not a document
272
+ pixel: stroke points, picks, bounds, guides, stencils, stickers and wet areas alike.
273
+ `get_canvas_info` `bounds` says where the painting lies on that canvas; paint inside it. The
274
+ host may leave out tools it owns (`new_canvas`, `set_canvas`, `extend_canvas`, exports, the
275
+ view), so work with the tools you are given.
276
+
268
277
  ## Tool reference
269
278
 
270
279
  ### Canvas and paper