@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.
- package/dist/agent/index.cjs +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.js +3 -3
- package/dist/brushes/index.cjs +31 -29
- package/dist/brushes/index.d.cts +1 -1
- package/dist/brushes/index.d.ts +1 -1
- package/dist/brushes/index.js +27 -25
- package/dist/brushes/library.d.cts +17 -0
- package/dist/brushes/library.d.ts +17 -0
- package/dist/{brushstrokeRender-5PYSKZIJ.js → brushstrokeRender-HCOQUSWW.js} +53 -18
- package/dist/{brushstrokeRender-7FLLSQEC.cjs → brushstrokeRender-ZZOXI5O5.cjs} +55 -20
- package/dist/canvas/brushTypes.d.cts +22 -3
- package/dist/canvas/brushTypes.d.ts +22 -3
- package/dist/canvas/brushstroke.d.cts +34 -12
- package/dist/canvas/brushstroke.d.ts +34 -12
- package/dist/canvas/brushstrokeRender.d.cts +14 -8
- package/dist/canvas/brushstrokeRender.d.ts +14 -8
- package/dist/canvas/drawController.d.cts +11 -3
- package/dist/canvas/drawController.d.ts +11 -3
- package/dist/canvas/freehand.d.cts +6 -1
- package/dist/canvas/freehand.d.ts +6 -1
- package/dist/canvas/index.cjs +114 -102
- package/dist/canvas/index.js +16 -4
- package/dist/{chunk-XVIK73LS.js → chunk-5P4JEOIV.js} +4 -4
- package/dist/{chunk-JPEHXIB5.cjs → chunk-5YWWQ36H.cjs} +66 -21
- package/dist/{chunk-CHAWTXBE.cjs → chunk-6WCQ35GI.cjs} +4 -4
- package/dist/{chunk-GYMMNCFT.js → chunk-AF23QK5F.js} +56 -11
- package/dist/{chunk-JFHBXNR2.cjs → chunk-B7V34GIH.cjs} +86 -86
- package/dist/{chunk-QC3OXRE4.js → chunk-DMTEQ6SH.js} +1 -1
- package/dist/{chunk-TLOGFIIV.cjs → chunk-GCTUOTCW.cjs} +35 -35
- package/dist/{chunk-34KLWUH4.cjs → chunk-HIMGBZAM.cjs} +1133 -597
- package/dist/{chunk-7P6VF3FG.js → chunk-HYWRU5VX.js} +1 -1
- package/dist/{chunk-QPHNVZHO.js → chunk-JVRLGV3C.js} +1118 -582
- package/dist/{chunk-LFMKWGFL.cjs → chunk-LRNUZKWV.cjs} +22 -8
- package/dist/chunk-PGPFWC35.cjs +517 -0
- package/dist/{chunk-QLSGTCYT.cjs → chunk-TBAVCLEQ.cjs} +23 -23
- package/dist/{chunk-2ZJ4DT3M.js → chunk-VUKUTT2W.js} +1 -1
- package/dist/{chunk-5JNA6LLY.js → chunk-WYGSR3IJ.js} +22 -8
- package/dist/chunk-YHUMGBZD.js +517 -0
- package/dist/commands/builtins.d.cts +1 -1
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.js +1 -1
- package/dist/image-editor/brush-presets.d.cts +32 -0
- package/dist/image-editor/brush-presets.d.ts +32 -0
- package/dist/image-editor/controller.d.cts +15 -0
- package/dist/image-editor/controller.d.ts +15 -0
- package/dist/image-editor/index.cjs +848 -775
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +81 -8
- package/dist/image-editor/types.d.cts +7 -0
- package/dist/image-editor/types.d.ts +7 -0
- package/dist/paint-editor/collaboration-tools.d.cts +19 -0
- package/dist/paint-editor/collaboration-tools.d.ts +19 -0
- package/dist/paint-editor/collaboration.d.cts +180 -0
- package/dist/paint-editor/collaboration.d.ts +180 -0
- package/dist/paint-editor/controller.d.cts +54 -0
- package/dist/paint-editor/controller.d.ts +54 -0
- package/dist/paint-editor/index.cjs +816 -283
- package/dist/paint-editor/index.d.cts +2 -0
- package/dist/paint-editor/index.d.ts +2 -0
- package/dist/paint-editor/index.js +567 -34
- package/dist/vector/brushstroke.d.cts +21 -5
- package/dist/vector/brushstroke.d.ts +21 -5
- package/dist/vector/controller.d.cts +14 -1
- package/dist/vector/controller.d.ts +14 -1
- package/dist/vector/index.cjs +1220 -1199
- package/dist/vector/index.js +34 -13
- package/dist/vector/types.d.cts +7 -0
- package/dist/vector/types.d.ts +7 -0
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +9 -3
- package/skills/pixerate-canvas/SKILL.md +10 -5
- package/skills/pixerate-image-editor/SKILL.md +2 -1
- package/skills/pixerate-paint-editor/SKILL.md +31 -46
- package/skills/pixerate-paint-editor/references/painting-alongside.md +19 -0
- package/skills/pixerate-vector/SKILL.md +1 -1
- package/dist/chunk-HS6QDWDF.cjs +0 -1029
- package/dist/chunk-MTKY6K3Q.js +0 -1029
package/dist/vector/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
traceField,
|
|
27
27
|
traceVectorImage,
|
|
28
28
|
vectorTraceContours
|
|
29
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
530
|
-
import
|
|
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 =
|
|
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`
|
|
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
|
|
8174
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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,
|
package/dist/vector/types.d.cts
CHANGED
|
@@ -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. */
|
package/dist/vector/types.d.ts
CHANGED
|
@@ -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
|
@@ -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
|
|
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.**
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
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. |
|
|
@@ -101,58 +101,32 @@ the pitfalls, and uses the tools below.
|
|
|
101
101
|
|
|
102
102
|
## Best practices
|
|
103
103
|
|
|
104
|
-
- **Big to small, dark to light (or light to dark), one decision per layer.** Name layers by
|
|
105
|
-
|
|
106
|
-
- **
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- **
|
|
110
|
-
ink or brush pen: velocity dynamics thin fast strokes. Without `t` there is no velocity effect.
|
|
111
|
-
- **Tilt** (`altitude` near 0.3..0.6 radians with an `azimuth`) turns a pencil into broad side
|
|
112
|
-
shading; upright points draw a fine line.
|
|
113
|
-
- **Space points** about a third of the brush size apart or closer; the brush fills in dabs
|
|
114
|
-
between them. Long smooth curves need more points than straight lines.
|
|
115
|
-
- **Use `rotation: "stroke"`** for flat tips (pastel, charcoal, chisel marker) so the mark turns
|
|
116
|
-
with the path; `fixed` for calligraphic nibs.
|
|
104
|
+
- **Big to small, dark to light (or light to dark), one decision per layer.** Name layers by purpose ("Underpainting", "Shadows", "Line"). Lower a layer's `opacity` instead of repainting.
|
|
105
|
+
- **Pressure is your value control** on grainy media: points with `pressure` 0.2..0.4 give texture-catching, broken marks; 0.8..1 fills the grain. Vary pressure along a stroke (swell in the middle, lighten at the ends) for natural marks.
|
|
106
|
+
- **Give points a time `t`** (milliseconds, about 8..16 ms apart for natural speed) when using an ink or brush pen: velocity dynamics thin fast strokes. Without `t` there is no velocity effect.
|
|
107
|
+
- **Tilt** (`altitude` near 0.3..0.6 radians with an `azimuth`) turns a pencil into broad side shading; upright points draw a fine line.
|
|
108
|
+
- **Space points** about a third of the brush size apart or closer; the brush fills in dabs between them. Long smooth curves need more points than straight lines.
|
|
109
|
+
- **Use `rotation: "stroke"`** for flat tips (pastel, charcoal, chisel marker) so the mark turns with the path; `fixed` for calligraphic nibs.
|
|
117
110
|
- **Blending tools**:
|
|
118
|
-
- `blender`: `strength` 0.3..0.6 for soft transitions, `distance` 0.2..0.5 (fraction of the
|
|
119
|
-
|
|
120
|
-
blends like a soft blur. Work along an edge in short strokes rather than over everything.
|
|
121
|
-
- `smudge`: `length` near 0.9 drags paint far (long streaks), 0.5 short pushes; `strength`
|
|
122
|
-
0.5..0.8. `mixing: "pigment"` (the `wet-smudge` preset) smears colours into each other like
|
|
123
|
-
wet paint.
|
|
111
|
+
- `blender`: `strength` 0.3..0.6 for soft transitions, `distance` 0.2..0.5 (fraction of the tip) for how far colours travel. Keep `mixing: "pigment"` for painterly blends; `rgb` blends like a soft blur. Work along an edge in short strokes rather than over everything.
|
|
112
|
+
- `smudge`: `length` near 0.9 drags paint far (long streaks), 0.5 short pushes; `strength` 0.5..0.8. `mixing: "pigment"` (the `wet-smudge` preset) smears colours into each other like wet paint.
|
|
124
113
|
- Mixer Brush: blend with flow 0.3..0.6; at full flow it paints only its own colour.
|
|
125
|
-
- **Pick-up and mix ratios**: `pickup` 0.3..0.7 with `mix` 0.4..0.7 gives a brush that drags
|
|
126
|
-
the colours it crosses into its own; `dirty: true` keeps them for the next stroke (wipe with
|
|
114
|
+
- **Pick-up and mix ratios**: `pickup` 0.3..0.7 with `mix` 0.4..0.7 gives a brush that drags the colours it crosses into its own; `dirty: true` keeps them for the next stroke (wipe with
|
|
127
115
|
`clean_mixer_brush`). Keep `pickup` 0 for clean, predictable colour.
|
|
128
|
-
- **Multi-colour**: give 2..8 `colors` (or none for the paint and secondary colours). `tip` mode
|
|
129
|
-
with a flat tip and `rotation: "stroke"` paints ribbons; `softness` 0.3..0.7 keeps the stripes
|
|
130
|
-
readable. `dab` mode with `order: "sequence"` and `length` 5..20 walks through a palette;
|
|
116
|
+
- **Multi-colour**: give 2..8 `colors` (or none for the paint and secondary colours). `tip` mode with a flat tip and `rotation: "stroke"` paints ribbons; `softness` 0.3..0.7 keeps the stripes readable. `dab` mode with `order: "sequence"` and `length` 5..20 walks through a palette;
|
|
131
117
|
`random` for foliage and confetti.
|
|
132
|
-
- **Pigment for paint, multiply for markers**: tip `blend: "pigment"` mixes subtractively on the
|
|
133
|
-
canvas; `multiply` darkens where marker strokes overlap. Pigment layers are written as Normal
|
|
134
|
-
to PSD (the export warns).
|
|
118
|
+
- **Pigment for paint, multiply for markers**: tip `blend: "pigment"` mixes subtractively on the canvas; `multiply` darkens where marker strokes overlap. Pigment layers are written as Normal to PSD (the export warns).
|
|
135
119
|
- **Painterly layer modes do the glazing for you** (`update_layer` `blend`, or a brush tip):
|
|
136
|
-
- `watercolor`: the layer is a transparent wash. Washes multiply wavelength by wavelength, so
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- `shadow`: shades as if light were blocked, coloured by the layer (paint shadows in a cool
|
|
144
|
-
blue-violet), never crushing to black. White does nothing.
|
|
145
|
-
|
|
146
|
-
PSD has none of them: `export_psd` writes the nearest standard mode (watercolor and shadow
|
|
147
|
-
as Multiply, tint as Color, highlight as Screen) and warns; the merged image stays exact.
|
|
148
|
-
- **Grow the canvas instead of starting big** with `extend_canvas` (pixels per side; nothing
|
|
149
|
-
painted moves relative to anything else). Adding `left` or `top` shifts every coordinate you
|
|
150
|
-
hold by (+left, +top): re-read `list_layers` or use the returned `rect`. Dry wet paint first.
|
|
120
|
+
- `watercolor`: the layer is a transparent wash. Washes multiply wavelength by wavelength, so each glaze darkens and blue over yellow makes green. White is clear: paint glazes on a layer over the underpainting, at opacity 0.4..0.8.
|
|
121
|
+
- `tint`: recolours what is below (its hue and chroma) and keeps its lightness, gently near white and black. Use it to warm or cool a whole passage.
|
|
122
|
+
- `highlight`: adds coloured light that rolls off towards white instead of clipping. Use it for glints and glow; black does nothing.
|
|
123
|
+
- `shadow`: shades as if light were blocked, coloured by the layer (paint shadows in a cool blue-violet), never crushing to black. White does nothing.
|
|
124
|
+
|
|
125
|
+
PSD has none of them: `export_psd` writes the nearest standard mode (watercolor and shadow as Multiply, tint as Color, highlight as Screen) and warns; the merged image stays exact.
|
|
126
|
+
- **Grow the canvas instead of starting big** with `extend_canvas` (pixels per side; nothing painted moves relative to anything else). Adding `left` or `top` shifts every coordinate you hold by (+left, +top): re-read `list_layers` or use the returned `rect`. Dry wet paint first.
|
|
151
127
|
`set_canvas` only crops or grows at the bottom-right.
|
|
152
|
-
- **Save brushes you build** in the brush library: `create_brush` with `basedOn` and the facets
|
|
153
|
-
|
|
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.
|
|
128
|
+
- **Save brushes you build** in the brush library: `create_brush` with `basedOn` and the facets you changed (or `get_brush` then `create_brush` with its `brush`), and share them with
|
|
129
|
+
`export_brushes` / `import_brushes`. A saved brush shows in every editor that shares the library; `set_brush` its id to paint with it.
|
|
156
130
|
|
|
157
131
|
Which facet sets which engine setting: [references/brush-facets.md](references/brush-facets.md).
|
|
158
132
|
|
|
@@ -272,6 +246,17 @@ is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hos
|
|
|
272
246
|
| `get_paper` | Read every setting of the current paper (pattern, seed, scale, tiling, depth, shading, dry and wet tuning, and `metallic`, `backlight`, `backlightColor` when set). |
|
|
273
247
|
| `set_paper` | Change the paper: `preset`, then any of `color`, `depth` (0..1), `scale` (0.25..8), `seed` (another sheet), `pattern`, `tiling`, `shading`, `dry` / `wet` tuning (`contrast` 0..3, `invert`), `metallic`, `backlight` (0..1, light from behind: glazes glow, opaque paint blocks; preset `stained-glass`) and `backlightColor`. Affects new strokes' grain and how the paper shows; strokes already painted keep their pixels. Returns the paper. |
|
|
274
248
|
|
|
249
|
+
### Suggesting paint
|
|
250
|
+
|
|
251
|
+
Contributing while the user paints: suggest, don't paint. Read [references/painting-alongside.md](references/painting-alongside.md).
|
|
252
|
+
|
|
253
|
+
| Tool | Use |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `propose_paint` | Suggest paint as a ghost the user accepts or dismisses: `title`, `rationale`, `ops` (`stroke` with points and a dry brush, `wash` over its layer, `echo` of stroke ids moved / turned / scaled / repeated) and where its new layer goes (`place` top, bottom or above `layerId`). Returns the proposal id, the ghost's `bounds` and the strokes it echoes. Use it for contributions the user didn't ask for. |
|
|
256
|
+
| `list_paint_proposals` | Read the pending paint proposals: id, title, rationale, who proposed it, op kinds, placement, bounds, the strokes it echoes and any since removed (`missing`), and whether accepting is allowed. Call before accepting or rejecting. |
|
|
257
|
+
| `accept_paint_proposal` | Paint a pending proposal on a new layer as one undo step recorded as the proposer's (`proposalId`). Only when the user said yes. Returns the layer id, stroke ids and undo step name. Refuses when a stroke it echoes is gone. |
|
|
258
|
+
| `reject_paint_proposal` | Dismiss a pending proposal (`proposalId`); nothing changes. Use it to withdraw a suggestion that no longer fits. Returns the id and title. |
|
|
259
|
+
|
|
275
260
|
### Layers
|
|
276
261
|
|
|
277
262
|
| Tool | Use |
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Painting alongside the user
|
|
2
|
+
|
|
3
|
+
When the user is painting and you contribute on your own initiative, **suggest, don't paint**:
|
|
4
|
+
`propose_paint` shows your idea as a ghost over the canvas, and the user accepts or dismisses it.
|
|
5
|
+
Accepted, it lands on a new layer of its own as one undo step recorded as yours, and the user's
|
|
6
|
+
active layer stays theirs. Paint directly only when asked to.
|
|
7
|
+
|
|
8
|
+
- **Play to what agents do well**: a `wash` (a warm ground with `place: "bottom"`, a glaze over a
|
|
9
|
+
passage), an `echo` of the user's own strokes (grass along the horizon, rain, hatching, a
|
|
10
|
+
repeated motif: `list_strokes` for the ids, then `dx` / `dy` / `rotate` / `scale` / `repeat`),
|
|
11
|
+
or a few deliberate `stroke`s. Not a stroke-by-stroke rendering of a subject.
|
|
12
|
+
- **Small and complementary.** One idea per proposal, with a `title` that says what it adds and a
|
|
13
|
+
`rationale` that says why it fits what the user just painted.
|
|
14
|
+
- **Dry brushes only** (dab, particle, bristle or mixer without pickup). Suggestions are previewed
|
|
15
|
+
and painted on their own layer, which wet paint, thick paint and pickup can't do.
|
|
16
|
+
- **Withdraw what no longer fits** with `reject_paint_proposal`; an echo whose strokes the user
|
|
17
|
+
removed can't be accepted (`missing` in `list_paint_proposals`).
|
|
18
|
+
|
|
19
|
+
The tools are `propose_paint`, `list_paint_proposals`, `accept_paint_proposal` and `reject_paint_proposal` (see SKILL.md, Suggesting paint).
|
|
@@ -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
|
|
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
|
|