@pixerate/editor 0.252.0 → 0.253.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 (45) hide show
  1. package/dist/brushes/index.cjs +229 -2014
  2. package/dist/brushes/index.d.cts +5 -1
  3. package/dist/brushes/index.d.ts +5 -1
  4. package/dist/brushes/index.js +236 -2021
  5. package/dist/brushes/patch.d.cts +23 -0
  6. package/dist/brushes/patch.d.ts +23 -0
  7. package/dist/brushes/ui.d.cts +105 -0
  8. package/dist/brushes/ui.d.ts +105 -0
  9. package/dist/chunk-34KLWUH4.cjs +2284 -0
  10. package/dist/chunk-QPHNVZHO.js +2284 -0
  11. package/dist/paint-editor/brush-controls.d.cts +57 -0
  12. package/dist/paint-editor/brush-controls.d.ts +57 -0
  13. package/dist/paint-editor/brush-library-tools.d.cts +15 -0
  14. package/dist/paint-editor/brush-library-tools.d.ts +15 -0
  15. package/dist/paint-editor/brush-library.d.cts +29 -0
  16. package/dist/paint-editor/brush-library.d.ts +29 -0
  17. package/dist/paint-editor/controller.d.cts +76 -17
  18. package/dist/paint-editor/controller.d.ts +76 -17
  19. package/dist/paint-editor/index.cjs +537 -852
  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 +327 -642
  23. package/dist/paint-editor/serialize.d.cts +7 -4
  24. package/dist/paint-editor/serialize.d.ts +7 -4
  25. package/dist/paint-editor/sticker-tools.d.cts +1 -1
  26. package/dist/paint-editor/sticker-tools.d.ts +1 -1
  27. package/dist/paint-editor/strokes.d.cts +10 -1
  28. package/dist/paint-editor/strokes.d.ts +10 -1
  29. package/dist/paint-editor/tools.d.cts +1 -1
  30. package/dist/paint-editor/tools.d.ts +1 -1
  31. package/dist/paint-editor/ui.d.cts +3 -51
  32. package/dist/paint-editor/ui.d.ts +3 -51
  33. package/dist/paint-editor/wet-tools.d.cts +1 -1
  34. package/dist/paint-editor/wet-tools.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/skills/pixerate-brushes/SKILL.md +5 -3
  37. package/skills/pixerate-paint-editor/SKILL.md +26 -24
  38. package/skills/pixerate-paint-editor/references/brush-facets.md +30 -0
  39. package/skills/pixerate-paint-editor/references/gradient-paint.md +1 -1
  40. package/skills/pixerate-paint-editor/references/light-paint.md +2 -2
  41. package/skills/pixerate-paint-editor/references/oils.md +1 -1
  42. package/skills/pixerate-paint-editor/references/particle-brushes.md +8 -5
  43. package/skills/pixerate-paint-oil-impasto/SKILL.md +1 -1
  44. package/skills/pixerate-stickers/SKILL.md +1 -1
  45. package/skills/pixerate-video-editor/SKILL.md +1 -1
@@ -9,11 +9,14 @@
9
9
  * as new assets keyed by layer id without breaking older readers (see `PaintDocumentExtensions`).
10
10
  */
11
11
  import { type ImageEditorAssetStore, type SerializedLayer } from '../image-editor/layers/serialize.cjs';
12
- import { type RecordedStroke } from './strokes.cjs';
12
+ import { strokeToJSON } from './strokes.cjs';
13
13
  import { type PaintDocumentState } from './document.cjs';
14
14
  import { type PaperSettings } from './paper.cjs';
15
15
  export declare const PAINT_FORMAT: "pixerate-paint";
16
- export declare const PAINT_SCHEMA_VERSION: 1;
16
+ /** 2: recorded strokes keep facet brushes (version 1 documents, with flat engine brushes, still load). */
17
+ export declare const PAINT_SCHEMA_VERSION: 2;
18
+ /** A recorded stroke as a document stores it (`strokeToJSON`). */
19
+ export type SerializedStroke = ReturnType<typeof strokeToJSON>;
17
20
  /** Where a host keeps layer PNGs outside the JSON (the image editor's asset store contract). */
18
21
  export type PaintAssetStore = ImageEditorAssetStore;
19
22
  /**
@@ -34,8 +37,8 @@ export interface SerializedPaintDocument {
34
37
  /** Asset id → PNG data URL (or an external reference). */
35
38
  assets: Record<string, string>;
36
39
  activeLayerId: string | null;
37
- /** Recorded strokes (strokes are data), oldest first; missing when saved without them. */
38
- strokes?: RecordedStroke[];
40
+ /** Recorded strokes (strokes are data), oldest first, each brush a facet `Brush`; missing when saved without them. */
41
+ strokes?: SerializedStroke[];
39
42
  /** Everything else (document-level state of later phases), written back as read. */
40
43
  [extension: string]: unknown;
41
44
  }
@@ -9,11 +9,14 @@
9
9
  * as new assets keyed by layer id without breaking older readers (see `PaintDocumentExtensions`).
10
10
  */
11
11
  import { type ImageEditorAssetStore, type SerializedLayer } from '../image-editor/layers/serialize.js';
12
- import { type RecordedStroke } from './strokes.js';
12
+ import { strokeToJSON } from './strokes.js';
13
13
  import { type PaintDocumentState } from './document.js';
14
14
  import { type PaperSettings } from './paper.js';
15
15
  export declare const PAINT_FORMAT: "pixerate-paint";
16
- export declare const PAINT_SCHEMA_VERSION: 1;
16
+ /** 2: recorded strokes keep facet brushes (version 1 documents, with flat engine brushes, still load). */
17
+ export declare const PAINT_SCHEMA_VERSION: 2;
18
+ /** A recorded stroke as a document stores it (`strokeToJSON`). */
19
+ export type SerializedStroke = ReturnType<typeof strokeToJSON>;
17
20
  /** Where a host keeps layer PNGs outside the JSON (the image editor's asset store contract). */
18
21
  export type PaintAssetStore = ImageEditorAssetStore;
19
22
  /**
@@ -34,8 +37,8 @@ export interface SerializedPaintDocument {
34
37
  /** Asset id → PNG data URL (or an external reference). */
