@pixerate/editor 0.106.0 → 0.108.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/THIRD_PARTY_NOTICES.md +4 -0
- package/dist/animation-editor/index.cjs +39 -39
- package/dist/animation-editor/index.d.cts +1 -1
- package/dist/animation-editor/index.d.ts +1 -1
- package/dist/animation-editor/index.js +1 -1
- package/dist/{chunk-DS7KDKPX.js → chunk-3PHA3UKI.js} +2 -2
- package/dist/{chunk-ZID226ZG.cjs → chunk-FRD3FUXI.cjs} +172 -172
- package/dist/{chunk-ZID226ZG.cjs.map → chunk-FRD3FUXI.cjs.map} +1 -1
- package/dist/{chunk-67FRIOPZ.js → chunk-G4BIYE6R.js} +2 -2
- package/dist/{chunk-PV42JITT.cjs → chunk-HUFQD4CB.cjs} +44 -44
- package/dist/{chunk-PV42JITT.cjs.map → chunk-HUFQD4CB.cjs.map} +1 -1
- package/dist/{chunk-63EFQCT5.js → chunk-HUQ7EHVI.js} +1140 -371
- package/dist/chunk-HUQ7EHVI.js.map +1 -0
- package/dist/{chunk-4X5MIVCE.js → chunk-SOROZ2BF.js} +3 -3
- package/dist/{chunk-CRMAXIUV.cjs → chunk-SZ6KMZNM.cjs} +157 -8
- package/dist/chunk-SZ6KMZNM.cjs.map +1 -0
- package/dist/{chunk-WSTEXEKD.cjs → chunk-T3QZ6S47.cjs} +92 -92
- package/dist/{chunk-WSTEXEKD.cjs.map → chunk-T3QZ6S47.cjs.map} +1 -1
- package/dist/{chunk-KANRAHTL.cjs → chunk-UTEVPQTS.cjs} +1353 -584
- package/dist/chunk-UTEVPQTS.cjs.map +1 -0
- package/dist/{chunk-HEW6KDNI.js → chunk-WMBIJCHA.js} +155 -6
- package/dist/chunk-WMBIJCHA.js.map +1 -0
- package/dist/{compositor-Bc3PNP3C.d.cts → compositor-D92URMlm.d.cts} +1 -1
- package/dist/{compositor-BsYYllvR.d.ts → compositor-Kt_M4ruo.d.ts} +1 -1
- package/dist/{export-_m3Es4ru.d.cts → export-JGWQEPJn.d.cts} +3 -3
- package/dist/{export-iMrop3Jm.d.ts → export-a9bnF7Cl.d.ts} +3 -3
- package/dist/image-editor/index.cjs +627 -619
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +8 -8
- package/dist/image-editor/index.d.ts +8 -8
- package/dist/image-editor/index.js +12 -4
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{mixer-C1M1Nua_.d.cts → mixer-5ziHU8OS.d.cts} +1 -1
- package/dist/{mixer-Cu5a9Bzd.d.ts → mixer-DNqiYXYQ.d.ts} +1 -1
- package/dist/paint/index.cjs +3 -3
- package/dist/paint/index.d.cts +2 -2
- package/dist/paint/index.d.ts +2 -2
- package/dist/paint/index.js +2 -2
- package/dist/paint-editor/index.cjs +365 -132
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +113 -8
- package/dist/paint-editor/index.d.ts +113 -8
- package/dist/paint-editor/index.js +249 -16
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +11 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +4 -4
- package/dist/raster/index.d.ts +4 -4
- package/dist/raster/index.js +10 -2
- package/dist/{surface-BBJN9wxg.d.ts → surface-CkKOHGPm.d.cts} +20 -2
- package/dist/{surface-BBJN9wxg.d.cts → surface-CkKOHGPm.d.ts} +20 -2
- package/dist/{svg-Bj1MQFh6.d.cts → svg-ClVIG4T7.d.cts} +26 -2
- package/dist/{svg-vme0PLNo.d.ts → svg-SseJTIrA.d.ts} +26 -2
- package/dist/vector/index.cjs +977 -948
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +266 -7
- package/dist/vector/index.d.ts +266 -7
- package/dist/vector/index.js +427 -398
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +50 -7
- package/skills/pixerate-paint-editor/SKILL.md +21 -1
- package/skills/pixerate-vector/SKILL.md +35 -5
- package/dist/chunk-63EFQCT5.js.map +0 -1
- package/dist/chunk-CRMAXIUV.cjs.map +0 -1
- package/dist/chunk-HEW6KDNI.js.map +0 -1
- package/dist/chunk-KANRAHTL.cjs.map +0 -1
- /package/dist/{chunk-DS7KDKPX.js.map → chunk-3PHA3UKI.js.map} +0 -0
- /package/dist/{chunk-67FRIOPZ.js.map → chunk-G4BIYE6R.js.map} +0 -0
- /package/dist/{chunk-4X5MIVCE.js.map → chunk-SOROZ2BF.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
resolveExternalAssets,
|
|
5
5
|
serializeLayers,
|
|
6
6
|
serializeLayersExternal
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-SOROZ2BF.js";
|
|
8
8
|
import {
|
|
9
9
|
RasterCompositor,
|
|
10
10
|
bytesToBase64,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
mapLayer,
|
|
18
18
|
parseDataUrl,
|
|
19
19
|
pngDataUrl
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-G4BIYE6R.js";
|
|
21
21
|
import "../chunk-GX332W2C.js";
|
|
22
22
|
import {
|
|
23
23
|
CoverageMap,
|
|
@@ -35,9 +35,10 @@ import {
|
|
|
35
35
|
normalizeBrush,
|
|
36
36
|
normalizeMixer,
|
|
37
37
|
rasterizeDab
|
|
38
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-3PHA3UKI.js";
|
|
39
39
|
import {
|
|
40
40
|
BLEND_MODES,
|
|
41
|
+
PAINTERLY_BLEND_MODES,
|
|
41
42
|
TILE,
|
|
42
43
|
TiledSurface,
|
|
43
44
|
contentBounds,
|
|
@@ -45,12 +46,20 @@ import {
|
|
|
45
46
|
fillRect,
|
|
46
47
|
finishTile,
|
|
47
48
|
isBlendMode,
|
|
49
|
+
isPainterlyBlendMode,
|
|
48
50
|
readPixel,
|
|
49
51
|
readRegion,
|
|
52
|
+
standardBlendMode,
|
|
50
53
|
surfaceFromPixels,
|
|
51
54
|
tileKey,
|
|
52
55
|
tilesInRect
|
|
53
|
-
} from "../chunk-
|
|
56
|
+
} from "../chunk-WMBIJCHA.js";
|
|
57
|
+
import {
|
|
58
|
+
PerformanceQueue,
|
|
59
|
+
PresenceStore,
|
|
60
|
+
realPresenceClock,
|
|
61
|
+
tweenPresenceCursor
|
|
62
|
+
} from "../chunk-B6DSIEXA.js";
|
|
54
63
|
import "../chunk-KKREERP2.js";
|
|
55
64
|
|
|
56
65
|
// src/paint-editor/noise.ts
|
|
@@ -2713,6 +2722,7 @@ var PaintEditorController = class {
|
|
|
2713
2722
|
else this.emit([]);
|
|
2714
2723
|
return result;
|
|
2715
2724
|
}
|
|
2725
|
+
/** Checks stroke samples (finite x and y, pressure 0..1) and returns them; throws a sentence naming the bad point. */
|
|
2716
2726
|
checkPoints(points) {
|
|
2717
2727
|
if (!Array.isArray(points) || !points.length) throw new PaintEditorError("points is empty; give at least one {x, y} in document pixels.");
|
|
2718
2728
|
const out = [];
|
|
@@ -2936,9 +2946,22 @@ var PaintEditorController = class {
|
|
|
2936
2946
|
offset: { x: 0, y: 0 }
|
|
2937
2947
|
};
|
|
2938
2948
|
const merged = this.compositePixels({ paper: true }).data;
|
|
2939
|
-
const
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2949
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2950
|
+
const toStandard = (layers2) => layers2.map((l) => {
|
|
2951
|
+
const blend = l.blend === "pass-through" ? l.blend : standardBlendMode(l.blend);
|
|
2952
|
+
if (blend !== l.blend) changed.set(l.blend, [...changed.get(l.blend) ?? [], l.name]);
|
|
2953
|
+
const next = blend === l.blend ? l : { ...l, blend };
|
|
2954
|
+
return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
|
|
2955
|
+
});
|
|
2956
|
+
const layers = toStandard(this.doc.layers);
|
|
2957
|
+
const result = exportPsd({ width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
|
|
2958
|
+
for (const [mode, names] of changed) {
|
|
2959
|
+
const label = BLEND_MODES.find((m) => m.id === mode)?.label ?? mode;
|
|
2960
|
+
const std = BLEND_MODES.find((m) => m.id === standardBlendMode(mode))?.label ?? standardBlendMode(mode);
|
|
2961
|
+
const list = names.map((n) => `"${n}"`).join(", ");
|
|
2962
|
+
const one = names.length === 1;
|
|
2963
|
+
result.warnings.push(`${label} blending has no PSD equivalent; ${list} ${one ? "was" : "were"} written as ${std}, the nearest Photoshop mode, so ${one ? "it looks" : "they look"} different in Photoshop (the merged image is exact).`);
|
|
2964
|
+
}
|
|
2942
2965
|
return result;
|
|
2943
2966
|
}
|
|
2944
2967
|
/** `render_preview` as a base64 PNG. */
|
|
@@ -3234,6 +3257,169 @@ function resampleInto(src, rect, width, height) {
|
|
|
3234
3257
|
return { width, height, data: out };
|
|
3235
3258
|
}
|
|
3236
3259
|
|
|
3260
|
+
// src/paint-editor/presence.ts
|
|
3261
|
+
var PaintPresence = class extends PresenceStore {
|
|
3262
|
+
};
|
|
3263
|
+
var FRAME = 16;
|
|
3264
|
+
function strokeSchedule(points) {
|
|
3265
|
+
if (points.length < 2) return points.map(() => 1);
|
|
3266
|
+
const t0 = points[0].t;
|
|
3267
|
+
const t1 = points[points.length - 1].t;
|
|
3268
|
+
if (points.every((p) => Number.isFinite(p.t)) && t1 > t0) {
|
|
3269
|
+
return points.map((p) => Math.min(1, Math.max(0, (p.t - t0) / (t1 - t0))));
|
|
3270
|
+
}
|
|
3271
|
+
const acc = [0];
|
|
3272
|
+
for (let i = 1; i < points.length; i++) acc.push(acc[i - 1] + Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y));
|
|
3273
|
+
const total = acc[acc.length - 1];
|
|
3274
|
+
return total > 0 ? acc.map((d) => d / total) : points.map(() => 1);
|
|
3275
|
+
}
|
|
3276
|
+
function strokeDuration(points, max = 1800) {
|
|
3277
|
+
const t0 = points[0]?.t;
|
|
3278
|
+
const t1 = points[points.length - 1]?.t;
|
|
3279
|
+
let ms;
|
|
3280
|
+
if (points.every((p) => Number.isFinite(p.t)) && t1 > t0) ms = t1 - t0;
|
|
3281
|
+
else {
|
|
3282
|
+
let length = 0;
|
|
3283
|
+
for (let i = 1; i < points.length; i++) length += Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y);
|
|
3284
|
+
ms = length / 1.5;
|
|
3285
|
+
}
|
|
3286
|
+
return Math.min(max, Math.max(250, ms));
|
|
3287
|
+
}
|
|
3288
|
+
function sweepPath(rect, rows = 4) {
|
|
3289
|
+
const inset = Math.min(rect.width, rect.height) * 0.08;
|
|
3290
|
+
const left = rect.x + inset;
|
|
3291
|
+
const right = rect.x + rect.width - inset;
|
|
3292
|
+
const out = [];
|
|
3293
|
+
for (let r = 0; r < rows; r++) {
|
|
3294
|
+
const y = rect.y + inset + (rect.height - 2 * inset) * (r + 0.5) / rows;
|
|
3295
|
+
out.push(r % 2 ? { x: right, y } : { x: left, y }, r % 2 ? { x: left, y } : { x: right, y });
|
|
3296
|
+
}
|
|
3297
|
+
return out;
|
|
3298
|
+
}
|
|
3299
|
+
var centre = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
3300
|
+
var PaintDirector = class {
|
|
3301
|
+
constructor(controller, presence, options = {}) {
|
|
3302
|
+
this.controller = controller;
|
|
3303
|
+
this.presence = presence;
|
|
3304
|
+
this.options = options;
|
|
3305
|
+
this.clock = options.clock ?? realPresenceClock;
|
|
3306
|
+
this.queue = new PerformanceQueue(presence, { clock: this.clock, linger: options.linger });
|
|
3307
|
+
}
|
|
3308
|
+
controller;
|
|
3309
|
+
presence;
|
|
3310
|
+
options;
|
|
3311
|
+
clock;
|
|
3312
|
+
queue;
|
|
3313
|
+
/** Acts out `commands` in order for `actor`. Resolves with one result per command. */
|
|
3314
|
+
perform(actor, commands) {
|
|
3315
|
+
return this.queue.run(actor, async () => {
|
|
3316
|
+
const results = [];
|
|
3317
|
+
for (const command of commands) {
|
|
3318
|
+
try {
|
|
3319
|
+
results.push({ ok: true, command, result: await this.performOne(actor.id, command) });
|
|
3320
|
+
} catch (error) {
|
|
3321
|
+
results.push({ ok: false, command, message: error instanceof Error ? error.message : String(error) });
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
return results;
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
ms(n) {
|
|
3328
|
+
return Math.round(n / Math.max(0.1, this.options.speed ?? 1));
|
|
3329
|
+
}
|
|
3330
|
+
where(id) {
|
|
3331
|
+
const a = this.presence.get(id);
|
|
3332
|
+
return { x: a?.x ?? 0, y: a?.y ?? 0 };
|
|
3333
|
+
}
|
|
3334
|
+
/** Glides to `to`; an actor that just appeared starts there instead of the corner. */
|
|
3335
|
+
async glide(id, to, ms = 380) {
|
|
3336
|
+
const a = this.presence.get(id);
|
|
3337
|
+
if (!a || a.x === 0 && a.y === 0) {
|
|
3338
|
+
this.presence.set({ id, x: to.x, y: to.y });
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
await tweenPresenceCursor(this.presence, this.clock, id, this.where(id), to, this.ms(ms));
|
|
3342
|
+
}
|
|
3343
|
+
async say(id, text, ms) {
|
|
3344
|
+
if (!text) return;
|
|
3345
|
+
this.presence.set({ id, message: text });
|
|
3346
|
+
await this.clock.wait(this.ms(ms));
|
|
3347
|
+
}
|
|
3348
|
+
async performOne(id, command) {
|
|
3349
|
+
const c = this.controller;
|
|
3350
|
+
switch (command.kind) {
|
|
3351
|
+
case "say":
|
|
3352
|
+
await this.say(id, command.text, 900);
|
|
3353
|
+
return void 0;
|
|
3354
|
+
case "point":
|
|
3355
|
+
await this.say(id, command.note, 500);
|
|
3356
|
+
await this.glide(id, command.at);
|
|
3357
|
+
this.presence.set({ id, pressed: true });
|
|
3358
|
+
await this.clock.wait(this.ms(110));
|
|
3359
|
+
this.presence.set({ id, pressed: false });
|
|
3360
|
+
return void 0;
|
|
3361
|
+
case "stroke":
|
|
3362
|
+
await this.say(id, command.note, 500);
|
|
3363
|
+
return this.stroke(id, command.stroke);
|
|
3364
|
+
case "fill": {
|
|
3365
|
+
await this.say(id, command.note, 500);
|
|
3366
|
+
const { width, height } = c.getState().document.canvas;
|
|
3367
|
+
const path = sweepPath({ x: 0, y: 0, width, height });
|
|
3368
|
+
await this.glide(id, path[0]);
|
|
3369
|
+
this.presence.set({ id, pressed: true });
|
|
3370
|
+
try {
|
|
3371
|
+
for (let i = 1; i < path.length; i++) await tweenPresenceCursor(this.presence, this.clock, id, path[i - 1], path[i], this.ms(i % 2 ? 260 : 90));
|
|
3372
|
+
} finally {
|
|
3373
|
+
this.presence.set({ id, pressed: false });
|
|
3374
|
+
}
|
|
3375
|
+
return c.fillLayer(command.fill);
|
|
3376
|
+
}
|
|
3377
|
+
case "replay": {
|
|
3378
|
+
await this.say(id, command.note, 500);
|
|
3379
|
+
const source = c.getStrokes().filter((s) => command.replay.strokeIds?.includes(s.id));
|
|
3380
|
+
const first = source[0]?.points[0];
|
|
3381
|
+
if (first) await this.glide(id, first);
|
|
3382
|
+
const result = c.replayStrokes(command.replay);
|
|
3383
|
+
if (result.bounds) await this.glide(id, centre(result.bounds), 520);
|
|
3384
|
+
return result;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
/** Paints a stroke live: begins it, pushes its points frame by frame as the cursor reaches them, then ends it (one undo step). */
|
|
3389
|
+
async stroke(id, init) {
|
|
3390
|
+
const c = this.controller;
|
|
3391
|
+
const points = c.checkPoints(init.points);
|
|
3392
|
+
const { points: _points, ...rest } = init;
|
|
3393
|
+
const session = c.beginStroke({ snapToPerspective: false, start: points[0], ...rest });
|
|
3394
|
+
try {
|
|
3395
|
+
await this.glide(id, points[0]);
|
|
3396
|
+
this.presence.set({ id, pressed: true });
|
|
3397
|
+
const at = strokeSchedule(points);
|
|
3398
|
+
const duration = this.ms(strokeDuration(points, this.options.maxStrokeMs));
|
|
3399
|
+
let next = 0;
|
|
3400
|
+
const frames = Math.max(1, Math.round(duration / FRAME));
|
|
3401
|
+
for (let f = 1; f <= frames; f++) {
|
|
3402
|
+
const t = f / frames;
|
|
3403
|
+
const from = next;
|
|
3404
|
+
while (next < points.length && at[next] <= t + 1e-9) next++;
|
|
3405
|
+
if (next > from) {
|
|
3406
|
+
session.push(points.slice(from, next));
|
|
3407
|
+
const p = points[next - 1];
|
|
3408
|
+
this.presence.set({ id, x: p.x, y: p.y });
|
|
3409
|
+
}
|
|
3410
|
+
if (f < frames) await this.clock.wait(duration / frames);
|
|
3411
|
+
}
|
|
3412
|
+
if (next < points.length) session.push(points.slice(next));
|
|
3413
|
+
this.presence.set({ id, pressed: false });
|
|
3414
|
+
return session.end();
|
|
3415
|
+
} catch (error) {
|
|
3416
|
+
if (c.activeStroke === session) session.cancel();
|
|
3417
|
+
this.presence.set({ id, pressed: false });
|
|
3418
|
+
throw error;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
|
|
3237
3423
|
// src/paint-editor/guide-tools.ts
|
|
3238
3424
|
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
3239
3425
|
var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
@@ -3585,7 +3771,11 @@ var TIP = {
|
|
|
3585
3771
|
smoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Position smoothing of the input, 0..0.95." },
|
|
3586
3772
|
pressureSize: { type: "boolean", description: "Pressure scales the size (down to dynamics.minSize)." },
|
|
3587
3773
|
pressureOpacity: { type: "boolean", description: "Pressure scales the opacity." },
|
|
3588
|
-
blend: {
|
|
3774
|
+
blend: {
|
|
3775
|
+
type: "string",
|
|
3776
|
+
enum: BLEND_MODES.map((m) => m.id),
|
|
3777
|
+
description: "How paint combines with the layer. pigment mixes it in like real paint (blue into yellow gives green); multiply darkens like marker ink; watercolor glazes like a transparent wash; tint, highlight and shadow recolour, light and shade (see list_blend_modes)."
|
|
3778
|
+
}
|
|
3589
3779
|
},
|
|
3590
3780
|
additionalProperties: false
|
|
3591
3781
|
};
|
|
@@ -3665,6 +3855,12 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3665
3855
|
const c = controller;
|
|
3666
3856
|
const tools = [];
|
|
3667
3857
|
const add = (t) => tools.push(t);
|
|
3858
|
+
const act = async (command, direct) => {
|
|
3859
|
+
if (!options.director) return direct();
|
|
3860
|
+
const [r] = await options.director.perform(options.actor ?? { id: "agent", name: "Agent" }, [command]);
|
|
3861
|
+
if (!r || !r.ok) throw new Error(r && !r.ok ? r.message : "The director did not run the command.");
|
|
3862
|
+
return r.result;
|
|
3863
|
+
};
|
|
3668
3864
|
add({
|
|
3669
3865
|
name: "get_canvas_info",
|
|
3670
3866
|
title: "Get the canvas",
|
|
@@ -3820,7 +4016,11 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3820
4016
|
name: { type: "string", minLength: 1, description: "New name." },
|
|
3821
4017
|
visible: { type: "boolean", description: "Show or hide." },
|
|
3822
4018
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0..1." },
|
|
3823
|
-
blend: {
|
|
4019
|
+
blend: {
|
|
4020
|
+
type: "string",
|
|
4021
|
+
enum: [...BLEND_MODES.map((m) => m.id), "pass-through"],
|
|
4022
|
+
description: "Blend mode (pass-through for groups only). Painterly modes: watercolor (the layer glazes like a transparent wash), tint (recolours keeping lightness), highlight (adds soft coloured light), shadow (shades with ambient fill); see list_blend_modes."
|
|
4023
|
+
},
|
|
3824
4024
|
clipped: { type: "boolean", description: "Clip to the layer below (paint only shows where it has paint)." },
|
|
3825
4025
|
locks: {
|
|
3826
4026
|
type: "object",
|
|
@@ -3840,6 +4040,22 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3840
4040
|
annotations: { title: "Change a layer", ...SET2 },
|
|
3841
4041
|
handler: ({ layerId, ...u }) => c.updateLayer(layerId, u)
|
|
3842
4042
|
});
|
|
4043
|
+
add({
|
|
4044
|
+
name: "list_blend_modes",
|
|
4045
|
+
title: "List blend modes",
|
|
4046
|
+
description: "Lists every layer and brush blend mode: id, label, group (standard Photoshop modes, pigment, or painterly), what the pigment and painterly modes do, and the standard mode PSD export writes in their place. Changes nothing. Read it before choosing a painterly mode for update_layer or a brush tip.",
|
|
4047
|
+
inputSchema: EMPTY2,
|
|
4048
|
+
annotations: { title: "List blend modes", ...READ2 },
|
|
4049
|
+
handler: () => ({
|
|
4050
|
+
modes: BLEND_MODES.map((m) => ({
|
|
4051
|
+
id: m.id,
|
|
4052
|
+
label: m.label,
|
|
4053
|
+
group: m.painterly ? "painterly" : m.id === "pigment" ? "pigment" : "standard",
|
|
4054
|
+
...m.description ? { description: m.description } : {},
|
|
4055
|
+
psd: standardBlendMode(m.id)
|
|
4056
|
+
}))
|
|
4057
|
+
})
|
|
4058
|
+
});
|
|
3843
4059
|
add({
|
|
3844
4060
|
name: "set_active_layer",
|
|
3845
4061
|
title: "Choose the active layer",
|
|
@@ -3904,12 +4120,12 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3904
4120
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 1)." },
|
|
3905
4121
|
grain: { type: "number", minimum: 0, maximum: 1, description: "Paper grain response 0..1 (default 0: flat)." },
|
|
3906
4122
|
pressure: { type: "number", minimum: 0, maximum: 1, description: "With grain: how far into the valleys, 0..1 (default 0.5)." },
|
|
3907
|
-
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal; pigment mixes like paint)." }
|
|
4123
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal; pigment mixes like paint, watercolor glazes; see list_blend_modes)." }
|
|
3908
4124
|
},
|
|
3909
4125
|
additionalProperties: false
|
|
3910
4126
|
},
|
|
3911
4127
|
annotations: { title: "Fill a layer", ...WRITE2 },
|
|
3912
|
-
handler: (a) => c.fillLayer(a)
|
|
4128
|
+
handler: (a) => act({ kind: "fill", fill: a }, () => c.fillLayer(a))
|
|
3913
4129
|
});
|
|
3914
4130
|
add({
|
|
3915
4131
|
name: "clear_layer",
|
|
@@ -4092,7 +4308,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
4092
4308
|
add({
|
|
4093
4309
|
name: "paint_stroke",
|
|
4094
4310
|
title: "Paint a brush stroke",
|
|
4095
|
-
description: "Paints one stroke through points (document pixels, with optional pressure, time, tilt and barrel rotation) on a pixel layer with a library brush or the active one, optionally changed for this stroke. Grain-responsive brushes follow the paper; the mixer engine (mixer-brush, blender, smudge) picks up and moves paint already on the layer. The symmetry guide adds its mirror copies; with a tracing image that samples colours, a stroke without color takes the image colour at its first point. One undo step. Returns the layer id, changed bounds, dab count, the recorded strokeId (for replay_strokes) and, for the mixer, its load after the stroke.",
|
|
4311
|
+
description: "Paints one stroke through points (document pixels, with optional pressure, time, tilt and barrel rotation) on a pixel layer with a library brush or the active one, optionally changed for this stroke. Grain-responsive brushes follow the paper; the mixer engine (mixer-brush, blender, smudge) picks up and moves paint already on the layer. The symmetry guide adds its mirror copies; with a tracing image that samples colours, a stroke without color takes the image colour at its first point. With an agent cursor (a director), the stroke is painted live along the points at the pace of their times. One undo step. Returns the layer id, changed bounds, dab count, the recorded strokeId (for replay_strokes) and, for the mixer, its load after the stroke.",
|
|
4096
4312
|
inputSchema: {
|
|
4097
4313
|
type: "object",
|
|
4098
4314
|
properties: {
|
|
@@ -4108,8 +4324,9 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
4108
4324
|
additionalProperties: false
|
|
4109
4325
|
},
|
|
4110
4326
|
annotations: { title: "Paint a brush stroke", ...WRITE2 },
|
|
4111
|
-
handler: (a) => {
|
|
4112
|
-
const
|
|
4327
|
+
handler: async (a) => {
|
|
4328
|
+
const stroke = { points: a.points, layerId: a.layerId, brushId: a.brushId, brush: a.brush, color: a.color, seed: a.seed, snapToPerspective: a.snapToPerspective };
|
|
4329
|
+
const r = await act({ kind: "stroke", stroke }, () => c.paintStroke(stroke));
|
|
4113
4330
|
return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
|
|
4114
4331
|
}
|
|
4115
4332
|
});
|
|
@@ -4183,7 +4400,10 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
4183
4400
|
additionalProperties: false
|
|
4184
4401
|
},
|
|
4185
4402
|
annotations: { title: "Replay strokes, varied", ...WRITE2 },
|
|
4186
|
-
handler: (a) =>
|
|
4403
|
+
handler: (a) => {
|
|
4404
|
+
const replay = a;
|
|
4405
|
+
return act({ kind: "replay", replay }, () => c.replayStrokes(replay));
|
|
4406
|
+
}
|
|
4187
4407
|
});
|
|
4188
4408
|
add({
|
|
4189
4409
|
name: "set_view",
|
|
@@ -4282,7 +4502,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
4282
4502
|
add({
|
|
4283
4503
|
name: "export_psd",
|
|
4284
4504
|
title: "Export as PSD",
|
|
4285
|
-
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
|
|
4505
|
+
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).',
|
|
4286
4506
|
inputSchema: EMPTY2,
|
|
4287
4507
|
annotations: { title: "Export as PSD", ...READ2 },
|
|
4288
4508
|
handler: () => {
|
|
@@ -4591,7 +4811,12 @@ function wheelPick(dx, dy, wheel, color) {
|
|
|
4591
4811
|
const [nr, ng, nb] = hsvToRgb(wheelToHue(wheelAngle(dx, dy), wheel), s || 1, v || 1);
|
|
4592
4812
|
return rgbToHexColor(nr, ng, nb);
|
|
4593
4813
|
}
|
|
4814
|
+
var BLEND_MENU_GROUPS = [
|
|
4815
|
+
{ label: "Standard", modes: BLEND_MODES.filter((m) => !m.painterly && m.id !== "pigment") },
|
|
4816
|
+
{ label: "Paint", modes: BLEND_MODES.filter((m) => m.painterly || m.id === "pigment") }
|
|
4817
|
+
];
|
|
4594
4818
|
export {
|
|
4819
|
+
BLEND_MENU_GROUPS,
|
|
4595
4820
|
BLEND_MODES,
|
|
4596
4821
|
BRUSH_ID,
|
|
4597
4822
|
BRUSH_ROTATIONS,
|
|
@@ -4619,6 +4844,7 @@ export {
|
|
|
4619
4844
|
MIXING_PAD_HEIGHT,
|
|
4620
4845
|
MIXING_PAD_WIDTH,
|
|
4621
4846
|
MixingPad,
|
|
4847
|
+
PAINTERLY_BLEND_MODES,
|
|
4622
4848
|
PAINT_BRUSH_CATEGORIES,
|
|
4623
4849
|
PAINT_BRUSH_ENGINES,
|
|
4624
4850
|
PAINT_FORMAT,
|
|
@@ -4629,10 +4855,12 @@ export {
|
|
|
4629
4855
|
PAPER_NODE_ID,
|
|
4630
4856
|
PAPER_SLIDERS,
|
|
4631
4857
|
PaintCanvasPresenter,
|
|
4858
|
+
PaintDirector,
|
|
4632
4859
|
PaintDocumentError,
|
|
4633
4860
|
PaintEditorController,
|
|
4634
4861
|
PaintEditorError,
|
|
4635
4862
|
PaintGesture,
|
|
4863
|
+
PaintPresence,
|
|
4636
4864
|
PaintStrokeSession,
|
|
4637
4865
|
PaperGrain,
|
|
4638
4866
|
PenDabWalker,
|
|
@@ -4673,6 +4901,7 @@ export {
|
|
|
4673
4901
|
hueToWheel,
|
|
4674
4902
|
invertAffine,
|
|
4675
4903
|
isBlendMode,
|
|
4904
|
+
isPainterlyBlendMode,
|
|
4676
4905
|
isPalmTouch,
|
|
4677
4906
|
mapPenPoint,
|
|
4678
4907
|
mix32,
|
|
@@ -4713,7 +4942,11 @@ export {
|
|
|
4713
4942
|
seedFromString,
|
|
4714
4943
|
serializePaintDocument,
|
|
4715
4944
|
serializePaintDocumentExternal,
|
|
4945
|
+
standardBlendMode,
|
|
4716
4946
|
strokeBounds,
|
|
4947
|
+
strokeDuration,
|
|
4948
|
+
strokeSchedule,
|
|
4949
|
+
sweepPath,
|
|
4717
4950
|
symmetryAxisAngles,
|
|
4718
4951
|
symmetryAxisSegments,
|
|
4719
4952
|
symmetryCopies,
|