@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.
- package/dist/animation-editor/index.cjs +62 -62
- package/dist/animation-editor/index.js +5 -5
- package/dist/brushes/index.cjs +69 -66
- package/dist/brushes/index.js +6 -3
- package/dist/brushes/patch.d.cts +1 -1
- package/dist/brushes/patch.d.ts +1 -1
- package/dist/brushes/schema.d.cts +1 -0
- package/dist/brushes/schema.d.ts +1 -0
- package/dist/brushes/types.d.cts +8 -0
- package/dist/brushes/types.d.ts +8 -0
- package/dist/{brushstrokeRender-WR2N5HHY.cjs → brushstrokeRender-5YBCLFDQ.cjs} +15 -15
- package/dist/{brushstrokeRender-A727TTNQ.js → brushstrokeRender-DZGFU3J4.js} +3 -3
- package/dist/canvas/index.cjs +15 -15
- package/dist/canvas/index.js +2 -2
- package/dist/{chunk-THBJHPUL.js → chunk-2F3YDXCN.js} +1 -1
- package/dist/{chunk-4CPOKP27.js → chunk-2XDNF73E.js} +1 -1
- package/dist/{chunk-3KNQ2W6X.cjs → chunk-3JKCREEU.cjs} +59 -53
- package/dist/{chunk-4YSPQGZW.js → chunk-5HP2VSAS.js} +1 -1
- package/dist/{chunk-BPJSI4NT.cjs → chunk-7W2EQQBP.cjs} +2 -2
- package/dist/{chunk-LETF75CA.js → chunk-CLB2I2FU.js} +1 -1
- package/dist/{chunk-BAZMCFVL.js → chunk-DZVRH63Y.js} +1 -1
- package/dist/{chunk-PY4GWSE2.cjs → chunk-EBFPMSJO.cjs} +27 -5
- package/dist/{chunk-VBRHE4AM.cjs → chunk-FXVCB6YG.cjs} +35 -35
- package/dist/{chunk-EU2ZZA6L.cjs → chunk-GHGBIAWQ.cjs} +126 -39
- package/dist/{chunk-CBVNCWJM.cjs → chunk-LBDC5434.cjs} +2 -2
- package/dist/{chunk-2WOGXEDX.cjs → chunk-M3RZOF3G.cjs} +1 -1
- package/dist/{chunk-GSTSZPSR.js → chunk-N3SKPQEH.js} +225 -18
- package/dist/{chunk-J5VQYK7T.cjs → chunk-N5IMDAHM.cjs} +251 -44
- package/dist/{chunk-ININNHLM.js → chunk-NAHJV3O6.js} +11 -5
- package/dist/{chunk-MWUZCEFT.js → chunk-S2XPXA47.js} +1 -1
- package/dist/{chunk-56RHCIJV.cjs → chunk-SNAFFCAX.cjs} +296 -203
- package/dist/{chunk-EHLOCVWB.js → chunk-T2URHAJX.js} +27 -5
- package/dist/{chunk-BENSE3VX.cjs → chunk-TNLKFW7D.cjs} +23 -23
- package/dist/{chunk-KZI3YWKW.js → chunk-UXYCY3HP.js} +93 -6
- package/dist/{chunk-4NMPUJUK.cjs → chunk-YJANF2BL.cjs} +86 -86
- package/dist/{chunk-FUDP5QBB.js → chunk-ZEI7ZEA4.js} +101 -8
- package/dist/effects/index.cjs +5 -2
- package/dist/effects/index.js +4 -1
- package/dist/effects/pool.d.cts +9 -0
- package/dist/effects/pool.d.ts +9 -0
- package/dist/effects/types.d.cts +2 -0
- package/dist/effects/types.d.ts +2 -0
- package/dist/image-editor/index.cjs +791 -791
- package/dist/image-editor/index.js +6 -6
- package/dist/paint/index.cjs +2 -2
- package/dist/paint/index.js +1 -1
- package/dist/paint/wet/world.d.cts +37 -1
- package/dist/paint/wet/world.d.ts +37 -1
- package/dist/paint-editor/brush-controls.d.cts +2 -0
- package/dist/paint-editor/brush-controls.d.ts +2 -0
- package/dist/paint-editor/brush-model.d.cts +14 -1
- package/dist/paint-editor/brush-model.d.ts +14 -1
- package/dist/paint-editor/controller.d.cts +75 -4
- package/dist/paint-editor/controller.d.ts +75 -4
- package/dist/paint-editor/effect-paint.d.cts +33 -8
- package/dist/paint-editor/effect-paint.d.ts +33 -8
- package/dist/paint-editor/effects.d.cts +4 -0
- package/dist/paint-editor/effects.d.ts +4 -0
- package/dist/paint-editor/index.cjs +814 -483
- package/dist/paint-editor/index.js +408 -77
- package/dist/paint-editor/stroke.d.cts +13 -5
- package/dist/paint-editor/stroke.d.ts +13 -5
- package/dist/paint-editor/ui.d.cts +1 -1
- package/dist/paint-editor/ui.d.ts +1 -1
- package/dist/paint-editor/wet.d.cts +8 -1
- package/dist/paint-editor/wet.d.ts +8 -1
- package/dist/vector/index.cjs +999 -999
- package/dist/vector/index.js +5 -5
- package/dist/video-editor/index.cjs +317 -317
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +1 -0
- package/skills/pixerate-paint-editor/SKILL.md +6 -13
- package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
- package/skills/pixerate-paint-editor/references/effect-paint.md +28 -6
- package/skills/pixerate-paint-editor/references/ghost.md +47 -0
- package/skills/pixerate-paint-watercolour/SKILL.md +1 -0
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
BRUSH_PATCH_PROPERTIES,
|
|
7
7
|
BRUSH_SLIDERS,
|
|
8
8
|
BRUSH_UNSET_PROPERTY,
|
|
9
|
+
GHOST_SLIDERS,
|
|
9
10
|
brushEngineControls,
|
|
10
11
|
createBrushLibraryToolProvider,
|
|
11
12
|
mergeBrushPatch,
|
|
12
13
|
multiColorSliders,
|
|
13
14
|
patchBrushValue
|
|
14
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-NAHJV3O6.js";
|
|
15
16
|
import {
|
|
16
17
|
createStickerTools,
|
|
17
18
|
describeStickerRef
|
|
@@ -19,10 +20,10 @@ import {
|
|
|
19
20
|
import {
|
|
20
21
|
BRUSH_PREVIEW_DARK_PAPER,
|
|
21
22
|
brushPreviewsOnDark
|
|
22
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-CLB2I2FU.js";
|
|
23
24
|
import {
|
|
24
25
|
toVideoFfmpeg
|
|
25
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-S2XPXA47.js";
|
|
26
27
|
import {
|
|
27
28
|
ToolRegistry
|
|
28
29
|
} from "../chunk-K4BGRCSH.js";
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
resolveExternalAssets,
|
|
35
36
|
serializeLayers,
|
|
36
37
|
serializeLayersExternal
|
|
37
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-5HP2VSAS.js";
|
|
38
39
|
import "../chunk-ADMRNWR7.js";
|
|
39
40
|
import "../chunk-WJ4WQJCC.js";
|
|
40
41
|
import {
|
|
@@ -79,6 +80,7 @@ import {
|
|
|
79
80
|
EFFECT_STRIP_HEIGHT,
|
|
80
81
|
GPU_LEAD,
|
|
81
82
|
IDENTITY_COPY,
|
|
83
|
+
MAX_EFFECT_PATH_POINTS,
|
|
82
84
|
PAPER_FAMILIES,
|
|
83
85
|
PAPER_LIBRARY,
|
|
84
86
|
PaintDabStroke,
|
|
@@ -95,6 +97,7 @@ import {
|
|
|
95
97
|
effectPaintKey,
|
|
96
98
|
effectPaintSize,
|
|
97
99
|
hexToRgb8,
|
|
100
|
+
inferEffectPathControl,
|
|
98
101
|
isPaintEffectPaint,
|
|
99
102
|
mixerTip,
|
|
100
103
|
multiColorForDab,
|
|
@@ -109,11 +112,13 @@ import {
|
|
|
109
112
|
sampleEffectAt,
|
|
110
113
|
sampleEffectField,
|
|
111
114
|
stampSampler,
|
|
115
|
+
strokePathPoints,
|
|
112
116
|
tiltToOrientation,
|
|
113
117
|
toStrokePoint,
|
|
114
118
|
wetPaperInput,
|
|
115
|
-
wetPigmentOf
|
|
116
|
-
|
|
119
|
+
wetPigmentOf,
|
|
120
|
+
withStrokePath
|
|
121
|
+
} from "../chunk-ZEI7ZEA4.js";
|
|
117
122
|
import {
|
|
118
123
|
BRUSH_ID,
|
|
119
124
|
BRUSH_PRESETS,
|
|
@@ -125,6 +130,7 @@ import {
|
|
|
125
130
|
DEFAULT_BRISTLE,
|
|
126
131
|
DEFAULT_BRUSH_ID,
|
|
127
132
|
DEFAULT_DYNAMICS,
|
|
133
|
+
DEFAULT_GHOST,
|
|
128
134
|
DEFAULT_IMPASTO,
|
|
129
135
|
DEFAULT_LIGHT,
|
|
130
136
|
DEFAULT_MULTI_COLOR,
|
|
@@ -164,6 +170,7 @@ import {
|
|
|
164
170
|
normalizeBristle,
|
|
165
171
|
normalizeBrushSpec,
|
|
166
172
|
normalizeDynamics,
|
|
173
|
+
normalizeGhost,
|
|
167
174
|
normalizeImpasto,
|
|
168
175
|
normalizeLight,
|
|
169
176
|
normalizeMultiColor,
|
|
@@ -176,7 +183,7 @@ import {
|
|
|
176
183
|
readBrushSource,
|
|
177
184
|
resolveBrush,
|
|
178
185
|
usesPaintExchange
|
|
179
|
-
} from "../chunk-
|
|
186
|
+
} from "../chunk-UXYCY3HP.js";
|
|
180
187
|
import {
|
|
181
188
|
STAMP_KINDS,
|
|
182
189
|
StickersSlot,
|
|
@@ -201,11 +208,12 @@ import {
|
|
|
201
208
|
} from "../chunk-SZTI6EER.js";
|
|
202
209
|
import {
|
|
203
210
|
EffectRendererPool
|
|
204
|
-
} from "../chunk-
|
|
211
|
+
} from "../chunk-T2URHAJX.js";
|
|
205
212
|
import {
|
|
206
213
|
EffectUnsupportedError,
|
|
207
214
|
EffectsSlot,
|
|
208
215
|
createEffectToolProvider,
|
|
216
|
+
pathControlsOf,
|
|
209
217
|
resolveEffectValues
|
|
210
218
|
} from "../chunk-MV7CYFMJ.js";
|
|
211
219
|
import "../chunk-V2ZCJRDM.js";
|
|
@@ -223,7 +231,7 @@ import {
|
|
|
223
231
|
normalizeWetParams,
|
|
224
232
|
pigmentOf,
|
|
225
233
|
wetSupport
|
|
226
|
-
} from "../chunk-
|
|
234
|
+
} from "../chunk-N3SKPQEH.js";
|
|
227
235
|
import "../chunk-W6JDUXP3.js";
|
|
228
236
|
import {
|
|
229
237
|
CoverageMap,
|
|
@@ -2103,7 +2111,6 @@ var PaintStrokeSession = class _PaintStrokeSession {
|
|
|
2103
2111
|
const brush = init.brush;
|
|
2104
2112
|
if (init.wet) this.wetDriver = init.wet.driver;
|
|
2105
2113
|
if (init.light && !init.wet && !init.sticker) this.lightTwin = this.makeLightTwin(init.light.surface);
|
|
2106
|
-
const effect = init.effect?.pixels ?? null;
|
|
2107
2114
|
this.apply = {
|
|
2108
2115
|
mode: brush.engine === "eraser" ? "erase" : "paint",
|
|
2109
2116
|
color: init.color,
|
|
@@ -2114,31 +2121,7 @@ var PaintStrokeSession = class _PaintStrokeSession {
|
|
|
2114
2121
|
selection: init.selection ?? null,
|
|
2115
2122
|
offset: init.offset
|
|
2116
2123
|
};
|
|
2117
|
-
if (this.wetDriver)
|
|
2118
|
-
} else if (init.sticker) {
|
|
2119
|
-
this.stickerSpray = new StickerSpray({ brush, target: this.tileTarget(), color: init.colorHex, seed: init.seed, copies: this.copies, nextId: init.sticker.nextId, paint: init.paint ?? null, effect, art: init.sticker.art, variant: init.sticker.variant });
|
|
2120
|
-
} else if (init.thick) {
|
|
2121
|
-
const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
|
|
2122
|
-
this.thickDriver = new ThickStrokeDriver({ brush, target: this.tileTarget(), channels: init.thick.channels, color: rgb(init.color), load: init.mixerLoad, seed: init.seed, copies: this.copies, paint: init.paint ?? null, effect });
|
|
2123
|
-
} else if (brush.engine === "mixer" || brush.engine === "blender" || brush.engine === "smudge" || usesPaintExchange(brush, Boolean(init.paint))) {
|
|
2124
|
-
const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
|
|
2125
|
-
this.wet = new PaintDabStroke(brush, {
|
|
2126
|
-
target: this.tileTarget(),
|
|
2127
|
-
color: rgb(init.color),
|
|
2128
|
-
secondary: rgb(init.secondary ?? init.background),
|
|
2129
|
-
load: init.mixerLoad,
|
|
2130
|
-
grain: init.grain,
|
|
2131
|
-
paperDepth: init.paperDepth,
|
|
2132
|
-
seed: init.seed,
|
|
2133
|
-
copies: this.copies,
|
|
2134
|
-
paint: init.paint ?? null,
|
|
2135
|
-
effect,
|
|
2136
|
-
stampArt: init.stampArt
|
|
2137
|
-
});
|
|
2138
|
-
} else {
|
|
2139
|
-
const opts = { grain: init.grain, paperDepth: init.paperDepth, seed: init.seed, copies: this.copies, paint: init.paint ?? null, effect, stampArt: init.stampArt };
|
|
2140
|
-
this.dry = brush.engine === "particle" ? new ParticleStroke(brush, opts) : brush.engine === "bristle" ? new BristleStroke(brush, opts) : new DryStroke(brush, opts);
|
|
2141
|
-
}
|
|
2124
|
+
if (!this.wetDriver) this.build(init.effect?.pixels ?? null);
|
|
2142
2125
|
}
|
|
2143
2126
|
host;
|
|
2144
2127
|
init;
|
|
@@ -2167,6 +2150,52 @@ var PaintStrokeSession = class _PaintStrokeSession {
|
|
|
2167
2150
|
result = null;
|
|
2168
2151
|
/** The mask stencils this stroke was painted with when they are not the editor's (replays). */
|
|
2169
2152
|
maskStencils = null;
|
|
2153
|
+
/** Sets up the marks' engine (every kind but wet, which the wet layer paints), sampling `effect` for effect paint. */
|
|
2154
|
+
build(effect) {
|
|
2155
|
+
const init = this.init;
|
|
2156
|
+
const brush = init.brush;
|
|
2157
|
+
this.dry = this.wet = this.thickDriver = this.stickerSpray = null;
|
|
2158
|
+
if (init.sticker) {
|
|
2159
|
+
this.stickerSpray = new StickerSpray({ brush, target: this.tileTarget(), color: init.colorHex, seed: init.seed, copies: this.copies, nextId: init.sticker.nextId, paint: init.paint ?? null, effect, art: init.sticker.art, variant: init.sticker.variant });
|
|
2160
|
+
} else if (init.thick) {
|
|
2161
|
+
const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
|
|
2162
|
+
this.thickDriver = new ThickStrokeDriver({ brush, target: this.tileTarget(), channels: init.thick.channels, color: rgb(init.color), load: init.mixerLoad, seed: init.seed, copies: this.copies, paint: init.paint ?? null, effect });
|
|
2163
|
+
} else if (brush.engine === "mixer" || brush.engine === "blender" || brush.engine === "smudge" || usesPaintExchange(brush, Boolean(init.paint))) {
|
|
2164
|
+
const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
|
|
2165
|
+
this.wet = new PaintDabStroke(brush, {
|
|
2166
|
+
target: this.tileTarget(),
|
|
2167
|
+
color: rgb(init.color),
|
|
2168
|
+
secondary: rgb(init.secondary ?? init.background),
|
|
2169
|
+
load: init.mixerLoad,
|
|
2170
|
+
grain: init.grain,
|
|
2171
|
+
paperDepth: init.paperDepth,
|
|
2172
|
+
seed: init.seed,
|
|
2173
|
+
copies: this.copies,
|
|
2174
|
+
paint: init.paint ?? null,
|
|
2175
|
+
effect,
|
|
2176
|
+
stampArt: init.stampArt
|
|
2177
|
+
});
|
|
2178
|
+
} else {
|
|
2179
|
+
const opts = { grain: init.grain, paperDepth: init.paperDepth, seed: init.seed, copies: this.copies, paint: init.paint ?? null, effect, stampArt: init.stampArt };
|
|
2180
|
+
this.dry = brush.engine === "particle" ? new ParticleStroke(brush, opts) : brush.engine === "bristle" ? new BristleStroke(brush, opts) : new DryStroke(brush, opts);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Paints the stroke so far again, sampling new effect pixels: a path-mapped effect rendered
|
|
2185
|
+
* along the path drawn so far (`map: { by: 'path' }`). Only the live preview changes; the stroke
|
|
2186
|
+
* keeps recording its own paint. Nothing for wet strokes or once the stroke has ended.
|
|
2187
|
+
*/
|
|
2188
|
+
repaintEffect(pixels) {
|
|
2189
|
+
if (this.closed || this.wetDriver || !this.init.effect) return;
|
|
2190
|
+
const was = this.bounds;
|
|
2191
|
+
this.live = new Map(this.base.tiles);
|
|
2192
|
+
this.touched.clear();
|
|
2193
|
+
this.fresh.clear();
|
|
2194
|
+
this.bounds = null;
|
|
2195
|
+
this.build(pixels);
|
|
2196
|
+
const rects = this.points.length ? this.dry ? this.dry.push(this.points) : this.thickDriver ? this.thickDriver.push(this.points) : this.stickerSpray ? this.stickerSpray.push(this.points) : this.wet.push(this.points) : [];
|
|
2197
|
+
this.update(was ? [...rects, was] : rects);
|
|
2198
|
+
}
|
|
2170
2199
|
/** The stroke's twin over the light channel: the same marks in grey at the brush's light amount (see `light.ts`). */
|
|
2171
2200
|
makeLightTwin(light) {
|
|
2172
2201
|
const init = this.init;
|
|
@@ -2703,7 +2732,8 @@ function varyStroke(stroke, m, v) {
|
|
|
2703
2732
|
const brush = s !== 1 ? normalizePaintBrush({ ...stroke.brush, tip: { ...stroke.brush.tip, size: stroke.brush.tip.size * s } }) : stroke.brush;
|
|
2704
2733
|
const out = { ...stroke, brush, points };
|
|
2705
2734
|
if (stroke.paint?.type === "gradient" && s !== 1) out.paint = scalePaintGradient(stroke.paint, s);
|
|
2706
|
-
if (stroke.paint?.type === "effect" && stroke.paint.map && s !== 1) out.paint = { ...stroke.paint, map: { ...stroke.paint.map, length: Math.min(4096, Math.max(16, Math.round(stroke.paint.map.length * s))) } };
|
|
2735
|
+
if (stroke.paint?.type === "effect" && stroke.paint.map?.by === "stroke" && s !== 1) out.paint = { ...stroke.paint, map: { ...stroke.paint.map, length: Math.min(4096, Math.max(16, Math.round(stroke.paint.map.length * s))) } };
|
|
2736
|
+
if (stroke.paint?.type === "effect" && stroke.paint.map?.by === "path") out.paint = withStrokePath(stroke.paint, points);
|
|
2707
2737
|
if (stroke.copies?.length) {
|
|
2708
2738
|
const inv = invertAffine(m);
|
|
2709
2739
|
out.copies = stroke.copies.map((c) => composeAffine(m, composeAffine(c, inv)));
|
|
@@ -4067,7 +4097,7 @@ async function replayPainting(source, target, options = {}) {
|
|
|
4067
4097
|
const chunk = Math.max(1, options.wetChunk ?? 0) || Infinity;
|
|
4068
4098
|
let strokes = 0;
|
|
4069
4099
|
const scaled = source.strokes.slice(0, maxStrokes).map((st) => s === 1 ? st : varyStroke(st, scaleM, { scale: s }));
|
|
4070
|
-
await target.prepareEffectPaints(scaled.flatMap((st) => st.paint?.type === "effect" ? [st.paint] : []));
|
|
4100
|
+
await target.prepareEffectPaints(scaled.flatMap((st) => st.paint?.type === "effect" && st.paint.map?.by !== "path" ? [st.paint] : []));
|
|
4071
4101
|
const advanceTo = async (clock) => {
|
|
4072
4102
|
const now = target.getState().wet.clock;
|
|
4073
4103
|
let left = clock - now;
|
|
@@ -4139,6 +4169,7 @@ async function replayPainting(source, target, options = {}) {
|
|
|
4139
4169
|
const st = s === 1 ? item.stroke : varyStroke(item.stroke, scaleM, { scale: s });
|
|
4140
4170
|
strokes++;
|
|
4141
4171
|
if (!target.getLayer(st.layerId)) continue;
|
|
4172
|
+
if (st.paint?.type === "effect" && st.paint.map?.by === "path") await target.prepareEffectPaints([st.paint]);
|
|
4142
4173
|
const init = { layerId: st.layerId, color: st.color, brush: st.brush, seed: st.seed, copies: st.copies ?? [], ...st.load ? { load: st.load } : {}, ...st.paint ? { paint: st.paint } : {}, snapToPerspective: false, stencils: false, maskStencils: (st.stencils ?? []).map((x) => s === 1 ? x : scaleStencil(x, s)) };
|
|
4143
4174
|
if (st.brush.engine === "wet" && st.steps?.length === st.points.length) {
|
|
4144
4175
|
await target.wetIdle();
|
|
@@ -4321,6 +4352,12 @@ var PaintEffectsRenderer = class {
|
|
|
4321
4352
|
}
|
|
4322
4353
|
return out;
|
|
4323
4354
|
}
|
|
4355
|
+
renderSync(provider, id, size, request) {
|
|
4356
|
+
this.follow(provider);
|
|
4357
|
+
const out = this.pool.renderSync(provider, id, { ...size, depth: 8 }, request);
|
|
4358
|
+
if (!out || out.width !== size.width || out.height !== size.height || out.data.length !== size.width * size.height * 4) return null;
|
|
4359
|
+
return out;
|
|
4360
|
+
}
|
|
4324
4361
|
dispose() {
|
|
4325
4362
|
this.pool.clear();
|
|
4326
4363
|
this.descriptions.clear();
|
|
@@ -4439,6 +4476,17 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4439
4476
|
paintEpoch = 0;
|
|
4440
4477
|
/** Rendered effect paints by `effectPaintKey` (most recently used last). */
|
|
4441
4478
|
effectPaintPixels = /* @__PURE__ */ new Map();
|
|
4479
|
+
/** Primary path control name (`format: 'svg-path'`) per effect id, learned from `describeEffect`. */
|
|
4480
|
+
effectPathControls = /* @__PURE__ */ new Map();
|
|
4481
|
+
/** How many renders the running batch needs together (`prepareEffectPaints`; a synchronous replay reads them all). */
|
|
4482
|
+
effectPaintBatch = 0;
|
|
4483
|
+
/** The hand-painted stroke waiting for its path render (`finishPathStroke`), or `null`. */
|
|
4484
|
+
pathStroke = null;
|
|
4485
|
+
/**
|
|
4486
|
+
* The hand-painted path stroke's live preview: the effect rendered along the path drawn so far
|
|
4487
|
+
* (one render at a time; the newest path when it lands), repainted into the stroke as it lands.
|
|
4488
|
+
*/
|
|
4489
|
+
pathPreview = null;
|
|
4442
4490
|
mixerLoad = EMPTY_MIXER_LOAD;
|
|
4443
4491
|
view = { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
|
|
4444
4492
|
hand;
|
|
@@ -4775,7 +4823,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4775
4823
|
secondaryColor: this.secondaryColor,
|
|
4776
4824
|
paint: this.gradient ?? { type: "color", color: this.color },
|
|
4777
4825
|
effectPaint: this.effectPaintPending ?? this.effectPaint,
|
|
4778
|
-
effectPaintRendering: Boolean(this.effectPaintPending),
|
|
4826
|
+
effectPaintRendering: Boolean(this.effectPaintPending || this.pathStroke),
|
|
4779
4827
|
mixerLoad: this.mixerLoad,
|
|
4780
4828
|
view: this.view,
|
|
4781
4829
|
hand: this.hand,
|
|
@@ -4837,6 +4885,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4837
4885
|
}
|
|
4838
4886
|
requireIdle(what) {
|
|
4839
4887
|
if (this.session) throw new PaintEditorError(`A stroke is in progress; end or cancel it before ${what}.`);
|
|
4888
|
+
if (this.pathStroke) throw new PaintEditorError(`The last stroke is still rendering the effect "${this.pathStroke.effect}" along its path; wait for it (strokeRendering) before ${what}.`);
|
|
4840
4889
|
if (this.grouping?.pending && !this.grouping.inside) throw new PaintEditorError(`"${this.grouping.label}" is still being painted (one undo step); wait for it to finish before ${what}.`);
|
|
4841
4890
|
}
|
|
4842
4891
|
commit(label, next, before = this.doc) {
|
|
@@ -5481,6 +5530,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5481
5530
|
/** The provider's description of an effect, checked for `place` (rejects without a provider or for an unknown id). */
|
|
5482
5531
|
async describeEffect(effect, place) {
|
|
5483
5532
|
const d = await this.effectsRenderer.describe(this.effects.require(), effect);
|
|
5533
|
+
const pcs = pathControlsOf(d);
|
|
5534
|
+
if (pcs.length) this.effectPathControls.set(d.id, pcs[0]);
|
|
5484
5535
|
if (place) checkPaintEffectPlace(d, place);
|
|
5485
5536
|
return d;
|
|
5486
5537
|
}
|
|
@@ -5681,7 +5732,11 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5681
5732
|
return { layerId: now.id, source: this.sourceOf(d, values, options.time) };
|
|
5682
5733
|
}
|
|
5683
5734
|
// --- Effect paint ----------------------------------------------------------------
|
|
5684
|
-
/**
|
|
5735
|
+
/**
|
|
5736
|
+
* At most this many rendered effect paints are kept (each is canvas-sized), more only while
|
|
5737
|
+
* something needs them all: a replay's batch, and the path-mapped strokes on stroke layers,
|
|
5738
|
+
* which each have a render of their own and are redrawn synchronously.
|
|
5739
|
+
*/
|
|
5685
5740
|
static EFFECT_PAINT_CACHE = 6;
|
|
5686
5741
|
/** The effect paint strokes use (or the one rendering), or `null`. */
|
|
5687
5742
|
getEffectPaint() {
|
|
@@ -5693,8 +5748,10 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5693
5748
|
* size, and brushes then take each pixel's colour (and alpha) from it, fixed to the canvas, as if
|
|
5694
5749
|
* painting a hidden picture into view. `setColor` and `setPaint` go back to colour. Editor state,
|
|
5695
5750
|
* not an undo step. Strokes that would paint with it throw while it renders. Returns the paint.
|
|
5696
|
-
* With `map: { by: 'stroke' }` the effect lies along each stroke
|
|
5697
|
-
* `
|
|
5751
|
+
* With `map: { by: 'stroke' }` the effect lies along each stroke as a strip; with
|
|
5752
|
+
* `map: { by: 'path' }` an effect with a path control (`format: 'svg-path'`) draws itself along
|
|
5753
|
+
* each stroke's centreline in canvas pixels; with `live`, strokes on a stroke layer keep playing
|
|
5754
|
+
* it (see `setEffectPlayback`).
|
|
5698
5755
|
*/
|
|
5699
5756
|
async setEffectPaint(source) {
|
|
5700
5757
|
if (!source || typeof source.effect !== "string" || !source.effect) throw new PaintEditorError("effect is missing; pass an effect id from search_effects (use generator, overlay or title).");
|
|
@@ -5709,6 +5766,9 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5709
5766
|
const epoch = ++this.paintEpoch;
|
|
5710
5767
|
const changed = (name) => new PaintEditorError(`The paint changed while "${name}" rendered (set_color, set_paint or another set_effect_paint); call set_effect_paint again to paint with it.`);
|
|
5711
5768
|
const d = await this.describeEffect(source.effect, "paint");
|
|
5769
|
+
if (map?.by === "path" && !pathControlsOf(d).length) {
|
|
5770
|
+
throw new PaintEditorError(`Effect "${d.id}" has no path control (format "svg-path"); use by: "stroke" (bent along each stroke) or by: "canvas" (fixed to the canvas).`);
|
|
5771
|
+
}
|
|
5712
5772
|
const values = this.effectValues(d, { ...d.defaults }, source.values);
|
|
5713
5773
|
if (epoch !== this.paintEpoch) throw changed(d.name);
|
|
5714
5774
|
const paint = { type: "effect", effect: d.id, provider: this.effects.require().id, values, time: source.time ?? 0, ...map ? { map } : {}, ...source.live ? { live: true } : {} };
|
|
@@ -5740,10 +5800,38 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5740
5800
|
this.effectPaintPixels.set(key, img);
|
|
5741
5801
|
return img;
|
|
5742
5802
|
}
|
|
5803
|
+
cacheEffectPaintPixels(p, pixels) {
|
|
5804
|
+
return this.cacheEffectPaintImage(p, toEffectPaintPixels(p, pixels));
|
|
5805
|
+
}
|
|
5806
|
+
cacheEffectPaintImage(p, img) {
|
|
5807
|
+
this.effectPaintPixels.set(effectPaintKey(p), img);
|
|
5808
|
+
this.trimEffectPaints();
|
|
5809
|
+
return img;
|
|
5810
|
+
}
|
|
5811
|
+
trimEffectPaints() {
|
|
5812
|
+
let live = 0;
|
|
5813
|
+
for (const list of Object.values(this.doc.liveStrokes ?? {})) for (const s of list) if (s.paint?.type === "effect" && s.paint.map?.by === "path") live++;
|
|
5814
|
+
const limit = Math.max(_PaintEditorController.EFFECT_PAINT_CACHE, this.effectPaintBatch, live + 1);
|
|
5815
|
+
while (this.effectPaintPixels.size > limit) this.effectPaintPixels.delete(this.effectPaintPixels.keys().next().value);
|
|
5816
|
+
}
|
|
5817
|
+
/** Renders an effect paint on the caller's stack when its renderer is warm and has `renderSync`; otherwise `null`. */
|
|
5818
|
+
renderEffectPaintSync(p) {
|
|
5819
|
+
const have = this.effectPaintImage(p);
|
|
5820
|
+
if (have) return have;
|
|
5821
|
+
const provider = this.effects.provider;
|
|
5822
|
+
if (!provider || p.provider && provider.id !== p.provider) return null;
|
|
5823
|
+
const size = effectPaintSize(p, this.doc.canvas);
|
|
5824
|
+
const pixels = this.effectsRenderer.renderSync(provider, p.effect, size, { time: p.time, values: p.values });
|
|
5825
|
+
return pixels ? this.cacheEffectPaintPixels(p, pixels) : null;
|
|
5826
|
+
}
|
|
5743
5827
|
/** Renders an effect paint at the canvas size into the cache (nothing when it is there already). */
|
|
5744
5828
|
async renderEffectPaint(p) {
|
|
5745
5829
|
const have = this.effectPaintImage(p);
|
|
5746
5830
|
if (have) return have;
|
|
5831
|
+
return this.cacheEffectPaintImage(p, await this.renderEffectPaintPixels(p));
|
|
5832
|
+
}
|
|
5833
|
+
/** Renders an effect paint at the canvas size without keeping it. */
|
|
5834
|
+
async renderEffectPaintPixels(p) {
|
|
5747
5835
|
const provider = this.effects.provider;
|
|
5748
5836
|
const what = `the effect "${p.effect}"${p.provider ? ` from provider "${p.provider}"` : ""}`;
|
|
5749
5837
|
if (!provider) throw new PaintEditorError(`Effect paint needs ${what}, but no effects provider is registered; register it (setEffectsProvider) and try again.`);
|
|
@@ -5755,28 +5843,107 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5755
5843
|
} catch (e) {
|
|
5756
5844
|
throw new PaintEditorError(`Effect paint could not render ${what}: ${e instanceof Error ? e.message : String(e)}`);
|
|
5757
5845
|
}
|
|
5758
|
-
|
|
5759
|
-
this.effectPaintPixels.set(effectPaintKey(p), img);
|
|
5760
|
-
while (this.effectPaintPixels.size > _PaintEditorController.EFFECT_PAINT_CACHE) this.effectPaintPixels.delete(this.effectPaintPixels.keys().next().value);
|
|
5761
|
-
return img;
|
|
5846
|
+
return toEffectPaintPixels(p, pixels);
|
|
5762
5847
|
}
|
|
5763
5848
|
/**
|
|
5764
5849
|
* Renders effect paints that are not rendered yet (strokes and replays need them rendered
|
|
5765
5850
|
* first, since strokes are synchronous). Throws naming the effect and provider when one cannot be.
|
|
5766
5851
|
*/
|
|
5767
5852
|
async prepareEffectPaints(paints2) {
|
|
5853
|
+
const list = [...paints2];
|
|
5854
|
+
this.effectPaintBatch = new Set(list.map(effectPaintKey)).size;
|
|
5768
5855
|
const seen = /* @__PURE__ */ new Set();
|
|
5769
|
-
for (const p of
|
|
5856
|
+
for (const p of list) {
|
|
5770
5857
|
const key = effectPaintKey(p);
|
|
5771
5858
|
if (seen.has(key)) continue;
|
|
5772
5859
|
seen.add(key);
|
|
5773
5860
|
await this.renderEffectPaint(p);
|
|
5774
5861
|
}
|
|
5775
5862
|
}
|
|
5863
|
+
/**
|
|
5864
|
+
* Renders the path-mapped effect paint for one or more strokes' points before a synchronous
|
|
5865
|
+
* stroke call runs (`paint_stroke` and `stroke_path` do; `paintStroke` throws without it on a
|
|
5866
|
+
* provider that can't render synchronously). For a stroke layer edit, see `prepareStrokeEdit`.
|
|
5867
|
+
*/
|
|
5868
|
+
async prepareStrokePathEffect(init, pointsList) {
|
|
5869
|
+
const brush = this.strokeBrush(init);
|
|
5870
|
+
if (!brushTakesPaint(brush)) return;
|
|
5871
|
+
let source = null;
|
|
5872
|
+
if (init.paint !== void 0) {
|
|
5873
|
+
if (!isPaintEffectPaint(init.paint)) return;
|
|
5874
|
+
source = readPaintEffectPaint(init.paint);
|
|
5875
|
+
} else if (init.color === void 0 && !brush.paint && !this.effectPaintPending) {
|
|
5876
|
+
source = this.effectPaint;
|
|
5877
|
+
}
|
|
5878
|
+
if (!source || source.map?.by !== "path") return;
|
|
5879
|
+
if (!this.effectPathControls.has(source.effect)) await this.describeEffect(source.effect);
|
|
5880
|
+
const control = this.effectPathControls.get(source.effect);
|
|
5881
|
+
const paints2 = pointsList.filter((pts) => pts.length > 0).map((pts) => withStrokePath(source, pts, control));
|
|
5882
|
+
if (paints2.length) await this.prepareEffectPaints(paints2);
|
|
5883
|
+
}
|
|
5884
|
+
/**
|
|
5885
|
+
* Renders what an edit of a stroke layer's stroke paints with (`updateStroke`, `editStrokePoints`):
|
|
5886
|
+
* a new effect paint, or a path-mapped stroke's new path. Those calls are synchronous, and throw
|
|
5887
|
+
* without it on a provider that can't render synchronously. Bad edits are left for them to refuse.
|
|
5888
|
+
*/
|
|
5889
|
+
async prepareStrokeEdit(id, edit) {
|
|
5890
|
+
const p = this.strokeEditPaint(id, edit);
|
|
5891
|
+
if (!p) return;
|
|
5892
|
+
if (p.map?.by === "path" && !this.effectPathControls.has(p.effect)) await this.describeEffect(p.effect);
|
|
5893
|
+
await this.prepareEffectPaints([this.strokeEditPaint(id, edit) ?? p]);
|
|
5894
|
+
}
|
|
5895
|
+
/** Whether `prepareStrokeEdit` has to be awaited before this edit can run. */
|
|
5896
|
+
strokeEditNeedsRender(id, edit) {
|
|
5897
|
+
const p = this.strokeEditPaint(id, edit);
|
|
5898
|
+
return Boolean(p && !this.effectPaintImage(p) && !this.renderEffectPaintSync(p));
|
|
5899
|
+
}
|
|
5900
|
+
strokeEditPaint(id, edit) {
|
|
5901
|
+
const was = Object.values(this.doc.liveStrokes ?? {}).flat().find((s) => s.id === id);
|
|
5902
|
+
if (!was) return null;
|
|
5903
|
+
try {
|
|
5904
|
+
if (edit.paint !== void 0) {
|
|
5905
|
+
if (!isPaintEffectPaint(edit.paint)) return null;
|
|
5906
|
+
const p = readPaintEffectPaint(edit.paint);
|
|
5907
|
+
return p && withStrokePath(p, was.points, this.effectPathControls.get(p.effect));
|
|
5908
|
+
}
|
|
5909
|
+
if (was.paint?.type !== "effect" || was.paint.map?.by !== "path") return null;
|
|
5910
|
+
if (edit.points !== void 0) return withStrokePath(was.paint, this.checkPoints(edit.points), this.effectPathControls.get(was.paint.effect));
|
|
5911
|
+
if (!edit.transform) return null;
|
|
5912
|
+
const varied = varyStroke(was, this.strokeTransform(was, edit.transform), {}).paint;
|
|
5913
|
+
return varied?.type === "effect" ? varied : null;
|
|
5914
|
+
} catch {
|
|
5915
|
+
return null;
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
/** A stroke edit's transform as an affine map, checked. */
|
|
5919
|
+
strokeTransform(stroke, t) {
|
|
5920
|
+
if (t.scale !== void 0 && !(t.scale > 0 && t.scale <= 20)) throw new PaintEditorError(`scale ${t.scale} is out of range; use above 0 up to 20.`);
|
|
5921
|
+
for (const k of ["dx", "dy", "rotate"]) if (t[k] !== void 0 && !Number.isFinite(t[k])) throw new PaintEditorError(`${k} must be a number.`);
|
|
5922
|
+
const b = strokeBounds(stroke.points);
|
|
5923
|
+
return variationAffine(t, t.origin ?? { x: b.x + b.width / 2, y: b.y + b.height / 2 });
|
|
5924
|
+
}
|
|
5776
5925
|
/** Renders the effect paints of recorded strokes (default all of them) before a replay. */
|
|
5777
|
-
async prepareStrokeEffects(strokeIds) {
|
|
5926
|
+
async prepareStrokeEffects(strokeIds, variation) {
|
|
5778
5927
|
const ids = strokeIds ? new Set(strokeIds) : null;
|
|
5779
|
-
const
|
|
5928
|
+
const strokes = this.doc.strokes.filter((s) => (!ids || ids.has(s.id)) && s.paint?.type === "effect");
|
|
5929
|
+
const paints2 = strokes.map((s) => s.paint);
|
|
5930
|
+
if (variation && strokes.length) {
|
|
5931
|
+
const repeat = Math.max(1, Math.min(100, variation.repeat ?? 1));
|
|
5932
|
+
let origin = variation.origin;
|
|
5933
|
+
if (!origin) {
|
|
5934
|
+
const b = strokeBounds(strokes.flatMap((s) => s.points));
|
|
5935
|
+
origin = { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
5936
|
+
}
|
|
5937
|
+
const step = variationAffine(variation, origin);
|
|
5938
|
+
let m = IDENTITY_AFFINE;
|
|
5939
|
+
for (let k = 0; k < repeat; k++) {
|
|
5940
|
+
m = [step[0] * m[0] + step[1] * m[2], step[0] * m[1] + step[1] * m[3], step[2] * m[0] + step[3] * m[2], step[2] * m[1] + step[3] * m[3], step[0] * m[4] + step[1] * m[5] + step[4], step[2] * m[4] + step[3] * m[5] + step[5]];
|
|
5941
|
+
for (const s of strokes) {
|
|
5942
|
+
const v = varyStroke(s, m, variation);
|
|
5943
|
+
if (v.paint?.type === "effect") paints2.push(v.paint);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5780
5947
|
await this.prepareEffectPaints(paints2);
|
|
5781
5948
|
}
|
|
5782
5949
|
/** A small preview of the effect paint (a PNG data URL `size` px wide), or `null` without a rendered one. */
|
|
@@ -5804,7 +5971,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5804
5971
|
* Throws while the editor's is still rendering, for an engine that cannot paint with it, or when
|
|
5805
5972
|
* its pixels are not rendered.
|
|
5806
5973
|
*/
|
|
5807
|
-
strokeEffect(init, brush) {
|
|
5974
|
+
strokeEffect(init, brush, points) {
|
|
5808
5975
|
if (!brushTakesPaint(brush)) return null;
|
|
5809
5976
|
let source = null;
|
|
5810
5977
|
if (init.paint !== void 0) {
|
|
@@ -5816,8 +5983,10 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5816
5983
|
source = this.effectPaint;
|
|
5817
5984
|
}
|
|
5818
5985
|
if (!source) return null;
|
|
5819
|
-
const
|
|
5820
|
-
if (
|
|
5986
|
+
const path = source.map?.by === "path";
|
|
5987
|
+
if (path && points?.length) source = withStrokePath(source, points, this.effectPathControls.get(source.effect));
|
|
5988
|
+
const pixels = this.effectPaintImage(source) ?? (path ? this.renderEffectPaintSync(source) : null);
|
|
5989
|
+
if (!pixels) throw new PaintEditorError(`The effect paint "${source.effect}"${source.provider ? ` from provider "${source.provider}"` : ""} is not rendered at this canvas size; call ${path ? "prepareStrokePathEffect (the paint_stroke tool does this) or prepareStrokeEdit" : "set_effect_paint (or prepareEffectPaints)"} first.`);
|
|
5821
5990
|
return { source, pixels };
|
|
5822
5991
|
}
|
|
5823
5992
|
/**
|
|
@@ -6205,7 +6374,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6205
6374
|
strokeCopies() {
|
|
6206
6375
|
return symmetryCopies(this.guides.symmetry);
|
|
6207
6376
|
}
|
|
6208
|
-
sessionFor(doc, init, host) {
|
|
6377
|
+
sessionFor(doc, init, host, points) {
|
|
6209
6378
|
if (init.paint !== void 0 && !isPaintEffectPaint(init.paint)) {
|
|
6210
6379
|
const p = this.readStrokePaint(init.paint);
|
|
6211
6380
|
if (p.type === "color") init = { ...init, color: p.color, paint: void 0 };
|
|
@@ -6224,9 +6393,27 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6224
6393
|
})();
|
|
6225
6394
|
const brush = this.strokeBrush(init);
|
|
6226
6395
|
if (this.previewing && isWetBrush(brush)) throw new PaintEditorError(`Wet paint can't be previewed; the ${brush.name} brush paints wet media. Suggest it with a dry brush.`);
|
|
6227
|
-
|
|
6396
|
+
if (brush.ghost) {
|
|
6397
|
+
if (brush.light.amount > 0) throw new PaintEditorError(`Ghost paint (${brush.name}) shares the wet light channel and can't emit light; set light.amount to 0 or ghost to null (set_brush).`);
|
|
6398
|
+
if (isPaintEffectPaint(init.paint) || init.paint === void 0 && init.color === void 0 && Boolean(this.effectPaint)) {
|
|
6399
|
+
throw new PaintEditorError(`Ghost paint (${brush.name}) needs a single colour to subtract as it fades, so it can't paint with an effect; set a solid colour (set_color) or pass color to paint_stroke.`);
|
|
6400
|
+
}
|
|
6401
|
+
if (brush.behavior.multicolor.mode !== "off") throw new PaintEditorError(`Ghost paint (${brush.name}) needs a single colour to subtract as it fades, so it can't use multi-colour paint; set behavior.multicolor.mode to "off" (set_brush).`);
|
|
6402
|
+
const gCol = init.color === void 0 ? this.ownColour(init.brushId) ?? this.color : parseColor(init.color);
|
|
6403
|
+
if (this.strokeGradient(init, brush, gCol)) throw new PaintEditorError(`Ghost paint (${brush.name}) needs a single colour to subtract as it fades, so it can't paint a gradient; set a solid colour (set_color) or pass color to paint_stroke.`);
|
|
6404
|
+
if (Boolean(doc.light?.[layer.id]?.tiles.size) || Boolean(this.wet.world?.hasLane(layer.id) && this.wet.world.isLit(layer.id))) {
|
|
6405
|
+
throw new PaintEditorError(`Layer "${layer.name}" holds light paint, and ghost paint shares the wet light channel; paint on another layer (add_layer) or clear the light first (clear_light).`);
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
const effect = this.strokeEffect(init, brush, points);
|
|
6409
|
+
if (!points && init.paint === void 0 && effect?.source.map?.by === "path" && isWetBrush(brush)) {
|
|
6410
|
+
throw new PaintEditorError(`The ${brush.name} brush paints wet media, which can't follow a path that isn't known until the stroke ends; paint with a dry brush, set_effect_paint with by: "stroke", or give the points to paint_stroke.`);
|
|
6411
|
+
}
|
|
6228
6412
|
if (brush.light.amount > 0) {
|
|
6229
6413
|
if (brush.engine === "sticker") throw new PaintEditorError(`The ${brush.name} brush sprays stickers, which don't take light yet; paint light with a dab or particle light brush (glow-gel, neon-marker; list_brush_presets lists them), or set light.amount to 0 (set_brush).`);
|
|
6414
|
+
if (Boolean(this.wet.world?.hasLane(layer.id) && this.wet.world.getGhost(layer.id)) || Boolean(this.wet.settings.get(layer.id)?.ghost)) {
|
|
6415
|
+
throw new PaintEditorError(`Layer "${layer.name}" has ghost paint fading on it, which uses the wet light channel; paint light on another layer (add_layer), or finish the ghost first (dry_paint).`);
|
|
6416
|
+
}
|
|
6230
6417
|
}
|
|
6231
6418
|
if (doc.liveStrokes?.[layer.id]) {
|
|
6232
6419
|
const why = liveStrokeRefusal(brush);
|
|
@@ -6279,7 +6466,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6279
6466
|
copies: [IDENTITY_AFFINE, ...copiesW].map(affineCopy),
|
|
6280
6467
|
mask: init.maskStencils ? this.stencilSelection(init.maskStencils) : init.stencils !== false ? this.stencilSelection() : null,
|
|
6281
6468
|
paint: effect ? null : wetGradient,
|
|
6282
|
-
effect: effect?.pixels ?? null
|
|
6469
|
+
effect: effect?.pixels ?? null,
|
|
6470
|
+
speed: this.wet.speed
|
|
6283
6471
|
});
|
|
6284
6472
|
init = { ...init, seed: seedW, load };
|
|
6285
6473
|
wet = { driver, surface: () => world.display(id).surface };
|
|
@@ -6374,9 +6562,19 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6374
6562
|
this.liveThick = ch ? { layerId, channels: ch } : null;
|
|
6375
6563
|
this.emit(rects);
|
|
6376
6564
|
},
|
|
6377
|
-
sampled: (s, points) =>
|
|
6565
|
+
sampled: (s, points) => {
|
|
6566
|
+
this.notifySample("points", s, points);
|
|
6567
|
+
this.previewPathStroke(s);
|
|
6568
|
+
},
|
|
6378
6569
|
commit: (s, surface, cancelled) => {
|
|
6379
6570
|
this.session = null;
|
|
6571
|
+
const preview = this.pathPreview?.session === s ? this.pathPreview : null;
|
|
6572
|
+
this.pathPreview = null;
|
|
6573
|
+
const pathPaint = cancelled ? null : this.pathStrokePaint(s);
|
|
6574
|
+
if (pathPaint) {
|
|
6575
|
+
this.finishPathStroke(s, pathPaint, preview);
|
|
6576
|
+
return;
|
|
6577
|
+
}
|
|
6380
6578
|
this.live = null;
|
|
6381
6579
|
this.liveLayers = null;
|
|
6382
6580
|
this.liveThick = null;
|
|
@@ -6425,14 +6623,99 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6425
6623
|
throw e;
|
|
6426
6624
|
}
|
|
6427
6625
|
this.session = session;
|
|
6626
|
+
if (session.init.effect?.source.map?.by === "path" && !session.init.wet) this.pathPreview = { session, key: "", pending: null, landed: null };
|
|
6428
6627
|
this.emit([]);
|
|
6429
6628
|
this.notifySample("begin", session);
|
|
6430
6629
|
return session;
|
|
6431
6630
|
}
|
|
6631
|
+
/**
|
|
6632
|
+
* Renders a hand-painted path stroke's effect along the path drawn so far and paints the stroke
|
|
6633
|
+
* again with it when it lands, so the effect follows the pen while it draws. One render at a
|
|
6634
|
+
* time: when one lands, the next takes the newest path. A failed render leaves the preview as it
|
|
6635
|
+
* was; the render on release reports it.
|
|
6636
|
+
*/
|
|
6637
|
+
previewPathStroke(s) {
|
|
6638
|
+
const pv = this.pathPreview;
|
|
6639
|
+
if (!pv || pv.session !== s || pv.pending || !s.open) return;
|
|
6640
|
+
const paint = this.pathStrokePaint(s);
|
|
6641
|
+
if (!paint) return;
|
|
6642
|
+
const key = effectPaintKey(paint);
|
|
6643
|
+
if (key === pv.key) return;
|
|
6644
|
+
pv.key = key;
|
|
6645
|
+
const cached = this.effectPaintImage(paint);
|
|
6646
|
+
const pending = cached ? Promise.resolve(cached) : this.renderEffectPaintPixels(paint);
|
|
6647
|
+
pv.pending = pending;
|
|
6648
|
+
pending.then(
|
|
6649
|
+
(pixels) => {
|
|
6650
|
+
pv.pending = null;
|
|
6651
|
+
pv.landed = { key, pixels };
|
|
6652
|
+
if (this.pathPreview !== pv || !s.open) return;
|
|
6653
|
+
s.repaintEffect(pixels);
|
|
6654
|
+
this.previewPathStroke(s);
|
|
6655
|
+
},
|
|
6656
|
+
() => {
|
|
6657
|
+
pv.pending = null;
|
|
6658
|
+
}
|
|
6659
|
+
);
|
|
6660
|
+
}
|
|
6432
6661
|
/** The current live stroke, if any. */
|
|
6433
6662
|
get activeStroke() {
|
|
6434
6663
|
return this.session;
|
|
6435
6664
|
}
|
|
6665
|
+
/**
|
|
6666
|
+
* While a hand-painted stroke waits for its path render (`map: { by: 'path' }`), settles when it
|
|
6667
|
+
* has landed and rejects if it could not (the stroke is dropped); `null` otherwise. Until then
|
|
6668
|
+
* the editor is busy, as during a stroke, and shows the stroke as it was painted.
|
|
6669
|
+
*/
|
|
6670
|
+
get strokeRendering() {
|
|
6671
|
+
return this.pathStroke?.done ?? null;
|
|
6672
|
+
}
|
|
6673
|
+
/** The paint a finished hand-painted stroke must be painted again with, or `null` when it already painted with it. */
|
|
6674
|
+
pathStrokePaint(s) {
|
|
6675
|
+
const source = s.init.effect?.source;
|
|
6676
|
+
if (source?.map?.by !== "path" || !s.points.length) return null;
|
|
6677
|
+
const paint = withStrokePath(source, s.points, this.effectPathControls.get(source.effect));
|
|
6678
|
+
return effectPaintKey(paint) === effectPaintKey(source) ? null : paint;
|
|
6679
|
+
}
|
|
6680
|
+
/**
|
|
6681
|
+
* Ends a hand-painted stroke with a path-mapped effect. Its whole path isn't known until now, so
|
|
6682
|
+
* the live stroke was only a preview from renders along the path so far (`previewPathStroke`):
|
|
6683
|
+
* nothing of it was committed. Paints it again with the path's own render, as one stroke and one undo step, at
|
|
6684
|
+
* once if that can render synchronously and when the render lands if not.
|
|
6685
|
+
*/
|
|
6686
|
+
finishPathStroke(s, paint, preview) {
|
|
6687
|
+
const finish = () => {
|
|
6688
|
+
this.pathStroke = null;
|
|
6689
|
+
this.live = null;
|
|
6690
|
+
this.liveLayers = null;
|
|
6691
|
+
this.liveThick = null;
|
|
6692
|
+
const masks = s.init.selection ? (s.maskStencils ?? this.guides.stencils).filter((x) => x.enabled && x.mode === "mask") : [];
|
|
6693
|
+
const load = s.init.mixerLoad;
|
|
6694
|
+
const result = this.paintStroke({ layerId: s.layerId, points: s.points, color: s.init.colorHex, brush: brushToJSON(s.init.brush), seed: s.init.seed, copies: s.init.affineCopies, ...load.reservoir || load.pickup ? { load } : {}, paint, snapToPerspective: false, stencils: false, maskStencils: masks });
|
|
6695
|
+
Object.assign(s.result, result);
|
|
6696
|
+
this.notifySample("end", s, [], result.strokeId);
|
|
6697
|
+
};
|
|
6698
|
+
const key = effectPaintKey(paint);
|
|
6699
|
+
if (preview?.landed?.key === key) this.cacheEffectPaintImage(paint, preview.landed.pixels);
|
|
6700
|
+
if (this.effectPaintImage(paint) || this.renderEffectPaintSync(paint)) {
|
|
6701
|
+
finish();
|
|
6702
|
+
return;
|
|
6703
|
+
}
|
|
6704
|
+
const render = preview?.pending && preview.key === key ? preview.pending.then((img) => this.cacheEffectPaintImage(paint, img)) : this.renderEffectPaint(paint);
|
|
6705
|
+
const done = render.then(finish, (e) => {
|
|
6706
|
+
this.pathStroke = null;
|
|
6707
|
+
this.live = null;
|
|
6708
|
+
this.liveLayers = null;
|
|
6709
|
+
this.liveThick = null;
|
|
6710
|
+
this.emit();
|
|
6711
|
+
this.notifySample("cancel", s, []);
|
|
6712
|
+
throw e;
|
|
6713
|
+
});
|
|
6714
|
+
done.catch(() => {
|
|
6715
|
+
});
|
|
6716
|
+
this.pathStroke = { effect: paint.effect, done };
|
|
6717
|
+
this.emit([]);
|
|
6718
|
+
}
|
|
6436
6719
|
/** Runs a whole stroke against `doc` without touching the editor; returns the new document. */
|
|
6437
6720
|
runStroke(doc, init, points) {
|
|
6438
6721
|
let out = doc;
|
|
@@ -6459,7 +6742,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6459
6742
|
}, stroke);
|
|
6460
6743
|
}
|
|
6461
6744
|
};
|
|
6462
|
-
const session = this.sessionFor(doc, init, host);
|
|
6745
|
+
const session = this.sessionFor(doc, init, host, points);
|
|
6463
6746
|
session.push(points);
|
|
6464
6747
|
const result = session.end();
|
|
6465
6748
|
if (session.init.wet) {
|
|
@@ -6609,6 +6892,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6609
6892
|
}
|
|
6610
6893
|
}
|
|
6611
6894
|
if (doc !== this.doc) this.commit(strokes.length === 1 && repeat === 1 ? "Replay stroke" : "Replay strokes", doc);
|
|
6895
|
+
this.effectPaintBatch = 0;
|
|
6896
|
+
this.trimEffectPaints();
|
|
6612
6897
|
return { strokeIds: ids, bounds, layerIds: [...layerIds] };
|
|
6613
6898
|
}
|
|
6614
6899
|
// --- Light paint --------------------------------------------------------------------
|
|
@@ -7104,7 +7389,11 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7104
7389
|
/** Wet settings saved with the document (`null` when they are all defaults). */
|
|
7105
7390
|
wetJSON() {
|
|
7106
7391
|
const layers = {};
|
|
7107
|
-
for (const [id, set] of this.wet.settings)
|
|
7392
|
+
for (const [id, set] of this.wet.settings) {
|
|
7393
|
+
if (!findLayer(this.doc.layers, id)) continue;
|
|
7394
|
+
const ghost = this.wet.world?.hasLane(id) ? this.wet.world.getGhost(id) : set.ghost ?? null;
|
|
7395
|
+
layers[id] = { params: { ...set.params }, flow: { ...set.flow }, ...ghost ? { ghost } : {} };
|
|
7396
|
+
}
|
|
7108
7397
|
const flat = this.wet.tilt.angle === 0;
|
|
7109
7398
|
if (!Object.keys(layers).length && flat) return void 0;
|
|
7110
7399
|
return { tilt: { ...this.wet.tilt }, layers };
|
|
@@ -7121,7 +7410,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7121
7410
|
continue;
|
|
7122
7411
|
}
|
|
7123
7412
|
const x = v;
|
|
7124
|
-
|
|
7413
|
+
const ghost = readWetGhost(x.ghost);
|
|
7414
|
+
this.wet.settings.set(id, { params: normalizeWetParams(x.params ?? {}), flow: normalizeFlow(x.flow ?? {}), ...ghost ? { ghost } : {} });
|
|
7125
7415
|
}
|
|
7126
7416
|
}
|
|
7127
7417
|
}
|
|
@@ -7912,8 +8202,9 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7912
8202
|
if (update.paint !== void 0) {
|
|
7913
8203
|
if (update.paint === null) delete next.paint;
|
|
7914
8204
|
else if (isPaintEffectPaint(update.paint)) {
|
|
7915
|
-
|
|
7916
|
-
|
|
8205
|
+
const ep = update.paint.map?.by === "path" ? withStrokePath(update.paint, next.points, this.effectPathControls.get(update.paint.effect)) : update.paint;
|
|
8206
|
+
if (!this.effectPaintImage(ep) && !this.renderEffectPaintSync(ep)) throw new PaintEditorError(`The effect "${update.paint.effect}" is not rendered yet; render it first (prepareEffectPaints; the update_stroke tool does this).`);
|
|
8207
|
+
next.paint = JSON.parse(JSON.stringify(ep));
|
|
7917
8208
|
} else {
|
|
7918
8209
|
const p = this.readStrokePaint(update.paint);
|
|
7919
8210
|
if (p.type === "color") {
|
|
@@ -7944,13 +8235,11 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7944
8235
|
if (edit.points !== void 0) {
|
|
7945
8236
|
const points = this.checkPoints(edit.points);
|
|
7946
8237
|
next = { ...was, points: points.map((p) => ({ ...p })) };
|
|
8238
|
+
if (next.paint?.type === "effect" && next.paint.map?.by === "path") {
|
|
8239
|
+
next.paint = withStrokePath(next.paint, next.points, this.effectPathControls.get(next.paint.effect));
|
|
8240
|
+
}
|
|
7947
8241
|
} else {
|
|
7948
|
-
|
|
7949
|
-
if (t.scale !== void 0 && !(t.scale > 0 && t.scale <= 20)) throw new PaintEditorError(`scale ${t.scale} is out of range; use above 0 up to 20.`);
|
|
7950
|
-
for (const k of ["dx", "dy", "rotate"]) if (t[k] !== void 0 && !Number.isFinite(t[k])) throw new PaintEditorError(`${k} must be a number.`);
|
|
7951
|
-
const b = strokeBounds(was.points);
|
|
7952
|
-
const m = variationAffine(t, t.origin ?? { x: b.x + b.width / 2, y: b.y + b.height / 2 });
|
|
7953
|
-
next = varyStroke(was, m, {});
|
|
8242
|
+
next = varyStroke(was, this.strokeTransform(was, edit.transform), {});
|
|
7954
8243
|
if (was.stencils) next.stencils = was.stencils;
|
|
7955
8244
|
}
|
|
7956
8245
|
const list = strokes.map((x, i) => i === index ? next : x);
|
|
@@ -8606,6 +8895,19 @@ function clipToCanvas(rect, canvas) {
|
|
|
8606
8895
|
const y1 = Math.min(canvas.height, rect.y + rect.height);
|
|
8607
8896
|
return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;
|
|
8608
8897
|
}
|
|
8898
|
+
function toEffectPaintPixels(p, pixels) {
|
|
8899
|
+
return { width: pixels.width, height: pixels.height, data: new Uint8ClampedArray(pixels.data), ...p.map?.by === "stroke" ? { map: p.map } : {} };
|
|
8900
|
+
}
|
|
8901
|
+
function readWetGhost(raw) {
|
|
8902
|
+
if (!raw || typeof raw !== "object") return null;
|
|
8903
|
+
const g = raw;
|
|
8904
|
+
if (!Array.isArray(g.pigment) || g.pigment.length !== 4 || !g.pigment.every((x) => typeof x === "number" && Number.isFinite(x))) return null;
|
|
8905
|
+
const remaining = typeof g.remaining === "number" && Number.isFinite(g.remaining) ? Math.max(0, Math.round(g.remaining)) : 0;
|
|
8906
|
+
if (remaining <= 0) return null;
|
|
8907
|
+
const total = typeof g.total === "number" && Number.isFinite(g.total) ? Math.max(1, Math.round(g.total)) : remaining;
|
|
8908
|
+
const linger = typeof g.linger === "number" && Number.isFinite(g.linger) ? Math.max(0.5, Math.min(60, g.linger)) : 4;
|
|
8909
|
+
return { pigment: [g.pigment[0], g.pigment[1], g.pigment[2], g.pigment[3]], remaining, total, linger };
|
|
8910
|
+
}
|
|
8609
8911
|
|
|
8610
8912
|
// src/paint-editor/presence.ts
|
|
8611
8913
|
var PaintPresence = class extends PresenceStore {
|
|
@@ -9528,7 +9830,7 @@ function createPaintWetTools(c) {
|
|
|
9528
9830
|
{
|
|
9529
9831
|
name: "get_wet_paint",
|
|
9530
9832
|
title: "Get the wet paint",
|
|
9531
|
-
description: "Reads the wet media: whether any paint is still wet, the simulation clock (steps), whether time is paused, its speed (steps per second), the canvas tilt, the kernel running it (typescript, wasm, wasm-threads with the thread count, and a message when it runs slower than it could, e.g. without cross-origin isolation), and each wet layer: wet cells, where the water is (wetBounds), live tiles, its settings and
|
|
9833
|
+
description: "Reads the wet media: whether any paint is still wet, the simulation clock (steps), whether time is paused, its speed (steps per second), the canvas tilt, the kernel running it (typescript, wasm, wasm-threads with the thread count, and a message when it runs slower than it could, e.g. without cross-origin isolation), and each wet layer: wet cells, where the water is (wetBounds), live tiles, its settings, flow map, and ghost: { remaining } (seconds of simulated time left when ghost paint is fading on the layer). Also engine: Engine B, the GPU accelerator: WebGPU in browsers, Metal in the Swift editors (mode auto/off, state, reason, adapter, minTiles, activeTiles). Call it after wet strokes to see what is still wet.",
|
|
9532
9834
|
inputSchema: EMPTY2,
|
|
9533
9835
|
annotations: { title: "Get the wet paint", ...READ3 },
|
|
9534
9836
|
coordinates: { out: ["layers[].wetBounds", "layers[].flow.center"] },
|
|
@@ -10320,6 +10622,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
10320
10622
|
if (refs.length) await c.prepareStickers(refs);
|
|
10321
10623
|
}
|
|
10322
10624
|
const stroke = { points: a.points, layerId: a.layerId, brushId: a.brushId, brush, color: a.color, paint: a.paint, seed: a.seed, snapToPerspective: a.snapToPerspective };
|
|
10625
|
+
await c.prepareStrokePathEffect(stroke, [stroke.points]);
|
|
10323
10626
|
const r = await act({ kind: "stroke", stroke }, () => c.paintStroke(stroke));
|
|
10324
10627
|
return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
|
|
10325
10628
|
}
|
|
@@ -10357,6 +10660,13 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
10357
10660
|
}
|
|
10358
10661
|
const path = options.origin && typeof a.path === "string" && a.path.trim() ? bezierPathToSvg(a.path, { offset: { x: -options.origin.x, y: -options.origin.y } }) : a.path;
|
|
10359
10662
|
const init = { path, spacing: a.spacing, pressure: a.pressure, speed: a.speed, layerId: a.layerId, brushId: a.brushId, brush, color: a.color, paint: a.paint, seed: a.seed };
|
|
10663
|
+
if (typeof path === "string" && path.trim()) {
|
|
10664
|
+
try {
|
|
10665
|
+
const sampled = samplePath(path, init);
|
|
10666
|
+
await c.prepareStrokePathEffect(init, sampled);
|
|
10667
|
+
} catch {
|
|
10668
|
+
}
|
|
10669
|
+
}
|
|
10360
10670
|
const r = await act(
|
|
10361
10671
|
() => {
|
|
10362
10672
|
if (typeof path !== "string" || !path.trim()) throw new PaintEditorError('path is empty; give SVG path data in document pixels, e.g. "M 10 10 C 40 0 80 60 120 30".');
|
|
@@ -10508,7 +10818,8 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
10508
10818
|
},
|
|
10509
10819
|
annotations: { title: "Move or reshape a stroke", ...WRITE5 },
|
|
10510
10820
|
coordinates: { in: ["points[]", "transform.origin"], out: ["bounds", "stroke.bounds", "stroke.points[]"] },
|
|
10511
|
-
handler: ({ id, points, transform }) => {
|
|
10821
|
+
handler: async ({ id, points, transform }) => {
|
|
10822
|
+
await c.prepareStrokeEdit(id, { points, transform });
|
|
10512
10823
|
const r = c.editStrokePoints(id, { points, transform });
|
|
10513
10824
|
return { stroke: strokeSummary(r.stroke, true, true), bounds: r.bounds };
|
|
10514
10825
|
}
|
|
@@ -10591,7 +10902,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
10591
10902
|
coordinates: { in: ["origin"], out: ["bounds"] },
|
|
10592
10903
|
handler: async (a) => {
|
|
10593
10904
|
const replay = a;
|
|
10594
|
-
if (replay.color === void 0) await c.prepareStrokeEffects(replay.strokeIds);
|
|
10905
|
+
if (replay.color === void 0) await c.prepareStrokeEffects(replay.strokeIds, replay);
|
|
10595
10906
|
return act({ kind: "replay", replay }, () => c.replayStrokes(replay));
|
|
10596
10907
|
}
|
|
10597
10908
|
});
|
|
@@ -10967,15 +11278,28 @@ var PaintGesture = class {
|
|
|
10967
11278
|
if (!m || m.pointerId !== e.pointerId) return;
|
|
10968
11279
|
this.mode = null;
|
|
10969
11280
|
this.predicted = [];
|
|
10970
|
-
if (m.kind === "stroke")
|
|
11281
|
+
if (m.kind === "stroke") {
|
|
11282
|
+
this.controller.activeStroke?.end();
|
|
11283
|
+
this.controller.strokeRendering?.catch((err) => {
|
|
11284
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
11285
|
+
});
|
|
11286
|
+
}
|
|
10971
11287
|
if (m.kind === "move-stroke") {
|
|
10972
11288
|
const dx = Math.round((this.doc(e).x - m.from.x) * 100) / 100;
|
|
10973
11289
|
const dy = Math.round((this.doc(e).y - m.from.y) * 100) / 100;
|
|
10974
11290
|
if (dx || dy) {
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
} catch (err) {
|
|
11291
|
+
const edit = { transform: { dx, dy } };
|
|
11292
|
+
const failed = (err) => {
|
|
10978
11293
|
this.error = err instanceof Error ? err.message : String(err);
|
|
11294
|
+
};
|
|
11295
|
+
if (this.controller.strokeEditNeedsRender(m.id, edit)) {
|
|
11296
|
+
this.controller.prepareStrokeEdit(m.id, edit).then(() => this.controller.editStrokePoints(m.id, edit)).catch(failed);
|
|
11297
|
+
} else {
|
|
11298
|
+
try {
|
|
11299
|
+
this.controller.editStrokePoints(m.id, edit);
|
|
11300
|
+
} catch (err) {
|
|
11301
|
+
failed(err);
|
|
11302
|
+
}
|
|
10979
11303
|
}
|
|
10980
11304
|
}
|
|
10981
11305
|
}
|
|
@@ -11482,9 +11806,9 @@ function createPaintEffectsToolProvider(controller, options = {}) {
|
|
|
11482
11806
|
time: { type: "number", minimum: 0, description: "Which moment of an animated effect to paint with, in ms (default 0)." },
|
|
11483
11807
|
map: {
|
|
11484
11808
|
type: "object",
|
|
11485
|
-
description:
|
|
11809
|
+
description: `How the effect lies on the paint: by canvas (default: fixed to the canvas, painting reveals a hidden picture), by stroke (along each stroke: the effect is rendered as a strip length px long, and each pixel takes the colour at its distance along the stroke and its place across the tip), or by path (for effects with a path control format svg-path, e.g. search_effects accepts ["path"]: passes each stroke's centreline [x0, y0, x1, y1, \u2026] in canvas pixels to that control so the effect draws itself along the stroke; strokes longer than 256 points are simplified rather than refused).`,
|
|
11486
11810
|
properties: {
|
|
11487
|
-
by: { type: "string", enum: ["canvas", "stroke"], description: "canvas (default) or
|
|
11811
|
+
by: { type: "string", enum: ["canvas", "stroke", "path"], description: "canvas (default), stroke (bent strip along each stroke) or path (native path control for effects with format svg-path)." },
|
|
11488
11812
|
length: { type: "number", minimum: 16, maximum: 4096, description: "by stroke: px of stroke per repeat of the effect (default 300)." },
|
|
11489
11813
|
repeat: { type: "string", enum: ["repeat", "mirror", "clamp"], description: "by stroke: past the strip end start again (default), run back, or hold the last colour." }
|
|
11490
11814
|
},
|
|
@@ -12095,7 +12419,7 @@ function uiTables() {
|
|
|
12095
12419
|
brushOptions: BRUSH_OPTION_CONTROLS,
|
|
12096
12420
|
blendMenu: BLEND_MENU_GROUPS,
|
|
12097
12421
|
layerEffects: LAYER_EFFECT_MENU.map(({ kind, label }) => ({ kind, label, controls: layerEffectControls(defaultLayerEffect(kind, kind)) })),
|
|
12098
|
-
sliders: { paper: PAPER_SLIDERS, wet: WET_PARAM_SLIDERS, wetTilt: WET_TILT_SLIDERS, wetSpeed: WET_SPEED_SLIDER, light: LIGHT_SLIDERS, stroke: STROKE_SLIDERS, sticker: STICKER_SLIDERS },
|
|
12422
|
+
sliders: { paper: PAPER_SLIDERS, wet: WET_PARAM_SLIDERS, wetTilt: WET_TILT_SLIDERS, wetSpeed: WET_SPEED_SLIDER, light: LIGHT_SLIDERS, ghost: GHOST_SLIDERS, stroke: STROKE_SLIDERS, sticker: STICKER_SLIDERS },
|
|
12099
12423
|
pathStrokeTapers: PATH_STROKE_TAPERS,
|
|
12100
12424
|
padSampleSizes: PAD_SAMPLE_SIZES,
|
|
12101
12425
|
wetGpu: WET_GPU_OPTIONS,
|
|
@@ -12172,6 +12496,7 @@ export {
|
|
|
12172
12496
|
DEFAULT_DPI,
|
|
12173
12497
|
DEFAULT_DYNAMICS,
|
|
12174
12498
|
DEFAULT_FLOW,
|
|
12499
|
+
DEFAULT_GHOST,
|
|
12175
12500
|
DEFAULT_IMPASTO,
|
|
12176
12501
|
DEFAULT_LIGHT,
|
|
12177
12502
|
DEFAULT_LIGHTING,
|
|
@@ -12194,6 +12519,7 @@ export {
|
|
|
12194
12519
|
DryStroke,
|
|
12195
12520
|
EFFECT_MAP_REPEATS,
|
|
12196
12521
|
EFFECT_STRIP_HEIGHT,
|
|
12522
|
+
GHOST_SLIDERS,
|
|
12197
12523
|
GPU_LEAD,
|
|
12198
12524
|
GRAIN_PATTERNS,
|
|
12199
12525
|
GRAIN_TILE,
|
|
@@ -12209,6 +12535,7 @@ export {
|
|
|
12209
12535
|
MAX_CANVAS_SIDE,
|
|
12210
12536
|
MAX_COORDINATE,
|
|
12211
12537
|
MAX_DOT_TEXTURES,
|
|
12538
|
+
MAX_EFFECT_PATH_POINTS,
|
|
12212
12539
|
MAX_IMAGE_SIDE,
|
|
12213
12540
|
MAX_LIVE_EFFECT_SOURCES,
|
|
12214
12541
|
MAX_PATH_SAMPLES,
|
|
@@ -12370,6 +12697,7 @@ export {
|
|
|
12370
12697
|
hslToRgb,
|
|
12371
12698
|
hsvToRgb,
|
|
12372
12699
|
hueToWheel,
|
|
12700
|
+
inferEffectPathControl,
|
|
12373
12701
|
invertAffine,
|
|
12374
12702
|
isBlendMode,
|
|
12375
12703
|
isGradientPaint,
|
|
@@ -12400,6 +12728,7 @@ export {
|
|
|
12400
12728
|
normalizeCanvas,
|
|
12401
12729
|
normalizeDynamics,
|
|
12402
12730
|
normalizeFlow,
|
|
12731
|
+
normalizeGhost,
|
|
12403
12732
|
normalizeHexColor,
|
|
12404
12733
|
normalizeImpasto,
|
|
12405
12734
|
normalizeLight,
|
|
@@ -12492,6 +12821,7 @@ export {
|
|
|
12492
12821
|
stickerSurface,
|
|
12493
12822
|
strokeBounds,
|
|
12494
12823
|
strokeDuration,
|
|
12824
|
+
strokePathPoints,
|
|
12495
12825
|
strokeSchedule,
|
|
12496
12826
|
strokeToJSON,
|
|
12497
12827
|
sweepPath,
|
|
@@ -12524,5 +12854,6 @@ export {
|
|
|
12524
12854
|
wheelPick,
|
|
12525
12855
|
wheelPoint,
|
|
12526
12856
|
wheelToHue,
|
|
12527
|
-
withPaintPlayback
|
|
12857
|
+
withPaintPlayback,
|
|
12858
|
+
withStrokePath
|
|
12528
12859
|
};
|