35
38
  assets: Record<string, string>;
36
39
  activeLayerId: string | null;
37
- /** Recorded strokes (strokes are data), oldest first; missing when saved without them. */
38
- strokes?: RecordedStroke[];
40
+ /** Recorded strokes (strokes are data), oldest first, each brush a facet `Brush`; missing when saved without them. */
41
+ strokes?: SerializedStroke[];
39
42
  /** Everything else (document-level state of later phases), written back as read. */
40
43
  [extension: string]: unknown;
41
44
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Agent tools for stickers: stamps sprayed with a sticker brush (`paint_stroke`, list_brushes
2
+ * Agent tools for stickers: stamps sprayed with a sticker brush (`paint_stroke`, list_brush_presets
3
3
  * category sticker) stay objects on their layer, which these tools list, move, turn, resize,
4
4
  * recolour, reorder and delete, find by point (`sticker_at`), and places new ones exactly (`add_stickers`). Each change is one undo step and redraws the layer.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Agent tools for stickers: stamps sprayed with a sticker brush (`paint_stroke`, list_brushes
2
+ * Agent tools for stickers: stamps sprayed with a sticker brush (`paint_stroke`, list_brush_presets
3
3
  * category sticker) stay objects on their layer, which these tools list, move, turn, resize,
4
4
  * recolour, reorder and delete, find by point (`sticker_at`), and places new ones exactly (`add_stickers`). Each change is one undo step and redraws the layer.
5
5
  */
@@ -2,8 +2,13 @@
2
2
  * Strokes as data. Every committed stroke is recorded with the brush, colour, raw pen samples,
3
3
  * jitter seed and symmetry copies it was painted with, so it replays to the same pixels, and can
4
4
  * be replayed moved, scaled, rotated, recoloured or with another brush (`replay_strokes`).
5
+ *
6
+ * In memory a stroke keeps its brush as the engine painted it. Saved, the brush is a facet brush
7
+ * (`strokeToJSON`, documents of version 2); strokes saved with the flat engine brush (version 1)
8
+ * still read, and both replay to the same pixels.
5
9
  */
6
10
  import { type PaintBrush } from './brushes.cjs';
11
+ import type { Brush } from '../brushes/types.cjs';
7
12
  import type { PenPoint, StrokeCopy } from './engine.cjs';
8
13
  import type { MixerLoad } from '../paint/exchange.cjs';
9
14
  import { type Stencil } from './stencils.cjs';
@@ -46,7 +51,11 @@ export declare function invertAffine(m: Affine): Affine;
46
51
  export declare function mapPenPoint(m: Affine, p: PenPoint): PenPoint;
47
52
  /** An affine copy as the engine's `StrokeCopy`. */
48
53
  export declare function affineCopy(m: Affine): StrokeCopy;
49
- /** A recorded stroke read from JSON, or `null` when it is not one. */
54
+ /** A recorded stroke as saved: plain JSON with its brush as a facet brush. */
55
+ export declare function strokeToJSON(stroke: RecordedStroke): Omit<RecordedStroke, 'brush'> & {
56
+ brush: Brush;
57
+ };
58
+ /** A recorded stroke read from JSON (its brush version 1 or 2), or `null` when it is not one. */
50
59
  export declare function normalizeBrushStroke(raw: unknown): RecordedStroke | null;
51
60
  /** The bounding box of a stroke's points. */
52
61
  export declare function strokeBounds(points: readonly PenPoint[]): {
@@ -2,8 +2,13 @@
2
2
  * Strokes as data. Every committed stroke is recorded with the brush, colour, raw pen samples,
3
3
  * jitter seed and symmetry copies it was painted with, so it replays to the same pixels, and can
4
4
  * be replayed moved, scaled, rotated, recoloured or with another brush (`replay_strokes`).
5
+ *
6
+ * In memory a stroke keeps its brush as the engine painted it. Saved, the brush is a facet brush
7
+ * (`strokeToJSON`, documents of version 2); strokes saved with the flat engine brush (version 1)
8
+ * still read, and both replay to the same pixels.
5
9
  */
6
10
  import { type PaintBrush } from './brushes.js';
11
+ import type { Brush } from '../brushes/types.js';
7
12
  import type { PenPoint, StrokeCopy } from './engine.js';
8
13
  import type { MixerLoad } from '../paint/exchange.js';
9
14
  import { type Stencil } from './stencils.js';
@@ -46,7 +51,11 @@ export declare function invertAffine(m: Affine): Affine;
46
51
  export declare function mapPenPoint(m: Affine, p: PenPoint): PenPoint;
47
52
  /** An affine copy as the engine's `StrokeCopy`. */
48
53
  export declare function affineCopy(m: Affine): StrokeCopy;
49
- /** A recorded stroke read from JSON, or `null` when it is not one. */
54
+ /** A recorded stroke as saved: plain JSON with its brush as a facet brush. */
55
+ export declare function strokeToJSON(stroke: RecordedStroke): Omit<RecordedStroke, 'brush'> & {
56
+ brush: Brush;
57
+ };
58
+ /** A recorded stroke read from JSON (its brush version 1 or 2), or `null` when it is not one. */
50
59
  export declare function normalizeBrushStroke(raw: unknown): RecordedStroke | null;
51
60
  /** The bounding box of a stroke's points. */
52
61
  export declare function strokeBounds(points: readonly PenPoint[]): {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Agent tools for the paint editor (`createPaintEditorToolProvider`). Strict schemas, every
3
3
  * argument described, all four annotations. Each write is one undo step (or editor state, said so)
4
- * and returns what it changed; ids come from the read tools (`list_layers`, `list_brushes`,
4
+ * and returns what it changed; ids come from the read tools (`list_layers`, the brush library's `list_brush_presets`,
5
5
  * `list_papers`, `list_strokes`).
6
6
  */
7
7
  import type { AgentToolProvider } from '../tools/registry.cjs';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Agent tools for the paint editor (`createPaintEditorToolProvider`). Strict schemas, every
3
3
  * argument described, all four annotations. Each write is one undo step (or editor state, said so)
4
- * and returns what it changed; ids come from the read tools (`list_layers`, `list_brushes`,
4
+ * and returns what it changed; ids come from the read tools (`list_layers`, the brush library's `list_brush_presets`,
5
5
  * `list_papers`, `list_strokes`).
6
6
  */
7
7
  import type { AgentToolProvider } from '../tools/registry.js';
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { MixerLoad } from '../paint/exchange.cjs';
12
12
  import { type BlendModeInfo } from '../raster/blend.cjs';
13
- import type { ImpastoTool, PaintBrush, PaintBrushInput, WetMediumKind, WetTool } from './brushes.cjs';
13
+ import type { ImpastoTool, WetMediumKind, WetTool } from './brushes.cjs';
14
14
  import type { PaintEditorController } from './controller.cjs';
15
15
  import type { PenPoint } from './engine.cjs';
16
16
  import { type Point2 } from './guides.cjs';
@@ -133,15 +133,8 @@ export declare function guideOverlay(controller: PaintEditorController, drag?: {
133
133
  to: Point2;
134
134
  kind: GuideOverlayKind;
135
135
  }>;
136
- /** One slider of a panel: a path into the brush (or paper) settings. */
137
- export interface SliderControl {
138
- path: string;
139
- label: string;
140
- min: number;
141
- max: number;
142
- step: number;
143
- }
144
- export declare const BRUSH_SLIDERS: readonly SliderControl[];
136
+ export { BRUSH_SLIDERS, brushEngineControls, multiColorSliders, patchBrushValue, type BrushEngineControls, type SliderControl } from './brush-controls.cjs';
137
+ import type { SliderControl } from './brush-controls.cjs';
145
138
  export declare const PAPER_SLIDERS: readonly SliderControl[];
146
139
  /** Reads `a.b` from an object. */
147
140
  export declare function readPath(obj: unknown, path: string): number;
@@ -174,47 +167,6 @@ export interface BlendMenuGroup {
174
167
  */
175
168
  export declare const BLEND_MENU_GROUPS: readonly BlendMenuGroup[];
176
169
  /** The engine-specific part of the brush panel: which sliders and toggles the active brush has. */
177
- export interface BrushEngineControls {
178
- /** Section heading. */
179
- title: string;
180
- sliders: readonly SliderControl[];
181
- /** Where the RGB / pigment choice is stored, or `null` when the engine has none. */
182
- mixingPath: 'behavior.mixing' | 'blender.mixing' | 'smudge.mixing' | null;
183
- /** The dirty-brush toggle (`behavior.dirty`). */
184
- dirty: boolean;
185
- /** The multi-colour controls (`behavior.multicolor`; the panels show the brush gradient, `brush.paint`, instead). */
186
- multicolor: boolean;
187
- /** Load / Clean buttons and a swatch of what the brush carries. */
188
- load: boolean;
189
- /** The wet tool menu (`wet.tool`, `WET_TOOLS`) and medium menu (`wet.medium`, `WET_MEDIA`). */
190
- wetTool?: boolean;
191
- /** The impasto tool menu (`impasto.tool`, `IMPASTO_TOOLS`). */
192
- impastoTool?: boolean;
193
- /**
194
- * The particle menus: kind and render (`particle.kind`, `particle.render`), ribbon pairs
195
- * (`particle.ribbonPairs`), and the textured-dot stamps, order and rotation
196
- * (`particle.dotTextures` up to `MAX_DOT_TEXTURES` of `STAMP_KINDS`, `dotTextureOrder`, `dotRotation`).
197
- */
198
- particleMenus?: boolean;
199
- /** The sticker picker for the brush's stickers (`sticker.stamps`, refs from the sticker library; `toggleStickerRef`). */
200
- stickerStamps?: boolean;
201
- /** Light paint: the "light follows pressure" toggle (`light.pressure`); the Light slider is in `sliders`. */
202
- light?: boolean;
203
- }
204
- /**
205
- * The controls the active brush's engine adds to the brush panel (`null` for the eraser):
206
- * blender strength and blend distance; smudge strength and length; the Mixer Brush's pick-up,
207
- * mix and load; a dab brush's pick-up and mix ratios. Every engine but the eraser has an RGB /
208
- * pigment choice; dab and mixer brushes also have the dirty and multi-colour controls. Pick-up
209
- * and mix always go through `behavior`, which keeps a Mixer Brush's `mixer` block in sync.
210
- */
211
- export declare function brushEngineControls(brush: Pick<PaintBrush, 'engine'> & {
212
- wet?: PaintBrush['wet'];
213
- }): BrushEngineControls | null;
214
- /** The multi-colour sliders for a mode: softness, and dabs per colour in `dab` mode. */
215
- export declare function multiColorSliders(mode: string): readonly SliderControl[];
216
- /** A brush change setting `path` (e.g. `smudge.mixing`, `behavior.dirty`) to any value, keeping the rest of its block. */
217
- export declare function patchBrushValue(brush: PaintBrush, path: string, value: unknown): PaintBrushInput;
218
170
  /** What the brush carries (picked-up paint, else loaded paint) as #rrggbb, or `null` when it is clean and empty. */
219
171
  export declare function brushLoadColor(load: MixerLoad): string | null;
220
172
  /** Sample sizes (square, in pad pixels) for picking from the mixing pad. */
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { MixerLoad } from '../paint/exchange.js';
12
12
  import { type BlendModeInfo } from '../raster/blend.js';
13
- import type { ImpastoTool, PaintBrush, PaintBrushInput, WetMediumKind, WetTool } from './brushes.js';
13
+ import type { ImpastoTool, WetMediumKind, WetTool } from './brushes.js';
14
14
  import type { PaintEditorController } from './controller.js';
15
15
  import type { PenPoint } from './engine.js';
16
16
  import { type Point2 } from './guides.js';
@@ -133,15 +133,8 @@ export declare function guideOverlay(controller: PaintEditorController, drag?: {
133
133
  to: Point2;
134
134
  kind: GuideOverlayKind;
135
135
  }>;
136
- /** One slider of a panel: a path into the brush (or paper) settings. */
137
- export interface SliderControl {
138
- path: string;
139
- label: string;
140
- min: number;
141
- max: number;
142
- step: number;
143
- }
144
- export declare const BRUSH_SLIDERS: readonly SliderControl[];
136
+ export { BRUSH_SLIDERS, brushEngineControls, multiColorSliders, patchBrushValue, type BrushEngineControls, type SliderControl } from './brush-controls.js';
137
+ import type { SliderControl } from './brush-controls.js';
145
138
  export declare const PAPER_SLIDERS: readonly SliderControl[];
146
139
  /** Reads `a.b` from an object. */
147
140
  export declare function readPath(obj: unknown, path: string): number;
@@ -174,47 +167,6 @@ export interface BlendMenuGroup {
174
167
  */
175
168
  export declare const BLEND_MENU_GROUPS: readonly BlendMenuGroup[];
176
169
  /** The engine-specific part of the brush panel: which sliders and toggles the active brush has. */
177
- export interface BrushEngineControls {
178
- /** Section heading. */
179
- title: string;
180
- sliders: readonly SliderControl[];
181
- /** Where the RGB / pigment choice is stored, or `null` when the engine has none. */
182
- mixingPath: 'behavior.mixing' | 'blender.mixing' | 'smudge.mixing' | null;
183
- /** The dirty-brush toggle (`behavior.dirty`). */
184
- dirty: boolean;
185
- /** The multi-colour controls (`behavior.multicolor`; the panels show the brush gradient, `brush.paint`, instead). */
186
- multicolor: boolean;
187
- /** Load / Clean buttons and a swatch of what the brush carries. */
188
- load: boolean;
189
- /** The wet tool menu (`wet.tool`, `WET_TOOLS`) and medium menu (`wet.medium`, `WET_MEDIA`). */
190
- wetTool?: boolean;
191
- /** The impasto tool menu (`impasto.tool`, `IMPASTO_TOOLS`). */
192
- impastoTool?: boolean;
193
- /**
194
- * The particle menus: kind and render (`particle.kind`, `particle.render`), ribbon pairs
195
- * (`particle.ribbonPairs`), and the textured-dot stamps, order and rotation
196
- * (`particle.dotTextures` up to `MAX_DOT_TEXTURES` of `STAMP_KINDS`, `dotTextureOrder`, `dotRotation`).
197
- */
198
- particleMenus?: boolean;
199
- /** The sticker picker for the brush's stickers (`sticker.stamps`, refs from the sticker library; `toggleStickerRef`). */
200
- stickerStamps?: boolean;
201
- /** Light paint: the "light follows pressure" toggle (`light.pressure`); the Light slider is in `sliders`. */
202
- light?: boolean;
203
- }
204
- /**
205
- * The controls the active brush's engine adds to the brush panel (`null` for the eraser):
206
- * blender strength and blend distance; smudge strength and length; the Mixer Brush's pick-up,
207
- * mix and load; a dab brush's pick-up and mix ratios. Every engine but the eraser has an RGB /
208
- * pigment choice; dab and mixer brushes also have the dirty and multi-colour controls. Pick-up
209
- * and mix always go through `behavior`, which keeps a Mixer Brush's `mixer` block in sync.
210
- */
211
- export declare function brushEngineControls(brush: Pick<PaintBrush, 'engine'> & {
212
- wet?: PaintBrush['wet'];
213
- }): BrushEngineControls | null;
214
- /** The multi-colour sliders for a mode: softness, and dabs per colour in `dab` mode. */
215
- export declare function multiColorSliders(mode: string): readonly SliderControl[];
216
- /** A brush change setting `path` (e.g. `smudge.mixing`, `behavior.dirty`) to any value, keeping the rest of its block. */
217
- export declare function patchBrushValue(brush: PaintBrush, path: string, value: unknown): PaintBrushInput;
218
170
  /** What the brush carries (picked-up paint, else loaded paint) as #rrggbb, or `null` when it is clean and empty. */
219
171
  export declare function brushLoadColor(load: MixerLoad): string | null;
220
172
  /** Sample sizes (square, in pad pixels) for picking from the mixing pad. */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Agent tools for wet media (watercolour and gouache on a simulated wet layer). Wet strokes are
3
- * painted with `paint_stroke` and a wet brush (`list_brushes` category `watercolor` or
3
+ * painted with `paint_stroke` and a wet brush (`list_brush_presets` variety `watercolor` or
4
4
  * `wet-tool`); these tools read and steer the wet paint. Every result says whether paint is still
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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Agent tools for wet media (watercolour and gouache on a simulated wet layer). Wet strokes are
3
- * painted with `paint_stroke` and a wet brush (`list_brushes` category `watercolor` or
3
+ * painted with `paint_stroke` and a wet brush (`list_brush_presets` variety `watercolor` or
4
4
  * `wet-tool`); these tools read and steer the wet paint. Every result says whether paint is still
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.252.0",
3
+ "version": "0.253.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -80,8 +80,10 @@ paints without (warnings), each with a fix; lists and writes use the current hos
80
80
  marks take any sticker ref from `find_stickers`.
81
81
  - **Out-of-range values** are refused by the schema (`tip.size` is 1..2500 px); a stored brush
82
82
  is clamped on import and reported in `problems`.
83
- - **The paint editor's own brush tools** (`set_brush`, `save_brush`, its `export_brushes`)
84
- still work on its brushes; this library's tools are namespaced apart.
83
+ - **The paint editor paints from this library.** Its `set_brush` picks a brush by id here and
84
+ lays facets over it without saving; `create_brush` / `update_brush` store one. A paint host
85
+ registers these tools with its own preview (`createPaintBrushLibraryToolProvider`), so the
86
+ paint editor has no list, save, export, import or thumbnail tools of its own.
85
87
 
86
88
  ## Workflow skills
87
89
 
@@ -94,7 +96,7 @@ paints without (warnings), each with a fix; lists and writes use the current hos
94
96
  | --- | --- |
95
97
  | `list_brush_presets` | Find brushes this host renders: `kind`, `medium`, `variety`, `tool`, `tag`, `text`, `source` (all, builtin, user), `all`, `offset`, `limit`. Returns `brushes` (id, name, builtin, kind, medium, variety or tool, category, engine, tip, size, marks, surface, light) and `total`. Call first. |
96
98
  | `describe_brush` | One brush by `id`: its full facet `brush`, `builtin`, `engine`, `category` and `check` for this host. Unknown ids are refused with the closest ones. |
97
- | `create_brush` | Save a new brush: `id`, `name`, optional `basedOn`, and the facets. Normalised and checked for this host; refused brushes save nothing. Returns `id`, `brush`, `engine`, `problems`, `warnings`. |
99
+ | `create_brush` | Save a new brush: `id`, `name`, optional `basedOn`, and the facets (to keep an editor's current brush, pass the `brush` its `get_brush` returns). Normalised and checked for this host; refused brushes save nothing. Returns `id`, `brush`, `engine`, `problems`, `warnings`. |
98
100
  | `update_brush` | Change a saved brush: facets to merge in, `unset` facets to drop. Refuses built-ins (duplicate first) and changes the host can't render. Returns `brush`, `changed` paths, `problems`, `warnings`. |
99
101
  | `duplicate_brush` | Copy any brush, built-ins included, to a saved brush (`newId` default `<id>-copy`, `name`). Returns the new `id` and `brush`; then `update_brush` it. |
100
102
  | `remove_brush` | Delete a saved brush for good (export it first to keep a copy). Built-ins are refused. Returns `removed`. |
@@ -11,25 +11,27 @@ seed: its grain is a height map (peaks and valleys) that grain-responsive brushe
11
11
  is never part of a layer. Coordinates are document pixels, (0, 0) top-left, y down. See
12
12
  `pixerate-agent-tools` for errors, namespacing and batching.
13
13
 
14
- - **Brushes** are library presets (`list_brushes`): a tip from the shared engine (size,
15
- hardness, spacing, angle, roundness, opacity, flow, blend), a **grain** response, pen dynamics
16
- (pressure, tilt, velocity, jitter), a **rotation** mode (`fixed`, `stroke`, `barrel`) and an
17
- engine: `dab` (most media), `eraser`, `mixer` (the Mixer Brush: a reservoir of paint plus paint
14
+ - **Brushes** come from the shared brush library (`pixerate-brushes`: `list_brush_presets`,
15
+ `create_brush`, `preview_brush`…) and are made of facets: a tip (size, hardness, spacing, angle,
16
+ roundness, opacity, flow, blend), dynamics (pressure, tilt, velocity, jitter, a **rotation**
17
+ mode `fixed`, `stroke` or `barrel`), behaviour (paper **grain** response, pick-up, wet or thick
18
+ surface), marks, light, motion and colour. `set_brush` chooses one and lays facet changes over
19
+ it; `get_brush` reads it with the engine it paints with: `dab` (most media), `eraser`, `mixer` (the Mixer Brush: a reservoir of paint plus paint
18
20
  it picks up), `blender` (mixes the colours under and near the tip) or `smudge` (drags paint
19
21
  along the stroke), `wet` (watercolour, gouache, oil and the wet tools, below), `impasto`
20
22
  (thick paint with relief and material, below), `particle` (procedural particle brushes),
21
23
  `bristle` (a tuft of bristles) or `sticker` (sprays editable stickers).
22
- - **Wet media** (`engine: "wet"`, categories `watercolor` and `wet-tool`): a stroke lays water
24
+ - **Wet media** (engine `wet`: `behaviour.surface` `wet`, and the wet tools; categories `watercolor` and `wet-tool`): a stroke lays water
23
25
  and pigment on the layer's **wet layer**, a fluid simulation that keeps running after the
24
26
  stroke: the water spreads until its edge is pinned by the paper, carries pigment out to the
25
27
  rim as it dries (edge darkening), drops pigment into the paper's valleys (granulation), pushes
26
28
  pigment back where new water flows in (blooms), bleeds between touching wet colours and runs
27
29
  downhill on a tilted canvas. Paint stays wet until it dries by itself (a few thousand steps)
28
30
  or `dry_paint` dries it; a dried wash is fixed, so the next wash **glazes** over it. A wet
29
- brush's `wet.tool` is `paint`, `water`, `dry`, `blow`, `blend`, `smudge` or `blot`; `water`,
30
- `pigment` (concentration), `body` (0 transparent watercolour .. 1 opaque gouache), `strength`
31
- and `rewet` set it up. Every result says whether paint is still wet (`wet` / `stillWet`).
32
- - **Behaviour** (`behavior`, dab and mixer brushes) is how a brush treats paint already on the
31
+ brush paints, or is a tool (`tool` `water`, `dryer`, `blow`, `blot`, `wet-blend`, `wet-drag`);
32
+ `behaviour.wet` `water`, `pigment` (concentration), `body` (0 transparent watercolour .. 1
33
+ opaque gouache) and `rewet`, and `behaviour.strength`, set it up. Every result says whether paint is still wet (`wet` / `stillWet`).
34
+ - **Behaviour** (`behaviour.exchange`, dab and mixer brushes) is how a brush treats paint already on the
33
35
  canvas: a **pick-up ratio** (how much paint each dab lifts and carries), a **mix ratio** (how
34
36
  much of the carried paint goes into what it lays down), **dirty** (keep the carried colour into
35
37
  the next stroke) and **multi-colour** (`tip`: several colours side by side across the tip;
@@ -147,8 +149,12 @@ the pitfalls, and uses the tools below.
147
149
  painted moves relative to anything else). Adding `left` or `top` shifts every coordinate you
148
150
  hold by (+left, +top): re-read `list_layers` or use the returned `rect`. Dry wet paint first.
149
151
  `set_canvas` only crops or grows at the bottom-right.
150
- - **Save brushes you build** (`set_brush` then `save_brush`), and share them with
151
- `export_brushes` / `import_brushes`.
152
+ - **Save brushes you build** in the brush library: `create_brush` with `basedOn` and the facets
153
+ you changed (or `get_brush` then `create_brush` with its `brush`), and share them with
154
+ `export_brushes` / `import_brushes`. A saved brush shows in every editor that shares the
155
+ library; `set_brush` its id to paint with it.
156
+
157
+ Which facet sets which engine setting: [references/brush-facets.md](references/brush-facets.md).
152
158
 
153
159
  ## Wet media
154
160
 
@@ -178,12 +184,12 @@ inner glow, shadows, stroke, bevel, satin and overlays, as in the image editor
178
184
 
179
185
  - **Particle brushes** (category `procedural`; ribbons, flames and bokeh are in `light`):
180
186
  `hair-strands`, `spring-ink`, `orbit-curls`, `flow-fur`, `sparkle-dust`, `bead-trail`. Tune
181
- with `particle`: kind (string, spring, orbit, flow, elastic, fuzzy), render (dot, line,
182
- sequence, ribbon), waves and textured dots; read [references/particle-brushes.md](references/particle-brushes.md).
187
+ with `marks` (kind `particles`): physics (string, spring, orbit, flow, elastic, fuzzy), render
188
+ (dot, line, sequence, ribbon), waves and textured dots (`tip.stamps`); read [references/particle-brushes.md](references/particle-brushes.md).
183
189
  Draw slowly and with varying direction: the particles react to movement.
184
190
  - **Bristle brushes** (category `bristle`): `bristle-round`, `bristle-flat`, `dry-brush`. Every
185
191
  bristle draws its own line: press harder to splay the tuft, tilt to flatten it; soft bristles
186
- bend into curves at turns; a low `bristle.load` runs dry into broken streaks.
192
+ bend into curves at turns; a low `tip.bristle.load` runs dry into broken streaks.
187
193
  - **Sticker spray** (category `sticker`): stickers by ref (`editor:star`, `editor:leaf`… or any
188
194
  from the host's library: `find_stickers`; tintable ones take the paint colour) sprayed onto an
189
195
  **empty layer**, each staying an object:
@@ -289,16 +295,12 @@ is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hos
289
295
 
290
296
  ### Brushes
291
297
 
298
+ Listing, saving, sharing and previewing brushes are the brush library's tools (`pixerate-brushes`), registered by the host next to these.
299
+
292
300
  | Tool | Use |
293
301
  | --- | --- |
294
- | `list_brushes` | Read the library (built-in and saved): id, name, category, engine, size, grain response, rotation, `wetCapable`, `pickup`, `dirty`, `gradient` (has its own gradient paint), `multicolor` mode, `builtin`; plus the active brush id and whether it is edited. |
295
- | `get_brush` | Read every setting of a brush, or of the active brush as edited (no `id`), including `paint` (its own gradient, or null) and `strokePaint` (what its next stroke paints). Read before changing a brush you did not build. |
296
- | `set_brush` | Choose the active brush (`id`) and / or change it: `tip` (size px, hardness, spacing, angle, roundness, opacity, flow, smoothing, pressureSize, pressureOpacity, blend), `grain`, `pressureSmoothing`, `rotation`, `dynamics` (minSize, pressureFlow, tiltSize, tiltRoundness, tiltOpacity, velocitySize, velocityOpacity, jitters, scatter), `engine`, `mixer`, `loadsColor`, `behavior` (pickup, mix, dirty, mixing; multicolor is the older form of `paint`), `paint` (the brush's own gradient, or null for the editor's paint), `blender` (strength, distance, mixing), `smudge` (strength, length, mixing). Editor state. Returns the brush. |
297
- | `save_brush` | Store the active brush in the user library under `id` (lowercase, digits, dashes) and `name`. Built-in ids are refused. |
298
- | `remove_brush` | Delete a user brush. Built-in brushes cannot be removed. |
299
- | `export_brushes` | Get a brush library file (JSON) of `ids`, or of every user brush. |
300
- | `import_brushes` | Add the brushes of a library file to the user library; taken ids are skipped unless `replace`. Returns `added`, `skipped`, `problems`. |
301
- | `render_brush_thumbnail` | See what a brush does: a PNG of a test stroke (S-curve, pressure swelling and fading) on the current paper. Cheaper than painting and undoing. |
302
+ | `get_brush` | Read a brush as facets (`brush`), or the active brush as edited (no `id`), with the `engine` and `category` it paints with, `builtin` (an unchanged preset) and `strokePaint` (what its next stroke paints). Read before changing a brush you did not build. |
303
+ | `set_brush` | Choose the active brush (`id`, from `list_brush_presets`) and / or lay facets over it: `tip`, `marks`, `dynamics`, `behaviour`, `light`, `motion`, `colour` (objects merge, lists replace; marks of another kind replace the marks), `unset` facets to drop. Editor state, not saved: `create_brush` stores a brush. Returns `brush` (facets), `engine` and `problems` (values clamped). The old flat fields (`particle`, `impasto`, `behavior`…) are refused. |
302
304
 
303
305
  ### Colour
304
306
 
@@ -329,10 +331,10 @@ is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hos
329
331
 
330
332
  | Tool | Use |
331
333
  | --- | --- |
332
- | `paint_stroke` | Paint one stroke through `points` (document px; `pressure` 0..1, `t` ms, `tiltX` / `tiltY` or `altitude` / `azimuth`, `twist`) with `brushId` (default the active brush), optional per-stroke `brush` changes, `color` or `paint` (a gradient for this stroke), `layerId`, `seed`, `snapToPerspective`. The symmetry guide adds its copies; a sampling tracing image supplies the colour when `color` is missing. Returns `layerId`, `bounds`, `dabs`, `strokeId` and, for brushes that pick up paint, `mixerLoad` (what the brush carries now). Refused on locked, hidden or group layers, with the fix in the message. |
334
+ | `paint_stroke` | Paint one stroke through `points` (document px; `pressure` 0..1, `t` ms, `tiltX` / `tiltY` or `altitude` / `azimuth`, `twist`) with `brushId` (default the active brush), optional per-stroke `brush` facet changes (as `set_brush` takes), `color` or `paint` (a gradient for this stroke), `layerId`, `seed`, `snapToPerspective`. The symmetry guide adds its copies; a sampling tracing image supplies the colour when `color` is missing. Returns `layerId`, `bounds`, `dabs`, `strokeId` and, for brushes that pick up paint, `mixerLoad` (what the brush carries now). Refused on locked, hidden or group layers, with the fix in the message. |
333
335
  | `list_strokes` | Read the recorded strokes, oldest first (default the last 100): id, layer, brush, colour, point count, seed, bounds, `live`. With `ids`, their points too; with `layerId`, one layer's. With `live: true`, the editable strokes of stroke layers as they are now, bottom first: the ids for the stroke layer tools. |
334
336
  | `stroke_at` | Read the top-most stroke of a stroke layer that painted the pixel at `x`, `y`, or null. Use it to pick one stroke among overlapping ones. |
335
- | `update_stroke` | Change a stroke on a stroke layer: `brushId` (keeps its size), `brush` settings, `color`, `paint` (null drops a gradient), `seed`. Redraws exactly. One undo step. Returns the stroke and the changed `bounds`. |
337
+ | `update_stroke` | Change a stroke on a stroke layer: `brushId` (keeps its size), `brush` facets (as `set_brush` takes), `color`, `paint` (null drops a gradient), `seed`. Redraws exactly. One undo step. Returns the stroke and the changed `bounds`. |
336
338
  | `edit_stroke_points` | Replace a stroke's `points`, or `transform` it (`dx`, `dy`, `scale`, `rotate`, `flipX`, about its centre or `origin`). One undo step. Returns the stroke with its points and the changed `bounds`. |
337
339
  | `reorder_stroke` | Move a stroke to `index` in its stroke layer (0 = bottom). One undo step. Returns the order. |
338
340
  | `remove_strokes` | Delete strokes from stroke layers (their eraser). One undo step. Returns the ids and `bounds`. |
@@ -0,0 +1,30 @@
1
+ # Brush facets in the paint editor
2
+
3
+ `set_brush`, `paint_stroke` `brush` and `update_stroke` `brush` take the brush library's facets
4
+ (see `pixerate-brushes`). `get_brush` returns a brush as facets plus the `engine` it paints with.
5
+ The engine's flat settings (`particle`, `impasto`, `behavior`, `paint`…) are not arguments any
6
+ more; this is where each one went.
7
+
8
+ | Engine setting | Facet |
9
+ | --- | --- |
10
+ | Tip size, hardness, spacing, angle, roundness, opacity, flow, blend | `tip` |
11
+ | Smoothing, pressure, tilt, velocity, jitter, scatter, rotation | `dynamics` |
12
+ | Grain, pick-up and mix (behavior), blender and smudge strength | `behaviour` (`grain`, `exchange`, `strength`, `distance`, `length`, `mixing`) |
13
+ | Mixer Brush reservoir | `behaviour.exchange.reservoir` (`load`, `refill`, `loadsColour`) |
14
+ | Wet engine (medium, water, pigment, body…) | `behaviour.surface` `wet` and `behaviour.wet` |
15
+ | Impasto (thickness, gloss, metallic, rim, clarity…) | `behaviour.surface` `thick` and `behaviour.thick` |
16
+ | Particle settings | `marks` `{ kind: 'particles', physics, render, count… }`; textured dots are `tip.stamps` on a `stamp` tip |
17
+ | Sticker spray | `marks` `{ kind: 'objects', density… }` and `tip.stamps` (sticker refs) |
18
+ | Bristles | `tip.shape` `bristle` and `tip.bristle` |
19
+ | A brush's own gradient (paint) | `colour.default` (a gradient); `unset: ['colour']` drops it |
20
+ | Light | `light` (`amount`, `pressure`) |
21
+
22
+ ## Examples
23
+
24
+ - A softer, bigger airbrush for one stroke: `paint_stroke` `{ brushId: "airbrush", brush: { tip:
25
+ { size: 80, hardness: 0 } }, points }`.
26
+ - A dirty round that picks up more: `set_brush` `{ id: "dirty-round", behaviour: { exchange: {
27
+ pickup: 0.5, mix: 0.6 } } }`.
28
+ - Elastic ribbons with star dots: `set_brush` `{ id: "spring-ink", tip: { shape: "stamp", stamps:
29
+ ["editor:star"] }, marks: { kind: "particles", physics: "elastic", render: "ribbon" } }`.
30
+ - A brush's own gradient off again: `set_brush` `{ unset: ["colour"] }`.
@@ -4,7 +4,7 @@ Paint that changes colour as the stroke goes: give a gradient where a colour wou
4
4
 
5
5
  1. **`set_paint`** with `{ type: "gradient", stops, map }` makes it the paint for every following
6
6
  stroke (`set_color` goes back to one colour); `paint_stroke` `paint` overrides it for one stroke;
7
- a brush can carry its own (`set_brush` `paint`, presets `gradient-round`, `pressure-ombre`,
7
+ a brush can carry its own (`set_brush` `colour.default`, presets `gradient-round`, `pressure-ombre`,
8
8
  `rainbow-marker`, `gradient-wash`).
9
9
  2. **Choose the mapping by what the stroke should do**: `distance` (default, 300 px then
10
10
  `mirror` back) for "drag it out" colour; `pressure` for light-to-heavy shading in one stroke;
@@ -18,8 +18,8 @@ particle ones are tuned in [particle-brushes.md](particle-brushes.md)).
18
18
  `strength` 0..2 (default 1). It is not an undo step and repaints nothing; it shows the
19
19
  painting as committed, so a stroke's spill appears when the stroke ends. It spills onto
20
20
  everything below, paper included, so it reads best on dark paper (`set_paper` colour).
21
- - **Glassy beads** (impasto): `impasto.rim` makes a bead's light stronger at its edges, where its
22
- surface turns away (backlit glass); `impasto.clarity` lets the middle of a bead show the layers
21
+ - **Glassy beads** (impasto): `behaviour.thick.rim` (engine `impasto.rim`) makes a bead's light stronger at its edges, where its
22
+ surface turns away (backlit glass); `behaviour.thick.clarity` lets the middle of a bead show the layers
23
23
  below while its edges stay solid. `glow-gel` uses both: paint loops with a distance-mapped
24
24
  gradient (`set_paint`) for glowing glass tubes.
25
25
  - **Crossings.** Beads on one layer fuse where they cross (their heights combine by maximum), so
@@ -11,7 +11,7 @@
11
11
  (azimuth, elevation, relief). Tools: `impasto-brush` (bristle ridges), `paint-tube` (blobs),
12
12
  `palette-knife` (scrapes the relief flat and smears the paint), `paint-roller` (flat stippled
13
13
  band), `gloop-pen` (glossy bead), `metallic-paint` (highlights in the paint's colour).
14
- - **Loading and thinner**: `wet.load` / `impasto.load` below 1 make the brush run out along a
14
+ - **Loading and thinner**: `behaviour.wet.load` / `behaviour.thick.load` below 1 make the brush run out along a
15
15
  long stroke (dry-brush ends); `thinner` makes paint flatter and more transparent.
16
16
  - **Light** is a view of the relief, not paint: move it (`set_lighting`) to read the texture
17
17
  (a low elevation, 15..30°, shows brushwork best) and pick the final light before exporting.
@@ -1,12 +1,15 @@
1
1
  # Particle brushes
2
2
 
3
- Particle brushes (`engine: 'particle'`) make marks with a small simulation driven by the pen. They
3
+ Particle brushes (`marks` kind `particles`; engine `particle`) make marks with a small simulation driven by the pen. They
4
4
  are seeded and stepped by distance, so the same points and `seed` paint the same pixels and
5
5
  replays are exact. Their marks are ordinary coverage: opacity, blend modes, locks, stencils,
6
6
  symmetry, gradient paint, effect paint and light paint all work as for a dab brush. Change them
7
- with `set_brush` `particle` (missing fields keep their values); `get_brush` returns them all.
7
+ with `set_brush` `marks` `{ kind: 'particles', … }` (missing fields keep their values; the
8
+ engine's `particle.kind` is `marks.physics`, its textured dots `tip.stamps` on a `stamp` tip,
9
+ `dotTextureOrder` / `dotRotation` are `marks.stampOrder` / `marks.stampRotation`, `waveSpeed` is
10
+ `motion.waveSpeed`); `get_brush` returns them all.
8
11
 
9
- ## Kinds (`particle.kind`)
12
+ ## Kinds (`marks.physics`)
10
13
 
11
14
  | Kind | Motion | Good for |
12
15
  | --- | --- | --- |
@@ -21,7 +24,7 @@ with `set_brush` `particle` (missing fields keep their values); `get_brush` retu
21
24
  `elastic` and `fuzzy` damp their strands enough to stay together at any setting; a `string` with
22
25
  low damping and high stiffness can still swing wildly, which is part of its look.
23
26
 
24
- ## Renders (`particle.render`)
27
+ ## Renders (`marks.render`)
25
28
 
26
29
  - `dot`: a mark at every step; `line`: each particle's path as a line; `sequence`: beads spaced
27
30
  by `interval` along each path.
@@ -59,7 +62,7 @@ light paint; see [light-paint.md](light-paint.md) for spill and the light-painti
59
62
 
60
63
  ## Best practice
61
64
 
62
- - Check a setting with `render_brush_thumbnail` before painting with it.
65
+ - Check a setting with `preview_brush` (the brush library) before painting with it.
63
66
  - Change one thing at a time: kind for the motion, render for the look, then waves and textures.
64
67
  - Many `flow` particles with long `life` in a `ribbon` are slow (every pair of live particles is
65
68
  a band each step): keep `count` × `life` low (count 3..6, life ≤ 0.4).
@@ -67,7 +67,7 @@ This skill is the method. `pixerate-paint-editor` has the full tool reference an
67
67
  | Blobs of paint straight from the tube | `paint-tube` |
68
68
  | A glossy bead or line | `gloop-pen` |
69
69
  | A glowing, glassy bead (a neon or glass tube) | `glow-gel` (light paint with rim and clarity, on dark paper; one loop per layer where they cross) |
70
- | Dry, broken strokes over texture | `dry-brush`, or a lower `impasto.load` |
70
+ | Dry, broken strokes over texture | `dry-brush`, or a lower `behaviour.thick.load` |
71
71
  | A metallic highlight | `metallic-paint` |
72
72
  | Paint that stays exactly as laid | `set_instant_dry` on |
73
73
 
@@ -19,7 +19,7 @@ none, it is the editor's own stamps (`editorStickers()`: `editor:dot`, `editor:s
19
19
  2. **Check.** `describe_sticker` when the choice matters: its licence (and the attribution line
20
20
  it asks for), dominant colours, variants and outline.
21
21
  3. **Use.** Pass refs to the editor that places them: in the paint editor, a sticker brush
22
- (`set_brush` `sticker.stamps`, then `paint_stroke`) sprays them and `add_stickers` places
22
+ (`set_brush` `tip.stamps`, then `paint_stroke`) sprays them and `add_stickers` places
23
23
  them exactly. In the vector, image and canvas editors, `insert_sticker` puts one in (`x`,
24
24
  `y` centre, `width`): vector, a group of editable paths; image, a pixel layer; canvas, a
25
25
  shape node from its outline in one colour. A provider's art loads before it is drawn.
@@ -300,7 +300,7 @@ and every write tool is refused with the reason.
300
300
 
301
301
  | Tool | Use it to |
302
302
  | --- | --- |
303
- | `search_effects` | Find effects: words in `query`, filtered by `kind`, `category`, `use` (filter, overlay for clips; transition; generator, title) or `accepts: ["path"]` (effects that draw along a path you give). Returns ids, names, uses, whether they are transparent or loop, and their length. Camera moves (`kind` camera) are listed but go on clips with `set_clip_camera_move`, not `apply_effect`. Call `describe_effect` next. |
303
+ | `search_effects` | Find effects: words in `query`, filtered by `kind`, `category`, `use` (filter, overlay for clips; transition; generator, title) or `accepts: ["path"]` (effects that draw along a path you give). Returns ids, names, uses, whether they are transparent or loop, and their length. Camera moves (`kind` camera) are listed but go on clips with `set_clip_camera_move`, not `apply_effect`. Timing effects (`kind` timing: speed changes, reverses, freezes, echoes) are listed but cannot be applied to clips yet (`appliesHere` false). Call `describe_effect` next. |
304
304
  | `describe_effect` | Read one effect's `controls` (a strict JSON Schema: names, types, ranges, units), `defaults`, `presets` and `inputs`, and whether it `appliesHere` (as a clip effect). Use the names and ranges for values. |
305
305
  | `list_applied_effects` | See the effects in the project per clip, in order: clip effects (`fx1`...), an effect clip's own effect (`generator`) and an effect transition (`transition`), with their saved values and `available` (false when the provider lacks it: drawn as a placeholder). |
306
306
  | `apply_effect` | Add a filter or overlay effect to a clip's stack (`target` clip id, `effect`, `values`, `index`, `enabled`, `opacity`). Effects run in order on the clip's picture before its grade and placement. Returns the effect's new id and the clip's effects. |