@pixerate/editor 0.254.0 → 0.255.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 (73) 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/changeFeed.d.cts +6 -0
  20. package/dist/canvas/changeFeed.d.ts +6 -0
  21. package/dist/canvas/drawController.d.cts +11 -3
  22. package/dist/canvas/drawController.d.ts +11 -3
  23. package/dist/canvas/freehand.d.cts +6 -1
  24. package/dist/canvas/freehand.d.ts +6 -1
  25. package/dist/canvas/index.cjs +123 -104
  26. package/dist/canvas/index.js +25 -6
  27. package/dist/{chunk-XVIK73LS.js → chunk-5P4JEOIV.js} +4 -4
  28. package/dist/{chunk-JPEHXIB5.cjs → chunk-5YWWQ36H.cjs} +66 -21
  29. package/dist/{chunk-CHAWTXBE.cjs → chunk-6WCQ35GI.cjs} +4 -4
  30. package/dist/{chunk-GYMMNCFT.js → chunk-AF23QK5F.js} +56 -11
  31. package/dist/{chunk-JFHBXNR2.cjs → chunk-B7V34GIH.cjs} +86 -86
  32. package/dist/{chunk-QC3OXRE4.js → chunk-DMTEQ6SH.js} +1 -1
  33. package/dist/{chunk-TLOGFIIV.cjs → chunk-GCTUOTCW.cjs} +35 -35
  34. package/dist/{chunk-34KLWUH4.cjs → chunk-HIMGBZAM.cjs} +1133 -597
  35. package/dist/{chunk-7P6VF3FG.js → chunk-HYWRU5VX.js} +1 -1
  36. package/dist/{chunk-QPHNVZHO.js → chunk-JVRLGV3C.js} +1118 -582
  37. package/dist/{chunk-LFMKWGFL.cjs → chunk-LRNUZKWV.cjs} +22 -8
  38. package/dist/chunk-PGPFWC35.cjs +517 -0
  39. package/dist/{chunk-QLSGTCYT.cjs → chunk-TBAVCLEQ.cjs} +23 -23
  40. package/dist/{chunk-2ZJ4DT3M.js → chunk-VUKUTT2W.js} +1 -1
  41. package/dist/{chunk-5JNA6LLY.js → chunk-WYGSR3IJ.js} +22 -8
  42. package/dist/chunk-YHUMGBZD.js +517 -0
  43. package/dist/commands/builtins.d.cts +1 -1
  44. package/dist/commands/builtins.d.ts +1 -1
  45. package/dist/commands/index.cjs +2 -2
  46. package/dist/commands/index.js +1 -1
  47. package/dist/image-editor/brush-presets.d.cts +32 -0
  48. package/dist/image-editor/brush-presets.d.ts +32 -0
  49. package/dist/image-editor/controller.d.cts +15 -0
  50. package/dist/image-editor/controller.d.ts +15 -0
  51. package/dist/image-editor/index.cjs +848 -775
  52. package/dist/image-editor/index.d.cts +1 -0
  53. package/dist/image-editor/index.d.ts +1 -0
  54. package/dist/image-editor/index.js +81 -8
  55. package/dist/image-editor/types.d.cts +7 -0
  56. package/dist/image-editor/types.d.ts +7 -0
  57. package/dist/paint-editor/index.cjs +80 -80
  58. package/dist/paint-editor/index.js +13 -13
  59. package/dist/vector/brushstroke.d.cts +21 -5
  60. package/dist/vector/brushstroke.d.ts +21 -5
  61. package/dist/vector/controller.d.cts +14 -1
  62. package/dist/vector/controller.d.ts +14 -1
  63. package/dist/vector/index.cjs +1220 -1199
  64. package/dist/vector/index.js +34 -13
  65. package/dist/vector/types.d.cts +7 -0
  66. package/dist/vector/types.d.ts +7 -0
  67. package/package.json +1 -1
  68. package/skills/pixerate-brushes/SKILL.md +9 -3
  69. package/skills/pixerate-canvas/SKILL.md +10 -5
  70. package/skills/pixerate-image-editor/SKILL.md +2 -1
  71. package/skills/pixerate-vector/SKILL.md +1 -1
  72. package/dist/chunk-HS6QDWDF.cjs +0 -1029
  73. package/dist/chunk-MTKY6K3Q.js +0 -1029
