@pixerate/editor 0.251.0 → 0.252.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/brushes/index.cjs +687 -6
- package/dist/brushes/index.d.cts +6 -1
- package/dist/brushes/index.d.ts +6 -1
- package/dist/brushes/index.js +684 -3
- package/dist/brushes/library.d.cts +104 -0
- package/dist/brushes/library.d.ts +104 -0
- package/dist/brushes/profiles.d.cts +85 -0
- package/dist/brushes/profiles.d.ts +85 -0
- package/dist/brushes/tools.d.cts +37 -0
- package/dist/brushes/tools.d.ts +37 -0
- package/dist/tools/skill-evals.d.cts +8 -1
- package/dist/tools/skill-evals.d.ts +8 -1
- package/package.json +1 -1
- package/skills/README.md +2 -0
- package/skills/pixerate-brushes/SKILL.md +104 -0
- package/skills/pixerate-brushes-design/SKILL.md +88 -0
- package/skills/pixerate-brushes-design/evals/canvas-chalk.json +15 -0
- package/skills/pixerate-brushes-design/evals/pink-highlighter.json +15 -0
- package/skills/pixerate-brushes-design/evals/port-to-vector.json +17 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brush library: the built-in presets plus the user's brushes, kept on any `StorageAdapter`
|
|
3
|
+
* (`createMemoryStorage`, `createLocalStorageAdapter`, `createIndexedDbAdapter`,
|
|
4
|
+
* `createOpfsAdapter`) or, without one, in memory. One library shared by every editor makes a
|
|
5
|
+
* brush saved in one show up in all of them.
|
|
6
|
+
*
|
|
7
|
+
* The user's brushes are stored as one `pixerate-brushes` version 2 file under one key. Library
|
|
8
|
+
* files of version 1 (the paint editor's flat brushes) still import: each brush is migrated.
|
|
9
|
+
*/
|
|
10
|
+
import type { StorageAdapter } from '../persistence/storage.cjs';
|
|
11
|
+
import { type BrushHostProfile } from './profiles.cjs';
|
|
12
|
+
import type { Brush, BrushKind, BrushMedium, BrushTool } from './types.cjs';
|
|
13
|
+
/** The JSON file a brush library exports to and imports from. */
|
|
14
|
+
export interface BrushLibraryFile {
|
|
15
|
+
format: 'pixerate-brushes';
|
|
16
|
+
version: 2;
|
|
17
|
+
brushes: Brush[];
|
|
18
|
+
}
|
|
19
|
+
export type BrushLibraryErrorCode = 'unknown_brush' | 'builtin' | 'exists' | 'invalid';
|
|
20
|
+
/** A refusal; the message says what to do instead. */
|
|
21
|
+
export declare class BrushLibraryError extends Error {
|
|
22
|
+
readonly code: BrushLibraryErrorCode;
|
|
23
|
+
constructor(code: BrushLibraryErrorCode, message: string);
|
|
24
|
+
}
|
|
25
|
+
export interface BrushLibraryEntry {
|
|
26
|
+
brush: Brush;
|
|
27
|
+
/** A built-in preset: it can be duplicated but not changed or removed. */
|
|
28
|
+
builtin: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface BrushFilter {
|
|
31
|
+
kind?: BrushKind;
|
|
32
|
+
medium?: BrushMedium;
|
|
33
|
+
variety?: string;
|
|
34
|
+
tool?: BrushTool;
|
|
35
|
+
/** Has this tag. */
|
|
36
|
+
tag?: string;
|
|
37
|
+
/** Words in the id, name, description, variety or tags (case-insensitive; every word must match). */
|
|
38
|
+
text?: string;
|
|
39
|
+
/** Only built-in presets or only the user's brushes. */
|
|
40
|
+
source?: 'builtin' | 'user';
|
|
41
|
+
/** Only brushes this host can render (`checkBrush` has no errors). */
|
|
42
|
+
profile?: BrushHostProfile;
|
|
43
|
+
}
|
|
44
|
+
export interface SaveBrushResult {
|
|
45
|
+
/** The brush as stored (normalised). */
|
|
46
|
+
brush: Brush;
|
|
47
|
+
/** What normalising changed. */
|
|
48
|
+
problems: string[];
|
|
49
|
+
/** It replaced a user brush of the same id. */
|
|
50
|
+
replaced: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface ImportBrushesResult {
|
|
53
|
+
added: string[];
|
|
54
|
+
replaced: string[];
|
|
55
|
+
skipped: string[];
|
|
56
|
+
problems: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface BrushLibraryOptions {
|
|
59
|
+
/** Where the user's brushes are kept (default: memory only, gone on reload). */
|
|
60
|
+
storage?: Pick<StorageAdapter, 'get' | 'set'>;
|
|
61
|
+
/** Storage key prefix (default `pixerate.brushes`). */
|
|
62
|
+
namespace?: string;
|
|
63
|
+
/** The built-in presets (default `BRUSH_PRESETS`). */
|
|
64
|
+
presets?: readonly Brush[];
|
|
65
|
+
}
|
|
66
|
+
export interface BrushLibrary {
|
|
67
|
+
/** Built-in presets first (in library order), then the user's brushes (in the order saved). */
|
|
68
|
+
list(filter?: BrushFilter): Promise<BrushLibraryEntry[]>;
|
|
69
|
+
get(id: string): Promise<BrushLibraryEntry | null>;
|
|
70
|
+
isBuiltin(id: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Stores a user brush (normalised first). Refuses a built-in id and, without `overwrite`, an
|
|
73
|
+
* id already taken.
|
|
74
|
+
*/
|
|
75
|
+
save(brush: unknown, options?: {
|
|
76
|
+
overwrite?: boolean;
|
|
77
|
+
}): Promise<SaveBrushResult>;
|
|
78
|
+
/** Removes a user brush; refuses a built-in one. False when there was no such brush. */
|
|
79
|
+
remove(id: string): Promise<boolean>;
|
|
80
|
+
/** Copies any brush (built-ins included) to a new user brush: `id` default `<id>-copy`, `-copy-2`…; `name` default `<name> copy`. */
|
|
81
|
+
duplicate(id: string, options?: {
|
|
82
|
+
id?: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
}): Promise<Brush>;
|
|
85
|
+
/** A library file of the user's brushes, or of the given ids (built-ins included). */
|
|
86
|
+
exportFile(ids?: readonly string[]): Promise<BrushLibraryFile>;
|
|
87
|
+
/** Adds the brushes of a library file (version 1 or 2, or a bare array). Built-in ids are skipped; taken ids too, unless `overwrite`. */
|
|
88
|
+
importFile(raw: unknown, options?: {
|
|
89
|
+
overwrite?: boolean;
|
|
90
|
+
}): Promise<ImportBrushesResult>;
|
|
91
|
+
/** Called after every change. Returns an unsubscribe function. */
|
|
92
|
+
subscribe(listener: () => void): () => void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Brushes read from a library file: `{ format: 'pixerate-brushes', version: 1 | 2, brushes }` or
|
|
96
|
+
* a bare array. Version 1 brushes are migrated (`migrateBrushV1`); every brush is normalised.
|
|
97
|
+
* Brushes without a valid id or name are skipped, and everything fixed or skipped is reported
|
|
98
|
+
* in `problems`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parseBrushLibrary(raw: unknown, problems?: string[]): Brush[];
|
|
101
|
+
/** A brush library over `options.storage` (see the module comment). */
|
|
102
|
+
export declare function createBrushLibrary(options?: BrushLibraryOptions): BrushLibrary;
|
|
103
|
+
/** "Closest: a, b. " for an unknown id (shared prefix or substring), or "" when nothing is near. */
|
|
104
|
+
export declare function closest(id: string, ids: readonly string[], max?: number): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brush library: the built-in presets plus the user's brushes, kept on any `StorageAdapter`
|
|
3
|
+
* (`createMemoryStorage`, `createLocalStorageAdapter`, `createIndexedDbAdapter`,
|
|
4
|
+
* `createOpfsAdapter`) or, without one, in memory. One library shared by every editor makes a
|
|
5
|
+
* brush saved in one show up in all of them.
|
|
6
|
+
*
|
|
7
|
+
* The user's brushes are stored as one `pixerate-brushes` version 2 file under one key. Library
|
|
8
|
+
* files of version 1 (the paint editor's flat brushes) still import: each brush is migrated.
|
|
9
|
+
*/
|
|
10
|
+
import type { StorageAdapter } from '../persistence/storage.js';
|
|
11
|
+
import { type BrushHostProfile } from './profiles.js';
|
|
12
|
+
import type { Brush, BrushKind, BrushMedium, BrushTool } from './types.js';
|
|
13
|
+
/** The JSON file a brush library exports to and imports from. */
|
|
14
|
+
export interface BrushLibraryFile {
|
|
15
|
+
format: 'pixerate-brushes';
|
|
16
|
+
version: 2;
|
|
17
|
+
brushes: Brush[];
|
|
18
|
+
}
|
|
19
|
+
export type BrushLibraryErrorCode = 'unknown_brush' | 'builtin' | 'exists' | 'invalid';
|
|
20
|
+
/** A refusal; the message says what to do instead. */
|
|
21
|
+
export declare class BrushLibraryError extends Error {
|
|
22
|
+
readonly code: BrushLibraryErrorCode;
|
|
23
|
+
constructor(code: BrushLibraryErrorCode, message: string);
|
|
24
|
+
}
|
|
25
|
+
export interface BrushLibraryEntry {
|
|
26
|
+
brush: Brush;
|
|
27
|
+
/** A built-in preset: it can be duplicated but not changed or removed. */
|
|
28
|
+
builtin: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface BrushFilter {
|
|
31
|
+
kind?: BrushKind;
|
|
32
|
+
medium?: BrushMedium;
|
|
33
|
+
variety?: string;
|
|
34
|
+
tool?: BrushTool;
|
|
35
|
+
/** Has this tag. */
|
|
36
|
+
tag?: string;
|
|
37
|
+
/** Words in the id, name, description, variety or tags (case-insensitive; every word must match). */
|
|
38
|
+
text?: string;
|
|
39
|
+
/** Only built-in presets or only the user's brushes. */
|
|
40
|
+
source?: 'builtin' | 'user';
|
|
41
|
+
/** Only brushes this host can render (`checkBrush` has no errors). */
|
|
42
|
+
profile?: BrushHostProfile;
|
|
43
|
+
}
|
|
44
|
+
export interface SaveBrushResult {
|
|
45
|
+
/** The brush as stored (normalised). */
|
|
46
|
+
brush: Brush;
|
|
47
|
+
/** What normalising changed. */
|
|
48
|
+
problems: string[];
|
|
49
|
+
/** It replaced a user brush of the same id. */
|
|
50
|
+
replaced: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface ImportBrushesResult {
|
|
53
|
+
added: string[];
|
|
54
|
+
replaced: string[];
|
|
55
|
+
skipped: string[];
|
|
56
|
+
problems: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface BrushLibraryOptions {
|
|
59
|
+
/** Where the user's brushes are kept (default: memory only, gone on reload). */
|
|
60
|
+
storage?: Pick<StorageAdapter, 'get' | 'set'>;
|
|
61
|
+
/** Storage key prefix (default `pixerate.brushes`). */
|
|
62
|
+
namespace?: string;
|
|
63
|
+
/** The built-in presets (default `BRUSH_PRESETS`). */
|
|
64
|
+
presets?: readonly Brush[];
|
|
65
|
+
}
|
|
66
|
+
export interface BrushLibrary {
|
|
67
|
+
/** Built-in presets first (in library order), then the user's brushes (in the order saved). */
|
|
68
|
+
list(filter?: BrushFilter): Promise<BrushLibraryEntry[]>;
|
|
69
|
+
get(id: string): Promise<BrushLibraryEntry | null>;
|
|
70
|
+
isBuiltin(id: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Stores a user brush (normalised first). Refuses a built-in id and, without `overwrite`, an
|
|
73
|
+
* id already taken.
|
|
74
|
+
*/
|
|
75
|
+
save(brush: unknown, options?: {
|
|
76
|
+
overwrite?: boolean;
|
|
77
|
+
}): Promise<SaveBrushResult>;
|
|
78
|
+
/** Removes a user brush; refuses a built-in one. False when there was no such brush. */
|
|
79
|
+
remove(id: string): Promise<boolean>;
|
|
80
|
+
/** Copies any brush (built-ins included) to a new user brush: `id` default `<id>-copy`, `-copy-2`…; `name` default `<name> copy`. */
|
|
81
|
+
duplicate(id: string, options?: {
|
|
82
|
+
id?: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
}): Promise<Brush>;
|
|
85
|
+
/** A library file of the user's brushes, or of the given ids (built-ins included). */
|
|
86
|
+
exportFile(ids?: readonly string[]): Promise<BrushLibraryFile>;
|
|
87
|
+
/** Adds the brushes of a library file (version 1 or 2, or a bare array). Built-in ids are skipped; taken ids too, unless `overwrite`. */
|
|
88
|
+
importFile(raw: unknown, options?: {
|
|
89
|
+
overwrite?: boolean;
|
|
90
|
+
}): Promise<ImportBrushesResult>;
|
|
91
|
+
/** Called after every change. Returns an unsubscribe function. */
|
|
92
|
+
subscribe(listener: () => void): () => void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Brushes read from a library file: `{ format: 'pixerate-brushes', version: 1 | 2, brushes }` or
|
|
96
|
+
* a bare array. Version 1 brushes are migrated (`migrateBrushV1`); every brush is normalised.
|
|
97
|
+
* Brushes without a valid id or name are skipped, and everything fixed or skipped is reported
|
|
98
|
+
* in `problems`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parseBrushLibrary(raw: unknown, problems?: string[]): Brush[];
|
|
101
|
+
/** A brush library over `options.storage` (see the module comment). */
|
|
102
|
+
export declare function createBrushLibrary(options?: BrushLibraryOptions): BrushLibrary;
|
|
103
|
+
/** "Closest: a, b. " for an unknown id (shared prefix or substring), or "" when nothing is near. */
|
|
104
|
+
export declare function closest(id: string, ids: readonly string[], max?: number): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host capability profiles: what a host (the paint editor, the image editor, brushed canvas and
|
|
3
|
+
* vector strokes, later Swift) can render. `checkBrush` explains each refusal with a fix,
|
|
4
|
+
* `listPresets` filters a library down to what the host can use, and `degrade` strips the facets
|
|
5
|
+
* a host can't render and says what it dropped.
|
|
6
|
+
*
|
|
7
|
+
* A capability is `full` (rendered), `ignored` (the brush is accepted but the host paints without
|
|
8
|
+
* it: a warning) or `none` (the brush is refused: an error).
|
|
9
|
+
*/
|
|
10
|
+
import { type Brush, type BrushKind, type BrushMarksKind, type BrushPaint, type BrushSurface, type BrushTipShape, type BrushTool } from './types.cjs';
|
|
11
|
+
export type BrushSupport = 'full' | 'ignored' | 'none';
|
|
12
|
+
/** What a colour is: a solid colour, a light / dark pair, a gradient or effect paint. */
|
|
13
|
+
export type BrushColourKind = 'solid' | 'dual' | 'gradient' | 'effect';
|
|
14
|
+
export interface BrushHostProfile {
|
|
15
|
+
/** `paint`, `image`, `canvas`, `vector`… */
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
kinds: readonly BrushKind[];
|
|
19
|
+
/** Tools the host has (for `kind: 'tool'`). */
|
|
20
|
+
tools: readonly BrushTool[];
|
|
21
|
+
tipShapes: readonly BrushTipShape[];
|
|
22
|
+
marks: readonly BrushMarksKind[];
|
|
23
|
+
surfaces: readonly BrushSurface[];
|
|
24
|
+
/** Picking paint up and mixing it in (`behaviour.exchange` pickup, mix, dirty). */
|
|
25
|
+
exchange: BrushSupport;
|
|
26
|
+
/** The Mixer Brush (`behaviour.exchange.reservoir`). */
|
|
27
|
+
reservoir: BrushSupport;
|
|
28
|
+
/** Paper grain response (`behaviour.grain`). */
|
|
29
|
+
grain: BrushSupport;
|
|
30
|
+
light: BrushSupport;
|
|
31
|
+
motion: BrushSupport;
|
|
32
|
+
/** Colour kinds the brush's default colour may be (palette entries of other kinds are warned about). */
|
|
33
|
+
colours: readonly BrushColourKind[];
|
|
34
|
+
/** Velocity dynamics (needs timed input). */
|
|
35
|
+
velocity: BrushSupport;
|
|
36
|
+
/** Tilt dynamics (needs pen tilt). */
|
|
37
|
+
tilt: BrushSupport;
|
|
38
|
+
}
|
|
39
|
+
export interface BrushIssue {
|
|
40
|
+
/** The facet path the issue is about, e.g. `tip.shape`, `light`, `behaviour.exchange.reservoir`. */
|
|
41
|
+
facet: string;
|
|
42
|
+
/** `error`: the host can't render the brush; `warning`: it renders it without this setting. */
|
|
43
|
+
severity: 'error' | 'warning';
|
|
44
|
+
reason: string;
|
|
45
|
+
fix: string;
|
|
46
|
+
}
|
|
47
|
+
export interface BrushCheck {
|
|
48
|
+
/** No errors (warnings may remain). */
|
|
49
|
+
ok: boolean;
|
|
50
|
+
issues: BrushIssue[];
|
|
51
|
+
}
|
|
52
|
+
/** The paint editor: every facet. */
|
|
53
|
+
export declare const PAINT_PROFILE: BrushHostProfile;
|
|
54
|
+
/** The image editor's brush: dry dabs on pixels, its Mixer Brush and the eraser. */
|
|
55
|
+
export declare const IMAGE_PROFILE: BrushHostProfile;
|
|
56
|
+
/**
|
|
57
|
+
* Brushed canvas strokes: dry dab brushes along a path. Light paint needs the paint editor's
|
|
58
|
+
* light channel (glow and spill), which canvas strokes don't have, so it is refused.
|
|
59
|
+
*/
|
|
60
|
+
export declare const CANVAS_PROFILE: BrushHostProfile;
|
|
61
|
+
/** Vector brushstrokes: dry dab brushes along a path; light brushes paint as plain colour. */
|
|
62
|
+
export declare const VECTOR_PROFILE: BrushHostProfile;
|
|
63
|
+
/** The built-in profiles by id. */
|
|
64
|
+
export declare const BRUSH_HOST_PROFILES: Readonly<Record<string, BrushHostProfile>>;
|
|
65
|
+
/** What kind of colour a `BrushPaint` is. */
|
|
66
|
+
export declare function brushColourKind(paint: BrushPaint): BrushColourKind;
|
|
67
|
+
/** What a brush needs from a host, and what the host does about it. */
|
|
68
|
+
export declare function checkBrush(brush: Brush, profile: BrushHostProfile): BrushCheck;
|
|
69
|
+
/** The presets (default: the built-in ones) a host can render, in library order. */
|
|
70
|
+
export declare function listPresets(profile: BrushHostProfile, presets?: readonly Brush[]): Brush[];
|
|
71
|
+
export interface DegradedBrush {
|
|
72
|
+
/** The brush with what the host can't render stripped (a copy). */
|
|
73
|
+
brush: Brush;
|
|
74
|
+
/** The errors fixed by stripping a facet. */
|
|
75
|
+
dropped: BrushIssue[];
|
|
76
|
+
/** Errors that stripping cannot fix (an unsupported kind or tool): the host still refuses it. */
|
|
77
|
+
issues: BrushIssue[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The brush the host can render: facets it refuses are removed (light, motion, a reservoir, a
|
|
81
|
+
* wet or thick surface, particle or object marks, bristle and stamp tips become round, an
|
|
82
|
+
* unsupported default colour), each reported in `dropped`. A tool the host lacks can't be
|
|
83
|
+
* stripped and stays in `issues`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function degrade(brush: Brush, profile: BrushHostProfile): DegradedBrush;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host capability profiles: what a host (the paint editor, the image editor, brushed canvas and
|
|
3
|
+
* vector strokes, later Swift) can render. `checkBrush` explains each refusal with a fix,
|
|
4
|
+
* `listPresets` filters a library down to what the host can use, and `degrade` strips the facets
|
|
5
|
+
* a host can't render and says what it dropped.
|
|
6
|
+
*
|
|
7
|
+
* A capability is `full` (rendered), `ignored` (the brush is accepted but the host paints without
|
|
8
|
+
* it: a warning) or `none` (the brush is refused: an error).
|
|
9
|
+
*/
|
|
10
|
+
import { type Brush, type BrushKind, type BrushMarksKind, type BrushPaint, type BrushSurface, type BrushTipShape, type BrushTool } from './types.js';
|
|
11
|
+
export type BrushSupport = 'full' | 'ignored' | 'none';
|
|
12
|
+
/** What a colour is: a solid colour, a light / dark pair, a gradient or effect paint. */
|
|
13
|
+
export type BrushColourKind = 'solid' | 'dual' | 'gradient' | 'effect';
|
|
14
|
+
export interface BrushHostProfile {
|
|
15
|
+
/** `paint`, `image`, `canvas`, `vector`… */
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
kinds: readonly BrushKind[];
|
|
19
|
+
/** Tools the host has (for `kind: 'tool'`). */
|
|
20
|
+
tools: readonly BrushTool[];
|
|
21
|
+
tipShapes: readonly BrushTipShape[];
|
|
22
|
+
marks: readonly BrushMarksKind[];
|
|
23
|
+
surfaces: readonly BrushSurface[];
|
|
24
|
+
/** Picking paint up and mixing it in (`behaviour.exchange` pickup, mix, dirty). */
|
|
25
|
+
exchange: BrushSupport;
|
|
26
|
+
/** The Mixer Brush (`behaviour.exchange.reservoir`). */
|
|
27
|
+
reservoir: BrushSupport;
|
|
28
|
+
/** Paper grain response (`behaviour.grain`). */
|
|
29
|
+
grain: BrushSupport;
|
|
30
|
+
light: BrushSupport;
|
|
31
|
+
motion: BrushSupport;
|
|
32
|
+
/** Colour kinds the brush's default colour may be (palette entries of other kinds are warned about). */
|
|
33
|
+
colours: readonly BrushColourKind[];
|
|
34
|
+
/** Velocity dynamics (needs timed input). */
|
|
35
|
+
velocity: BrushSupport;
|
|
36
|
+
/** Tilt dynamics (needs pen tilt). */
|
|
37
|
+
tilt: BrushSupport;
|
|
38
|
+
}
|
|
39
|
+
export interface BrushIssue {
|
|
40
|
+
/** The facet path the issue is about, e.g. `tip.shape`, `light`, `behaviour.exchange.reservoir`. */
|
|
41
|
+
facet: string;
|
|
42
|
+
/** `error`: the host can't render the brush; `warning`: it renders it without this setting. */
|
|
43
|
+
severity: 'error' | 'warning';
|
|
44
|
+
reason: string;
|
|
45
|
+
fix: string;
|
|
46
|
+
}
|
|
47
|
+
export interface BrushCheck {
|
|
48
|
+
/** No errors (warnings may remain). */
|
|
49
|
+
ok: boolean;
|
|
50
|
+
issues: BrushIssue[];
|
|
51
|
+
}
|
|
52
|
+
/** The paint editor: every facet. */
|
|
53
|
+
export declare const PAINT_PROFILE: BrushHostProfile;
|
|
54
|
+
/** The image editor's brush: dry dabs on pixels, its Mixer Brush and the eraser. */
|
|
55
|
+
export declare const IMAGE_PROFILE: BrushHostProfile;
|
|
56
|
+
/**
|
|
57
|
+
* Brushed canvas strokes: dry dab brushes along a path. Light paint needs the paint editor's
|
|
58
|
+
* light channel (glow and spill), which canvas strokes don't have, so it is refused.
|
|
59
|
+
*/
|
|
60
|
+
export declare const CANVAS_PROFILE: BrushHostProfile;
|
|
61
|
+
/** Vector brushstrokes: dry dab brushes along a path; light brushes paint as plain colour. */
|
|
62
|
+
export declare const VECTOR_PROFILE: BrushHostProfile;
|
|
63
|
+
/** The built-in profiles by id. */
|
|
64
|
+
export declare const BRUSH_HOST_PROFILES: Readonly<Record<string, BrushHostProfile>>;
|
|
65
|
+
/** What kind of colour a `BrushPaint` is. */
|
|
66
|
+
export declare function brushColourKind(paint: BrushPaint): BrushColourKind;
|
|
67
|
+
/** What a brush needs from a host, and what the host does about it. */
|
|
68
|
+
export declare function checkBrush(brush: Brush, profile: BrushHostProfile): BrushCheck;
|
|
69
|
+
/** The presets (default: the built-in ones) a host can render, in library order. */
|
|
70
|
+
export declare function listPresets(profile: BrushHostProfile, presets?: readonly Brush[]): Brush[];
|
|
71
|
+
export interface DegradedBrush {
|
|
72
|
+
/** The brush with what the host can't render stripped (a copy). */
|
|
73
|
+
brush: Brush;
|
|
74
|
+
/** The errors fixed by stripping a facet. */
|
|
75
|
+
dropped: BrushIssue[];
|
|
76
|
+
/** Errors that stripping cannot fix (an unsupported kind or tool): the host still refuses it. */
|
|
77
|
+
issues: BrushIssue[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The brush the host can render: facets it refuses are removed (light, motion, a reservoir, a
|
|
81
|
+
* wet or thick surface, particle or object marks, bristle and stamp tips become round, an
|
|
82
|
+
* unsupported default colour), each reported in `dropped`. A tool the host lacks can't be
|
|
83
|
+
* stripped and stays in `issues`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function degrade(brush: Brush, profile: BrushHostProfile): DegradedBrush;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for a brush library (`createBrushLibraryToolProvider`): list, describe, create,
|
|
3
|
+
* update, duplicate, remove, export, import and check brushes, and preview one when the host
|
|
4
|
+
* injects a renderer. Writes are checked against the host's profile before anything is stored,
|
|
5
|
+
* and refusals name what to do instead. The provider imports no renderer: `preview_brush` is
|
|
6
|
+
* the host's.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentTool, AgentToolProvider } from '../tools/registry.cjs';
|
|
9
|
+
import { type BrushLibrary } from './library.cjs';
|
|
10
|
+
import { type BrushHostProfile } from './profiles.cjs';
|
|
11
|
+
import { type Brush } from './types.cjs';
|
|
12
|
+
/** A preview image of a brush: base64 data and its type. */
|
|
13
|
+
export interface BrushPreviewImage {
|
|
14
|
+
data: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
}
|
|
17
|
+
/** Draws a sample stroke with a brush (the host's renderer: the paint engine, a canvas…). */
|
|
18
|
+
export type BrushPreviewRenderer = (brush: Brush, options: {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
color: string;
|
|
22
|
+
}) => BrushPreviewImage | Promise<BrushPreviewImage>;
|
|
23
|
+
export interface BrushLibraryToolProviderOptions {
|
|
24
|
+
/** What this host can render (default `PAINT_PROFILE`): lists are filtered by it and writes checked against it. */
|
|
25
|
+
profile?: BrushHostProfile;
|
|
26
|
+
/** With it, `preview_brush` draws a sample stroke. */
|
|
27
|
+
renderPreview?: BrushPreviewRenderer;
|
|
28
|
+
/** Only the read tools (list, describe, export, check, preview). */
|
|
29
|
+
readOnly?: boolean;
|
|
30
|
+
/** Provider id (default `brushes`), also the tool namespace unless `namespace` is set. */
|
|
31
|
+
id?: string;
|
|
32
|
+
namespace?: string;
|
|
33
|
+
}
|
|
34
|
+
/** The brush library tools for one host (see the module comment). */
|
|
35
|
+
export declare function createBrushLibraryTools(library: BrushLibrary, options?: BrushLibraryToolProviderOptions): AgentTool[];
|
|
36
|
+
/** The brush library tools as a provider (id `brushes` by default; namespaced, so they don't meet the paint editor's brush tools). */
|
|
37
|
+
export declare function createBrushLibraryToolProvider(library: BrushLibrary, options?: BrushLibraryToolProviderOptions): AgentToolProvider;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for a brush library (`createBrushLibraryToolProvider`): list, describe, create,
|
|
3
|
+
* update, duplicate, remove, export, import and check brushes, and preview one when the host
|
|
4
|
+
* injects a renderer. Writes are checked against the host's profile before anything is stored,
|
|
5
|
+
* and refusals name what to do instead. The provider imports no renderer: `preview_brush` is
|
|
6
|
+
* the host's.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentTool, AgentToolProvider } from '../tools/registry.js';
|
|
9
|
+
import { type BrushLibrary } from './library.js';
|
|
10
|
+
import { type BrushHostProfile } from './profiles.js';
|
|
11
|
+
import { type Brush } from './types.js';
|
|
12
|
+
/** A preview image of a brush: base64 data and its type. */
|
|
13
|
+
export interface BrushPreviewImage {
|
|
14
|
+
data: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
}
|
|
17
|
+
/** Draws a sample stroke with a brush (the host's renderer: the paint engine, a canvas…). */
|
|
18
|
+
export type BrushPreviewRenderer = (brush: Brush, options: {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
color: string;
|
|
22
|
+
}) => BrushPreviewImage | Promise<BrushPreviewImage>;
|
|
23
|
+
export interface BrushLibraryToolProviderOptions {
|
|
24
|
+
/** What this host can render (default `PAINT_PROFILE`): lists are filtered by it and writes checked against it. */
|
|
25
|
+
profile?: BrushHostProfile;
|
|
26
|
+
/** With it, `preview_brush` draws a sample stroke. */
|
|
27
|
+
renderPreview?: BrushPreviewRenderer;
|
|
28
|
+
/** Only the read tools (list, describe, export, check, preview). */
|
|
29
|
+
readOnly?: boolean;
|
|
30
|
+
/** Provider id (default `brushes`), also the tool namespace unless `namespace` is set. */
|
|
31
|
+
id?: string;
|
|
32
|
+
namespace?: string;
|
|
33
|
+
}
|
|
34
|
+
/** The brush library tools for one host (see the module comment). */
|
|
35
|
+
export declare function createBrushLibraryTools(library: BrushLibrary, options?: BrushLibraryToolProviderOptions): AgentTool[];
|
|
36
|
+
/** The brush library tools as a provider (id `brushes` by default; namespaced, so they don't meet the paint editor's brush tools). */
|
|
37
|
+
export declare function createBrushLibraryToolProvider(library: BrushLibrary, options?: BrushLibraryToolProviderOptions): AgentToolProvider;
|
|
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
|
|
|
20
20
|
}
|
|
21
21
|
export interface SkillEval {
|
|
22
22
|
prompt: string;
|
|
23
|
-
fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture | CanvasEvalFixture;
|
|
23
|
+
fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture | CanvasEvalFixture | BrushesEvalFixture;
|
|
24
24
|
checks: SkillEvalCheck[];
|
|
25
25
|
solution: SkillEvalToolCall[];
|
|
26
26
|
}
|
|
@@ -116,6 +116,13 @@ export interface CanvasEvalFixture {
|
|
|
116
116
|
/** Tool calls that prepare the canvas (components, instances), run in order. */
|
|
117
117
|
setup?: SkillEvalToolCall[];
|
|
118
118
|
}
|
|
119
|
+
export interface BrushesEvalFixture {
|
|
120
|
+
editor: 'brushes';
|
|
121
|
+
/** The host profile the brush library tools run for: paint (default), image, canvas or vector. */
|
|
122
|
+
host?: 'paint' | 'image' | 'canvas' | 'vector';
|
|
123
|
+
/** Tool calls that prepare the library (saved or imported brushes), run in order. */
|
|
124
|
+
setup?: SkillEvalToolCall[];
|
|
125
|
+
}
|
|
119
126
|
/** One eval file of a skill. */
|
|
120
127
|
export interface SkillEvalFile {
|
|
121
128
|
skill: string;
|
|
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
|
|
|
20
20
|
}
|
|
21
21
|
export interface SkillEval {
|
|
22
22
|
prompt: string;
|
|
23
|
-
fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture | CanvasEvalFixture;
|
|
23
|
+
fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture | GenerationEvalFixture | EffectEvalFixture | CanvasEvalFixture | BrushesEvalFixture;
|
|
24
24
|
checks: SkillEvalCheck[];
|
|
25
25
|
solution: SkillEvalToolCall[];
|
|
26
26
|
}
|
|
@@ -116,6 +116,13 @@ export interface CanvasEvalFixture {
|
|
|
116
116
|
/** Tool calls that prepare the canvas (components, instances), run in order. */
|
|
117
117
|
setup?: SkillEvalToolCall[];
|
|
118
118
|
}
|
|
119
|
+
export interface BrushesEvalFixture {
|
|
120
|
+
editor: 'brushes';
|
|
121
|
+
/** The host profile the brush library tools run for: paint (default), image, canvas or vector. */
|
|
122
|
+
host?: 'paint' | 'image' | 'canvas' | 'vector';
|
|
123
|
+
/** Tool calls that prepare the library (saved or imported brushes), run in order. */
|
|
124
|
+
setup?: SkillEvalToolCall[];
|
|
125
|
+
}
|
|
119
126
|
/** One eval file of a skill. */
|
|
120
127
|
export interface SkillEvalFile {
|
|
121
128
|
skill: string;
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -30,6 +30,7 @@ model and its general practice.
|
|
|
30
30
|
| [`pixerate-color`](pixerate-color/SKILL.md) | Colour inspection, WCAG accessibility and contrast optimization, CVD simulation, palette audits, harmonies, and OKLab/OKLCH blends. |
|
|
31
31
|
| [`pixerate-typography`](pixerate-typography/SKILL.md) | Fonts and fallback stacks, font loading, modular type scales and hierarchies, fluid sizes, readability and type-aware contrast audits, text segmentation, the font picker. |
|
|
32
32
|
| [`pixerate-stickers`](pixerate-stickers/SKILL.md) | Sticker libraries: finding stickers by ref (`provider:id`), categories, tags, tintable art and licences, for the editors that spray or place them. |
|
|
33
|
+
| [`pixerate-brushes`](pixerate-brushes/SKILL.md) | The shared brush library: brushes made of facets (medium, tip, marks, dynamics, behaviour, light, motion, colour), the presets, host profiles for paint, image, canvas and vector, and creating, checking and sharing brushes. |
|
|
33
34
|
|
|
34
35
|
## Workflow skills
|
|
35
36
|
|
|
@@ -51,6 +52,7 @@ request like "clean up this export" finds it.
|
|
|
51
52
|
| [`pixerate-paint-watercolour`](pixerate-paint-watercolour/SKILL.md) | Paint (`pixerate-paint-editor`) | Watercolour paper, light to dark with the paper's white kept, wet-in-wet and wet-on-dry washes, drying before glazing, steering the water. |
|
|
52
53
|
| [`pixerate-paint-oil-impasto`](pixerate-paint-oil-impasto/SKILL.md) | Paint (`pixerate-paint-editor`) | A toned ground, thin block-in to thick paint, pigment mixing, impasto relief and its light. |
|
|
53
54
|
| [`pixerate-paint-sketch-to-finish`](pixerate-paint-sketch-to-finish/SKILL.md) | Paint (`pixerate-paint-editor`) | Sketch, ink, flats under the lines, clipped shading and light, one named layer per pass. |
|
|
55
|
+
| [`pixerate-brushes-design`](pixerate-brushes-design/SKILL.md) | Brushes (`pixerate-brushes`) | From a description to a saved brush: the nearest preset, only the facets that change, checked for the host that will use it, previewed. |
|
|
54
56
|
| [`pixerate-video-social-cut`](pixerate-video-social-cut/SKILL.md) | Video (`pixerate-video-editor`) | Vertical or square reframes that fill the frame, the hook first, platform lengths, titles in the safe area. |
|
|
55
57
|
| [`pixerate-video-colour-grade`](pixerate-video-colour-grade/SKILL.md) | Video (`pixerate-video-editor`) | Correct and match shots, then one look on an adjustment clip; LUTs at moderate strength with skin protected. |
|
|
56
58
|
| [`pixerate-video-montage`](pixerate-video-montage/SKILL.md) | Video (`pixerate-video-editor`) | Selects on the beat, varied pacing, short transitions with handles, music under everything and a clean ending. |
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-brushes
|
|
3
|
+
description: How to find, describe, create, change, check and share brushes in the Pixerate brush library through list_brush_presets, describe_brush, create_brush, update_brush, duplicate_brush, remove_brush, check_brush, export_brushes, import_brushes and preview_brush - brushes made of facets (kind, medium and variety or tool, tip, marks, dynamics, behaviour, light, motion, colour), the built-in presets, and the host profiles that say what the paint editor, image editor, canvas and vector strokes can render. Use when asked to make, design, tweak, save, copy, delete, export, import or check a brush, find a brush for a medium (pencil, ink, watercolour, oil, airbrush, highlighter), or make a brush work in another editor.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Brushes
|
|
7
|
+
|
|
8
|
+
A brush is made of **facets** that say what it is and how it behaves, not which engine draws it.
|
|
9
|
+
Only `id`, `name`, `kind` and `tip.shape` are required; every other field takes the default of
|
|
10
|
+
the engine the facets pick, so a brush stores only what makes it what it is. The library holds the
|
|
11
|
+
built-in presets (read-only) and the user's brushes, shared by every editor that paints.
|
|
12
|
+
|
|
13
|
+
| Facet (field) | What it holds |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Kind | `mark` lays a material; `tool` works on paint already down. |
|
|
16
|
+
| Medium and variety | Marks: what it lays (`paint`, `ink`, `dry`, `spray`) and what kind (`oil`, `watercolor`, `gouache`, `pencil`, `charcoal`, `airbrush`, `marker`, `highlighter`…). Descriptive, for finding brushes; a wet brush whose variety is `watercolor`, `gouache` or `oil` paints that medium. |
|
|
17
|
+
| Tool | Tools: `eraser`, `blender`, `smudge`, and on wet paint `water`, `dryer`, `blow`, `blot`, `wet-blend`, `wet-drag`. |
|
|
18
|
+
| Tip | `shape` (`round`, `flat`, `chisel` are ellipses set by `roundness` and `angle`; `bristle` has a tuft; `stamp` lays stickers), size in px, hardness, spacing, opacity, flow, blend mode. |
|
|
19
|
+
| Marks | `continuous` dabs (default), `particles` (strands, springs, ribbons, sparks) or `objects` (each mark an editable sticker). |
|
|
20
|
+
| Dynamics | Pen feel: pressure to size and opacity, smoothing, tilt, velocity, jitter, rotation. |
|
|
21
|
+
| Behaviour | `surface` `dry`, `wet` (`wet` settings) or `thick` (`thick` impasto settings); paper `grain`; `exchange` (pick-up and mix; a `reservoir` makes a Mixer Brush); tool `strength`. |
|
|
22
|
+
| Light | Light the brush gives off (`amount`), plus suggested layer `spill` and `glow`. |
|
|
23
|
+
| Motion | Animation after painting (live effect paint, particle `waveSpeed`). |
|
|
24
|
+
| Colour | A `default` colour, a `palette` of up to 16, `own` (always its own colour). A gradient default is always painted; a solid one is a suggestion. |
|
|
25
|
+
|
|
26
|
+
How media map to facets: a **pencil** or **charcoal** is `dry` with high `grain` and tilt;
|
|
27
|
+
**ink** is a hard round tip with pressure to size (a pen thins with velocity); **watercolour**
|
|
28
|
+
and **wet oil** are `paint` on `surface: 'wet'`; **impasto** is `surface: 'thick'`; an
|
|
29
|
+
**airbrush** is `spray`, soft and low flow; a **highlighter** is `ink` / `highlighter`, a
|
|
30
|
+
multiply chisel tip with a little light and `spill` radius 0; **particle** and **sticker**
|
|
31
|
+
brushes are `marks` particles or objects on a `stamp` tip.
|
|
32
|
+
|
|
33
|
+
## Host profiles
|
|
34
|
+
|
|
35
|
+
Each host renders a part of this. `check_brush` says what a host refuses (errors) and what it
|
|
36
|
+
paints without (warnings), each with a fix; lists and writes use the current host.
|
|
37
|
+
|
|
38
|
+
| Host | Renders |
|
|
39
|
+
| --- | --- |
|
|
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. |
|
|
44
|
+
|
|
45
|
+
## Working method
|
|
46
|
+
|
|
47
|
+
1. **Find the nearest brush.** `list_brush_presets` with `medium`, `variety`, `kind` or `text`.
|
|
48
|
+
It lists only what this host can render (`all: true` lists the rest with `renderable`).
|
|
49
|
+
2. **Read it.** `describe_brush` for the full spec, the engine it resolves to and its check.
|
|
50
|
+
3. **Make a brush.** `create_brush` with a new `id`, a `name`, `basedOn` the nearest preset and
|
|
51
|
+
only the facets that change (objects merge; lists replace). Read `problems` (values clamped or
|
|
52
|
+
dropped) and `warnings`. A brush the host can't render is refused and nothing is saved.
|
|
53
|
+
4. **Look at it.** `preview_brush` when the host has a renderer; otherwise paint a test stroke
|
|
54
|
+
in the editor (`set_brush` with the new id, then `paint_stroke`).
|
|
55
|
+
5. **Tune it.** `update_brush` with the facets to change, or `unset` to drop whole facets. It
|
|
56
|
+
returns the leaf paths `changed`. Built-in presets can't be changed: `duplicate_brush` first.
|
|
57
|
+
6. **Share it.** `export_brushes` writes a library file; `import_brushes` adds one (version 1
|
|
58
|
+
files of the paint editor's older brushes are migrated). `check_brush` with `host` before
|
|
59
|
+
handing a brush to another editor.
|
|
60
|
+
|
|
61
|
+
## Best practices
|
|
62
|
+
|
|
63
|
+
- Start from the nearest preset and change two or three facets; a brush from nothing gets
|
|
64
|
+
engine defaults that rarely suit a medium.
|
|
65
|
+
- Test on the paper and colour the brush will be used on: grain response and glazes look
|
|
66
|
+
different on smooth and rough paper.
|
|
67
|
+
- Give light brushes dark paper, and give highlighters a `light.spill` radius of 0 so the
|
|
68
|
+
glow stays on the stroke.
|
|
69
|
+
- Keep `variety` truthful (it is how people find the brush) and add `tags` for the job
|
|
70
|
+
(`lettering`, `foliage`).
|
|
71
|
+
- Use `colour.palette` for suggested colours and `colour.own` only for brushes that must
|
|
72
|
+
always paint their own (a rainbow marker).
|
|
73
|
+
|
|
74
|
+
## Pitfalls
|
|
75
|
+
|
|
76
|
+
- **A facet the host refuses.** `create_brush` and `update_brush` refuse it with the facet and
|
|
77
|
+
fix; `check_brush` returns `degraded`, the brush with those facets stripped, to start from.
|
|
78
|
+
- **Ids.** Built-in ids can't be reused or changed; a taken id is refused (update it instead).
|
|
79
|
+
- **Stamps.** Particle marks lay only the editor's stamps (`editor:star`…); sticker (`objects`)
|
|
80
|
+
marks take any sticker ref from `find_stickers`.
|
|
81
|
+
- **Out-of-range values** are refused by the schema (`tip.size` is 1..2500 px); a stored brush
|
|
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.
|
|
85
|
+
|
|
86
|
+
## Workflow skills
|
|
87
|
+
|
|
88
|
+
- `pixerate-brushes-design`: from a description ("a soft pink highlighter", "chalk for the
|
|
89
|
+
canvas") to a saved, checked brush.
|
|
90
|
+
|
|
91
|
+
## Tool reference
|
|
92
|
+
|
|
93
|
+
| Tool | Use |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `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
|
+
| `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`. |
|
|
98
|
+
| `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
|
+
| `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
|
+
| `remove_brush` | Delete a saved brush for good (export it first to keep a copy). Built-ins are refused. Returns `removed`. |
|
|
101
|
+
| `check_brush` | Check a brush (`id`, or an unsaved `brush` spec) for a `host` (paint, image, canvas, vector). Returns `ok`, `issues` (facet, severity, reason, fix) and, when refused, `degraded`. |
|
|
102
|
+
| `export_brushes` | Library file (pixerate-brushes v2) of the saved brushes or the `ids` given. Returns `file`, for `import_brushes`. |
|
|
103
|
+
| `import_brushes` | Add a library `file` (v2, v1 migrated, or a bare list): built-in ids and taken ids are skipped unless `overwrite`. Returns `added`, `replaced`, `skipped`, `problems`. |
|
|
104
|
+
| `preview_brush` | Only when the host renders previews: a sample stroke with a brush (`id`, `width`, `height`, `color`) as an image. |
|