@pixerate/editor 0.254.1 → 0.256.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 (81) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/animation-editor/index.cjs +59 -59
  4. package/dist/animation-editor/index.js +3 -3
  5. package/dist/brushes/index.cjs +31 -29
  6. package/dist/brushes/index.d.cts +1 -1
  7. package/dist/brushes/index.d.ts +1 -1
  8. package/dist/brushes/index.js +27 -25
  9. package/dist/brushes/library.d.cts +17 -0
  10. package/dist/brushes/library.d.ts +17 -0
  11. package/dist/{brushstrokeRender-5PYSKZIJ.js → brushstrokeRender-HCOQUSWW.js} +53 -18
  12. package/dist/{brushstrokeRender-7FLLSQEC.cjs → brushstrokeRender-ZZOXI5O5.cjs} +55 -20
  13. package/dist/canvas/brushTypes.d.cts +22 -3
  14. package/dist/canvas/brushTypes.d.ts +22 -3
  15. package/dist/canvas/brushstroke.d.cts +34 -12
  16. package/dist/canvas/brushstroke.d.ts +34 -12
  17. package/dist/canvas/brushstrokeRender.d.cts +14 -8
  18. package/dist/canvas/brushstrokeRender.d.ts +14 -8
  19. package/dist/canvas/drawController.d.cts +11 -3
  20. package/dist/canvas/drawController.d.ts +11 -3
  21. package/dist/canvas/freehand.d.cts +6 -1
  22. package/dist/canvas/freehand.d.ts +6 -1
  23. package/dist/canvas/index.cjs +114 -102
  24. package/dist/canvas/index.js +16 -4
  25. package/dist/{chunk-XVIK73LS.js → chunk-5P4JEOIV.js} +4 -4
  26. package/dist/{chunk-JPEHXIB5.cjs → chunk-5YWWQ36H.cjs} +66 -21
  27. package/dist/{chunk-CHAWTXBE.cjs → chunk-6WCQ35GI.cjs} +4 -4
  28. package/dist/{chunk-GYMMNCFT.js → chunk-AF23QK5F.js} +56 -11
  29. package/dist/{chunk-JFHBXNR2.cjs → chunk-B7V34GIH.cjs} +86 -86
  30. package/dist/{chunk-QC3OXRE4.js → chunk-DMTEQ6SH.js} +1 -1
  31. package/dist/{chunk-TLOGFIIV.cjs → chunk-GCTUOTCW.cjs} +35 -35
  32. package/dist/{chunk-34KLWUH4.cjs → chunk-HIMGBZAM.cjs} +1133 -597
  33. package/dist/{chunk-7P6VF3FG.js → chunk-HYWRU5VX.js} +1 -1
  34. package/dist/{chunk-QPHNVZHO.js → chunk-JVRLGV3C.js} +1118 -582
  35. package/dist/{chunk-LFMKWGFL.cjs → chunk-LRNUZKWV.cjs} +22 -8
  36. package/dist/chunk-PGPFWC35.cjs +517 -0
  37. package/dist/{chunk-QLSGTCYT.cjs → chunk-TBAVCLEQ.cjs} +23 -23
  38. package/dist/{chunk-2ZJ4DT3M.js → chunk-VUKUTT2W.js} +1 -1
  39. package/dist/{chunk-5JNA6LLY.js → chunk-WYGSR3IJ.js} +22 -8
  40. package/dist/chunk-YHUMGBZD.js +517 -0
  41. package/dist/commands/builtins.d.cts +1 -1
  42. package/dist/commands/builtins.d.ts +1 -1
  43. package/dist/commands/index.cjs +2 -2
  44. package/dist/commands/index.js +1 -1
  45. package/dist/image-editor/brush-presets.d.cts +32 -0
  46. package/dist/image-editor/brush-presets.d.ts +32 -0
  47. package/dist/image-editor/controller.d.cts +15 -0
  48. package/dist/image-editor/controller.d.ts +15 -0
  49. package/dist/image-editor/index.cjs +848 -775
  50. package/dist/image-editor/index.d.cts +1 -0
  51. package/dist/image-editor/index.d.ts +1 -0
  52. package/dist/image-editor/index.js +81 -8
  53. package/dist/image-editor/types.d.cts +7 -0
  54. package/dist/image-editor/types.d.ts +7 -0
  55. package/dist/paint-editor/collaboration-tools.d.cts +19 -0
  56. package/dist/paint-editor/collaboration-tools.d.ts +19 -0
  57. package/dist/paint-editor/collaboration.d.cts +180 -0
  58. package/dist/paint-editor/collaboration.d.ts +180 -0
  59. package/dist/paint-editor/controller.d.cts +54 -0
  60. package/dist/paint-editor/controller.d.ts +54 -0
  61. package/dist/paint-editor/index.cjs +816 -283
  62. package/dist/paint-editor/index.d.cts +2 -0
  63. package/dist/paint-editor/index.d.ts +2 -0
  64. package/dist/paint-editor/index.js +567 -34
  65. package/dist/vector/brushstroke.d.cts +21 -5
  66. package/dist/vector/brushstroke.d.ts +21 -5
  67. package/dist/vector/controller.d.cts +14 -1
  68. package/dist/vector/controller.d.ts +14 -1
  69. package/dist/vector/index.cjs +1220 -1199
  70. package/dist/vector/index.js +34 -13
  71. package/dist/vector/types.d.cts +7 -0
  72. package/dist/vector/types.d.ts +7 -0
  73. package/package.json +1 -1
  74. package/skills/pixerate-brushes/SKILL.md +9 -3
  75. package/skills/pixerate-canvas/SKILL.md +10 -5
  76. package/skills/pixerate-image-editor/SKILL.md +2 -1
  77. package/skills/pixerate-paint-editor/SKILL.md +31 -46
  78. package/skills/pixerate-paint-editor/references/painting-alongside.md +19 -0
  79. package/skills/pixerate-vector/SKILL.md +1 -1
  80. package/dist/chunk-HS6QDWDF.cjs +0 -1029
  81. package/dist/chunk-MTKY6K3Q.js +0 -1029