@@ -26,7 +26,7 @@ import {
26
26
  traceField,
27
27
  traceVectorImage,
28
28
  vectorTraceContours
29
- } from "../chunk-2ZJ4DT3M.js";
29
+ } from "../chunk-VUKUTT2W.js";
30
30
  import {
31
31
  applyHomography,
32
32
  homographyFromPoints,
@@ -53,7 +53,7 @@ import {
53
53
  reverseMorphContour,
54
54
  subpathToMorphContour,
55
55
  turnMorphContour
56
- } from "../chunk-7P6VF3FG.js";
56
+ } from "../chunk-HYWRU5VX.js";
57
57
  import {
58
58
  DEFAULT_VECTOR_FONT,
59
59
  IDENTITY_MATRIX,
@@ -376,6 +376,8 @@ import {
376
376
  vectorBoundsInside,
377
377
  vectorBoxMatrix,
378
378
  vectorBrushstrokeBrush,
379
+ vectorBrushstrokeBrushError,
380
+ vectorBrushstrokeBrushOf,
379
381
  vectorBrushstrokeBrushes,
380
382
  vectorBrushstrokeCharacterError,
381
383
  vectorBrushstrokeImages,
@@ -526,8 +528,10 @@ import {
526
528
  withVectorStrokes,
527
529
  withVectorTargetPaint,
528
530
  zigzagPaths
529
- } from "../chunk-GYMMNCFT.js";
530
- import "../chunk-MTKY6K3Q.js";
531
+ } from "../chunk-AF23QK5F.js";
532
+ import {
533
+ snapshotBrushLibrary
534
+ } from "../chunk-JVRLGV3C.js";
531
535
  import "../chunk-APX63ILU.js";
532
536
  import {
533
537
  createProvenance
@@ -3689,7 +3693,7 @@ function getVectorAppearancePanel(state) {
3689
3693
  ...kind === "stroke" ? { strokeWidth: l.strokeWidth ?? 1, strokeAlign: l.strokeAlign ?? "center" } : {},
3690
3694
  ...isVectorBrushstrokeLayer(l) ? (() => {
3691
3695
  const b = l.brush;
3692
- const preset = vectorBrushstrokeBrush(b.brushId);
3696
+ const preset = vectorBrushstrokeBrushOf(b);
3693
3697
  return { brushstroke: { brushId: b.brushId, name: preset.name, size: b.size ?? preset.tip.size, ...b.seed !== void 0 ? { seed: b.seed } : {}, grain: b.seed !== void 0 && b.paper !== null } };
3694
3698
  })() : (l.kind ?? "paint") !== "paint" ? { unknownKind: String(l.kind) } : {},
3695
3699
  editing: edit.target === kind && (edit.layer === null ? index === count - 1 : edit.layer === index)
@@ -6818,6 +6822,7 @@ var VectorEditorController = class _VectorEditorController {
6818
6822
  revision = 0;
6819
6823
  state;
6820
6824
  constructor(options = {}) {
6825
+ this.brushes = options.brushLibrary ? snapshotBrushLibrary(options.brushLibrary, () => this.notify()) : null;
6821
6826
  this.history = options.history ?? new HistoryManager();
6822
6827
  this.doc = options.document ? loadedDocument(options.document) : { width: 800, height: 600, nodes: [] };
6823
6828
  this.doc = this.refreshed(this.doc);
@@ -6853,6 +6858,16 @@ var VectorEditorController = class _VectorEditorController {
6853
6858
  * Stops listening to the history and drops all listeners. A later `subscribe` reconnects, so a
6854
6859
  * controller torn down by React StrictMode's double effect keeps working when it is reused.
6855
6860
  */
6861
+ /** The saved brushes of the brush library as last read (empty without one). */
6862
+ brushes;
6863
+ /** The brushes a brushstroke can use: the presets, then the brush library's saved brushes the vector profile admits. */
6864
+ listBrushstrokeBrushes() {
6865
+ return vectorBrushstrokeBrushes(this.brushes?.user ?? []);
6866
+ }
6867
+ /** Resolves once the brush library has been read (its saved brushes can be used from then on). */
6868
+ whenBrushesLoaded() {
6869
+ return this.brushes?.ready ?? Promise.resolve();
6870
+ }
6856
6871
  destroy() {
6857
6872
  if (this.dwellTimer) clearTimeout(this.dwellTimer);
6858
6873
  this.dwellTimer = null;
@@ -8163,15 +8178,17 @@ var VectorEditorController = class _VectorEditorController {
8163
8178
  }
8164
8179
  /**
8165
8180
  * Adds a brushstroke to the selected (or given) paths: a stroke layer that paints the path with
8166
- * brush `brushId` from the paint editor's library (`vectorBrushstrokeBrushes`), `size` document
8181
+ * brush `brushId` (a preset, or a saved brush of the brush library: `listBrushstrokeBrushes`), `size` document
8167
8182
  * units across (default the brush's own), an optional `pressure` profile along the length, in
8168
8183
  * `color` (default the top stroke's paint, else black) at `opacity`. At the top of the stroke
8169
8184
  * stack or at `index`. A `seed` gives it the brush's character (jitter and the grain of `paper`,
8170
8185
  * `null` for none); without one it renders plain. One undo step.
8171
8186
  */
8172
8187
  addBrushstroke(brush, options = {}) {
8173
- const clean = sanitizeVectorBrushstroke(brush);
8174
- if (!clean) return { ok: false, message: `Unknown brush "${brush?.brushId}": use one of ${vectorBrushstrokeBrushes().map((b) => b.id).join(", ")}.` };
8188
+ const user = this.brushes?.user ?? [];
8189
+ const brushError = typeof brush?.brushId === "string" ? vectorBrushstrokeBrushError(brush.brushId, user) : "brushId must be a brush id (vector.listBrushes lists them).";
8190
+ const clean = brushError ? null : sanitizeVectorBrushstroke(brush, user);
8191
+ if (!clean) return { ok: false, message: brushError ?? `Brush "${brush.brushId}" can't paint a brushstroke (vector.listBrushes lists the ones that can).` };
8175
8192
  if (brush.size !== void 0 && !(brush.size > 0 && brush.size <= 500)) return { ok: false, message: "size must be above 0 and at most 500 (document units)." };
8176
8193
  const characterError = vectorBrushstrokeCharacterError(brush);
8177
8194
  if (characterError) return { ok: false, message: characterError };
@@ -8193,7 +8210,9 @@ var VectorEditorController = class _VectorEditorController {
8193
8210
  * brushstroke. With `merge`, consecutive edits fold into one undo step.
8194
8211
  */
8195
8212
  setBrushstroke(index, patch, options = {}) {
8196
- if (patch.brushId !== void 0 && !vectorBrushstrokeBrush(patch.brushId)) return { ok: false, message: `Unknown brush "${patch.brushId}": use one of ${vectorBrushstrokeBrushes().map((b) => b.id).join(", ")}.` };
8213
+ const user = this.brushes?.user ?? [];
8214
+ const brushError = patch.brushId !== void 0 ? vectorBrushstrokeBrushError(patch.brushId, user) : null;
8215
+ if (brushError) return { ok: false, message: brushError };
8197
8216
  if (patch.size !== void 0 && patch.size !== null && !(patch.size > 0 && patch.size <= 500)) return { ok: false, message: "size must be above 0 and at most 500 (document units)." };
8198
8217
  if (patch.color !== void 0 && !parseVectorColor(patch.color)) return { ok: false, message: `"${patch.color}" is not a colour.` };
8199
8218
  if (patch.opacity !== void 0 && !(patch.opacity >= 0 && patch.opacity <= 1)) return { ok: false, message: "opacity must be from 0 to 1." };
@@ -8207,7 +8226,7 @@ var VectorEditorController = class _VectorEditorController {
8207
8226
  (stack, path) => {
8208
8227
  const layer = stack[index];
8209
8228
  if (!layer || layer.kind !== "brushstroke" || !layer.brush) return `Path "${path.id}" has no brushstroke at stroke ${index} (vector.getAppearance lists its strokes; 0 is the bottom).`;
8210
- const brush = { ...layer.brush, ...patch.brushId ? { brushId: patch.brushId } : {} };
8229
+ const brush = patch.brushId && patch.brushId !== layer.brush.brushId ? { ...layer.brush, brushId: patch.brushId, spec: void 0 } : { ...layer.brush };
8211
8230
  if (patch.size === null) delete brush.size;
8212
8231
  else if (patch.size !== void 0) brush.size = patch.size;
8213
8232
  if (patch.pressure === null) delete brush.pressure;
@@ -8215,7 +8234,7 @@ var VectorEditorController = class _VectorEditorController {
8215
8234
  if (patch.seed === null) delete brush.seed;
8216
8235
  else if (patch.seed !== void 0) brush.seed = patch.seed;
8217
8236
  if (patch.paper !== void 0) brush.paper = patch.paper;
8218
- const next = { ...layer, brush: sanitizeVectorBrushstroke(brush) ?? layer.brush, ...patch.color ? { paint: patch.color } : {}, ...patch.opacity !== void 0 ? { opacity: patch.opacity } : {} };
8237
+ const next = { ...layer, brush: sanitizeVectorBrushstroke(brush, user) ?? layer.brush, ...patch.color ? { paint: patch.color } : {}, ...patch.opacity !== void 0 ? { opacity: patch.opacity } : {} };
8219
8238
  return stack.map((l, i) => i === index ? next : l);
8220
8239
  },
8221
8240
  merge
@@ -15894,9 +15913,9 @@ function createVectorCommands(controller, options = {}) {
15894
15913
  label: "Brushstroke Brushes",
15895
15914
  readOnly: true,
15896
15915
  keywords: ["brush", "brushstroke", "pencil", "pastel", "charcoal", "ink", "marker", "natural media"],
15897
- description: "Lists the brushes a brushstroke can use (the paint editor's dry brushes): { brushes: [{ id, name, category, size }] } (size: the brush's own diameter in document units).",
15916
+ description: "Lists the brushes a brushstroke can use (the dry presets, then saved brushes from the brush library the vector profile admits): { brushes: [{ id, name, category, size, builtin }] } (size: the brush's own diameter in document units).",
15898
15917
  params: { type: "object", properties: {}, additionalProperties: false },
15899
- run: () => ({ brushes: vectorBrushstrokeBrushes().map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size })) })
15918
+ run: () => ({ brushes: controller.listBrushstrokeBrushes().map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: vectorBrushstrokeBrush(b.id) !== null })) })
15900
15919
  },
15901
15920
  {
15902
15921
  ...base,
@@ -18848,6 +18867,8 @@ export {
18848
18867
  vectorBoxCorners,
18849
18868
  vectorBoxMatrix,
18850
18869
  vectorBrushstrokeBrush,
18870
+ vectorBrushstrokeBrushError,
18871
+ vectorBrushstrokeBrushOf,
18851
18872
  vectorBrushstrokeBrushes,
18852
18873
  vectorBrushstrokeCharacterError,
18853
18874
  vectorBrushstrokeImages,
@@ -2,6 +2,7 @@ import type { GenerationProvenance } from '../generation/provenance.cjs';
2
2
  import type { CanvasGradientDef } from '../canvas/gradient.cjs';
3
3
  import type { WarpMesh, WarpStyle } from '../geom/types.cjs';
4
4
  import type { GrainPattern } from '../paint/grain.cjs';
5
+ import type { Brush } from '../brushes/types.cjs';
5
6
  /** An affine matrix `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f` (SVG order). */
6
7
  export type VectorMatrix = readonly [number, number, number, number, number, number];
7
8
  /** A point in document coordinates. */
@@ -268,7 +269,13 @@ export interface VectorBrushstrokePaper {
268
269
  * renders plain dabs, as before, and `paper` is ignored.
269
270
  */
270
271
  export interface VectorBrushstroke {
272
+ /** A preset id, or a saved brush's id from the brush library. */
271
273
  brushId: string;
274
+ /**
275
+ * The saved brush `brushId` names (a facet brush of `@pixerate/editor/brushes`), embedded when it
276
+ * is not a preset, so the document renders without the library.
277
+ */
278
+ spec?: Brush;
272
279
  size?: number;
273
280
  pressure?: number[];
274
281
  /** Jitter seed, an integer 0–2147483647. Turns the brush's character on. */
@@ -2,6 +2,7 @@ import type { GenerationProvenance } from '../generation/provenance.js';
2
2
  import type { CanvasGradientDef } from '../canvas/gradient.js';
3
3
  import type { WarpMesh, WarpStyle } from '../geom/types.js';
4
4
  import type { GrainPattern } from '../paint/grain.js';
5
+ import type { Brush } from '../brushes/types.js';
5
6
  /** An affine matrix `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f` (SVG order). */
6
7
  export type VectorMatrix = readonly [number, number, number, number, number, number];
7
8
  /** A point in document coordinates. */
@@ -268,7 +269,13 @@ export interface VectorBrushstrokePaper {
268
269
  * renders plain dabs, as before, and `paper` is ignored.
269
270
  */
270
271
  export interface VectorBrushstroke {
272
+ /** A preset id, or a saved brush's id from the brush library. */
271
273
  brushId: string;
274
+ /**
275
+ * The saved brush `brushId` names (a facet brush of `@pixerate/editor/brushes`), embedded when it
276
+ * is not a preset, so the document renders without the library.
277
+ */
278
+ spec?: Brush;
272
279
  size?: number;
273
280
  pressure?: number[];
274
281
  /** Jitter seed, an integer 0–2147483647. Turns the brush's character on. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.254.0",
3
+ "version": "0.255.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -38,9 +38,15 @@ paints without (warnings), each with a fix; lists and writes use the current hos
38
38
  | Host | Renders |
39
39
  | --- | --- |
40
40
  | Paint editor (`paint`) | Everything. |
41
- | Image editor (`image`) | Dry dabs on round, flat and chisel tips, the Mixer Brush and the eraser; no wet or thick surface, particles, objects, light or motion. |
42
- | Canvas (`canvas`) | Dry dab brushes along a path (no tools, wet, thick, particles, objects or light); velocity is ignored. |
43
- | Vector strokes (`vector`) | As canvas, but light brushes paint as plain colour. |
41
+ | Image editor (`image`) | Dry dabs on round, flat and chisel tips, the Mixer Brush and the eraser; no wet or thick surface, particles, objects, light or motion. Pick one with `set_brush_preset`. |
42
+ | Canvas (`canvas`) | Dry dab brushes along a path (no tools, wet, thick, particles, objects or light); velocity is ignored. Draw with one as `canvas.drawFreehand` `brush`. |
43
+ | Vector strokes (`vector`) | As canvas, but light brushes paint as plain colour. Draw with one as `vector.addBrushstroke` `brushId`. |
44
+
45
+ Every host lists the presets its profile admits and the library's saved brushes it admits, and
46
+ each registers these tools with its own profile, so `list_brush_presets` shows that host's brushes.
47
+ A saved brush used in canvas or vector strokes is copied into the stroke, so the document renders
48
+ where the library is not; changing the brush in the library later doesn't change strokes already
49
+ drawn.
44
50
 
45
51
  ## Working method
46
52
 
@@ -250,8 +250,8 @@ character: seeded jitter and the paper grain they catch) and, when a width was c
250
250
 
251
251
  | Command | Use it to |
252
252
  | --- | --- |
253
- | `canvas.drawFreehand` | Draw a pen or highlighter stroke through `points` (`[x, y]` pairs in canvas units, in drawing order; one undo step). `tool` picks the defaults (`pen`: blue, width 3; `highlighter`: yellow, width 24, opacity 0.35); `color`, `width`, `opacity` override them (with a `brush`, `width` is the brush diameter and defaults to the preset's own size). Pass `brush` (a preset id from `canvas.listBrushes`, or tip settings) to paint it with a dry brush, plus `pressure` / `tiltX` / `tiltY` profiles spread along its length (`[0.2, 1, 0.3]` swells in the middle). Add `paint` (only with `brush`) for gradient paint that changes colour along the stroke: `{ "type": "gradient", "stops": [...], "map": { "by": "distance", "length": 200 } }` (length in canvas units), or `by` `pressure`, `tilt`, `direction`, `across-tip`, `random`. Returns the node `id`, its `bounds` and the stored `brush` (with `paint` normalised: defaults filled in, colours lowercased). |
254
- | `canvas.listBrushes` | Read-only: the dry paint presets `canvas.drawFreehand` and the gestures accept as `brush` (`id`, `name`, `category`, the preset's own `size`, which a stroke uses unless you pass `width`). Call it before the first brushed stroke instead of guessing ids. |
253
+ | `canvas.drawFreehand` | Draw a pen or highlighter stroke through `points` (`[x, y]` pairs in canvas units, in drawing order; one undo step). `tool` picks the defaults (`pen`: blue, width 3; `highlighter`: yellow, width 24, opacity 0.35); `color`, `width`, `opacity` override them (with a `brush`, `width` is the brush diameter and defaults to the preset's own size). Pass `brush` (a preset or saved brush id from `canvas.listBrushes`, or tip settings) to paint it with a dry brush, plus `pressure` / `tiltX` / `tiltY` profiles spread along its length (`[0.2, 1, 0.3]` swells in the middle). Add `paint` (only with `brush`) for gradient paint that changes colour along the stroke: `{ "type": "gradient", "stops": [...], "map": { "by": "distance", "length": 200 } }` (length in canvas units), or `by` `pressure`, `tilt`, `direction`, `across-tip`, `random`. Returns the node `id`, its `bounds` and the stored `brush` (with `paint` normalised: defaults filled in, colours lowercased). |
254
+ | `canvas.listBrushes` | Read-only: the brushes `canvas.drawFreehand` and the gestures accept as `brush`: the dry presets, then saved brushes from the brush library the canvas can paint (`id`, `name`, `category`, the brush's own `size`, which a stroke uses unless you pass `width`, and `builtin`). Call it before the first brushed stroke instead of guessing ids. |
255
255
 
256
256
  Gestures draw one hand-drawn stroke node each (one undo step) through the same path as
257
257
  `canvas.drawFreehand`, and the agent's cursor traces them. Targets are boxes
@@ -348,9 +348,14 @@ effects provider (for example Flair); without one the read tools say so and the
348
348
  - **A shape node holds one effect.** `apply_effect` replaces the effect that is there. Effects draw on shape nodes only (nodes with `data.shape`).
349
349
  - **Effects belong to the provider they came from.** Each saved effect keeps its provider id. After the provider changes (another effects library), check `list_applied_effects` for `available: false` and re-apply those effects with ids from the new provider's `search_effects`; the old effect ids mean nothing to it.
350
350
 
351
- - **Brushes are dry only.** Wet presets (watercolour, gouache, wet oils), mixer, blender, smudge and
352
- eraser presets are refused: wet paint needs an area to flow in, and the others need paint under
353
- them. `pressure` and tilt without a `brush` are refused too.
351
+ - **Brushes are dry only.** The canvas profile (`CANVAS_PROFILE`, see `pixerate-brushes`) refuses
352
+ wet and thick brushes (watercolour, gouache, wet oils, impasto), particle and sticker brushes,
353
+ tools (mixer, blender, smudge, eraser) and light brushes, saying why and which ids to use:
354
+ wet paint needs an area to flow in, tools need paint under them, light needs the paint editor's
355
+ light channel. Saved brushes from the brush library work when they are dry (`check_brush` with
356
+ `host: "canvas"` says, and gives a `degraded` brush to save instead); a stroke keeps a copy of
357
+ the saved brush, so it renders without the library. `pressure` and tilt without a `brush` are
358
+ refused too.
354
359
  - **Gradient paint needs a brush, and no timing.** `paint` without `brush` is refused; use
355
360
  `color` for a plain stroke. Canvas strokes record no timing, so `velocity` and `time` mappings
356
361
  are refused: use `distance` for colour that changes along the stroke. The stroke `color` still
@@ -313,7 +313,8 @@ in the source frame at the document's bit depth, colour mode and profile. Read
313
313
  | `set_paint` | Set what the brush paints: one colour (it becomes the foreground) or gradient paint (`stops` sorted by offset, `space` pigment / oklab / srgb, `map`, `softness`). Errors say what is wrong. Returns `paint`, `foreground`, `background`, `brush`. |
314
314
  | `sample_color` | The Eyedropper: read the composite colour at a document point (`size` averages a square; default 1 px) and, unless `setForeground` is false, make it the paint foreground. Use it to match a colour already in the image before painting or retouching. Fails outside the canvas or on transparent pixels. Returns `color` and `foreground`. |
315
315
  | `sample_gradient` | Read the composite along a line (`start` → `end`, document pixels) and fit the fewest gradient stops to it (`tolerance` OKLab ΔE, `maxStops`, `sampleSize` averaging, `snapStep`). Changes nothing. Returns a linear `gradient` (angle of the line, oklab), its `css` and `pointsRead`. Use it to match a sky, a backdrop or a light falloff before painting a gradient or styling one. |
316
- | `get_paint` | Read-only. What the brush paints (`paint`), the foreground and background colours and the brush settings. |
316
+ | `get_paint` | Read-only. What the brush paints (`paint`), the foreground and background colours, the brush settings, and `brushPreset` (the preset they came from, null once changed by hand). |
317
+ | `set_brush_preset` | Paint with a brush from the brush library: `id` from `list_brush_presets` (the image host lists only what it can paint). Sets the paint tool it needs (`brush`, `mixer` for a Mixer Brush, `eraser`), its brush and mixer settings and a brush's own gradient. Refuses wet, thick, particle, sticker and light brushes with the reason. Returns `tool`, `brush`, `mixer`, `paint`, `warnings` (e.g. paper grain, which the image editor paints without). Then `paint_stroke` with that tool. |
317
318
  | `get_mixer_brush` | Read-only. The Mixer Brush settings and what it carries (`reservoir` #rrggbb or null, `amount` left 0..1, `pickup` #rrggbb or null). Call it before a mixer stroke with `loadEachStroke: false`. |
318
319
  | `load_mixer_brush` | Fill the Mixer Brush reservoir with `color` (default the foreground), amount 1; keeps the pickup. View state, not an undo step. Use before strokes with `loadEachStroke: false`. Returns the load. |
319
320
  | `clean_mixer_brush` | Empty the Mixer Brush pickup well (`emptyReservoir: true` also drops the loaded paint, so it only smears). View state, not an undo step. Returns the load. |
@@ -373,7 +373,7 @@ contents whole while you work, without losing the mask.
373
373
 
374
374
  | Command | Use it to |
375
375
  | --- | --- |
376
- | `vector.listBrushes` | List the natural-media brushes (pencils, pastels, charcoal, markers, inks) with their sizes. |
376
+ | `vector.listBrushes` | List the brushes a brushstroke can use: the dry presets (pencils, pastels, charcoal, markers, inks), then saved brushes from the brush library the vector profile admits, with their sizes and `builtin`. Brushes it refuses (wet, thick, particles, stickers, tools) are refused by `vector.addBrushstroke` with the reason. A saved brush is kept in the layer, so the document renders without the library. |
377
377
  | `vector.addBrushstroke` | Paint paths with a brush along their outline (`brushId`, `size`, `pressure` profile such as [0.2, 1, 0.2] for a taper, `color`): sketchy, hand-drawn or inked lines that stay editable. Pass a `seed` (any integer) so the brush gets its character, the seeded jitter and paper grain that make pencil, pastel and charcoal look real; `paper` picks the grain (`{ pattern, seed, scale, depth }`) or `null` turns it off. Returns the stroke stacks. |
378
378
  | `vector.setBrushstroke` | Change brushstroke layer `index` of path `id`: brush, size, pressure, colour, opacity, `seed` (a new seed is a new take on the same stroke; null: plain dabs) and `paper` (replaced whole; null: no grain; {}: the default sanded paper). Remove it with `vector.removeStroke`. |
379
379