@pixerate/editor 0.255.0 → 0.257.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 (129) hide show
  1. package/dist/agent/index.cjs +40 -38
  2. package/dist/agent/index.js +9 -7
  3. package/dist/animation/index.cjs +4 -2
  4. package/dist/animation/index.js +4 -2
  5. package/dist/animation-editor/index.cjs +63 -61
  6. package/dist/animation-editor/index.js +7 -5
  7. package/dist/brushes/colour.d.cts +11 -0
  8. package/dist/brushes/colour.d.ts +11 -0
  9. package/dist/brushes/index.cjs +51 -30
  10. package/dist/brushes/index.d.cts +1 -0
  11. package/dist/brushes/index.d.ts +1 -0
  12. package/dist/brushes/index.js +25 -4
  13. package/dist/brushes/resolve.d.cts +2 -0
  14. package/dist/brushes/resolve.d.ts +2 -0
  15. package/dist/brushes/schema.d.cts +1 -0
  16. package/dist/brushes/schema.d.ts +1 -0
  17. package/dist/brushes/tools.d.cts +7 -0
  18. package/dist/brushes/tools.d.ts +7 -0
  19. package/dist/brushes/types.d.cts +10 -2
  20. package/dist/brushes/types.d.ts +10 -2
  21. package/dist/brushes/ui.d.cts +14 -3
  22. package/dist/brushes/ui.d.ts +14 -3
  23. package/dist/{brushstrokeRender-HCOQUSWW.js → brushstrokeRender-JI4W6F2G.js} +2 -2
  24. package/dist/{brushstrokeRender-ZZOXI5O5.cjs → brushstrokeRender-MQ6PSDM5.cjs} +11 -11
  25. package/dist/canvas/index.cjs +20 -18
  26. package/dist/canvas/index.js +7 -5
  27. package/dist/chart/index.cjs +159 -157
  28. package/dist/chart/index.js +9 -7
  29. package/dist/{chunk-7272D3K2.js → chunk-2EUW2XZX.js} +27 -276
  30. package/dist/{chunk-L6Y7TSQ4.js → chunk-2VJ6OVJV.js} +1 -1
  31. package/dist/{chunk-6ZZKRH7R.js → chunk-47V3WXDG.js} +2 -2
  32. package/dist/{chunk-H7ET2ISK.cjs → chunk-4IMD2TLY.cjs} +52 -52
  33. package/dist/{chunk-VUKUTT2W.js → chunk-4M4E3N6X.js} +1 -1
  34. package/dist/{chunk-TBAVCLEQ.cjs → chunk-5Q5MLXAD.cjs} +23 -23
  35. package/dist/{chunk-MONXNVZK.js → chunk-6I23ZCGK.js} +5 -3
  36. package/dist/{chunk-GOBOCZZI.js → chunk-6NQPKNUZ.js} +1 -1
  37. package/dist/{chunk-WYGSR3IJ.js → chunk-AORZ5M5C.js} +1 -1
  38. package/dist/chunk-AVTPZO4Z.cjs +142 -0
  39. package/dist/{chunk-WKMUBATU.js → chunk-C2WEIEUB.js} +1 -1
  40. package/dist/{chunk-P3K2JCJA.js → chunk-D5BXSOSU.js} +3 -3
  41. package/dist/chunk-E2V5DA3W.js +36 -0
  42. package/dist/{chunk-WZOGDKCQ.js → chunk-F7CPSRLN.js} +0 -133
  43. package/dist/chunk-FJBQTZS4.js +17 -0
  44. package/dist/{chunk-SOSBOS5T.cjs → chunk-G23BTQA5.cjs} +22 -22
  45. package/dist/{chunk-EUIDTZNQ.cjs → chunk-I3Y2Q3PM.cjs} +59 -308
  46. package/dist/{chunk-YHUMGBZD.js → chunk-ICTPLVSR.js} +22 -4
  47. package/dist/{chunk-LRNUZKWV.cjs → chunk-IPUTIUGQ.cjs} +1 -1
  48. package/dist/{chunk-6GRUVGKU.js → chunk-ITURMC2M.js} +3 -3
  49. package/dist/{chunk-HYWRU5VX.js → chunk-IXQZHCOF.js} +1 -1
  50. package/dist/chunk-J5UQHWVL.js +142 -0
  51. package/dist/{chunk-YBAPT7NL.js → chunk-JC2SHA4A.js} +1 -23
  52. package/dist/chunk-LRTR4ASZ.cjs +17 -0
  53. package/dist/{chunk-HUAISCUX.cjs → chunk-M2HG5DJ2.cjs} +18 -18
  54. package/dist/{chunk-N76AMOKE.cjs → chunk-MDLEGVEN.cjs} +42 -42
  55. package/dist/chunk-MEGSQUQO.cjs +36 -0
  56. package/dist/chunk-MLLTWAXH.cjs +276 -0
  57. package/dist/{chunk-RHPSE2YR.js → chunk-MQ6MGIQM.js} +2 -2
  58. package/dist/{chunk-PGPFWC35.cjs → chunk-MQGQTKVW.cjs} +51 -33
  59. package/dist/{chunk-5YWWQ36H.cjs → chunk-O3ZVZSNJ.cjs} +9 -9
  60. package/dist/{chunk-GCTUOTCW.cjs → chunk-P3PZ63M3.cjs} +35 -35
  61. package/dist/{chunk-WRZ6C4TU.cjs → chunk-P6WYDBTH.cjs} +4 -4
  62. package/dist/{chunk-CJ6TBL2N.cjs → chunk-RHEMMGCN.cjs} +16 -16
  63. package/dist/{chunk-HIMGBZAM.cjs → chunk-RSIKDWOP.cjs} +201 -38
  64. package/dist/{chunk-SABJMH6M.cjs → chunk-RVGP2XLF.cjs} +2 -135
  65. package/dist/{chunk-JVRLGV3C.js → chunk-SMTUQWGY.js} +184 -21
  66. package/dist/{chunk-DMTEQ6SH.js → chunk-SSFMWJDK.js} +4 -4
  67. package/dist/{chunk-TFF2GTGN.cjs → chunk-SXQHWXEL.cjs} +2 -24
  68. package/dist/{chunk-IWEVV63O.cjs → chunk-UEYLANO3.cjs} +18 -16
  69. package/dist/chunk-VR6OGHU5.js +276 -0
  70. package/dist/{chunk-AF23QK5F.js → chunk-YQ4PQTBB.js} +1 -1
  71. package/dist/{chunk-B7V34GIH.cjs → chunk-YSGW7VV4.cjs} +88 -88
  72. package/dist/{chunk-3NS5C7FB.cjs → chunk-YYBW6DF2.cjs} +76 -76
  73. package/dist/{chunk-UFEDHACQ.js → chunk-Z7BX4TWO.js} +2 -2
  74. package/dist/{chunk-RUONPPMC.cjs → chunk-ZSE25IZA.cjs} +8 -8
  75. package/dist/color/index.cjs +7 -3
  76. package/dist/color/index.js +18 -14
  77. package/dist/export/index.cjs +8 -6
  78. package/dist/export/index.js +7 -5
  79. package/dist/extensions/index.cjs +11 -9
  80. package/dist/extensions/index.js +10 -8
  81. package/dist/gradient/index.cjs +33 -31
  82. package/dist/gradient/index.js +5 -3
  83. package/dist/image-editor/brush-presets.d.cts +4 -1
  84. package/dist/image-editor/brush-presets.d.ts +4 -1
  85. package/dist/image-editor/controller.d.cts +10 -5
  86. package/dist/image-editor/controller.d.ts +10 -5
  87. package/dist/image-editor/index.cjs +1090 -1073
  88. package/dist/image-editor/index.js +37 -20
  89. package/dist/index.cjs +17 -15
  90. package/dist/index.js +11 -9
  91. package/dist/paint-editor/brushes.d.cts +18 -1
  92. package/dist/paint-editor/brushes.d.ts +18 -1
  93. package/dist/paint-editor/collaboration-tools.d.cts +19 -0
  94. package/dist/paint-editor/collaboration-tools.d.ts +19 -0
  95. package/dist/paint-editor/collaboration.d.cts +180 -0
  96. package/dist/paint-editor/collaboration.d.ts +180 -0
  97. package/dist/paint-editor/controller.d.cts +83 -0
  98. package/dist/paint-editor/controller.d.ts +83 -0
  99. package/dist/paint-editor/engine.d.cts +7 -0
  100. package/dist/paint-editor/engine.d.ts +7 -0
  101. package/dist/paint-editor/index.cjs +2027 -1331
  102. package/dist/paint-editor/index.d.cts +2 -0
  103. package/dist/paint-editor/index.d.ts +2 -0
  104. package/dist/paint-editor/index.js +1671 -975
  105. package/dist/paint-editor/mark-settings.d.cts +1 -1
  106. package/dist/paint-editor/mark-settings.d.ts +1 -1
  107. package/dist/paint-editor/procedural.d.cts +4 -2
  108. package/dist/paint-editor/procedural.d.ts +4 -2
  109. package/dist/paint-editor/stamp-tip.d.cts +45 -0
  110. package/dist/paint-editor/stamp-tip.d.ts +45 -0
  111. package/dist/paint-editor/stroke.d.cts +2 -0
  112. package/dist/paint-editor/stroke.d.ts +2 -0
  113. package/dist/stickers/index.cjs +4 -2
  114. package/dist/stickers/index.js +6 -4
  115. package/dist/typography/index.cjs +3 -2
  116. package/dist/typography/index.js +2 -1
  117. package/dist/vector/index.cjs +971 -969
  118. package/dist/vector/index.js +8 -6
  119. package/dist/video-editor/index.cjs +4 -2
  120. package/dist/video-editor/index.js +4 -2
  121. package/package.json +1 -1
  122. package/skills/pixerate-brushes/SKILL.md +21 -11
  123. package/skills/pixerate-brushes-design/SKILL.md +6 -2
  124. package/skills/pixerate-brushes-design/evals/canvas-chalk.json +8 -8
  125. package/skills/pixerate-canvas/SKILL.md +3 -2
  126. package/skills/pixerate-image-editor/SKILL.md +1 -1
  127. package/skills/pixerate-paint-editor/SKILL.md +35 -47
  128. package/skills/pixerate-paint-editor/references/painting-alongside.md +19 -0
  129. package/skills/pixerate-vector/SKILL.md +1 -1