@@ -73,3 +73,4 @@ export * from './paths.cjs';
73
73
  export * from './path-edit.cjs';
74
74
  export * from './content-aware-fill-session.cjs';
75
75
  export * from './stickers.cjs';
76
+ export * from './brush-presets.cjs';
@@ -73,3 +73,4 @@ export * from './paths.js';
73
73
  export * from './path-edit.js';
74
74
  export * from './content-aware-fill-session.js';
75
75
  export * from './stickers.js';
76
+ export * from './brush-presets.js';
@@ -8,7 +8,7 @@ import {
8
8
  parseOpenTypeFont,
9
9
  removeAnchorsKeepingShape,
10
10
  traceField
11
- } from "../chunk-2ZJ4DT3M.js";
11
+ } from "../chunk-VUKUTT2W.js";
12
12
  import {
13
13
  ColorGmm,
14
14
  GAMMA,
@@ -413,7 +413,7 @@ import {
413
413
  writePsd,
414
414
  xyToTempTint,
415
415
  xyToUv
416
- } from "../chunk-QC3OXRE4.js";
416
+ } from "../chunk-DMTEQ6SH.js";
417
417
  import {
418
418
  parseCssColor
419
419
  } from "../chunk-3CI7FRMK.js";
@@ -743,8 +743,14 @@ import {
743
743
  warpMeshParamAt,
744
744
  warpMeshRows,
745
745
  warpPresetMesh
746
- } from "../chunk-GYMMNCFT.js";
747
- import "../chunk-MTKY6K3Q.js";
746
+ } from "../chunk-AF23QK5F.js";
747
+ import {
748
+ IMAGE_PROFILE,
749
+ checkBrush,
750
+ closest,
751
+ createBrushLibrary,
752
+ resolveBrush
753
+ } from "../chunk-JVRLGV3C.js";
748
754
  import "../chunk-APX63ILU.js";
749
755
  import "../chunk-M6DNZNVY.js";
