@pixerate/editor 0.270.0 → 0.272.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 (77) hide show
  1. package/dist/animation-editor/index.cjs +62 -62
  2. package/dist/animation-editor/index.js +5 -5
  3. package/dist/brushes/index.cjs +69 -66
  4. package/dist/brushes/index.js +6 -3
  5. package/dist/brushes/patch.d.cts +1 -1
  6. package/dist/brushes/patch.d.ts +1 -1
  7. package/dist/brushes/schema.d.cts +1 -0
  8. package/dist/brushes/schema.d.ts +1 -0
  9. package/dist/brushes/types.d.cts +8 -0
  10. package/dist/brushes/types.d.ts +8 -0
  11. package/dist/{brushstrokeRender-WR2N5HHY.cjs → brushstrokeRender-5YBCLFDQ.cjs} +15 -15
  12. package/dist/{brushstrokeRender-A727TTNQ.js → brushstrokeRender-DZGFU3J4.js} +3 -3
  13. package/dist/canvas/index.cjs +15 -15
  14. package/dist/canvas/index.js +2 -2
  15. package/dist/{chunk-THBJHPUL.js → chunk-2F3YDXCN.js} +1 -1
  16. package/dist/{chunk-4CPOKP27.js → chunk-2XDNF73E.js} +1 -1
  17. package/dist/{chunk-3KNQ2W6X.cjs → chunk-3JKCREEU.cjs} +59 -53
  18. package/dist/{chunk-4YSPQGZW.js → chunk-5HP2VSAS.js} +1 -1
  19. package/dist/{chunk-BPJSI4NT.cjs → chunk-7W2EQQBP.cjs} +2 -2
  20. package/dist/{chunk-LETF75CA.js → chunk-CLB2I2FU.js} +1 -1
  21. package/dist/{chunk-BAZMCFVL.js → chunk-DZVRH63Y.js} +1 -1
  22. package/dist/{chunk-PY4GWSE2.cjs → chunk-EBFPMSJO.cjs} +27 -5
  23. package/dist/{chunk-VBRHE4AM.cjs → chunk-FXVCB6YG.cjs} +35 -35
  24. package/dist/{chunk-EU2ZZA6L.cjs → chunk-GHGBIAWQ.cjs} +126 -39
  25. package/dist/{chunk-CBVNCWJM.cjs → chunk-LBDC5434.cjs} +2 -2
  26. package/dist/{chunk-2WOGXEDX.cjs → chunk-M3RZOF3G.cjs} +1 -1
  27. package/dist/{chunk-GSTSZPSR.js → chunk-N3SKPQEH.js} +225 -18
  28. package/dist/{chunk-J5VQYK7T.cjs → chunk-N5IMDAHM.cjs} +251 -44
  29. package/dist/{chunk-ININNHLM.js → chunk-NAHJV3O6.js} +11 -5
  30. package/dist/{chunk-MWUZCEFT.js → chunk-S2XPXA47.js} +1 -1
  31. package/dist/{chunk-56RHCIJV.cjs → chunk-SNAFFCAX.cjs} +296 -203
  32. package/dist/{chunk-EHLOCVWB.js → chunk-T2URHAJX.js} +27 -5
  33. package/dist/{chunk-BENSE3VX.cjs → chunk-TNLKFW7D.cjs} +23 -23
  34. package/dist/{chunk-KZI3YWKW.js → chunk-UXYCY3HP.js} +93 -6
  35. package/dist/{chunk-4NMPUJUK.cjs → chunk-YJANF2BL.cjs} +86 -86
  36. package/dist/{chunk-FUDP5QBB.js → chunk-ZEI7ZEA4.js} +101 -8
  37. package/dist/effects/index.cjs +5 -2
  38. package/dist/effects/index.js +4 -1
  39. package/dist/effects/pool.d.cts +9 -0
  40. package/dist/effects/pool.d.ts +9 -0
  41. package/dist/effects/types.d.cts +2 -0
  42. package/dist/effects/types.d.ts +2 -0
  43. package/dist/image-editor/index.cjs +791 -791
  44. package/dist/image-editor/index.js +6 -6
  45. package/dist/paint/index.cjs +2 -2
  46. package/dist/paint/index.js +1 -1
  47. package/dist/paint/wet/world.d.cts +37 -1
  48. package/dist/paint/wet/world.d.ts +37 -1
  49. package/dist/paint-editor/brush-controls.d.cts +2 -0
  50. package/dist/paint-editor/brush-controls.d.ts +2 -0
  51. package/dist/paint-editor/brush-model.d.cts +14 -1
  52. package/dist/paint-editor/brush-model.d.ts +14 -1
  53. package/dist/paint-editor/controller.d.cts +75 -4
  54. package/dist/paint-editor/controller.d.ts +75 -4
  55. package/dist/paint-editor/effect-paint.d.cts +33 -8
  56. package/dist/paint-editor/effect-paint.d.ts +33 -8
  57. package/dist/paint-editor/effects.d.cts +4 -0
  58. package/dist/paint-editor/effects.d.ts +4 -0
  59. package/dist/paint-editor/index.cjs +814 -483
  60. package/dist/paint-editor/index.js +408 -77
  61. package/dist/paint-editor/stroke.d.cts +13 -5
  62. package/dist/paint-editor/stroke.d.ts +13 -5
  63. package/dist/paint-editor/ui.d.cts +1 -1
  64. package/dist/paint-editor/ui.d.ts +1 -1
  65. package/dist/paint-editor/wet.d.cts +8 -1
  66. package/dist/paint-editor/wet.d.ts +8 -1
  67. package/dist/vector/index.cjs +999 -999
  68. package/dist/vector/index.js +5 -5
  69. package/dist/video-editor/index.cjs +317 -317
  70. package/dist/video-editor/index.js +2 -2
  71. package/package.json +1 -1
  72. package/skills/pixerate-brushes/SKILL.md +1 -0
  73. package/skills/pixerate-paint-editor/SKILL.md +6 -13
  74. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  75. package/skills/pixerate-paint-editor/references/effect-paint.md +28 -6
  76. package/skills/pixerate-paint-editor/references/ghost.md +47 -0
  77. package/skills/pixerate-paint-watercolour/SKILL.md +1 -0