@@ -26,7 +26,7 @@ import {
26
26
  traceField,
27
27
  traceVectorImage,
28
28
  vectorTraceContours
29
- } from "../chunk-VUKUTT2W.js";
29
+ } from "../chunk-4M4E3N6X.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-HYWRU5VX.js";
56
+ } from "../chunk-IXQZHCOF.js";
57
57
  import {
58
58
  DEFAULT_VECTOR_FONT,
59
59
  IDENTITY_MATRIX,
@@ -528,10 +528,10 @@ import {
528
528
  withVectorStrokes,
529
529
  withVectorTargetPaint,
530
530
  zigzagPaths
531
- } from "../chunk-AF23QK5F.js";
531
+ } from "../chunk-YQ4PQTBB.js";
532
532
  import {
533
533
  snapshotBrushLibrary
534
- } from "../chunk-JVRLGV3C.js";
534
+ } from "../chunk-SMTUQWGY.js";
535
535
  import "../chunk-APX63ILU.js";
536
536
  import {
537
537
  createProvenance
@@ -599,8 +599,10 @@ import "../chunk-M2TPMYJE.js";
599
599
  import {
600
600
  hexToRgba
601
601
  } from "../chunk-2WPNAGHI.js";
602
- import "../chunk-7272D3K2.js";
603
- import "../chunk-YBAPT7NL.js";
602
+ import "../chunk-2EUW2XZX.js";
603
+ import "../chunk-E2V5DA3W.js";
604
+ import "../chunk-VR6OGHU5.js";
605
+ import "../chunk-JC2SHA4A.js";
604
606
  import "../chunk-MHLU6GRJ.js";
605
607
  import "../chunk-6ONMQ6WF.js";
606
608
 
@@ -189,8 +189,10 @@ var _chunkM5GLQQWGcjs = require('../chunk-M5GLQQWG.cjs');
189
189
  var _chunkDWJJI6RTcjs = require('../chunk-DWJJI6RT.cjs');
190
190
  require('../chunk-4ZC5QJE2.cjs');
191
191
  require('../chunk-A6QWKBJP.cjs');
192
- require('../chunk-EUIDTZNQ.cjs');
193
- require('../chunk-TFF2GTGN.cjs');
192
+ require('../chunk-I3Y2Q3PM.cjs');
193
+ require('../chunk-MEGSQUQO.cjs');
194
+ require('../chunk-MLLTWAXH.cjs');
195
+ require('../chunk-SXQHWXEL.cjs');
194
196
  require('../chunk-5UEFBM2M.cjs');
195
197
  require('../chunk-IPJ3TEYR.cjs');
196
198
 
@@ -189,8 +189,10 @@ import {
189
189
  } from "../chunk-5UJHB3BW.js";
190
190
  import "../chunk-M2TPMYJE.js";
191
191
  import "../chunk-2WPNAGHI.js";
192
- import "../chunk-7272D3K2.js";
193
- import "../chunk-YBAPT7NL.js";
192
+ import "../chunk-2EUW2XZX.js";
193
+ import "../chunk-E2V5DA3W.js";
194
+ import "../chunk-VR6OGHU5.js";
195
+ import "../chunk-JC2SHA4A.js";
194
196
  import "../chunk-MHLU6GRJ.js";
195
197
  import "../chunk-6ONMQ6WF.js";
196
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.255.0",
3
+ "version": "0.257.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -15,20 +15,23 @@ built-in presets (read-only) and the user's brushes, shared by every editor that
15
15
  | Kind | `mark` lays a material; `tool` works on paint already down. |
16
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
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). |
18
+ | Tip | `shape` (`round`, `flat`, `chisel` are ellipses set by `roundness` and `angle`; `bristle` has a tuft; `stamp` takes the shape of `stamps`: an editor stamp `editor:star` or any sticker ref from `find_stickers`), size in px, hardness, spacing, opacity, flow, blend mode. |
19
+ | Marks | `continuous` dabs (default; on a stamp tip every dab is the first stamp's shape, in the paint colour), `scatter` (a spray: droplets across the tip, `density`, `size`), `particles` (strands, springs, ribbons, sparks; textured dots from the stamps) or `objects` (each mark an editable sticker, in its own colours). |
20
20
  | Dynamics | Pen feel: pressure to size and opacity, smoothing, tilt, velocity, jitter, rotation. |
21
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
22
  | Light | Light the brush gives off (`amount`), plus suggested layer `spill` and `glow`. |
23
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. |
24
+ | Colour | A `default` colour, a `palette` of up to 16, `own` (always its own colour). Each is a hex or `rgba()` colour, a light / dark pair `{ light, dark }`, a gradient or effect paint. A gradient default is always painted; a solid or light / dark default becomes the editor's colour when the brush is chosen (unless the user locked theirs), resolved for the colour scheme. |
25
25
 
26
26
  How media map to facets: a **pencil** or **charcoal** is `dry` with high `grain` and tilt;
27
27
  **ink** is a hard round tip with pressure to size (a pen thins with velocity); **watercolour**
28
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.
29
+ **airbrush** is `spray`, soft and low flow; a **spray can** is `spray` with `marks` scatter; a
30
+ **ballpoint**, **gel pen** or **fineliner** is `ink` with a small hard tip (a fineliner without
31
+ pressure to size); **crayon** and **chalk** are `dry` with high `grain`; a **highlighter** is
32
+ `ink` / `highlighter`, a multiply chisel tip with a little light, `spill` radius 0, fluorescent
33
+ yellow by default; a **stamp brush** is a `stamp` tip with continuous marks; **particle** and
34
+ **sticker** brushes are `marks` particles or objects on a `stamp` tip.
32
35
 
33
36
  ## Host profiles
34
37
 
@@ -74,16 +77,23 @@ drawn.
74
77
  glow stays on the stroke.
75
78
  - Keep `variety` truthful (it is how people find the brush) and add `tags` for the job
76
79
  (`lettering`, `foliage`).
77
- - Use `colour.palette` for suggested colours and `colour.own` only for brushes that must
80
+ - Give a brush a `colour.default` only when the medium has a typical colour (blue ballpoint,
81
+ yellow highlighter): choosing the brush sets it as the user's colour. Use a light / dark pair
82
+ when the right colour depends on the background (white chalk on a dark board, graphite on
83
+ paper), and a `palette` for the other usual colours. `colour.own` only for brushes that must
78
84
  always paint their own (a rainbow marker).
85
+ - Stamp tips paint the sticker's shape in the paint colour; to keep a sticker's own colours, use
86
+ `marks` objects instead.
79
87
 
80
88
  ## Pitfalls
81
89
 
82
90
  - **A facet the host refuses.** `create_brush` and `update_brush` refuse it with the facet and
83
91
  fix; `check_brush` returns `degraded`, the brush with those facets stripped, to start from.
84
92
  - **Ids.** Built-in ids can't be reused or changed; a taken id is refused (update it instead).
85
- - **Stamps.** Particle marks lay only the editor's stamps (`editor:star`…); sticker (`objects`)
86
- marks take any sticker ref from `find_stickers`.
93
+ - **Stamps.** Any marks take any sticker ref from `find_stickers`, or an editor stamp
94
+ (`editor:star`…). A ref the host's sticker library can't load is refused with the closest
95
+ refs. Only the paint editor renders stamp tips and scatter marks; canvas, vector and the image
96
+ editor refuse them (`check_brush` says so).
87
97
  - **Out-of-range values** are refused by the schema (`tip.size` is 1..2500 px); a stored brush
88
98
  is clamped on import and reported in `problems`.
89
99
  - **The paint editor paints from this library.** Its `set_brush` picks a brush by id here and
@@ -101,8 +111,8 @@ drawn.
101
111
  | Tool | Use |
102
112
  | --- | --- |
103
113
  | `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. |
104
- | `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. |
105
- | `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`. |
114
+ | `describe_brush` | One brush by `id`: its full facet `brush` (with its `colour`: default, palette, own), `builtin`, `engine`, `category` and `check` for this host (stamp stickers included). Unknown ids are refused with the closest ones. |
115
+ | `create_brush` | Save a new brush: `id`, `name`, optional `basedOn`, and the facets, `colour` included (to keep an editor's current brush, pass the `brush` its `get_brush` returns). Normalised and checked for this host (and its sticker library, for stamp tips); refused brushes save nothing. Returns `id`, `brush`, `engine`, `problems`, `warnings`. |
106
116
  | `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`. |
107
117
  | `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. |
108
118
  | `remove_brush` | Delete a saved brush for good (export it first to keep a copy). Built-ins are refused. Returns `removed`. |
@@ -57,6 +57,9 @@ Ask when you cannot tell from the request; say what you assumed when you can.
57
57
  | A brush that must work in another editor | `check_brush` with that `host`, then `create_brush` from its `degraded` brush under a new id. |
58
58
  | A texture (chalk, charcoal, dry brush) | Raise `behaviour.grain` (0.8..1) and add `dynamics.sizeJitter`; test on rough paper. |
59
59
  | A light brush (neon, glow) | `light.amount`, and paint it on dark paper; a highlighter keeps `light.spill.radius` 0. |
60
+ | A colour that depends on the background (chalk, white ink) | `colour.default` as a light / dark pair `{ light, dark }`: the editor takes the side for its colour scheme when the brush is chosen. |
61
+ | A shape as the tip (stars, leaves, a logo) | `tip.shape` stamp with `stamps` (an editor stamp or a ref from `find_stickers`), continuous marks; it paints in the user's colour. Keep the sticker's colours with `marks` objects. Paint editor only. |
62
+ | A spray can or mist | `basedOn` `spray-can`, or `medium` spray with `marks` scatter (`density`, `size`). Paint editor only. |
60
63
  | A change to a built-in | `duplicate_brush`, then `update_brush` the copy. |
61
64
 
62
65
  ## Recipes
@@ -64,8 +67,9 @@ Ask when you cannot tell from the request; say what you assumed when you can.
64
67
  **A pink highlighter.** `create_brush` `{ id: "pink-highlighter", name: "Pink highlighter",
65
68
  basedOn: "highlighter", colour: { default: "#ff6ec7" } }`, then `check_brush`.
66
69
 
67
- **Chalk for canvas strokes.** On the canvas host, `create_brush` `{ id: "chalk", name: "Chalk",
68
- basedOn: "soft-pastel", variety: "chalk", behaviour: { grain: 1 }, dynamics: { sizeJitter: 0.12 } }`.
70
+ **Chalk on a blackboard, for canvas strokes.** On the canvas host, `create_brush`
71
+ `{ id: "board-chalk", name: "Board chalk", basedOn: "chalk", colour: { default: { light:
72
+ "#334155", dark: "#f8fafc" } } }`: white chalk in dark mode, slate on light paper.
69
73
 
70
74
  **Port a wet brush to vector.** `check_brush` `{ id, host: "vector" }`; `create_brush` from the
71
75
  `degraded` brush with `id` `<id>-vector`; `check_brush` again.
@@ -1,15 +1,15 @@
1
1
  {
2
- "prompt": "I'm drawing brushed strokes on the canvas. Make a soft, grainy chalk brush with the id chalk that the canvas can actually use.",
2
+ "prompt": "I'm drawing brushed strokes on the canvas, on a dark blackboard in dark mode and on paper in light mode. Make a grainy chalk brush with the id board-chalk that the canvas can use: near-white chalk in dark mode, a dark slate on light backgrounds.",
3
3
  "fixture": { "editor": "brushes", "host": "canvas" },
4
4
  "checks": [
5
- { "why": "the canvas can render it", "tool": "check_brush", "args": { "id": "chalk", "host": "canvas" }, "expect": { "ok": true } },
6
- { "why": "a dry chalk", "tool": "describe_brush", "args": { "id": "chalk" }, "expect": { "brush": { "kind": "mark", "medium": "dry", "variety": "chalk" } } },
7
- { "why": "it catches the paper grain strongly", "tool": "describe_brush", "args": { "id": "chalk" }, "expect": { "brush": { "behaviour": { "grain": { "$min": 0.8 } } } } },
8
- { "why": "soft-edged", "tool": "describe_brush", "args": { "id": "chalk" }, "expect": { "brush": { "tip": { "hardness": { "$max": 0.6 } } } } }
5
+ { "why": "the canvas can render it", "tool": "check_brush", "args": { "id": "board-chalk", "host": "canvas" }, "expect": { "ok": true } },
6
+ { "why": "a dry chalk", "tool": "describe_brush", "args": { "id": "board-chalk" }, "expect": { "brush": { "kind": "mark", "medium": "dry", "variety": "chalk" } } },
7
+ { "why": "it catches the paper grain strongly", "tool": "describe_brush", "args": { "id": "board-chalk" }, "expect": { "brush": { "behaviour": { "grain": { "$min": 0.8 } } } } },
8
+ { "why": "its default colour is a light / dark pair: light chalk on dark, dark on light", "tool": "describe_brush", "args": { "id": "board-chalk" }, "expect": { "brush": { "colour": { "default": { "$expr": "typeof v === 'object' && /^#[c-f]/i.test(v.dark) && /^#[0-6]/i.test(v.light)" } } } } }
9
9
  ],
10
10
  "solution": [
11
- { "tool": "list_brush_presets", "args": { "medium": "dry" } },
12
- { "tool": "create_brush", "args": { "id": "chalk", "name": "Chalk", "basedOn": "soft-pastel", "variety": "chalk", "behaviour": { "grain": 1 }, "dynamics": { "sizeJitter": 0.12 } } },
13
- { "tool": "check_brush", "args": { "id": "chalk", "host": "canvas" } }
11
+ { "tool": "list_brush_presets", "args": { "variety": "chalk" } },
12
+ { "tool": "create_brush", "args": { "id": "board-chalk", "name": "Board chalk", "basedOn": "chalk", "colour": { "default": { "light": "#334155", "dark": "#f8fafc" } } } },
13
+ { "tool": "check_brush", "args": { "id": "board-chalk", "host": "canvas" } }
14
14
  ]
15
15
  }
@@ -94,8 +94,9 @@ you to. Withdraw stale ones with `reject_proposal`: when the user changes what a
94
94
  use the preset's own size (`canvas.listBrushes` gives it); pass it only to make the mark finer
95
95
  or bolder.
96
96
  - **Brush character.** Each preset draws with its own character: charcoal and pastel are broad and
97
- grainy (they catch the paper's tooth), pencil is fine and slightly grainy, ink, marker and brush
98
- pen are clean, the airbrush is a soft spray. The grain and jitter are seeded per stroke, so a
97
+ grainy (they catch the paper's tooth), as are crayon and chalk; pencil is fine and slightly
98
+ grainy; ink, marker, brush pen, ballpoint, gel pen and fineliner are clean (the fineliner keeps
99
+ one width), the airbrush is a soft spray. The grain and jitter are seeded per stroke, so a
99
100
  stroke always looks the same, and neighbouring strokes share one sheet of paper. Pick the brush
100
101
  for the look, then the colour; for a sketch, use one or two brushes rather than all of them.
101
102
  - **Annotate with gestures, not points.** To mark up work the way a designer marks up a print,
@@ -314,7 +314,7 @@ in the source frame at the document's bit depth, colour mode and profile. Read
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
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
+ | `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: pencils, pastels, charcoal, crayon, chalk, markers, ink pens, ballpoint, gel pen, fineliner, the airbrush, the Mixer Brush, erasers). Sets the paint tool it needs (`brush`, `mixer` for a Mixer Brush, `eraser`), its brush and mixer settings, a brush's own gradient, and its default colour as the foreground (`keepColour` keeps yours; `colorScheme` picks the side of a light / dark colour). 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. |
318
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`. |
319
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. |
320
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. |
@@ -11,6 +11,9 @@ 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
+ - **Brush colours.** Choosing a brush with a default colour (highlighter yellow, ballpoint
15
+ blue, chalk light or dark for the colour scheme) makes it the paint colour, unless the colour is
16
+ locked (`set_color` `locked`). Set the colour after choosing the brush, or lock it first.
14
17
  - **Brushes** come from the shared brush library (`pixerate-brushes`: `list_brush_presets`,
15
18
  `create_brush`, `preview_brush`…) and are made of facets: a tip (size, hardness, spacing, angle,
16
19
  roundness, opacity, flow, blend), dynamics (pressure, tilt, velocity, jitter, a **rotation**
@@ -101,58 +104,32 @@ the pitfalls, and uses the tools below.
101
104
 
102
105
  ## Best practices
103
106
 
104
- - **Big to small, dark to light (or light to dark), one decision per layer.** Name layers by
105
- purpose ("Underpainting", "Shadows", "Line"). Lower a layer's `opacity` instead of repainting.
106
- - **Pressure is your value control** on grainy media: points with `pressure` 0.2..0.4 give
107
- texture-catching, broken marks; 0.8..1 fills the grain. Vary pressure along a stroke (swell in
108
- the middle, lighten at the ends) for natural marks.
109
- - **Give points a time `t`** (milliseconds, about 8..16 ms apart for natural speed) when using an
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.
107
+ - **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.
108
+ - **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.
109
+ - **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.
110
+ - **Tilt** (`altitude` near 0.3..0.6 radians with an `azimuth`) turns a pencil into broad side shading; upright points draw a fine line.
111
+ - **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.
112
+ - **Use `rotation: "stroke"`** for flat tips (pastel, charcoal, chisel marker) so the mark turns with the path; `fixed` for calligraphic nibs.
117
113
  - **Blending tools**:
118
- - `blender`: `strength` 0.3..0.6 for soft transitions, `distance` 0.2..0.5 (fraction of the
119
- tip) for how far colours travel. Keep `mixing: "pigment"` for painterly blends; `rgb`
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.
114
+ - `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.
115
+ - `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
116
  - 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
117
+ - **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
118
  `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;
119
+ - **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
120
  `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).
121
+ - **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
122
  - **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
- each glaze darkens and blue over yellow makes green. White is clear: paint glazes on a
138
- layer over the underpainting, at opacity 0.4..0.8.
139
- - `tint`: recolours what is below (its hue and chroma) and keeps its lightness, gently near
140
- white and black. Use it to warm or cool a whole passage.
141
- - `highlight`: adds coloured light that rolls off towards white instead of clipping. Use it
142
- for glints and glow; black does nothing.
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.
123
+ - `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.
124
+ - `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.
125
+ - `highlight`: adds coloured light that rolls off towards white instead of clipping. Use it for glints and glow; black does nothing.
126
+ - `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.
127
+
128
+ 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.
129
+ - **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
130
  `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
- 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.
131
+ - **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
132
+ `export_brushes` / `import_brushes`. A saved brush shows in every editor that shares the library; `set_brush` its id to paint with it.
156
133
 
157
134
  Which facet sets which engine setting: [references/brush-facets.md](references/brush-facets.md).
158
135
 
@@ -272,6 +249,17 @@ is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hos
272
249
  | `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
250
  | `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
251
 
252
+ ### Suggesting paint
253
+
254
+ Contributing while the user paints: suggest, don't paint. Read [references/painting-alongside.md](references/painting-alongside.md).
255
+
256
+ | Tool | Use |
257
+ | --- | --- |
258
+ | `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. |
259
+ | `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. |
260
+ | `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. |
261
+ | `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. |
262
+
275
263
  ### Layers
276
264
 
277
265
  | Tool | Use |
@@ -307,7 +295,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
307
295
  | Tool | Use |
308
296
  | --- | --- |
309
297
  | `mix_colors` | Predict a mix: `colors` with `weight` parts (and optional tinting `strength`), `model` `pigment` (default, Kubelka-Munk: what pigment blends, the Mixer Brush, pigment blending tools and the pad do) or `rgb` (what normal blending and rgb mixing do). Returns `mixed` by the model plus `pigment` and `rgbLerp` for comparison. Changes nothing. Use it to choose stroke colours. |
310
- | `set_color` | Set the default paint `color` (and `secondaryColor`); back to one colour after gradient paint or effect paint. |
298
+ | `set_color` | Set the default paint `color` (and `secondaryColor`); back to one colour after gradient paint or effect paint. `locked: true` keeps it when you choose a brush with a default colour (by default choosing the highlighter makes your colour its yellow). Returns both colours and `locked`. |
311
299
  | `set_paint` | Set the paint: one colour, or gradient paint (`stops`, `space`, `map`, `softness`; see Gradient paint). Ends effect paint. Errors say what is wrong (unsorted stops, a bad mapping). Returns `paint`, `color`, `secondaryColor`. |
312
300
  | `pick_color` | Read the colour at `x`, `y`: of the painting with its paper, or of one layer (with alpha). |
313
301
  | `get_mixer_brush` | Read what the brush carries: the Mixer Brush `reservoir` and `amount` left, and `pickup` (paint picked up, kept by dirty brushes). |
@@ -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 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. |
376
+ | `vector.listBrushes` | List the brushes a brushstroke can use: the dry presets (pencils, pastels, charcoal, crayon, chalk, markers, inks, ballpoint, gel pen, fineliner), 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