750
756
  import {
@@ -5742,6 +5748,32 @@ function convertSurfaceColors(surface, t) {
5742
5748
  return new TiledSurface(4, tiles, surface.defaultValue);
5743
5749
  }
5744
5750
 
5751
+ // src/image-editor/brush-presets.ts
5752
+ var ImageBrushPresetError = class extends Error {
5753
+ constructor(message) {
5754
+ super(message);
5755
+ this.name = "ImageBrushPresetError";
5756
+ }
5757
+ };
5758
+ function imageBrushPreset(brush, ids = []) {
5759
+ const check = checkBrush(brush, IMAGE_PROFILE);
5760
+ if (!check.ok) {
5761
+ const why = check.issues.filter((i) => i.severity === "error").map((i) => `${i.reason} ${i.fix}`).join(" ");
5762
+ throw new ImageBrushPresetError(`Brush "${brush.id}" (${brush.name}) can't paint in the image editor: ${why}${ids.length ? ` ${closest(brush.id, ids)}list_brush_presets lists the ones it can.` : ""}`);
5763
+ }
5764
+ const resolved2 = resolveBrush(brush);
5765
+ const tool = resolved2.engine === "eraser" ? "eraser" : resolved2.engine === "mixer" ? "mixer" : "brush";
5766
+ return {
5767
+ id: brush.id,
5768
+ name: brush.name,
5769
+ tool,
5770
+ brush: { ...resolved2.tip },
5771
+ ...tool === "mixer" ? { mixer: { ...resolved2.mixer } } : {},
5772
+ ...resolved2.paint && tool === "brush" ? { paint: resolved2.paint } : {},
5773
+ warnings: check.issues.filter((i) => i.severity === "warning").map((i) => i.reason)
5774
+ };
5775
+ }
5776
+
5745
5777
  // src/image-editor/controller.ts
5746
5778
  var IMAGE_EDITOR_SCHEMA_VERSION = LAYERED_SCHEMA_VERSION;
5747
5779
  var RETOUCH_TOOLS = ["clone", "heal", "spot-heal", "smudge", "dodge", "burn", "sponge", "blur", "sharpen", "history"];
@@ -6230,6 +6262,7 @@ var ImageEditorController = class {
6230
6262
  effectsEngine = new ImageEffectsEngine({ slot: this.effects });
6231
6263
  effectRefresh = "none";
6232
6264
  constructor(options = {}) {
6265
+ this.brushLibrary = options.brushLibrary ?? createBrushLibrary();
6233
6266
  this.effects.subscribe(() => this.queueEffectRefresh("all"));
6234
6267
  this.effectsEngine.subscribe(() => this.queueEffectRefresh("incomplete"));
6235
6268
  if (options.effects) this.effects.set(options.effects);
@@ -9008,6 +9041,34 @@ var ImageEditorController = class {
9008
9041
  return pngDataUrl(thumb.width, thumb.height, thumb.data, 4);
9009
9042
  }
9010
9043
  // --- Painting ---
9044
+ /** The brush library the brush presets come from (shared with other editors when the host passes one). */
9045
+ brushLibrary;
9046
+ /** The brushes this editor can paint with (`IMAGE_PROFILE`): built-in presets, then saved brushes. */
9047
+ async listBrushPresets() {
9048
+ return this.brushLibrary.list({ profile: IMAGE_PROFILE });
9049
+ }
9050
+ /**
9051
+ * Paints with a brush from the brush library: sets the paint tool it needs (brush, Mixer Brush
9052
+ * or eraser), its brush and mixer settings and, for a brush with its own gradient, the paint.
9053
+ * View state, no undo. Throws for an unknown id (with the closest ones) or a brush the image
9054
+ * editor can't paint (wet, thick, particles, stickers, light), saying why.
9055
+ */
9056
+ async setBrushPreset(id) {
9057
+ const entry = await this.brushLibrary.get(id);
9058
+ const ids = (await this.listBrushPresets()).map((e) => e.brush.id);
9059
+ if (!entry) throw new LayerOperationError(`Unknown brush "${id}". ${closest(id, ids)}list_brush_presets lists the brushes the image editor can paint with.`);
9060
+ let preset;
9061
+ try {
9062
+ preset = imageBrushPreset(entry.brush, ids);
9063
+ } catch (e) {
9064
+ throw new LayerOperationError(e.message);
9065
+ }
9066
+ this.setPaintOptions({ brush: preset.brush, ...preset.mixer ? { mixer: preset.mixer } : {}, ...preset.paint ? { paint: preset.paint } : {} });
9067
+ this.state.paint = { ...this.state.paint, brushPreset: preset.id };
9068
+ if (this.state.activeTool !== preset.tool) this.setTool(preset.tool);
9069
+ else this.notify();
9070
+ return preset;
9071
+ }
9011
9072
  /** Brush settings, colours and tool options of the paint tools (view state, no undo). */
9012
9073
  setPaintOptions(options) {
9013
9074
  const cur = this.state.paint;
@@ -9022,6 +9083,8 @@ var ImageEditorController = class {
9022
9083
  }
9023
9084
  if (source.type === "color" || options.foreground !== void 0) source = { type: "color", color: foreground };
9024
9085
  const next = {
9086
+ // Brush settings changed by hand are no longer the preset's.
9087
+ brushPreset: options.brush || options.mixer ? null : cur.brushPreset ?? null,
9025
9088
  brush: options.brush ? normalizeBrush({ ...cur.brush, ...options.brush }) : cur.brush,
9026
9089
  foreground,
9027
9090
  background: options.background !== void 0 ? toHex(parseColor(options.background)) : cur.background,
@@ -13625,7 +13688,7 @@ var DEPTH_MASK_CONTROLS = [
13625
13688
 
13626
13689
  // src/image-editor/playback.ts
13627
13690
  var IMAGE_PLAYBACK_KIND = "layer";
13628
- 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|sample_color|set_retouch_options|load_mixer_brush|clean_mixer_brush)$/;
13691
+ 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|sample_color|set_retouch_options|load_mixer_brush|clean_mixer_brush|set_brush_preset)$/;
13629
13692
  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|compare_selections|edit_selection_step|insert_selection_step|remove_selection_step|freeze_selection|separate_subject)$/;
13630
13693
  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)$/;
13631
13694
  var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
@@ -17191,7 +17254,7 @@ function createImagePaintTools(controller) {
17191
17254
  };
17192
17255
  const paintView = () => {
17193
17256
  const p = controller.getState().paint;
17194
- return { paint: p.paint, foreground: p.foreground, background: p.background, brush: p.brush };
17257
+ return { paint: p.paint, foreground: p.foreground, background: p.background, brush: p.brush, brushPreset: p.brushPreset ?? null };
17195
17258
  };
17196
17259
  const setPaint = {
17197
17260
  name: "set_paint",
@@ -17207,7 +17270,7 @@ function createImagePaintTools(controller) {
17207
17270
  const getPaint = {
17208
17271
  name: "get_paint",
17209
17272
  title: "Get the paint",
17210
- description: "Reads what the brush paints (paint: the foreground colour or gradient paint), the foreground and background colours and the brush settings (size, hardness, spacing, opacity, flow \u2026). Changes nothing.",
17273
+ description: "Reads what the brush paints (paint: the foreground colour or gradient paint), the foreground and background colours, the brush settings (size, hardness, spacing, opacity, flow \u2026) and brushPreset (the set_brush_preset id they came from, null once changed by hand). Changes nothing.",
17211
17274
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
17212
17275
  annotations: { title: "Get the paint", ...READ3 },
17213
17276
  handler: () => paintView()
@@ -17277,7 +17340,15 @@ function createImagePaintTools(controller) {
17277
17340
  return { gradient, css: gradientToCss(gradient), pointsRead: read };
17278
17341
  }
17279
17342
  };
17280
- return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool, setPaint, getPaint, sampleColor2, sampleGradient];
17343
+ const setBrushPreset = {
17344
+ name: "set_brush_preset",
17345
+ title: "Paint with a brush preset",
17346
+ description: "Chooses a brush from the brush library (a built-in preset or a saved brush; list_brush_presets lists the ones the image editor can paint, describe_brush reads one) for the brush tools: it sets the paint tool the brush needs (brush, mixer for a Mixer Brush, eraser), its brush settings (size, hardness, spacing, angle, roundness, opacity, flow, smoothing, pressure, blend), its Mixer Brush settings and, for a brush with its own gradient, the paint. paint_stroke with that tool then paints with it. Brushes the image editor cannot paint (wet or thick paint, particles, stickers, light, the wet tools) are refused with the reason. View state, not an undo step. Returns the id, name, tool, brush, mixer, paint and warnings (settings it paints without, such as paper grain).",
17347
+ inputSchema: { type: "object", properties: { id: { type: "string", minLength: 1, maxLength: 64, description: 'Brush id from list_brush_presets (e.g. "pencil-hb", "marker", "mixer-brush", "eraser-soft").' } }, required: ["id"], additionalProperties: false },
17348
+ annotations: { title: "Paint with a brush preset", ...SET },
17349
+ handler: async ({ id }) => controller.setBrushPreset(id)
17350
+ };
17351
+ return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool, setPaint, getPaint, sampleColor2, sampleGradient, setBrushPreset];
17281
17352
  }
17282
17353
 
17283
17354
  // src/image-editor/color-tools.ts
@@ -22616,6 +22687,7 @@ export {
22616
22687
  INTENTS,
22617
22688
  INTENT_OPTIONS,
22618
22689
  INTERPOLATIONS,
22690
+ ImageBrushPresetError,
22619
22691
  ImageEditorController,
22620
22692
  ImageEditorRenderer,
22621
22693
  ImageEffectsEngine,
@@ -23127,6 +23199,7 @@ export {
23127
23199
  identityLevels,
23128
23200
  identityTransform,
23129
23201
  identityWarpMesh,
23202
+ imageBrushPreset,
23130
23203
  imageEditorStateMigrations,
23131
23204
  imageLayerAppliedEffects,
23132
23205
  imagePanelTool,
@@ -9,6 +9,7 @@ import type { ImageLayer, LayerTarget } from './layers/model.cjs';
9
9
  import type { SerializedLayeredImageEditorState } from './layers/serialize.cjs';
10
10
  import type { PaintOptions } from './controller.cjs';
11
11
  import type { TextShaper } from './layers/text.cjs';
12
+ import type { BrushLibrary } from '../brushes/library.cjs';
12
13
  export interface ImageDimensions {
13
14
  width: number;
14
15
  height: number;
@@ -452,6 +453,12 @@ export interface ExportOptions {
452
453
  import type { EffectProvider } from '../effects/types.cjs';
453
454
  import type { SegmentationProvider } from './selection/segmentation.cjs';
454
455
  export interface ImageEditorOptions {
456
+ /**
457
+ * The brush library the brush presets come from (`setBrushPreset`): built-in presets and saved
458
+ * brushes, filtered by `IMAGE_PROFILE`. Share one with the paint editor so a brush saved there
459
+ * paints here (default: a library in memory).
460
+ */
461
+ brushLibrary?: BrushLibrary;
455
462
  /**
456
463
  * What lays out and draws type layers. Default: the browser's fonts through Canvas 2D
457
464
  * (`createCanvasTextShaper`) where there is a 2D canvas; `null` (or no canvas) keeps type layers'
@@ -9,6 +9,7 @@ import type { ImageLayer, LayerTarget } from './layers/model.js';
9
9
  import type { SerializedLayeredImageEditorState } from './layers/serialize.js';
10
10
  import type { PaintOptions } from './controller.js';
11
11
  import type { TextShaper } from './layers/text.js';
12
+ import type { BrushLibrary } from '../brushes/library.js';
12
13
  export interface ImageDimensions {
13
14
  width: number;
14
15
  height: number;
@@ -452,6 +453,12 @@ export interface ExportOptions {
452
453
  import type { EffectProvider } from '../effects/types.js';
453
454
  import type { SegmentationProvider } from './selection/segmentation.js';
454
455
  export interface ImageEditorOptions {
456
+ /**
457
+ * The brush library the brush presets come from (`setBrushPreset`): built-in presets and saved
458
+ * brushes, filtered by `IMAGE_PROFILE`. Share one with the paint editor so a brush saved there
459
+ * paints here (default: a library in memory).
460
+ */
461
+ brushLibrary?: BrushLibrary;
455
462
  /**
456
463
  * What lays out and draws type layers. Default: the browser's fonts through Canvas 2D
457
464
  * (`createCanvasTextShaper`) where there is a 2D canvas; `null` (or no canvas) keeps type layers'
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Agent tools for paint proposals: `propose_paint`, `list_paint_proposals`,
3
+ * `accept_paint_proposal` and `reject_paint_proposal`. An agent suggests paint as a ghost the user
4
+ * can take or leave, instead of painting it outright. They drive a {@link PaintProposals} store.
5
+ */
6
+ import type { AgentToolProvider } from '../tools/registry.cjs';
7
+ import type { PaintProposals } from './collaboration.cjs';
8
+ export interface PaintProposalToolProviderOptions {
9
+ /** Provider id (unique per registry). Default `paint-proposals`. */
10
+ id?: string;
11
+ /** Tool-name prefix (`<namespace>__<name>`); defaults to the provider id, `''` for bare names. */
12
+ namespace?: string;
13
+ /** Accepting is not allowed: `accept_paint_proposal` refuses with this reason (a sentence). */
14
+ readOnly?: boolean | string | (() => boolean | string);
15
+ /** Origin of proposals from calls that name no actor (default the store's). */
16
+ fallbackOrigin?: string;
17
+ }
18
+ /** The proposal tools for a paint editor (see the module comment). */
19
+ export declare function createPaintProposalToolProvider(proposals: PaintProposals, options?: PaintProposalToolProviderOptions): AgentToolProvider;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Agent tools for paint proposals: `propose_paint`, `list_paint_proposals`,
3
+ * `accept_paint_proposal` and `reject_paint_proposal`. An agent suggests paint as a ghost the user
4
+ * can take or leave, instead of painting it outright. They drive a {@link PaintProposals} store.
5
+ */
6
+ import type { AgentToolProvider } from '../tools/registry.js';
7
+ import type { PaintProposals } from './collaboration.js';
8
+ export interface PaintProposalToolProviderOptions {
9
+ /** Provider id (unique per registry). Default `paint-proposals`. */
10
+ id?: string;
11
+ /** Tool-name prefix (`<namespace>__<name>`); defaults to the provider id, `''` for bare names. */
12
+ namespace?: string;
13
+ /** Accepting is not allowed: `accept_paint_proposal` refuses with this reason (a sentence). */
14
+ readOnly?: boolean | string | (() => boolean | string);
15
+ /** Origin of proposals from calls that name no actor (default the store's). */
16
+ fallbackOrigin?: string;
17
+ }
18
+ /** The proposal tools for a paint editor (see the module comment). */
19
+ export declare function createPaintProposalToolProvider(proposals: PaintProposals, options?: PaintProposalToolProviderOptions): AgentToolProvider;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Painting alongside an agent: a change feed that says what the user (or an agent) just painted,
3
+ * and proposals, the paint an agent suggests without painting it. A proposal is a few operations
4
+ * (strokes, a wash, an echo of strokes already on the canvas) that `previewPaint` renders as a
5
+ * ghost the user sees over the canvas; accepting paints them as one undo step recorded as the
6
+ * proposer's, on a layer of their own, and dismissing changes nothing. The canvas equivalents are
7
+ * `createCanvasChangeFeed` and `createCanvasProposals`.
8
+ */
9
+ import type { RasterRect } from '../raster/surface.cjs';
10
+ import { type PaintCommitEvent, type PaintEditorController } from './controller.cjs';
11
+ import type { PaintEvent } from './document.cjs';
12
+ import type { RecordedStroke } from './strokes.cjs';
13
+ /** A layer as the feed reports it. */
14
+ export interface PaintChangeLayer {
15
+ id: string;
16
+ name: string;
17
+ }
18
+ /** What changed in the painting since the feed's last report. */
19
+ export interface PaintChange {
20
+ /** Counts up from 1 for each change reported. */
21
+ seq: number;
22
+ /** `user`, or `agent:<id>` when an agent's step made it (see `PaintEditorController.withOrigin`). */
23
+ origin: string;
24
+ /** The history step behind it, when there was one. */
25
+ commit?: PaintCommitEvent;
26
+ addedStrokes: RecordedStroke[];
27
+ removedStrokes: RecordedStroke[];
28
+ /** Fills, clears and other recorded operations that aren't strokes. */
29
+ events: PaintEvent[];
30
+ addedLayers: PaintChangeLayer[];
31
+ removedLayers: PaintChangeLayer[];
32
+ /** Layers whose name, visibility, opacity or blend changed, with the fields. */
33
+ changedLayers: (PaintChangeLayer & {
34
+ fields: string[];
35
+ })[];
36
+ /** A stroke is being painted: more of the same gesture will follow. */
37
+ inGesture: boolean;
38
+ /** A stroke started since the last report (reported even when nothing else changed). */
39
+ gestureStarted?: true;
40
+ /** The stroke in progress ended (reported even when it painted nothing). */
41
+ gestureEnded?: true;
42
+ /** Milliseconds since the epoch. */
43
+ at: number;
44
+ }
45
+ export interface PaintChangeFeed {
46
+ /** Compares the painting with the last snapshot and reports the change, if any. Runs on every controller change. */
47
+ check(): PaintChange | null;
48
+ subscribe(listener: (change: PaintChange) => void): () => void;
49
+ /** Takes a new snapshot without reporting the difference (e.g. after opening a document). */
50
+ reset(): void;
51
+ dispose(): void;
52
+ }
53
+ /**
54
+ * What changed in a painting, after each controller change: strokes added or removed (undo
55
+ * included), fills and clears, layers added, removed or changed, and the start and end of each
56
+ * stroke, tagged with who made it. Agents that paint alongside the user read it.
57
+ */
58
+ export declare function createPaintChangeFeed(controller: PaintEditorController, options?: {
59
+ now?: () => number;
60
+ }): PaintChangeFeed;
61
+ /** A stroke to paint: pen samples in document pixels. */
62
+ export interface PaintProposalStrokeOp {
63
+ kind: 'stroke';
64
+ points: {
65
+ x: number;
66
+ y: number;
67
+ pressure?: number;
68
+ }[];
69
+ /** A dry brush from the library (default the editor's brush). */
70
+ brushId?: string;
71
+ /** #rrggbb (default the editor colour). */
72
+ color?: string;
73
+ /** Brush size in document pixels (default the brush's). */
74
+ size?: number;
75
+ }
76
+ /** A wash over the suggestion's whole layer, like a ground or a glaze. */
77
+ export interface PaintProposalWashOp {
78
+ kind: 'wash';
79
+ color: string;
80
+ /** 0..1 (default 0.35). */
81
+ opacity?: number;
82
+ /** Paper grain response 0..1 (default 0.4). */
83
+ grain?: number;
84
+ }
85
+ /** Strokes already on the canvas, painted again moved, turned, scaled or mirrored. */
86
+ export interface PaintProposalEchoOp {
87
+ kind: 'echo';
88
+ strokeIds: string[];
89
+ dx?: number;
90
+ dy?: number;
91
+ /** Degrees clockwise about the strokes' centre (default 0). */
92
+ rotate?: number;
93
+ scale?: number;
94
+ flipX?: boolean;
95
+ /** Repeat the move this many times, each on top of the last (default 1). */
96
+ repeat?: number;
97
+ /** Recolour (default each stroke's colour). */
98
+ color?: string;
99
+ }
100
+ export type PaintProposalOp = PaintProposalStrokeOp | PaintProposalWashOp | PaintProposalEchoOp;
101
+ /** Where accepted paint goes: a new layer of its own (above a layer, on top, or at the bottom, behind everything). */
102
+ export interface PaintProposalPlacement {
103
+ /** Default `top`. `above` needs `layerId`. */
104
+ place?: 'top' | 'bottom' | 'above';
105
+ layerId?: string;
106
+ /** The new layer's name (default the proposal's title). */
107
+ layerName?: string;
108
+ }
109
+ export interface PaintProposalInput extends PaintProposalPlacement {
110
+ /** What the suggestion adds, in a few words. Also the undo step's name once accepted. */
111
+ title: string;
112
+ /** Why it fits what the user is doing, in a sentence. */
113
+ rationale?: string;
114
+ /** Who suggests it (`agent:<id>`); defaults to the store's `origin`. */
115
+ origin?: string;
116
+ ops: PaintProposalOp[];
117
+ }
118
+ export interface PaintProposal {
119
+ id: string;
120
+ title: string;
121
+ rationale?: string;
122
+ origin: string;
123
+ ops: PaintProposalOp[];
124
+ placement: PaintProposalPlacement;
125
+ /** Where the ghost sits (document pixels), or `null` when it painted nothing. */
126
+ bounds: RasterRect | null;
127
+ /** The ghost: the picture as it would look, inside `bounds` (straight RGBA). */
128
+ ghost: Uint8ClampedArray;
129
+ /** Strokes on the canvas it builds on (echo ops). */
130
+ anchors: string[];
131
+ createdAt: number;
132
+ }
133
+ export interface PaintProposalAcceptResult {
134
+ proposalId: string;
135
+ layerId: string;
136
+ /** The strokes it painted. */
137
+ strokeIds: string[];
138
+ /** The undo step's name. */
139
+ label: string;
140
+ }
141
+ export interface PaintProposalsState {
142
+ proposals: readonly PaintProposal[];
143
+ }
144
+ export interface PaintProposals {
145
+ getState(): PaintProposalsState;
146
+ subscribe(listener: (state: PaintProposalsState) => void): () => void;
147
+ /** Validates and previews a suggestion, and adds it to the pending list. Throws, changing nothing, when it can't be painted. */
148
+ propose(input: PaintProposalInput): PaintProposal;
149
+ /** Paints a pending proposal as one undo step recorded as the proposer's. The active layer stays the user's. */
150
+ accept(id: string): PaintProposalAcceptResult;
151
+ /** Drops a pending proposal; the painting is unchanged. */
152
+ reject(id: string): PaintProposal;
153
+ /** Drops every pending proposal (or those of one origin). Returns the ids dropped. */
154
+ clear(origin?: string): string[];
155
+ get(id: string): PaintProposal | undefined;
156
+ /** Strokes an echo builds on that are no longer on the canvas (it can't be accepted until re-proposed). */
157
+ missing(id: string): string[];
158
+ }
159
+ export interface PaintProposalsOptions {
160
+ controller: PaintEditorController;
161
+ /** Origin of proposals that name none (default `agent:anonymous`). */
162
+ origin?: string;
163
+ /** The most points one proposal may paint (default 4000). */
164
+ maxPoints?: number;
165
+ now?: () => number;
166
+ idGenerator?: () => string;
167
+ }
168
+ /** A store of paint an agent suggests (see the module comment). */
169
+ export declare function createPaintProposals(options: PaintProposalsOptions): PaintProposals;
170
+ /**
171
+ * Where to draw a proposal's ghost and its pixels, for a canvas element placed at `bounds` in
172
+ * document pixels (`new ImageData(data, width, height)`), or `null` when it paints nothing.
173
+ */
174
+ export declare function paintProposalGhost(proposal: PaintProposal): {
175
+ x: number;
176
+ y: number;
177
+ width: number;
178
+ height: number;
179
+ data: Uint8ClampedArray;
180
+ } | null;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Painting alongside an agent: a change feed that says what the user (or an agent) just painted,
3
+ * and proposals, the paint an agent suggests without painting it. A proposal is a few operations
4
+ * (strokes, a wash, an echo of strokes already on the canvas) that `previewPaint` renders as a
5
+ * ghost the user sees over the canvas; accepting paints them as one undo step recorded as the
6
+ * proposer's, on a layer of their own, and dismissing changes nothing. The canvas equivalents are
7
+ * `createCanvasChangeFeed` and `createCanvasProposals`.
8
+ */
9
+ import type { RasterRect } from '../raster/surface.js';
10
+ import { type PaintCommitEvent, type PaintEditorController } from './controller.js';
11
+ import type { PaintEvent } from './document.js';
12
+ import type { RecordedStroke } from './strokes.js';
13
+ /** A layer as the feed reports it. */
14
+ export interface PaintChangeLayer {
15
+ id: string;
16
+ name: string;
17
+ }
18
+ /** What changed in the painting since the feed's last report. */
19
+ export interface PaintChange {
20
+ /** Counts up from 1 for each change reported. */
21
+ seq: number;
22
+ /** `user`, or `agent:<id>` when an agent's step made it (see `PaintEditorController.withOrigin`). */
23
+ origin: string;
24
+ /** The history step behind it, when there was one. */
25
+ commit?: PaintCommitEvent;
26
+ addedStrokes: RecordedStroke[];
27
+ removedStrokes: RecordedStroke[];
28
+ /** Fills, clears and other recorded operations that aren't strokes. */
29
+ events: PaintEvent[];
30
+ addedLayers: PaintChangeLayer[];
31
+ removedLayers: PaintChangeLayer[];
32
+ /** Layers whose name, visibility, opacity or blend changed, with the fields. */
33
+ changedLayers: (PaintChangeLayer & {
34
+ fields: string[];
35
+ })[];
36
+ /** A stroke is being painted: more of the same gesture will follow. */
37
+ inGesture: boolean;
38
+ /** A stroke started since the last report (reported even when nothing else changed). */
39
+ gestureStarted?: true;
40
+ /** The stroke in progress ended (reported even when it painted nothing). */
41
+ gestureEnded?: true;
42
+ /** Milliseconds since the epoch. */
43
+ at: number;
44
+ }
45
+ export interface PaintChangeFeed {
46
+ /** Compares the painting with the last snapshot and reports the change, if any. Runs on every controller change. */
47
+ check(): PaintChange | null;
48
+ subscribe(listener: (change: PaintChange) => void): () => void;
49
+ /** Takes a new snapshot without reporting the difference (e.g. after opening a document). */
50
+ reset(): void;
51
+ dispose(): void;
52
+ }
53
+ /**
54
+ * What changed in a painting, after each controller change: strokes added or removed (undo
55
+ * included), fills and clears, layers added, removed or changed, and the start and end of each
56
+ * stroke, tagged with who made it. Agents that paint alongside the user read it.
57
+ */
58
+ export declare function createPaintChangeFeed(controller: PaintEditorController, options?: {
59
+ now?: () => number;
60
+ }): PaintChangeFeed;
61
+ /** A stroke to paint: pen samples in document pixels. */
62
+ export interface PaintProposalStrokeOp {
63
+ kind: 'stroke';
64
+ points: {
65
+ x: number;
66
+ y: number;
67
+ pressure?: number;
68
+ }[];
69
+ /** A dry brush from the library (default the editor's brush). */
70
+ brushId?: string;
71
+ /** #rrggbb (default the editor colour). */
72
+ color?: string;
73
+ /** Brush size in document pixels (default the brush's). */
74
+ size?: number;
75
+ }
76
+ /** A wash over the suggestion's whole layer, like a ground or a glaze. */
77
+ export interface PaintProposalWashOp {
78
+ kind: 'wash';
79
+ color: string;
80
+ /** 0..1 (default 0.35). */
81
+ opacity?: number;
82
+ /** Paper grain response 0..1 (default 0.4). */
83
+ grain?: number;
84
+ }
85
+ /** Strokes already on the canvas, painted again moved, turned, scaled or mirrored. */
86
+ export interface PaintProposalEchoOp {
87
+ kind: 'echo';
88
+ strokeIds: string[];
89
+ dx?: number;
90
+ dy?: number;
91
+ /** Degrees clockwise about the strokes' centre (default 0). */
92
+ rotate?: number;
93
+ scale?: number;
94
+ flipX?: boolean;
95
+ /** Repeat the move this many times, each on top of the last (default 1). */
96
+ repeat?: number;
97
+ /** Recolour (default each stroke's colour). */
98
+ color?: string;
99
+ }
100
+ export type PaintProposalOp = PaintProposalStrokeOp | PaintProposalWashOp | PaintProposalEchoOp;
101
+ /** Where accepted paint goes: a new layer of its own (above a layer, on top, or at the bottom, behind everything). */
102
+ export interface PaintProposalPlacement {
103
+ /** Default `top`. `above` needs `layerId`. */
104
+ place?: 'top' | 'bottom' | 'above';
105
+ layerId?: string;
106
+ /** The new layer's name (default the proposal's title). */
107
+ layerName?: string;
108
+ }
109
+ export interface PaintProposalInput extends PaintProposalPlacement {
110
+ /** What the suggestion adds, in a few words. Also the undo step's name once accepted. */
111
+ title: string;
112
+ /** Why it fits what the user is doing, in a sentence. */
113
+ rationale?: string;
114
+ /** Who suggests it (`agent:<id>`); defaults to the store's `origin`. */
115
+ origin?: string;
116
+ ops: PaintProposalOp[];
117
+ }
118
+ export interface PaintProposal {
119
+ id: string;
120
+ title: string;
121
+ rationale?: string;
122
+ origin: string;
123
+ ops: PaintProposalOp[];
124
+ placement: PaintProposalPlacement;
125
+ /** Where the ghost sits (document pixels), or `null` when it painted nothing. */
126
+ bounds: RasterRect | null;
127
+ /** The ghost: the picture as it would look, inside `bounds` (straight RGBA). */
128
+ ghost: Uint8ClampedArray;
129
+ /** Strokes on the canvas it builds on (echo ops). */
130
+ anchors: string[];
131
+ createdAt: number;
132
+ }
133
+ export interface PaintProposalAcceptResult {
134
+ proposalId: string;
135
+ layerId: string;
136
+ /** The strokes it painted. */
137
+ strokeIds: string[];
138
+ /** The undo step's name. */
139
+ label: string;
140
+ }
141
+ export interface PaintProposalsState {
142
+ proposals: readonly PaintProposal[];
143
+ }
144
+ export interface PaintProposals {
145
+ getState(): PaintProposalsState;
146
+ subscribe(listener: (state: PaintProposalsState) => void): () => void;
147
+ /** Validates and previews a suggestion, and adds it to the pending list. Throws, changing nothing, when it can't be painted. */
148
+ propose(input: PaintProposalInput): PaintProposal;
149
+ /** Paints a pending proposal as one undo step recorded as the proposer's. The active layer stays the user's. */
150
+ accept(id: string): PaintProposalAcceptResult;
151
+ /** Drops a pending proposal; the painting is unchanged. */
152
+ reject(id: string): PaintProposal;
153
+ /** Drops every pending proposal (or those of one origin). Returns the ids dropped. */
154
+ clear(origin?: string): string[];
155
+ get(id: string): PaintProposal | undefined;
156
+ /** Strokes an echo builds on that are no longer on the canvas (it can't be accepted until re-proposed). */
157
+ missing(id: string): string[];
158
+ }
159
+ export interface PaintProposalsOptions {
160
+ controller: PaintEditorController;
161
+ /** Origin of proposals that name none (default `agent:anonymous`). */
162
+ origin?: string;
163
+ /** The most points one proposal may paint (default 4000). */
164
+ maxPoints?: number;
165
+ now?: () => number;
166
+ idGenerator?: () => string;
167
+ }
168
+ /** A store of paint an agent suggests (see the module comment). */
169
+ export declare function createPaintProposals(options: PaintProposalsOptions): PaintProposals;
170
+ /**
171
+ * Where to draw a proposal's ghost and its pixels, for a canvas element placed at `bounds` in
172
+ * document pixels (`new ImageData(data, width, height)`), or `null` when it paints nothing.
173
+ */
174
+ export declare function paintProposalGhost(proposal: PaintProposal): {
175
+ x: number;
176
+ y: number;
177
+ width: number;
178
+ height: number;
179
+ data: Uint8ClampedArray;
180
+ } | null;