@@ -149,7 +149,7 @@ import {
149
149
  videoTransitionEase,
150
150
  videoValueAt,
151
151
  videoWipeRect
152
- } from "../chunk-MWUZCEFT.js";
152
+ } from "../chunk-S2XPXA47.js";
153
153
  import {
154
154
  namedWithSuggestion
155
155
  } from "../chunk-EUKY4KFX.js";
@@ -160,7 +160,7 @@ import {
160
160
  effectTemplateTools,
161
161
  fillEffectTemplate
162
162
  } from "../chunk-DHQPRKXF.js";
163
- import "../chunk-EHLOCVWB.js";
163
+ import "../chunk-T2URHAJX.js";
164
164
  import {
165
165
  CAMERA_MOVE_MAX_MS,
166
166
  EffectsSlot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.270.0",
3
+ "version": "0.272.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@ built-in presets (read-only) and the user's brushes, shared by every editor that
20
20
  | Dynamics | Pen feel: pressure to size and opacity, smoothing, tilt, velocity, jitter (`sizeJitter`, `angleJitter` for rotation, `opacityJitter`, `scatter`), rotation, `count` (dabs per spacing step, each jittered) and `flip` (mirror dabs at random: `x`, `y`, `xy`). |
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
+ | Ghost | On wet paint (`behaviour.surface: "wet"`): `linger` (0.5..60 s before the brush's own colour fades away) and `smear` (0..1, how much it lifts and drags the paint it crosses). Presets `ghost-wash`, `ghost-gouache`, `ghost-oil` and `invisible-ink`. |
23
24
  | Motion | Animation after painting (live effect paint, particle `waveSpeed`). |
24
25
  | 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. Canvas strokes and vector brushstrokes take it too, when they are given no colour. Canvas and vector have the same lock (`canvas.setColorLocked`, `vector.setColorLocked`), and a canvas colour taken from a light / dark default follows the scheme like the paint editor's; vector strokes already made keep theirs. |
25
26
 
@@ -142,10 +142,7 @@ Which facet sets which engine setting: [references/brush-facets.md](references/b
142
142
 
143
143
  ## Wet media
144
144
 
145
- Watercolour, gouache and wet oil run on a fluid simulation: lay washes light to dark, let them
146
- flow (`advance_wet_paint`), dry them (`dry_paint`) before glazing. Nothing moves for an agent
147
- until you advance or dry it, and saving dries the paint. Load `pixerate-paint-watercolour`, and
148
- read [references/wet-media.md](references/wet-media.md) for the engine and the wet tools.
145
+ Watercolour, gouache and wet oil run on a fluid simulation: lay washes light to dark, let them flow (`advance_wet_paint`), dry them (`dry_paint`) before glazing. Nothing moves for an agent until you advance or dry it, and saving dries the paint. Load `pixerate-paint-watercolour`, and read [references/wet-media.md](references/wet-media.md) for the engine and the wet tools. **Ghost paint and invisible ink** (`ghost` facet: `ghost-wash`, `ghost-gouache`, `ghost-oil`, `invisible-ink`) mix and flow while wet, then fade without leaving their own colour, leaving only a smear in the paint they touched: read [references/ghost.md](references/ghost.md).
149
146
 
150
147
  ## Oils and thick paint
151
148
 
@@ -235,7 +232,7 @@ When to use which:
235
232
  | The effect in a wash that flows, or in thick paint | `set_effect_paint` and a watercolour, gouache, oil or impasto brush |
236
233
  | The effect inside an exact shape, unchanged by water | Paint the shape, dry it, then `fill_layer_with_effect` with `lockTransparency: true` |
237
234
  | The effect over the whole sheet | `set_paper_effect` or `add_effect_layer` |
238
- | A pattern that follows the stroke, or paint that moves | `map: { by: "stroke" }`, `live: true` on a stroke layer |
235
+ | A pattern that follows the stroke, or paint that moves | `map: { by: "stroke" }` (bends any effect as a strip), or `map: { by: "path" }` for effects with a path control (`accepts: ["path"]`: beams, trails, signatures); `live: true` on a stroke layer |
239
236
 
240
237
  ## Canvas-hosted layer
241
238
 
@@ -295,7 +292,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
295
292
  | Tool | Use |
296
293
  | --- | --- |
297
294
  | `get_brush` | Read a brush as facets (`brush`), or the active brush as edited (no `id`), with the `engine` and `category` it paints with, `builtin` (an unchanged preset) and `strokePaint` (what its next stroke paints). Read before changing a brush you did not build. |
298
- | `set_brush` | Choose the active brush (`id`, from `list_brush_presets`) and / or lay facets over it: `tip`, `marks`, `dynamics`, `behaviour`, `light`, `motion`, `colour` (objects merge, lists replace; marks of another kind replace the marks), `unset` facets to drop. Editor state, not saved: `create_brush` stores a brush. Returns `brush` (facets), `engine` and `problems` (values clamped). The old flat fields (`particle`, `impasto`, `behavior`…) are refused. |
295
+ | `set_brush` | Choose the active brush (`id`, from `list_brush_presets`) and / or lay facets over it: `tip`, `marks`, `dynamics`, `behaviour`, `light`, `ghost`, `motion`, `colour` (objects merge, lists replace; marks of another kind replace the marks), `unset` facets to drop. Editor state, not saved: `create_brush` stores a brush. Returns `brush` (facets), `engine` and `problems` (values clamped). The old flat fields (`particle`, `impasto`, `behavior`…) are refused. |
299
296
 
300
297
  ### Colour
301
298
 
@@ -345,7 +342,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
345
342
 
346
343
  | Tool | Use |
347
344
  | --- | --- |
348
- | `get_wet_paint` | Read the wet paint: `wet`, the clock, `paused`, `speed`, `tilt`, the kernel (`backend`, `message` when it runs slower than it could, e.g. on one thread because the page is not cross-origin isolated (COOP / COEP)), the GPU accelerator (`engine`) and each wet layer (`wetCells`, `wetBounds`, `activeTiles`, settings, flow map). Call after wet strokes. |
345
+ | `get_wet_paint` | Read the wet paint: `wet`, the clock, `paused`, `speed`, `tilt`, the kernel (`backend`, `message` when it runs slower than it could, e.g. on one thread because the page is not cross-origin isolated (COOP / COEP)), the GPU accelerator (`engine`) and each wet layer (`wetCells`, `wetBounds`, `activeTiles`, settings, flow map, and `ghost: { remaining }` while ghost paint is fading). Call after wet strokes. |
349
346
  | `advance_wet_paint` | Let time pass: `steps` (120 = one second at the default speed) or `seconds`. The paint spreads, blooms, runs and dries. Not an undo step. Returns `steps`, `clock`, `stillWet`, `layers`. Call after a group of wet strokes, before `render_preview`. |
350
347
  | `dry_paint` | Dry wet paint at once where it is (one layer with `layerId` or all, optionally inside `rect`). One undo step. Returns `dried` (layer ids and rectangles) and `stillWet`. Dry before glazing over a wash. |
351
348
  | `wet_paper` | Pre-wet the paper with clear water over `rect` (default the canvas) on a layer, `amount` 0..1, for wet-in-wet. One undo step. Returns the rectangle and `wet`. Then paint into it. |
@@ -394,7 +391,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
394
391
  | `set_paper_effect` | Draw a generator over the paper (`values`, `opacity`, `blend` default `multiply`, `time`); `effect: null` removes it. One undo step. Returns the effect paper. |
395
392
  | `add_effect_layer` | Add a layer drawn by a generator, overlay or title effect (`values`, `name`, `aboveId`, `opacity`, `blend`, `time`); it becomes active. Returns the layer and its effect. |
396
393
  | `fill_layer_with_effect` | Composite a generator, overlay or title effect onto a layer (`layerId`, `values`, `opacity`, `lockTransparency`, `time`). One undo step. Returns the layer id and the effect. |
397
- | `set_effect_paint` | Paint with a generator, overlay or title effect (`effect`, `values`, `time` ms default 0): rendered once at the canvas size, strokes take each pixel's colour from it; `map` `{ by: "stroke", length, repeat }` lays it along each stroke; `live` keeps it playing on stroke layers. Call after `describe_effect`, then `paint_stroke`. Editor state, not an undo step. Returns `effectPaint`. |
394
+ | `set_effect_paint` | Paint with a generator, overlay or title effect (`effect`, `values`, `time` ms default 0): rendered once at the canvas size, strokes take each pixel's colour from it; `map` `{ by: "stroke", length, repeat }` bends a strip along each stroke, or `{ by: "path" }` passes the stroke's centreline to an effect with a path control (`format: "svg-path"`, `accepts: ["path"]`); `live` keeps it playing on stroke layers. Call after `describe_effect`, then `paint_stroke`. Editor state, not an undo step. Returns `effectPaint`. |
398
395
  | `set_effect_playback` | Play or pause live effect paint in the view (`playing`) and cap its frame rate (`fps` 1..60). Editor state. Returns `playing`, `fps`, `time` and `animation` (what plays, or null). Export a moment with `export_image` / `export_psd` `time`. |
399
396
 
400
397
  ### Guides
@@ -476,11 +473,7 @@ The host may add the shared document tools (see `pixerate-agent-tools`). A paint
476
473
  - **Wet paint keeps changing.** A preview right after a wet stroke shows it before it has
477
474
  flowed; `advance_wet_paint` first. Paint that should stay exactly where it was laid needs
478
475
  `dry_paint` (or a dry medium).
479
- - **Wet strokes on one layer interact**; washes that must not mix go on separate layers, or dry
480
- the first before painting the second (glazing). At most 8 layers hold wet paint at once, and the simulation holds at most 328 wet tiles (256 × 256 px each) across them; water that spreads past
481
- that limit is lost (`dry_paint` frees tiles). Multi-threaded wet media needs a cross-origin isolated
482
- page (COOP / COEP, for `SharedArrayBuffer`) and runs on one thread otherwise, more slowly but with
483
- the same result.
476
+ - **Wet strokes on one layer interact**; washes that must not mix go on separate layers, or dry the first before painting the second (glazing). Ghost paint (`ghost-wash`, `ghost-gouache`, `ghost-oil`, `invisible-ink`) only meets and smears paint on its own layer, so a ghost stroke on a new empty layer simply vanishes; ghost paint and light paint cannot share a layer while the ghost is fading. At most 8 layers hold wet paint at once, and the simulation holds at most 328 wet tiles (256 × 256 px each) across them; water that spreads past that limit is lost (`dry_paint` frees tiles). Multi-threaded wet media needs a cross-origin isolated page (COOP / COEP, for `SharedArrayBuffer`) and runs on one thread otherwise, more slowly but with the same result.
484
477
  - **Undoing far back** past the wet undo memory (512 MB by default) leaves that paint dry as it
485
478
  looked, rather than wet.
486
479
  - **Replays repeat what was recorded**: strokes, fills, clears, flattened relief and wet events.
@@ -20,6 +20,7 @@ more; this is where each one went.
20
20
  | Bristles | `tip.shape` `bristle` and `tip.bristle` |
21
21
  | A brush's own gradient (paint) | `colour.default` (a gradient); `unset: ['colour']` drops it |
22
22
  | Light | `light` (`amount`, `pressure`) |
23
+ | Ghost paint (linger, smear) | `ghost` (`linger`, `smear`); `unset: ['ghost']` drops it |
23
24
 
24
25
  ## Examples
25
26
 
@@ -1,14 +1,36 @@
1
1
  # Effect paint along the stroke, and live effect paint
2
2
 
3
- - **Along the stroke** (`set_effect_paint` `map: { by: "stroke", length, repeat }`): the effect is
4
- rendered as a strip `length` px long (16..4096, default 300) and 256 px across. Each pixel takes
3
+ - **Along the stroke (native path)** (`set_effect_paint` `map: { by: "path" }`): when an effect has
4
+ a path control (`format: "svg-path"`, reported by `describe_effect` as `pathControls` and
5
+ `accepts: ["path"]`), each stroke passes its centreline `[x0, y0, x1, y1, …]` in canvas pixels
6
+ directly to that control and renders at the canvas size. The effect draws and lights itself along
7
+ the stroke's exact curve (e.g. a neon beam, laser trail or signature whose glow and lighting
8
+ belong to the path), while the brush's dabs and coverage still decide where paint lands. Strokes
9
+ with more than 256 points are evenly resampled to 256 points rather than refused. **Prefer
10
+ `by: "path"` over `by: "stroke"` whenever the effect has a path control** (`accepts: ["path"]`);
11
+ `set_effect_paint` refuses `by: "path"` on effects without a path control and tells you to use
12
+ `by: "stroke"` or `by: "canvas"`. Each stroke is a render of its own at the canvas size, so:
13
+ - A stroke painted by hand shows the effect along the path drawn so far while it is drawn,
14
+ re-rendered one render at a time, so it trails the pen by about one render. It is painted
15
+ again along its whole path when the pen lifts, as one undo step; if the preview already
16
+ rendered the whole path, at once. On a slow provider the editor is busy until that lands
17
+ (`effectPaintRendering`). `paint_stroke` and `stroke_path` know the path up front and paint
18
+ it at once.
19
+ - Wet brushes can't be painted by hand this way, as the simulation can't run twice; give them
20
+ the points through `paint_stroke`, or use `by: "stroke"`.
21
+ - With `live: true`, every stroke is a live source of its own, so at most 8 can play.
22
+ - Symmetry copies sample the same render, which only follows the original stroke: paint each
23
+ copy as its own stroke if it should draw too.
24
+ - **Along the stroke (strip)** (`set_effect_paint` `map: { by: "stroke", length, repeat }`): for
25
+ effects without a path control, the effect is rendered as a strip `length` px long (16..4096,
26
+ default 300) and 256 px across. Each pixel takes
5
27
  the colour at its distance along the stroke and its place across the tip, so the pattern follows
6
28
  the stroke's turns instead of staying fixed to the canvas. `repeat` starts the strip again
7
29
  (default), `mirror` runs it back, `clamp` holds the last colour. Use it for ribbons, ropes,
8
- flames and textures that should flow with the mark; use the default canvas mapping to reveal one
9
- hidden picture across many strokes. Every colour-laying brush takes it (watercolour, gouache, oil
10
- and impasto too; sprayed stickers at their centre); particle and bristle marks take the place of
11
- their pen dab.
30
+ flames and textures that have no path control of their own; use the default canvas mapping to
31
+ reveal one hidden picture across many strokes. Every colour-laying brush takes it (watercolour,
32
+ gouache, oil and impasto too; sprayed stickers at their centre); particle and bristle marks take
33
+ the place of their pen dab.
12
34
  Scaled replays (`export_high_res`) scale `length` with the canvas.
13
35
  - **Live** (`live: true`): on a stroke layer (`add_layer {type: "strokes"}`) each stroke keeps
14
36
  playing the effect, so the paint moves. Paint the strokes, then `set_effect_playback`
@@ -0,0 +1,47 @@
1
+ # Ghost paint and invisible ink
2
+
3
+ Ghost paint is wet paint (`behaviour.surface: "wet"`) with a `ghost` facet (`linger` 0.5..60 s,
4
+ default 4; `smear` 0..1, default 0.3). While wet it looks and behaves like any watercolour,
5
+ gouache or wet oil: it flows with the water and its colour mixes visibly with the paint it meets.
6
+ Then its own colour fades away completely, leaving only what it did to the paint already on the
7
+ layer — a smear where it lifted and dragged that paint along. On bare paper it leaves nothing at
8
+ all.
9
+
10
+ ## Presets and settings
11
+
12
+ - **`ghost-wash`**: transparent watercolour ghost (`linger: 4`, `smear: 0.35`).
13
+ - **`ghost-gouache`**: opaque gouache ghost that mixes strongly while wet, then vanishes (`linger: 4`, `smear: 0.45`).
14
+ - **`ghost-oil`**: slow wet oil ghost (`linger: 8`, `smear: 0.55`).
15
+ - **`invisible-ink`**: a ghost brush with `behaviour.wet.pigment: 0` and `smear: 0.6`. Nothing
16
+ shows on bare paper; it reveals itself only as a smear in the wet or dried paint it crosses.
17
+ Because it lays no pigment, it lays no tracer, needs no fade, and does not interrupt another
18
+ ghost already fading on the layer.
19
+ - Any wet paint brush becomes a ghost brush with `set_brush` `{ ghost: { linger, smear } }`;
20
+ `unset: ["ghost"]` makes it ordinary wet paint again.
21
+
22
+ ## Working method
23
+
24
+ 1. **Paint on the layer that holds the paint you want to disturb.** Wet paint is per layer:
25
+ a ghost stroke on a new empty layer has nothing to smear and vanishes into bare paper.
26
+ 2. **Watch it mix while wet.** Call `advance_wet_paint` in short steps (`seconds: 0.5..1`) and
27
+ `render_preview` to see the ghost colour mixing and flowing with the layer's paint, and check
28
+ `get_wet_paint` (`layers[].ghost.remaining`, from 1 down to 0) to see how much of the fade is
29
+ left.
30
+ 3. **Finish the fade.** Advance past `linger` seconds, or call `dry_paint` (which finishes the
31
+ ghost fade at once before baking). With `smear: 0`, ghost paint passes over dried paint and
32
+ leaves it untouched; with `smear > 0`, it lifts dried paint along its path (`rewet` scaled by
33
+ `smear`) and drags both wet and lifted paint along the stroke.
34
+
35
+ ## Pitfalls
36
+
37
+ - **Wet paint is per layer.** Ghost paint only meets paint on its own layer. Painting a ghost
38
+ stroke on a new empty layer leaves nothing behind once it fades.
39
+ - **One ghost colour per layer at a time.** Painting a ghost stroke in a new colour on a layer
40
+ whose earlier ghost is still fading finishes the earlier ghost's colour fade at once (keeping
41
+ its smear) before starting the new colour.
42
+ - **Ghost and light paint cannot share a layer.** The ghost tracer uses the wet layer's light
43
+ channel, so a ghost stroke is refused on a layer that has light paint (`clear_light` or paint on
44
+ another layer), and a light brush is refused on a layer where ghost paint is still fading
45
+ (`dry_paint` first or use another layer). Once the ghost has faded, the layer is free again.
46
+ - **One solid colour per stroke.** Gradient paint, effect paint and multi-colour are refused on
47
+ ghost brushes.
@@ -77,6 +77,7 @@ block the light. See `references/light-paint.md` in `pixerate-paint-editor`.
77
77
  | Cauliflower blooms | Add water into a drying wash; `bloom` up |
78
78
  | A glowing (neon) wash | Dark paper (`set_paper` `color`), `glow-wash` or `light.amount` on a wet brush; flow, dry, `get_light` |
79
79
  | Stained glass, a lightbox | `set_paper` `stained-glass` (or `backlight`); strong glazes for panes, opaque black lines above |
80
+ | Disturb or smear a wash without adding colour | `ghost-wash`, `ghost-gouache` or `invisible-ink` (`ghost` `linger`, `smear`) on the same layer; see `references/ghost.md` |
80
81
 
81
82
  ## Recipes
82
83