@pixerate/editor 0.240.0 → 0.241.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 +58 -58
- package/dist/animation-editor/index.js +3 -3
- package/dist/{brushstrokeRender-WB67GMUP.js → brushstrokeRender-MOYGEXOC.js} +2 -2
- package/dist/{brushstrokeRender-MPOM7BFW.cjs → brushstrokeRender-UKADOV5D.cjs} +5 -5
- package/dist/canvas/index.cjs +8 -8
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-NKTNLJUJ.cjs → chunk-5BR7LC24.cjs} +99 -11
- package/dist/{chunk-ZXNO3ZOX.cjs → chunk-7F4G74UQ.cjs} +35 -35
- package/dist/{chunk-AD7GA3TL.cjs → chunk-AFLMXITL.cjs} +23 -23
- package/dist/{chunk-PP6BXVRL.cjs → chunk-HGGJ36HR.cjs} +1 -1
- package/dist/{chunk-BBUE3UJL.cjs → chunk-HW72ABGR.cjs} +86 -86
- package/dist/{chunk-7YGBSFC6.js → chunk-ILM6GYAX.js} +1 -1
- package/dist/{chunk-THXFU3JB.cjs → chunk-KCMV2534.cjs} +2 -2
- package/dist/{chunk-3E36VDMJ.js → chunk-NE6OOKWJ.js} +1 -1
- package/dist/{chunk-RV5DKZHU.js → chunk-QP2B3MID.js} +1 -1
- package/dist/{chunk-UUBO6RB5.js → chunk-UDBDYZET.js} +1 -1
- package/dist/{chunk-PJYG6FCF.js → chunk-XBZK47LV.js} +98 -10
- package/dist/{chunk-XK4SST43.js → chunk-ZDPLZDTT.js} +1 -1
- package/dist/image-editor/index.cjs +775 -775
- package/dist/image-editor/index.js +4 -4
- package/dist/paint-editor/controller.d.cts +67 -0
- package/dist/paint-editor/controller.d.ts +67 -0
- package/dist/paint-editor/effect-paint.d.cts +60 -3
- package/dist/paint-editor/effect-paint.d.ts +60 -3
- package/dist/paint-editor/index.cjs +676 -430
- package/dist/paint-editor/index.js +282 -36
- package/dist/paint-editor/stickers.d.cts +6 -0
- package/dist/paint-editor/stickers.d.ts +6 -0
- package/dist/paint-editor/thick.d.cts +4 -0
- package/dist/paint-editor/thick.d.ts +4 -0
- package/dist/paint-editor/wet.d.cts +8 -0
- package/dist/paint-editor/wet.d.ts +8 -0
- package/dist/vector/index.cjs +962 -962
- package/dist/vector/index.js +4 -4
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +17 -21
- package/skills/pixerate-paint-editor/references/effect-paint.md +25 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
resolveExternalAssets,
|
|
8
8
|
serializeLayers,
|
|
9
9
|
serializeLayersExternal
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-NE6OOKWJ.js";
|
|
11
11
|
import "../chunk-3CI7FRMK.js";
|
|
12
12
|
import "../chunk-ZNA3IQZF.js";
|
|
13
13
|
import {
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
realPresenceClock,
|
|
56
56
|
tweenPresenceCursor
|
|
57
57
|
} from "../chunk-IVSWRVB4.js";
|
|
58
|
-
import "../chunk-
|
|
58
|
+
import "../chunk-ILM6GYAX.js";
|
|
59
59
|
import {
|
|
60
60
|
BRUSH_ID,
|
|
61
61
|
BRUSH_ROTATIONS,
|
|
@@ -72,6 +72,8 @@ import {
|
|
|
72
72
|
DEFAULT_STICKER,
|
|
73
73
|
DEFAULT_WET_BRUSH,
|
|
74
74
|
DryStroke,
|
|
75
|
+
EFFECT_MAP_REPEATS,
|
|
76
|
+
EFFECT_STRIP_HEIGHT,
|
|
75
77
|
IDENTITY_COPY,
|
|
76
78
|
IMPASTO_TOOLS,
|
|
77
79
|
MULTI_COLOR_MODES,
|
|
@@ -92,7 +94,9 @@ import {
|
|
|
92
94
|
brushToJSON,
|
|
93
95
|
clonePaper,
|
|
94
96
|
drawSticker,
|
|
97
|
+
effectDabField,
|
|
95
98
|
effectPaintKey,
|
|
99
|
+
effectPaintSize,
|
|
96
100
|
hexToRgb8,
|
|
97
101
|
isPaintEffectPaint,
|
|
98
102
|
isWetBrush,
|
|
@@ -114,6 +118,7 @@ import {
|
|
|
114
118
|
paperPreset,
|
|
115
119
|
parseBrushLibrary,
|
|
116
120
|
penOrientation,
|
|
121
|
+
readEffectStrokeMap,
|
|
117
122
|
readPaintEffectPaint,
|
|
118
123
|
renderPaper,
|
|
119
124
|
renderStickers,
|
|
@@ -126,7 +131,7 @@ import {
|
|
|
126
131
|
tiltToOrientation,
|
|
127
132
|
toStrokePoint,
|
|
128
133
|
usesPaintExchange
|
|
129
|
-
} from "../chunk-
|
|
134
|
+
} from "../chunk-XBZK47LV.js";
|
|
130
135
|
import "../chunk-M6DNZNVY.js";
|
|
131
136
|
import "../chunk-WEIK3UYB.js";
|
|
132
137
|
import "../chunk-PWBZH3FK.js";
|
|
@@ -458,7 +463,7 @@ var PaintDabStroke = class {
|
|
|
458
463
|
let flow = 1;
|
|
459
464
|
if (this.options.effect) {
|
|
460
465
|
const rgb = [0, 0, 0];
|
|
461
|
-
flow = sampleEffectAt(this.options.effect, d.x, d.y, rgb);
|
|
466
|
+
flow = sampleEffectAt(this.options.effect, d.x, d.y, rgb, d);
|
|
462
467
|
ex.setReservoirColor(rgb[0], rgb[1], rgb[2]);
|
|
463
468
|
} else if (this.sampler && !this.gradient) {
|
|
464
469
|
const g = this.sampler.dab(d, this.sample);
|
|
@@ -711,6 +716,7 @@ var ThickStrokeDriver = class {
|
|
|
711
716
|
height = /* @__PURE__ */ new Map();
|
|
712
717
|
material = /* @__PURE__ */ new Map();
|
|
713
718
|
paint = [0, 0, 0];
|
|
719
|
+
centre = [0, 0, 0];
|
|
714
720
|
tip = new Float32Array(0);
|
|
715
721
|
lastRaw = null;
|
|
716
722
|
lastFed = null;
|
|
@@ -837,7 +843,11 @@ var ThickStrokeDriver = class {
|
|
|
837
843
|
}
|
|
838
844
|
const ex = st.exchange;
|
|
839
845
|
let flow = 1;
|
|
840
|
-
|
|
846
|
+
const effect = this.init.effect;
|
|
847
|
+
if (effect) {
|
|
848
|
+
sampleEffectAt(effect, d.x, d.y, this.centre, d);
|
|
849
|
+
ex.setReservoirColor(this.centre[0], this.centre[1], this.centre[2]);
|
|
850
|
+
} else if (this.sampler) {
|
|
841
851
|
const g = this.sampler.dab(d, this.sample);
|
|
842
852
|
ex.setReservoirColor(g.rgb[0], g.rgb[1], g.rgb[2]);
|
|
843
853
|
flow = g.alpha;
|
|
@@ -849,7 +859,12 @@ var ThickStrokeDriver = class {
|
|
|
849
859
|
if (dep > 0) {
|
|
850
860
|
const cov = new Float32Array(n);
|
|
851
861
|
for (let p = 0; p < n; p++) cov[p] = tip[p] > 0 ? Math.min(1, tip[p] * (im.tool === "roller" || im.tool === "gloop" || im.tool === "tube" ? 1.6 : 1.1)) : 0;
|
|
852
|
-
|
|
862
|
+
let field = null;
|
|
863
|
+
if (effect) {
|
|
864
|
+
field = new Float32Array(n * 3);
|
|
865
|
+
effectDabField(effect, rect, cov, this.centre, this.paint, field, 0, d);
|
|
866
|
+
}
|
|
867
|
+
this.medium.deposit(rect, cov, dep, this.paint, ex.paintPigment, field);
|
|
853
868
|
}
|
|
854
869
|
const seed = this.init.seed;
|
|
855
870
|
for (let yy = 0, p = 0; yy < rect.height; yy++) {
|
|
@@ -996,7 +1011,6 @@ var PaintStrokeSession = class {
|
|
|
996
1011
|
const brush = init.brush;
|
|
997
1012
|
if (init.wet) this.wetDriver = init.wet.driver;
|
|
998
1013
|
const effect = init.effect?.pixels ?? null;
|
|
999
|
-
if (effect && (init.wet || init.thick || init.sticker)) throw new Error(`The ${brush.name} brush (${brush.engine} engine) cannot paint with effect paint.`);
|
|
1000
1014
|
this.apply = {
|
|
1001
1015
|
mode: brush.engine === "eraser" ? "erase" : "paint",
|
|
1002
1016
|
color: init.color,
|
|
@@ -1009,10 +1023,10 @@ var PaintStrokeSession = class {
|
|
|
1009
1023
|
};
|
|
1010
1024
|
if (this.wetDriver) {
|
|
1011
1025
|
} else if (init.sticker) {
|
|
1012
|
-
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 });
|
|
1026
|
+
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 });
|
|
1013
1027
|
} else if (init.thick) {
|
|
1014
1028
|
const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
|
|
1015
|
-
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 });
|
|
1029
|
+
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 });
|
|
1016
1030
|
} else if (brush.engine === "mixer" || brush.engine === "blender" || brush.engine === "smudge" || usesPaintExchange(brush, Boolean(init.paint))) {
|
|
1017
1031
|
const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
|
|
1018
1032
|
this.wet = new PaintDabStroke(brush, {
|
|
@@ -1530,6 +1544,7 @@ function varyStroke(stroke, m, v) {
|
|
|
1530
1544
|
const brush = s !== 1 ? normalizePaintBrush({ ...stroke.brush, tip: { ...stroke.brush.tip, size: stroke.brush.tip.size * s } }) : stroke.brush;
|
|
1531
1545
|
const out = { ...stroke, brush, points };
|
|
1532
1546
|
if (stroke.paint?.type === "gradient" && s !== 1) out.paint = scalePaintGradient(stroke.paint, s);
|
|
1547
|
+
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))) } };
|
|
1533
1548
|
if (stroke.copies?.length) {
|
|
1534
1549
|
const inv = invertAffine(m);
|
|
1535
1550
|
out.copies = stroke.copies.map((c) => composeAffine(m, composeAffine(c, inv)));
|
|
@@ -1941,6 +1956,7 @@ var WetStrokeDriver = class {
|
|
|
1941
1956
|
tip = new Float32Array(0);
|
|
1942
1957
|
cov = new Float32Array(0);
|
|
1943
1958
|
paint = [0, 0, 0];
|
|
1959
|
+
centre = [0, 0, 0];
|
|
1944
1960
|
depth;
|
|
1945
1961
|
sampler;
|
|
1946
1962
|
sample = { rgb: [0, 0, 0], alpha: 1 };
|
|
@@ -2041,14 +2057,23 @@ var WetStrokeDriver = class {
|
|
|
2041
2057
|
case "paint": {
|
|
2042
2058
|
const ex = st.exchange;
|
|
2043
2059
|
let flow = 1;
|
|
2044
|
-
|
|
2060
|
+
const effect = this.init.effect;
|
|
2061
|
+
if (effect) {
|
|
2062
|
+
sampleEffectAt(effect, d.x, d.y, this.centre, d);
|
|
2063
|
+
ex.setReservoirColor(this.centre[0], this.centre[1], this.centre[2]);
|
|
2064
|
+
} else if (this.sampler) {
|
|
2045
2065
|
const g = this.sampler.dab(d, this.sample);
|
|
2046
2066
|
ex.setReservoirColor(g.rgb[0], g.rgb[1], g.rgb[2]);
|
|
2047
2067
|
flow = g.alpha;
|
|
2048
2068
|
}
|
|
2049
2069
|
ex.pickUp(this.medium.sample(rect, tip));
|
|
2050
2070
|
const total = ex.paint(this.paint);
|
|
2051
|
-
if (total > 0 && flow > 0
|
|
2071
|
+
if (total > 0 && flow > 0 && effect) {
|
|
2072
|
+
const ec = cov.slice();
|
|
2073
|
+
const field = new Float32Array(ec.length * 3);
|
|
2074
|
+
effectDabField(effect, rect, ec, this.centre, this.paint, field, COVERAGE_STEPS, d);
|
|
2075
|
+
this.medium.deposit(rect, ec, Math.min(1, total) * k, this.paint, null, field);
|
|
2076
|
+
} else if (total > 0 && flow > 0) this.medium.deposit(rect, cov, Math.min(1, total) * k * flow, this.paint, null);
|
|
2052
2077
|
else world.paint(laneId, rect, cov, this.medium.options.water * k, null);
|
|
2053
2078
|
ex.drain();
|
|
2054
2079
|
if (wet.rewet > 0) world.rewet(laneId, rect, cov, wet.rewet * k * 0.15, this.init.paperColor);
|
|
@@ -3510,6 +3535,15 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
3510
3535
|
dirty = "all";
|
|
3511
3536
|
seedCounter = 1;
|
|
3512
3537
|
strokeCounter = 1;
|
|
3538
|
+
/** Live effect paint: playing and the frame rate cap (editor state), and the time the view shows. */
|
|
3539
|
+
effectPlaybackState = { playing: true, fps: 30 };
|
|
3540
|
+
effectTime = 0;
|
|
3541
|
+
/** While a frame is drawn: the time live effect strokes are drawn at (else their own time). */
|
|
3542
|
+
liveEffectAt = null;
|
|
3543
|
+
/** The view's stroke layers at `effectTime`, each with the document surface it was drawn from (stale once that changes). */
|
|
3544
|
+
animatedLayers = /* @__PURE__ */ new Map();
|
|
3545
|
+
/** `renderAnimationFrame` / `withEffectTime`: layer surfaces at another time, for one render. */
|
|
3546
|
+
frameOverride = null;
|
|
3513
3547
|
/** The area each stroke painted (document pixels; `null`: nothing), learnt as it is painted or drawn, so a stroke layer edit redraws only where it changes (see `drawLiveStrokes`). */
|
|
3514
3548
|
paintedBounds = /* @__PURE__ */ new WeakMap();
|
|
3515
3549
|
guides;
|
|
@@ -3598,6 +3632,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
3598
3632
|
mixerLoad: this.mixerLoad,
|
|
3599
3633
|
view: this.view,
|
|
3600
3634
|
hand: this.hand,
|
|
3635
|
+
effectPlayback: { ...this.effectPlaybackState, time: this.effectTime },
|
|
3601
3636
|
selectedStroke: this.selectedStroke,
|
|
3602
3637
|
selectedSticker: this.selectedSticker,
|
|
3603
3638
|
canUndo: this.undoStack.length > 0 && !this.session,
|
|
@@ -4401,16 +4436,25 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4401
4436
|
* size, and brushes then take each pixel's colour (and alpha) from it, fixed to the canvas, as if
|
|
4402
4437
|
* painting a hidden picture into view. `setColor` and `setPaint` go back to colour. Editor state,
|
|
4403
4438
|
* not an undo step. Strokes that would paint with it throw while it renders. Returns the paint.
|
|
4439
|
+
* With `map: { by: 'stroke' }` the effect lies along each stroke instead of the canvas; with
|
|
4440
|
+
* `live`, strokes on a stroke layer keep playing it (see `setEffectPlayback`).
|
|
4404
4441
|
*/
|
|
4405
4442
|
async setEffectPaint(source) {
|
|
4406
4443
|
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).");
|
|
4407
4444
|
this.checkTime(source.time);
|
|
4445
|
+
let map;
|
|
4446
|
+
try {
|
|
4447
|
+
map = readEffectStrokeMap(source.map);
|
|
4448
|
+
} catch (e) {
|
|
4449
|
+
throw new PaintEditorError(e.message);
|
|
4450
|
+
}
|
|
4451
|
+
if (source.live !== void 0 && typeof source.live !== "boolean") throw new PaintEditorError("live must be true or false.");
|
|
4408
4452
|
const epoch = ++this.paintEpoch;
|
|
4409
4453
|
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.`);
|
|
4410
4454
|
const d = await this.describeEffect(source.effect, "paint");
|
|
4411
4455
|
const values = this.effectValues(d, { ...d.defaults }, source.values);
|
|
4412
4456
|
if (epoch !== this.paintEpoch) throw changed(d.name);
|
|
4413
|
-
const paint = { type: "effect", effect: d.id, provider: this.effects.require().id, values, time: source.time ?? 0 };
|
|
4457
|
+
const paint = { type: "effect", effect: d.id, provider: this.effects.require().id, values, time: source.time ?? 0, ...map ? { map } : {}, ...source.live ? { live: true } : {} };
|
|
4414
4458
|
this.effectPaintPending = paint;
|
|
4415
4459
|
this.emit([]);
|
|
4416
4460
|
try {
|
|
@@ -4433,7 +4477,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4433
4477
|
effectPaintImage(p) {
|
|
4434
4478
|
const key = effectPaintKey(p);
|
|
4435
4479
|
const img = this.effectPaintPixels.get(key);
|
|
4436
|
-
|
|
4480
|
+
const size = effectPaintSize(p, this.doc.canvas);
|
|
4481
|
+
if (!img || img.width !== size.width || img.height !== size.height) return null;
|
|
4437
4482
|
this.effectPaintPixels.delete(key);
|
|
4438
4483
|
this.effectPaintPixels.set(key, img);
|
|
4439
4484
|
return img;
|
|
@@ -4446,14 +4491,14 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4446
4491
|
const what = `the effect "${p.effect}"${p.provider ? ` from provider "${p.provider}"` : ""}`;
|
|
4447
4492
|
if (!provider) throw new PaintEditorError(`Effect paint needs ${what}, but no effects provider is registered; register it (setEffectsProvider) and try again.`);
|
|
4448
4493
|
if (p.provider && provider.id !== p.provider) throw new PaintEditorError(`Effect paint needs ${what}, but the registered provider is "${provider.id}"; register "${p.provider}" (setEffectsProvider) and try again.`);
|
|
4449
|
-
const { width, height } = this.doc.canvas;
|
|
4494
|
+
const { width, height } = effectPaintSize(p, this.doc.canvas);
|
|
4450
4495
|
let pixels;
|
|
4451
4496
|
try {
|
|
4452
4497
|
pixels = await this.effectsRenderer.render(provider, p.effect, { width, height }, { time: p.time, values: p.values });
|
|
4453
4498
|
} catch (e) {
|
|
4454
4499
|
throw new PaintEditorError(`Effect paint could not render ${what}: ${e instanceof Error ? e.message : String(e)}`);
|
|
4455
4500
|
}
|
|
4456
|
-
const img = { width, height, data: new Uint8ClampedArray(pixels.data) };
|
|
4501
|
+
const img = { width, height, data: new Uint8ClampedArray(pixels.data), ...p.map ? { map: p.map } : {} };
|
|
4457
4502
|
this.effectPaintPixels.set(effectPaintKey(p), img);
|
|
4458
4503
|
while (this.effectPaintPixels.size > _PaintEditorController.EFFECT_PAINT_CACHE) this.effectPaintPixels.delete(this.effectPaintPixels.keys().next().value);
|
|
4459
4504
|
return img;
|
|
@@ -4514,11 +4559,6 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4514
4559
|
source = this.effectPaint;
|
|
4515
4560
|
}
|
|
4516
4561
|
if (!source) return null;
|
|
4517
|
-
if (brush.engine === "wet" || brush.engine === "impasto" || brush.engine === "sticker") {
|
|
4518
|
-
throw new PaintEditorError(
|
|
4519
|
-
`The ${brush.name} brush (${brush.engine} engine) cannot paint with effect paint: it carries one colour as ${brush.engine === "sticker" ? "stickers" : brush.engine === "wet" ? "wet pigment" : "thick paint"}. Paint with a dry brush (a dab, particle, bristle or mixer brush), or paint the shape first and fill it with fill_layer_with_effect {lockTransparency: true}.`
|
|
4520
|
-
);
|
|
4521
|
-
}
|
|
4522
4562
|
const pixels = this.effectPaintImage(source);
|
|
4523
4563
|
if (!pixels) throw new PaintEditorError(`The effect paint "${source.effect}"${source.provider ? ` from provider "${source.provider}"` : ""} is not rendered at this canvas size; call set_effect_paint (or prepareEffectPaints) first.`);
|
|
4524
4564
|
return { source, pixels };
|
|
@@ -4815,6 +4855,13 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4815
4855
|
const why = liveStrokeRefusal(brush);
|
|
4816
4856
|
if (why) throw new PaintEditorError(`Layer "${layer.name}" is a stroke layer. ${why}`);
|
|
4817
4857
|
}
|
|
4858
|
+
if (doc.liveStrokes?.[layer.id] && effect?.source.live) {
|
|
4859
|
+
const key = effectPaintKey(effect.source);
|
|
4860
|
+
const { sources } = this.liveEffects(doc);
|
|
4861
|
+
if (!sources.some((p) => effectPaintKey(p) === key) && sources.length >= MAX_LIVE_EFFECT_SOURCES) {
|
|
4862
|
+
throw new PaintEditorError(`The painting already plays ${MAX_LIVE_EFFECT_SOURCES} live effects, the most it can; paint with one of them, or set_effect_paint with live false to bake this one.`);
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4818
4865
|
const stickerLayer = Boolean(doc.stickers?.[layer.id]?.length);
|
|
4819
4866
|
if (brush.engine === "sticker") {
|
|
4820
4867
|
if (!stickerLayer && contentBounds(layer.surface)) throw new PaintEditorError(`Layer "${layer.name}" already has paint; spray stickers onto an empty layer (add_layer) so they stay editable.`);
|
|
@@ -4854,7 +4901,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
4854
4901
|
seed: seedW,
|
|
4855
4902
|
copies: [IDENTITY_AFFINE, ...copiesW].map(affineCopy),
|
|
4856
4903
|
mask: init.maskStencils ? this.stencilSelection(init.maskStencils) : init.stencils !== false ? this.stencilSelection() : null,
|
|
4857
|
-
paint: wetGradient
|
|
4904
|
+
paint: effect ? null : wetGradient,
|
|
4905
|
+
effect: effect?.pixels ?? null
|
|
4858
4906
|
});
|
|
4859
4907
|
init = { ...init, seed: seedW, load };
|
|
4860
4908
|
wet = { driver, surface: () => world.display(id).surface };
|
|
@@ -5418,8 +5466,10 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5418
5466
|
}
|
|
5419
5467
|
/** The layers with the live stroke's surface in place. */
|
|
5420
5468
|
currentLayers() {
|
|
5469
|
+
const override = this.frameOverride;
|
|
5470
|
+
if (override) return Array.from(override).reduce((ls, [id, surface]) => mapLayer(ls, id, (l) => ({ ...l, surface })), this.doc.layers);
|
|
5421
5471
|
const live = this.live;
|
|
5422
|
-
if (!live) return this.
|
|
5472
|
+
if (!live) return this.animatedView();
|
|
5423
5473
|
if (this.liveLayers?.key === live.surface) return this.liveLayers.layers;
|
|
5424
5474
|
const layers = mapLayer(this.doc.layers, live.layerId, (l) => ({ ...l, surface: live.surface }));
|
|
5425
5475
|
this.liveLayers = { key: live.surface, layers };
|
|
@@ -6357,11 +6407,13 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6357
6407
|
if (!cancelled && drawn) out = drawn;
|
|
6358
6408
|
}
|
|
6359
6409
|
};
|
|
6360
|
-
const
|
|
6410
|
+
const paint = st.paint && this.liveEffectAt !== null && isLiveEffect(st) ? framePaint(st.paint, this.liveEffectAt) : st.paint;
|
|
6411
|
+
const init = { layerId: plain.id, color: st.color, brush: st.brush, seed: st.seed, copies: st.copies ?? [], ...st.load ? { load: st.load } : {}, ...paint ? { paint } : {}, snapToPerspective: false, stencils: false, maskStencils: st.stencils ?? [] };
|
|
6361
6412
|
const session = this.sessionFor({ ...doc, layers: mapLayer(doc.layers, plain.id, () => ({ ...plain, surface })) }, init, host);
|
|
6362
6413
|
session.push(st.points);
|
|
6363
6414
|
const result = session.end();
|
|
6364
|
-
this.paintedBounds.
|
|
6415
|
+
const had = this.paintedBounds.get(st);
|
|
6416
|
+
this.paintedBounds.set(st, had && result.bounds ? unionRect(had, result.bounds) : result.bounds ?? had ?? null);
|
|
6365
6417
|
return { surface: out, bounds: result.bounds };
|
|
6366
6418
|
}
|
|
6367
6419
|
/**
|
|
@@ -6398,6 +6450,146 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6398
6450
|
for (const r of rects) surface = writeRegion(surface, r, readRegion(drawn, r));
|
|
6399
6451
|
return surface;
|
|
6400
6452
|
}
|
|
6453
|
+
// --- Live effect paint ------------------------------------------------------------
|
|
6454
|
+
/** The view's layers: stroke layers playing live effects at `effectTime`, the rest as they are. */
|
|
6455
|
+
animatedView() {
|
|
6456
|
+
if (!this.animatedLayers.size) return this.doc.layers;
|
|
6457
|
+
let layers = this.doc.layers;
|
|
6458
|
+
for (const [id, a] of this.animatedLayers) {
|
|
6459
|
+
const l = findLayer(layers, id)?.layer;
|
|
6460
|
+
if (isPixelLayer(l) && l.surface === a.base) layers = mapLayer(layers, id, () => ({ ...l, surface: a.surface }));
|
|
6461
|
+
}
|
|
6462
|
+
return layers;
|
|
6463
|
+
}
|
|
6464
|
+
/** Stroke layers with live effect strokes, and the distinct live sources (frame 0). */
|
|
6465
|
+
liveEffects(doc = this.doc) {
|
|
6466
|
+
const layers = [];
|
|
6467
|
+
const sources = /* @__PURE__ */ new Map();
|
|
6468
|
+
for (const [id, strokes] of Object.entries(doc.liveStrokes ?? {})) {
|
|
6469
|
+
const layer = findLayer(doc.layers, id)?.layer;
|
|
6470
|
+
const live = strokes.filter(isLiveEffect);
|
|
6471
|
+
if (!isPixelLayer(layer) || !live.length) continue;
|
|
6472
|
+
layers.push({ layer, strokes });
|
|
6473
|
+
for (const st of live) sources.set(effectPaintKey(st.paint), st.paint);
|
|
6474
|
+
}
|
|
6475
|
+
return { layers, sources: [...sources.values()] };
|
|
6476
|
+
}
|
|
6477
|
+
/** What plays: the live effect strokes and their layers, or `null` when nothing does. */
|
|
6478
|
+
getAnimation() {
|
|
6479
|
+
const { layers, sources } = this.liveEffects();
|
|
6480
|
+
if (!layers.length) return null;
|
|
6481
|
+
return { layerIds: layers.map((x) => x.layer.id), strokes: layers.reduce((n, x) => n + x.strokes.filter(isLiveEffect).length, 0), sources: sources.length };
|
|
6482
|
+
}
|
|
6483
|
+
/** Plays or pauses live effect paint, and caps its frame rate (1..60). Editor state, not an undo step. */
|
|
6484
|
+
setEffectPlayback(update) {
|
|
6485
|
+
if (update.playing !== void 0 && typeof update.playing !== "boolean") throw new PaintEditorError("playing must be true or false.");
|
|
6486
|
+
if (update.fps !== void 0 && !(Number.isInteger(update.fps) && update.fps >= 1 && update.fps <= 60)) throw new PaintEditorError(`fps ${update.fps} is out of range; use a whole number 1..60.`);
|
|
6487
|
+
this.effectPlaybackState = { ...this.effectPlaybackState, ...update };
|
|
6488
|
+
this.emit([]);
|
|
6489
|
+
return { ...this.effectPlaybackState, time: this.effectTime };
|
|
6490
|
+
}
|
|
6491
|
+
/** The surfaces of every stroke layer with live effects at `time` ms, drawn in full (frames rendered first). */
|
|
6492
|
+
async liveSurfacesAt(time) {
|
|
6493
|
+
const { layers, sources } = this.liveEffects();
|
|
6494
|
+
await this.prepareEffectPaints(sources.map((p) => framePaint(p, time)));
|
|
6495
|
+
const out = /* @__PURE__ */ new Map();
|
|
6496
|
+
this.liveEffectAt = time;
|
|
6497
|
+
try {
|
|
6498
|
+
for (const { layer, strokes } of layers) out.set(layer.id, this.drawLiveStrokes({ ...layer, offset: { x: 0, y: 0 } }, strokes));
|
|
6499
|
+
} finally {
|
|
6500
|
+
this.liveEffectAt = null;
|
|
6501
|
+
}
|
|
6502
|
+
return out;
|
|
6503
|
+
}
|
|
6504
|
+
/**
|
|
6505
|
+
* Shows the live effects at `time` ms (each live stroke's effect at its own time plus `time`):
|
|
6506
|
+
* renders their frames, then redraws only where live effect strokes paint. Nothing in the
|
|
6507
|
+
* document changes. Resolves `false` when the document changed while frames rendered (the next
|
|
6508
|
+
* frame catches up).
|
|
6509
|
+
*/
|
|
6510
|
+
async showEffectTime(time) {
|
|
6511
|
+
this.checkTime(time);
|
|
6512
|
+
const { layers, sources } = this.liveEffects();
|
|
6513
|
+
const revision = this.docRevision;
|
|
6514
|
+
await this.prepareEffectPaints(sources.map((p) => framePaint(p, time)));
|
|
6515
|
+
if (revision !== this.docRevision || this.session) return false;
|
|
6516
|
+
const next = /* @__PURE__ */ new Map();
|
|
6517
|
+
const dirty = [];
|
|
6518
|
+
this.liveEffectAt = time;
|
|
6519
|
+
try {
|
|
6520
|
+
for (const { layer, strokes } of layers) {
|
|
6521
|
+
const shown = this.animatedLayers.get(layer.id);
|
|
6522
|
+
const from = shown && shown.base === layer.surface ? shown.surface : layer.surface;
|
|
6523
|
+
const areas = strokes.filter(isLiveEffect).flatMap((st) => [liveStrokeBounds(st), ...this.paintedBounds.get(st) ? [this.paintedBounds.get(st)] : []]);
|
|
6524
|
+
const surface = this.drawLiveStrokes({ ...layer, surface: from, offset: { x: 0, y: 0 } }, strokes, layer.offset.x || layer.offset.y ? null : areas);
|
|
6525
|
+
next.set(layer.id, { base: layer.surface, surface });
|
|
6526
|
+
dirty.push(...areas);
|
|
6527
|
+
}
|
|
6528
|
+
} finally {
|
|
6529
|
+
this.liveEffectAt = null;
|
|
6530
|
+
}
|
|
6531
|
+
this.animatedLayers = next;
|
|
6532
|
+
this.effectTime = time;
|
|
6533
|
+
this.emit(dirty);
|
|
6534
|
+
return true;
|
|
6535
|
+
}
|
|
6536
|
+
/**
|
|
6537
|
+
* Plays live effect paint in the view until `stop`: a frame per tick of `clock` (default
|
|
6538
|
+
* animation frames) at most `fps` a second, one render in flight. Idle while paused, while a
|
|
6539
|
+
* stroke is painted, or when nothing is live. The bindings start it while the editor is shown.
|
|
6540
|
+
*/
|
|
6541
|
+
playEffects(clock = defaultEffectClock) {
|
|
6542
|
+
let stopped = false;
|
|
6543
|
+
let cancel = () => {
|
|
6544
|
+
};
|
|
6545
|
+
let last = -Infinity;
|
|
6546
|
+
let started = clock.now() - this.effectTime;
|
|
6547
|
+
const tick = () => {
|
|
6548
|
+
if (stopped) return;
|
|
6549
|
+
const now = clock.now();
|
|
6550
|
+
const { playing, fps } = this.effectPlaybackState;
|
|
6551
|
+
if (!playing) started = now - this.effectTime;
|
|
6552
|
+
if (!playing || this.session || now - last < 1e3 / fps - 1 || !this.liveEffects().layers.length) {
|
|
6553
|
+
cancel = clock.frame(tick);
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
6556
|
+
last = now;
|
|
6557
|
+
this.showEffectTime(Math.max(0, now - started)).then(
|
|
6558
|
+
() => {
|
|
6559
|
+
if (!stopped) cancel = clock.frame(tick);
|
|
6560
|
+
},
|
|
6561
|
+
() => {
|
|
6562
|
+
if (!stopped) cancel = clock.frame(tick);
|
|
6563
|
+
}
|
|
6564
|
+
);
|
|
6565
|
+
};
|
|
6566
|
+
cancel = clock.frame(tick);
|
|
6567
|
+
return {
|
|
6568
|
+
stop() {
|
|
6569
|
+
stopped = true;
|
|
6570
|
+
cancel();
|
|
6571
|
+
}
|
|
6572
|
+
};
|
|
6573
|
+
}
|
|
6574
|
+
/**
|
|
6575
|
+
* Runs `fn` with the painting as it is `time` ms into its live effects (see `getAnimation`): for
|
|
6576
|
+
* exports at a moment. Nothing in the document changes.
|
|
6577
|
+
*/
|
|
6578
|
+
async withEffectTime(time, fn) {
|
|
6579
|
+
this.checkTime(time);
|
|
6580
|
+
const surfaces = await this.liveSurfacesAt(time);
|
|
6581
|
+
const was = this.frameOverride;
|
|
6582
|
+
this.frameOverride = surfaces;
|
|
6583
|
+
try {
|
|
6584
|
+
return await fn();
|
|
6585
|
+
} finally {
|
|
6586
|
+
this.frameOverride = was;
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
/** The painting composited `time` ms into its live effects, like `compositePixels`. Nothing in the document changes. */
|
|
6590
|
+
async renderAnimationFrame(time, options = {}) {
|
|
6591
|
+
return this.withEffectTime(time, () => this.compositePixels(options));
|
|
6592
|
+
}
|
|
6401
6593
|
// --- Stencils --------------------------------------------------------------------
|
|
6402
6594
|
stencilCache = null;
|
|
6403
6595
|
nextStencilId = 1;
|
|
@@ -6615,6 +6807,24 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6615
6807
|
var MAX_IMAGE_SIDE = 8192;
|
|
6616
6808
|
var movePoint = (p, dx, dy) => ({ x: p.x + dx, y: p.y + dy });
|
|
6617
6809
|
var moveRect = (r, dx, dy) => ({ ...r, x: r.x + dx, y: r.y + dy });
|
|
6810
|
+
function isLiveEffect(s) {
|
|
6811
|
+
return s.paint?.type === "effect" && s.paint.live === true;
|
|
6812
|
+
}
|
|
6813
|
+
function framePaint(p, t) {
|
|
6814
|
+
return { ...p, time: p.time + t };
|
|
6815
|
+
}
|
|
6816
|
+
var MAX_LIVE_EFFECT_SOURCES = 8;
|
|
6817
|
+
var defaultEffectClock = {
|
|
6818
|
+
now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
|
|
6819
|
+
frame: (cb) => {
|
|
6820
|
+
if (typeof requestAnimationFrame === "undefined") {
|
|
6821
|
+
const t = setTimeout(cb, 16);
|
|
6822
|
+
return () => clearTimeout(t);
|
|
6823
|
+
}
|
|
6824
|
+
const id = requestAnimationFrame(cb);
|
|
6825
|
+
return () => cancelAnimationFrame(id);
|
|
6826
|
+
}
|
|
6827
|
+
};
|
|
6618
6828
|
function clipRect(r, width, height) {
|
|
6619
6829
|
const x0 = Math.max(0, Math.floor(r.x));
|
|
6620
6830
|
const y0 = Math.max(0, Math.floor(r.y));
|
|
@@ -8068,6 +8278,7 @@ var BRUSH_FIELDS = {
|
|
|
8068
8278
|
sticker: STICKER_BRUSH,
|
|
8069
8279
|
paint: BRUSH_PAINT
|
|
8070
8280
|
};
|
|
8281
|
+
var TIME = { type: "number", minimum: 0, description: "Draw live effect paint (strokes on stroke layers painted with live effect paint) this many ms into its animation (default: as painted, its own time)." };
|
|
8071
8282
|
var POINTS = {
|
|
8072
8283
|
type: "array",
|
|
8073
8284
|
minItems: 1,
|
|
@@ -8904,20 +9115,22 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
8904
9115
|
add({
|
|
8905
9116
|
name: "export_image",
|
|
8906
9117
|
title: "Export the painting",
|
|
8907
|
-
description: "Exports the painting as PNG, JPEG or WebP (base64). JPEG always includes the paper; JPEG and WebP need a browser canvas. Changes nothing. Returns the data, its MIME type and size.",
|
|
9118
|
+
description: "Exports the painting as PNG, JPEG or WebP (base64). JPEG always includes the paper; JPEG and WebP need a browser canvas. With time, live effect paint is drawn at that moment (get_effect_playback / set_effect_playback). Changes nothing. Returns the data, its MIME type and size.",
|
|
8908
9119
|
inputSchema: {
|
|
8909
9120
|
type: "object",
|
|
8910
9121
|
properties: {
|
|
8911
9122
|
format: { type: "string", enum: ["image/png", "image/jpeg", "image/webp"], description: "File type (default image/png)." },
|
|
8912
9123
|
quality: { type: "number", minimum: 0, maximum: 1, description: "JPEG / WebP quality 0..1 (default 0.92)." },
|
|
8913
9124
|
scale: { type: "number", exclusiveMinimum: 0, maximum: 1, description: "Shrink by this factor (default 1)." },
|
|
8914
|
-
paper: { type: "boolean", description: "Include the paper (default true)." }
|
|
9125
|
+
paper: { type: "boolean", description: "Include the paper (default true)." },
|
|
9126
|
+
time: TIME
|
|
8915
9127
|
},
|
|
8916
9128
|
additionalProperties: false
|
|
8917
9129
|
},
|
|
8918
9130
|
annotations: { title: "Export the painting", ...READ6 },
|
|
8919
|
-
handler: async ({ format, quality, scale, paper }) => {
|
|
8920
|
-
const
|
|
9131
|
+
handler: async ({ format, quality, scale, paper, time }) => {
|
|
9132
|
+
const run = () => c.exportImage({ format, quality, scale, paper });
|
|
9133
|
+
const bytes = time === void 0 ? await run() : await c.withEffectTime(time, run);
|
|
8921
9134
|
return { mimeType: format ?? "image/png", bytes: bytes.length, data: bytesToBase64(bytes) };
|
|
8922
9135
|
}
|
|
8923
9136
|
});
|
|
@@ -8925,10 +9138,10 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
8925
9138
|
name: "export_psd",
|
|
8926
9139
|
title: "Export as PSD",
|
|
8927
9140
|
description: 'Writes the painting as an 8-bit RGB PSD (PSB above 30000 px) through the shared PSD writer: every layer and group with its opacity, blend, mask and locks, the paper as a locked "Paper" pixel layer at the bottom, and the composite as the merged image. Changes nothing. Returns the file as base64, its size, whether it is a PSB, and warnings (pigment and painterly blend modes have no PSD equivalent: they are written as the nearest standard mode, see list_blend_modes).',
|
|
8928
|
-
inputSchema:
|
|
9141
|
+
inputSchema: { type: "object", properties: { time: TIME }, additionalProperties: false },
|
|
8929
9142
|
annotations: { title: "Export as PSD", ...READ6 },
|
|
8930
|
-
handler: () => {
|
|
8931
|
-
const r = c.exportPsd();
|
|
9143
|
+
handler: async ({ time }) => {
|
|
9144
|
+
const r = time === void 0 ? c.exportPsd() : await c.withEffectTime(time, () => c.exportPsd());
|
|
8932
9145
|
return { data: bytesToBase64(r.bytes), bytes: r.bytes.length, psb: r.psb, warnings: r.warnings };
|
|
8933
9146
|
}
|
|
8934
9147
|
});
|
|
@@ -9660,7 +9873,7 @@ function createPaintEffectsToolProvider(controller, options = {}) {
|
|
|
9660
9873
|
};
|
|
9661
9874
|
const paintInfo = () => {
|
|
9662
9875
|
const p = controller.getEffectPaint();
|
|
9663
|
-
return p ? [{ id: PAINT_TARGET, effect: p.effect, provider: p.provider, role: controller.getState().effectPaintRendering ? "effect paint (rendering)" : "effect paint", enabled: true, values: p.values, ...p.time ? { time: p.time } : {} }] : [];
|
|
9876
|
+
return p ? [{ id: PAINT_TARGET, effect: p.effect, provider: p.provider, role: controller.getState().effectPaintRendering ? "effect paint (rendering)" : "effect paint", enabled: true, values: p.values, ...p.time ? { time: p.time } : {}, ...p.map ? { map: p.map } : {}, ...p.live ? { live: true } : {} }] : [];
|
|
9664
9877
|
};
|
|
9665
9878
|
const layerInfo = (layerId) => {
|
|
9666
9879
|
const s = controller.getEffectLayer(layerId);
|
|
@@ -9729,7 +9942,7 @@ function createPaintEffectsToolProvider(controller, options = {}) {
|
|
|
9729
9942
|
if (target === PAINT_TARGET) {
|
|
9730
9943
|
if (changes.opacity !== void 0) throw new Error("opacity does not apply to the effect paint: change the brush opacity (set_brush tip.opacity).");
|
|
9731
9944
|
const p = controller.getEffectPaint();
|
|
9732
|
-
await controller.setEffectPaint({ effect: p.effect, values: { ...p.values, ...changes.values ?? {} }, time: p.time });
|
|
9945
|
+
await controller.setEffectPaint({ effect: p.effect, values: { ...p.values, ...changes.values ?? {} }, time: p.time, map: p.map, live: p.live });
|
|
9733
9946
|
const effects2 = paintInfo();
|
|
9734
9947
|
return { target, effect: effects2[0], effects: effects2 };
|
|
9735
9948
|
}
|
|
@@ -9788,13 +10001,25 @@ function createPaintEffectsToolProvider(controller, options = {}) {
|
|
|
9788
10001
|
{
|
|
9789
10002
|
name: "set_effect_paint",
|
|
9790
10003
|
title: "Paint with an effect",
|
|
9791
|
-
description: `Makes brushes paint with a provider generator, overlay or title effect (search_effects with use generator, overlay or title): it is rendered once at the canvas size, and each stroke then takes every pixel's colour from it, fixed to the canvas, as if painting a hidden picture into view (the effect's transparency paints nothing). Dab, particle and bristle brushes take the colour per pixel; mixer brushes and dab brushes that pick up paint take it per dab, at its centre.
|
|
10004
|
+
description: `Makes brushes paint with a provider generator, overlay or title effect (search_effects with use generator, overlay or title): it is rendered once at the canvas size, and each stroke then takes every pixel's colour from it, fixed to the canvas, as if painting a hidden picture into view (the effect's transparency paints nothing). Dab, particle and bristle brushes take the colour per pixel; mixer brushes and dab brushes that pick up paint take it per dab, at its centre. Watercolour, gouache, oil and impasto brushes take it per pixel too, shifted by paint they pick up (in wet paint the pattern then flows with the water; impasto keeps its relief), and sprayed stickers take the colour at their centre. Erasers, blenders and smudges ignore it. It replaces the paint colour or gradient until set_color or set_paint; a brush with its own gradient paints that instead. Strokes record it and replay exactly. Editor state, not an undo step. Returns the effect paint: { effect, provider, values (every control), time } (list_applied_effects target "paint" shows it; update_effect / remove_effect with target and effect "paint" change or clear it).`,
|
|
9792
10005
|
inputSchema: {
|
|
9793
10006
|
type: "object",
|
|
9794
10007
|
properties: {
|
|
9795
10008
|
effect: ctx.effectIdSchema(["generator", "overlay", "title"]),
|
|
9796
10009
|
values: ctx.valuesSchema("Control values over the effect's defaults"),
|
|
9797
|
-
time: { type: "number", minimum: 0, description: "Which moment of an animated effect to paint with, in ms (default 0)." }
|
|
10010
|
+
time: { type: "number", minimum: 0, description: "Which moment of an animated effect to paint with, in ms (default 0)." },
|
|
10011
|
+
map: {
|
|
10012
|
+
type: "object",
|
|
10013
|
+
description: "How the effect lies on the paint: by canvas (default: fixed to the canvas, painting reveals a hidden picture) or 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).",
|
|
10014
|
+
properties: {
|
|
10015
|
+
by: { type: "string", enum: ["canvas", "stroke"], description: "canvas (default) or stroke." },
|
|
10016
|
+
length: { type: "number", minimum: 16, maximum: 4096, description: "by stroke: px of stroke per repeat of the effect (default 300)." },
|
|
10017
|
+
repeat: { type: "string", enum: ["repeat", "mirror", "clamp"], description: "by stroke: past the strip end start again (default), run back, or hold the last colour." }
|
|
10018
|
+
},
|
|
10019
|
+
required: ["by"],
|
|
10020
|
+
additionalProperties: false
|
|
10021
|
+
},
|
|
10022
|
+
live: { type: "boolean", description: "Live effect paint (default false): strokes on a stroke layer (add_layer type strokes) keep playing the effect, so the paint moves (set_effect_playback); elsewhere strokes bake the frame at time." }
|
|
9798
10023
|
},
|
|
9799
10024
|
required: ["effect"],
|
|
9800
10025
|
additionalProperties: false
|
|
@@ -9802,9 +10027,24 @@ function createPaintEffectsToolProvider(controller, options = {}) {
|
|
|
9802
10027
|
annotations: { title: "Paint with an effect", ...ctx.annotations.writeIdempotent },
|
|
9803
10028
|
handler: async (args) => {
|
|
9804
10029
|
ctx.requireProvider();
|
|
9805
|
-
return { effectPaint: await controller.setEffectPaint({ effect: args.effect, values: values(args.values), time: args.time }) };
|
|
10030
|
+
return { effectPaint: await controller.setEffectPaint({ effect: args.effect, values: values(args.values), time: args.time, map: args.map, live: args.live }) };
|
|
9806
10031
|
}
|
|
9807
10032
|
},
|
|
10033
|
+
{
|
|
10034
|
+
name: "set_effect_playback",
|
|
10035
|
+
title: "Play or pause live effect paint",
|
|
10036
|
+
description: "Plays or pauses live effect paint (strokes painted on stroke layers with set_effect_paint live true) in the editor view, and caps its frame rate. Exports draw it at a chosen time with export_image / export_psd time. Editor state, not an undo step. Returns playing, fps, the time the view shows (ms) and what plays (animation: layer ids, live strokes, sources; null when nothing does).",
|
|
10037
|
+
inputSchema: {
|
|
10038
|
+
type: "object",
|
|
10039
|
+
properties: {
|
|
10040
|
+
playing: { type: "boolean", description: "Play (true, the default state) or pause at the current frame." },
|
|
10041
|
+
fps: { type: "integer", minimum: 1, maximum: 60, description: "Frame rate cap, frames a second (default 30); lower it on large canvases." }
|
|
10042
|
+
},
|
|
10043
|
+
additionalProperties: false
|
|
10044
|
+
},
|
|
10045
|
+
annotations: { title: "Play or pause live effect paint", ...ctx.annotations.writeIdempotent },
|
|
10046
|
+
handler: async (args) => ({ ...controller.setEffectPlayback({ playing: args.playing, fps: args.fps }), animation: controller.getAnimation() })
|
|
10047
|
+
},
|
|
9808
10048
|
{
|
|
9809
10049
|
name: "add_effect_layer",
|
|
9810
10050
|
title: "Add an effect layer",
|
|
@@ -9914,6 +10154,8 @@ export {
|
|
|
9914
10154
|
DEFAULT_WET_BRUSH,
|
|
9915
10155
|
DEFAULT_WET_PARAMS,
|
|
9916
10156
|
DryStroke,
|
|
10157
|
+
EFFECT_MAP_REPEATS,
|
|
10158
|
+
EFFECT_STRIP_HEIGHT,
|
|
9917
10159
|
GPU_LEAD,
|
|
9918
10160
|
GRAIN_PATTERNS,
|
|
9919
10161
|
GRAIN_TILE,
|
|
@@ -9929,6 +10171,7 @@ export {
|
|
|
9929
10171
|
MAX_CANVAS_SIDE,
|
|
9930
10172
|
MAX_COORDINATE,
|
|
9931
10173
|
MAX_IMAGE_SIDE,
|
|
10174
|
+
MAX_LIVE_EFFECT_SOURCES,
|
|
9932
10175
|
MAX_ZOOM,
|
|
9933
10176
|
MIN_CANVAS_SIDE,
|
|
9934
10177
|
MIN_ZOOM,
|
|
@@ -10048,8 +10291,10 @@ export {
|
|
|
10048
10291
|
downsampleRgba,
|
|
10049
10292
|
drawSticker,
|
|
10050
10293
|
drawWaterOverlay,
|
|
10294
|
+
effectDabField,
|
|
10051
10295
|
effectLayersToJSON,
|
|
10052
10296
|
effectPaintKey,
|
|
10297
|
+
effectPaintSize,
|
|
10053
10298
|
emptiedLayers,
|
|
10054
10299
|
emptyThickChannels,
|
|
10055
10300
|
fitZoom,
|
|
@@ -10135,6 +10380,7 @@ export {
|
|
|
10135
10380
|
quickStencil,
|
|
10136
10381
|
rand01,
|
|
10137
10382
|
randSigned,
|
|
10383
|
+
readEffectStrokeMap,
|
|
10138
10384
|
readPaintEffectPaint,
|
|
10139
10385
|
readPaintEffectSource,
|
|
10140
10386
|
readPaintSource,
|
|
@@ -14,6 +14,7 @@ import { TileMedium, type PaintTileTarget } from '../paint/exchange.cjs';
|
|
|
14
14
|
import { type PaintGradientSource } from '../paint/gradient-paint.cjs';
|
|
15
15
|
import type { PaintBrush } from './brushes.cjs';
|
|
16
16
|
import { type PenPoint, type StrokeCopy } from './engine.cjs';
|
|
17
|
+
import { type EffectPaintPixels } from './effect-paint.cjs';
|
|
17
18
|
export type StampKind = 'dot' | 'star' | 'heart' | 'leaf' | 'flower' | 'confetti' | 'splat' | 'sparkle' | 'ring';
|
|
18
19
|
export declare const STAMP_KINDS: readonly StampKind[];
|
|
19
20
|
/** One sticker (document pixels). */
|
|
@@ -73,6 +74,11 @@ export interface StickerSprayInit {
|
|
|
73
74
|
nextId: number;
|
|
74
75
|
/** Gradient paint (resolved): each sticker takes its dab's colour (across the tip: by its offset). */
|
|
75
76
|
paint?: PaintGradientSource | null;
|
|
77
|
+
/**
|
|
78
|
+
* Effect paint: each sticker takes the effect's colour at its centre (it keeps one colour, so it
|
|
79
|
+
* stays recolourable); none lands where the effect is transparent.
|
|
80
|
+
*/
|
|
81
|
+
effect?: EffectPaintPixels | null;
|
|
76
82
|
}
|
|
77
83
|
/** A sticker-spray stroke: drops stickers along the path and draws them as it goes. */
|
|
78
84
|
export declare class StickerSpray {
|
|
@@ -14,6 +14,7 @@ import { TileMedium, type PaintTileTarget } from '../paint/exchange.js';
|
|
|
14
14
|
import { type PaintGradientSource } from '../paint/gradient-paint.js';
|
|
15
15
|
import type { PaintBrush } from './brushes.js';
|
|
16
16
|
import { type PenPoint, type StrokeCopy } from './engine.js';
|
|
17
|
+
import { type EffectPaintPixels } from './effect-paint.js';
|
|
17
18
|
export type StampKind = 'dot' | 'star' | 'heart' | 'leaf' | 'flower' | 'confetti' | 'splat' | 'sparkle' | 'ring';
|
|
18
19
|
export declare const STAMP_KINDS: readonly StampKind[];
|
|
19
20
|
/** One sticker (document pixels). */
|
|
@@ -73,6 +74,11 @@ export interface StickerSprayInit {
|
|
|
73
74
|
nextId: number;
|
|
74
75
|
/** Gradient paint (resolved): each sticker takes its dab's colour (across the tip: by its offset). */
|
|
75
76
|
paint?: PaintGradientSource | null;
|
|
77
|
+
/**
|
|
78
|
+
* Effect paint: each sticker takes the effect's colour at its centre (it keeps one colour, so it
|
|
79
|
+
* stays recolourable); none lands where the effect is transparent.
|
|
80
|
+
*/
|
|
81
|
+
effect?: EffectPaintPixels | null;
|
|
76
82
|
}
|
|
77
83
|
/** A sticker-spray stroke: drops stickers along the path and draws them as it goes. */
|
|
78
84
|
export declare class StickerSpray {
|