@pixerate/editor 0.243.0 → 0.245.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/agent/index.cjs +34 -32
- package/dist/agent/index.js +6 -4
- package/dist/animation-editor/index.cjs +61 -61
- package/dist/animation-editor/index.js +4 -4
- package/dist/{brushstrokeRender-274HGNZR.js → brushstrokeRender-4542T5HP.js} +2 -2
- package/dist/{brushstrokeRender-C7RJZPMC.cjs → brushstrokeRender-QA5WVIDT.cjs} +5 -5
- package/dist/canvas/changeFeed.d.cts +71 -0
- package/dist/canvas/changeFeed.d.ts +71 -0
- package/dist/canvas/index.cjs +490 -34
- package/dist/canvas/index.d.cts +3 -0
- package/dist/canvas/index.d.ts +3 -0
- package/dist/canvas/index.js +483 -27
- package/dist/canvas/proposal-tools.d.cts +21 -0
- package/dist/canvas/proposal-tools.d.ts +21 -0
- package/dist/canvas/proposals.d.cts +112 -0
- package/dist/canvas/proposals.d.ts +112 -0
- package/dist/chart/index.cjs +21 -21
- package/dist/chart/index.js +2 -2
- package/dist/{chunk-6L3WWIDW.js → chunk-4EQY4TZJ.js} +1 -1
- package/dist/{chunk-5FZS7J7C.cjs → chunk-4T47Z4J3.cjs} +18 -2
- package/dist/{chunk-4I76OCVD.cjs → chunk-5DLCOQOQ.cjs} +16 -1
- package/dist/{chunk-4ZHXLFXS.js → chunk-5RFENYO5.js} +1 -1
- package/dist/{chunk-SRXWDOUI.js → chunk-7YQNKSQ7.js} +1 -1
- package/dist/{chunk-WGN7AUKB.js → chunk-7YW7T5TJ.js} +1 -1
- package/dist/{chunk-X3ZINLIP.js → chunk-AU6MWQKZ.js} +1 -1
- package/dist/{chunk-OONFIFEU.cjs → chunk-BDIH4DYR.cjs} +23 -23
- package/dist/{chunk-AIS56JOO.js → chunk-BVRRIW7F.js} +1 -1
- package/dist/{chunk-KK2FWMA2.cjs → chunk-HZ547SGQ.cjs} +1 -1
- package/dist/{chunk-3WOZEBXZ.cjs → chunk-IZL5V5RK.cjs} +2 -2
- package/dist/{chunk-IXI5KATU.js → chunk-L5PB7EG3.js} +1 -1
- package/dist/{chunk-R2FVAKFH.cjs → chunk-M6JORA5Z.cjs} +86 -86
- package/dist/{chunk-RNSANXPT.js → chunk-NS62CQ2B.js} +112 -14
- package/dist/{chunk-UZU6DIZA.cjs → chunk-NUTMINJZ.cjs} +45 -45
- package/dist/{chunk-H4Z72Z4Z.js → chunk-OSTKS3YN.js} +1 -1
- package/dist/{chunk-HLBTRSQJ.js → chunk-P3NIPIKS.js} +263 -63
- package/dist/{chunk-7V7QMFTI.cjs → chunk-QOBCBGGI.cjs} +2 -2
- package/dist/{chunk-UV7OOZJQ.cjs → chunk-SD23XMVJ.cjs} +35 -35
- package/dist/{chunk-FQVVO5YA.js → chunk-SGPIVHJB.js} +1 -1
- package/dist/{chunk-HQZHOPNE.js → chunk-TBJEMELS.js} +18 -2
- package/dist/{chunk-QTMQ5TGA.js → chunk-UCYW4M66.js} +16 -1
- package/dist/{chunk-XFYLA3ZO.cjs → chunk-VMROIWLL.cjs} +290 -90
- package/dist/{chunk-W7GRGGBH.cjs → chunk-W7L4HUT2.cjs} +8 -8
- package/dist/{chunk-FJ2MSI43.cjs → chunk-WKPXOGZZ.cjs} +124 -26
- package/dist/{chunk-D2SGV525.cjs → chunk-XZNU4DUX.cjs} +2 -2
- package/dist/diagram/index.cjs +2 -2
- package/dist/diagram/index.js +1 -1
- package/dist/effects/index.cjs +4 -4
- package/dist/effects/index.js +2 -2
- package/dist/extensions/index.cjs +5 -5
- package/dist/extensions/index.js +4 -4
- package/dist/gradient/index.cjs +2 -2
- package/dist/gradient/index.js +1 -1
- package/dist/history/HistoryManager.d.cts +20 -1
- package/dist/history/HistoryManager.d.ts +20 -1
- package/dist/history/index.cjs +8 -2
- package/dist/history/index.js +8 -2
- package/dist/history/types.d.cts +31 -0
- package/dist/history/types.d.ts +31 -0
- package/dist/image-editor/index.cjs +785 -777
- package/dist/image-editor/index.js +13 -5
- package/dist/index.cjs +16 -10
- package/dist/index.js +11 -5
- package/dist/kernel-wasm-4KVONDAU.js +9 -0
- package/dist/kernel-wasm-TTYRBGAK.cjs +9 -0
- package/dist/paint/index.cjs +10 -2
- package/dist/paint/index.js +9 -1
- package/dist/paint/wet/film.d.cts +19 -1
- package/dist/paint/wet/film.d.ts +19 -1
- package/dist/paint/wet/gpu-kernels.d.cts +2 -2
- package/dist/paint/wet/gpu-kernels.d.ts +2 -2
- package/dist/paint/wet/kernel-wasm.d.cts +5 -5
- package/dist/paint/wet/kernel-wasm.d.ts +5 -5
- package/dist/paint/wet/kernel.d.cts +1 -1
- package/dist/paint/wet/kernel.d.ts +1 -1
- package/dist/paint/wet/medium.d.cts +3 -1
- package/dist/paint/wet/medium.d.ts +3 -1
- package/dist/paint/wet/model.d.cts +19 -4
- package/dist/paint/wet/model.d.ts +19 -4
- package/dist/paint/wet/world.d.cts +34 -8
- package/dist/paint/wet/world.d.ts +34 -8
- package/dist/paint-editor/backlight.d.cts +77 -0
- package/dist/paint-editor/backlight.d.ts +77 -0
- package/dist/paint-editor/controller.d.cts +11 -0
- package/dist/paint-editor/controller.d.ts +11 -0
- package/dist/paint-editor/index.cjs +685 -397
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +308 -20
- package/dist/paint-editor/paper.d.cts +7 -0
- package/dist/paint-editor/paper.d.ts +7 -0
- package/dist/paint-editor/wet.d.cts +9 -0
- package/dist/paint-editor/wet.d.ts +9 -0
- package/dist/parameters/index.cjs +2 -2
- package/dist/parameters/index.js +1 -1
- package/dist/tools/index.cjs +4 -2
- package/dist/tools/index.d.cts +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -1
- package/dist/tools/origin.d.cts +22 -0
- package/dist/tools/origin.d.ts +22 -0
- package/dist/vector/index.cjs +964 -964
- package/dist/vector/index.js +5 -5
- package/dist/video-editor/index.cjs +4 -4
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-canvas/SKILL.md +25 -0
- package/skills/pixerate-paint-editor/SKILL.md +3 -3
- package/skills/pixerate-paint-editor/references/light-paint.md +30 -7
- package/skills/pixerate-paint-watercolour/SKILL.md +10 -1
- package/skills/pixerate-paint-watercolour/evals/glowing-wash.json +22 -0
- package/skills/pixerate-paint-watercolour/evals/stained-glass.json +16 -0
- package/dist/kernel-wasm-6FPOLVUZ.cjs +0 -9
- package/dist/kernel-wasm-WIMMJUF2.js +0 -9
|
@@ -32,6 +32,7 @@ export { BLEND_MODES, PAINTERLY_BLEND_MODES, isBlendMode, isPainterlyBlendMode,
|
|
|
32
32
|
export * from './wet.cjs';
|
|
33
33
|
export * from './wet-tools.cjs';
|
|
34
34
|
export * from './thick.cjs';
|
|
35
|
+
export { BACKLIGHT_SPILL, DEFAULT_BACKLIGHT_COLOR, backlightOf } from './backlight.cjs';
|
|
35
36
|
export * from './thick-tools.cjs';
|
|
36
37
|
export * from './replay.cjs';
|
|
37
38
|
export * from './replay-tools.cjs';
|
|
@@ -32,6 +32,7 @@ export { BLEND_MODES, PAINTERLY_BLEND_MODES, isBlendMode, isPainterlyBlendMode,
|
|
|
32
32
|
export * from './wet.js';
|
|
33
33
|
export * from './wet-tools.js';
|
|
34
34
|
export * from './thick.js';
|
|
35
|
+
export { BACKLIGHT_SPILL, DEFAULT_BACKLIGHT_COLOR, backlightOf } from './backlight.js';
|
|
35
36
|
export * from './thick-tools.js';
|
|
36
37
|
export * from './replay.js';
|
|
37
38
|
export * from './replay-tools.js';
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
resolveExternalAssets,
|
|
8
8
|
serializeLayers,
|
|
9
9
|
serializeLayersExternal
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-7YQNKSQ7.js";
|
|
11
11
|
import "../chunk-3CI7FRMK.js";
|
|
12
12
|
import "../chunk-ZNA3IQZF.js";
|
|
13
13
|
import {
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
pigmentOf,
|
|
45
45
|
pointerPressure,
|
|
46
46
|
wetSupport
|
|
47
|
-
} from "../chunk-
|
|
47
|
+
} from "../chunk-P3NIPIKS.js";
|
|
48
48
|
import "../chunk-HVQVHUTV.js";
|
|
49
49
|
import {
|
|
50
50
|
writeZip
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
realPresenceClock,
|
|
56
56
|
tweenPresenceCursor
|
|
57
57
|
} from "../chunk-IVSWRVB4.js";
|
|
58
|
-
import "../chunk-
|
|
58
|
+
import "../chunk-4EQY4TZJ.js";
|
|
59
59
|
import {
|
|
60
60
|
BRUSH_ID,
|
|
61
61
|
BRUSH_ROTATIONS,
|
|
@@ -133,7 +133,7 @@ import {
|
|
|
133
133
|
tiltToOrientation,
|
|
134
134
|
toStrokePoint,
|
|
135
135
|
usesPaintExchange
|
|
136
|
-
} from "../chunk-
|
|
136
|
+
} from "../chunk-TBJEMELS.js";
|
|
137
137
|
import "../chunk-M6DNZNVY.js";
|
|
138
138
|
import "../chunk-WEIK3UYB.js";
|
|
139
139
|
import "../chunk-PWBZH3FK.js";
|
|
@@ -2214,6 +2214,7 @@ function downloadBytes(bytes, fileName, mimeType) {
|
|
|
2214
2214
|
// src/paint-editor/wet.ts
|
|
2215
2215
|
var STROKE_WATER = 0.9;
|
|
2216
2216
|
var STROKE_MASS = PIGMENT_UNIT * 2.5;
|
|
2217
|
+
var STROKE_LIGHT = 1.5;
|
|
2217
2218
|
var COVERAGE_STEPS = 4096;
|
|
2218
2219
|
function union2(a, b) {
|
|
2219
2220
|
if (!a) return { ...b };
|
|
@@ -2245,8 +2246,10 @@ var WetStrokeDriver = class {
|
|
|
2245
2246
|
this.medium = new WetMedium(init.world, init.laneId, {
|
|
2246
2247
|
water: STROKE_WATER * Math.min(1, wet.water * (1 + thin)) * perDab,
|
|
2247
2248
|
mass: STROKE_MASS * wet.pigment * (1 - 0.6 * thin) * perDab,
|
|
2248
|
-
body: wet.body * (1 - thin)
|
|
2249
|
+
body: wet.body * (1 - thin),
|
|
2250
|
+
light: 0
|
|
2249
2251
|
});
|
|
2252
|
+
this.lightBase = wet.tool === "paint" ? STROKE_LIGHT * brush.light.amount * (1 - 0.6 * thin) * perDab : 0;
|
|
2250
2253
|
const rates = exchangeRates(brush.behavior.pickup, brush.behavior.mix, wet.load, brush.behavior.mixing);
|
|
2251
2254
|
this.states = copies.map((c, i) => ({
|
|
2252
2255
|
walker: new PenDabWalker(brush, init.seed + i * 7919 | 0, c.linear),
|
|
@@ -2276,6 +2279,7 @@ var WetStrokeDriver = class {
|
|
|
2276
2279
|
paint = [0, 0, 0];
|
|
2277
2280
|
centre = [0, 0, 0];
|
|
2278
2281
|
depth;
|
|
2282
|
+
lightBase;
|
|
2279
2283
|
sampler;
|
|
2280
2284
|
sample = { rgb: [0, 0, 0], alpha: 1 };
|
|
2281
2285
|
copies;
|
|
@@ -2374,6 +2378,7 @@ var WetStrokeDriver = class {
|
|
|
2374
2378
|
switch (wet.tool) {
|
|
2375
2379
|
case "paint": {
|
|
2376
2380
|
const ex = st.exchange;
|
|
2381
|
+
this.medium.options.light = this.lightBase * (brush.light.pressure ? d.pressure : 1);
|
|
2377
2382
|
let flow = 1;
|
|
2378
2383
|
const effect = this.init.effect;
|
|
2379
2384
|
if (effect) {
|
|
@@ -2562,6 +2567,7 @@ var PaintWetLayers = class {
|
|
|
2562
2567
|
const flow = this.settings.get(l.id)?.flow;
|
|
2563
2568
|
return {
|
|
2564
2569
|
base: translateSurface(l.base, left, top),
|
|
2570
|
+
...l.light ? { light: translateSurface(l.light, left, top) } : {},
|
|
2565
2571
|
flow: l.flow && flow && flow.kind !== "none" ? flowField(flow, width, height) : l.flow
|
|
2566
2572
|
};
|
|
2567
2573
|
});
|
|
@@ -2601,7 +2607,7 @@ var PaintWetLayers = class {
|
|
|
2601
2607
|
if (!w.hasLane(layerId)) {
|
|
2602
2608
|
const base = this.host.layerSurface(layerId) ?? new TiledSurface(4, /* @__PURE__ */ new Map(), 0);
|
|
2603
2609
|
const s = this.settingsOf(layerId, medium);
|
|
2604
|
-
w.ensureLane(layerId, { params: s.params, base });
|
|
2610
|
+
w.ensureLane(layerId, { params: s.params, base, light: this.host.layerLight(layerId) });
|
|
2605
2611
|
const { width, height } = this.host.canvas();
|
|
2606
2612
|
if (s.flow.kind !== "none") w.setFlow(layerId, flowField(s.flow, width, height), s.flow.strength);
|
|
2607
2613
|
}
|
|
@@ -2659,6 +2665,7 @@ var PaintWetLayers = class {
|
|
|
2659
2665
|
if (this.host.layerSurface(id) !== d.surface) surfaces.set(id, d.surface);
|
|
2660
2666
|
rects.push(...d.changed);
|
|
2661
2667
|
}
|
|
2668
|
+
this.syncLights();
|
|
2662
2669
|
if (surfaces.size) this.host.updateSurfaces(surfaces, rects);
|
|
2663
2670
|
return rects;
|
|
2664
2671
|
}
|
|
@@ -2674,9 +2681,21 @@ var PaintWetLayers = class {
|
|
|
2674
2681
|
if (this.host.layerSurface(id) !== d.surface) surfaces.set(id, d.surface);
|
|
2675
2682
|
rects.push(...d.changed);
|
|
2676
2683
|
}
|
|
2684
|
+
this.syncLights();
|
|
2677
2685
|
if (surfaces.size) this.host.updateSurfaces(surfaces, rects);
|
|
2678
2686
|
return rects;
|
|
2679
2687
|
}
|
|
2688
|
+
/** Shows each wet layer's live light paint (the light under it with the wet light shaded in). */
|
|
2689
|
+
syncLights() {
|
|
2690
|
+
const w = this.world;
|
|
2691
|
+
if (!w) return;
|
|
2692
|
+
const lights = /* @__PURE__ */ new Map();
|
|
2693
|
+
for (const id of w.laneIds()) {
|
|
2694
|
+
const l = w.lightDisplay(id);
|
|
2695
|
+
if (l && this.host.layerLight(id) !== l) lights.set(id, l);
|
|
2696
|
+
}
|
|
2697
|
+
if (lights.size) this.host.updateLights(lights);
|
|
2698
|
+
}
|
|
2680
2699
|
/** Drops the wet layers whose paint has all dried (they become ordinary pixel layers). */
|
|
2681
2700
|
retireDry() {
|
|
2682
2701
|
const w = this.world;
|
|
@@ -2684,7 +2703,9 @@ var PaintWetLayers = class {
|
|
|
2684
2703
|
for (const id of w.laneIds()) {
|
|
2685
2704
|
if (w.isActive(id) || this.driver?.init.laneId === id) continue;
|
|
2686
2705
|
const base = w.getBase(id);
|
|
2706
|
+
const light = w.isLit(id) ? w.getLight(id) : null;
|
|
2687
2707
|
w.removeLane(id);
|
|
2708
|
+
if (light && this.host.layerLight(id) !== light) this.host.updateLights(/* @__PURE__ */ new Map([[id, light]]));
|
|
2688
2709
|
this.host.updateSurfaces(/* @__PURE__ */ new Map([[id, base]]), []);
|
|
2689
2710
|
}
|
|
2690
2711
|
}
|
|
@@ -2803,6 +2824,12 @@ var PaintWetLayers = class {
|
|
|
2803
2824
|
this.retireDry();
|
|
2804
2825
|
return out;
|
|
2805
2826
|
}
|
|
2827
|
+
/** The layer's light paint with all its wet paint dried (`null` without a wet layer or light), without changing anything. */
|
|
2828
|
+
bakedLight(layerId) {
|
|
2829
|
+
const w = this.world;
|
|
2830
|
+
if (!w?.hasLane(layerId) || !w.isLit(layerId)) return null;
|
|
2831
|
+
return w.bakedLight(layerId);
|
|
2832
|
+
}
|
|
2806
2833
|
/** The layer's pixels with all its wet paint dried (deterministic), without changing anything. */
|
|
2807
2834
|
bakedSurface(layerId) {
|
|
2808
2835
|
const w = this.world;
|
|
@@ -3477,6 +3504,212 @@ function liveStrokeBounds(stroke) {
|
|
|
3477
3504
|
return { x: Math.floor(x0 - pad), y: Math.floor(y0 - pad), width: Math.ceil(x1 - x0 + 2 * pad), height: Math.ceil(y1 - y0 + 2 * pad) };
|
|
3478
3505
|
}
|
|
3479
3506
|
|
|
3507
|
+
// src/paint-editor/backlight.ts
|
|
3508
|
+
var DEFAULT_BACKLIGHT_COLOR = "#fff1d6";
|
|
3509
|
+
var BACKLIGHT_SPILL = { radius: 24, strength: 1.5 };
|
|
3510
|
+
var BLOCK_HEIGHT = 96;
|
|
3511
|
+
var VALLEY_GAIN = 0.35;
|
|
3512
|
+
function backlightOf(paper) {
|
|
3513
|
+
return paper.backlight ?? 0;
|
|
3514
|
+
}
|
|
3515
|
+
var LIN = new Float64Array(256);
|
|
3516
|
+
for (let i = 0; i < 256; i++) LIN[i] = srgbToLinear(i / 255);
|
|
3517
|
+
function needAlpha(e, p) {
|
|
3518
|
+
if (e < p) return 1 - e / p;
|
|
3519
|
+
if (e > p) return (e - p) / (255 - p);
|
|
3520
|
+
return 0;
|
|
3521
|
+
}
|
|
3522
|
+
function tilesUnder(surface, rect, offset, out) {
|
|
3523
|
+
if (!surface) {
|
|
3524
|
+
out.push(null);
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
const x0 = Math.floor((rect.x - offset.x) / TILE);
|
|
3528
|
+
const y0 = Math.floor((rect.y - offset.y) / TILE);
|
|
3529
|
+
const x1 = Math.floor((rect.x - offset.x + rect.width - 1) / TILE);
|
|
3530
|
+
const y1 = Math.floor((rect.y - offset.y + rect.height - 1) / TILE);
|
|
3531
|
+
for (let ty = y0; ty <= y1; ty++) for (let tx = x0; tx <= x1; tx++) out.push(surface.tiles.get(tileKey(tx, ty)) ?? null);
|
|
3532
|
+
}
|
|
3533
|
+
var sameRefs = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
3534
|
+
var BacklightCache = class {
|
|
3535
|
+
tiles = /* @__PURE__ */ new Map();
|
|
3536
|
+
key = "";
|
|
3537
|
+
spillOf = null;
|
|
3538
|
+
out = null;
|
|
3539
|
+
/**
|
|
3540
|
+
* The backlight layer's pixels for `layers` (bottom first) on `paper` over a `width` × `height`
|
|
3541
|
+
* canvas, or `null` when the paper has no backlight. `spillFrom` is the committed painting (the
|
|
3542
|
+
* spill is drawn from it, so a stroke in progress does not redo the blur every frame).
|
|
3543
|
+
*/
|
|
3544
|
+
surface(paper, grain, width, height, layers, spillFrom) {
|
|
3545
|
+
const amount = backlightOf(paper);
|
|
3546
|
+
if (!(amount > 0)) return null;
|
|
3547
|
+
const key = JSON.stringify([amount, paper.backlightColor ?? DEFAULT_BACKLIGHT_COLOR, paper.color, paper.depth, grain.flat, width, height, layers.length, layers.map((l) => [l.opacity, l.offset.x, l.offset.y, l.surface.channels])]);
|
|
3548
|
+
if (key !== this.key) {
|
|
3549
|
+
this.tiles.clear();
|
|
3550
|
+
this.key = key;
|
|
3551
|
+
}
|
|
3552
|
+
const light = /* @__PURE__ */ new Map();
|
|
3553
|
+
const color = /* @__PURE__ */ new Map();
|
|
3554
|
+
const painted = /* @__PURE__ */ new Map();
|
|
3555
|
+
const tilesX = Math.ceil(width / TILE);
|
|
3556
|
+
const tilesY = Math.ceil(height / TILE);
|
|
3557
|
+
const refs = [];
|
|
3558
|
+
for (let ty = 0; ty < tilesY; ty++) {
|
|
3559
|
+
for (let tx = 0; tx < tilesX; tx++) {
|
|
3560
|
+
const rect = { x: tx * TILE, y: ty * TILE, width: Math.min(TILE, width - tx * TILE), height: Math.min(TILE, height - ty * TILE) };
|
|
3561
|
+
refs.length = 0;
|
|
3562
|
+
for (const l of layers) {
|
|
3563
|
+
tilesUnder(l.surface, rect, l.offset, refs);
|
|
3564
|
+
tilesUnder(l.thick?.height, rect, l.offset, refs);
|
|
3565
|
+
tilesUnder(l.thick?.material, rect, l.offset, refs);
|
|
3566
|
+
}
|
|
3567
|
+
const k = tileKey(tx, ty);
|
|
3568
|
+
let r = this.tiles.get(k);
|
|
3569
|
+
if (!r || !sameRefs(r.refs, refs)) {
|
|
3570
|
+
r = { refs: [...refs], ...computeTile(paper, grain, amount, rect, layers) };
|
|
3571
|
+
this.tiles.set(k, r);
|
|
3572
|
+
}
|
|
3573
|
+
if (r.light) light.set(k, r.light);
|
|
3574
|
+
if (r.color) color.set(k, r.color);
|
|
3575
|
+
if (r.painted) painted.set(k, r.painted);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
return { light: new TiledSurface(4, light, 0), color: new TiledSurface(4, color, 0), painted: new TiledSurface(1, painted, 0) };
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* The full Backlight layer: the light through the paint (`current`) plus the spill of the light
|
|
3582
|
+
* through the committed paint (`committed`), both Linear Dodge. Cached on their tiles.
|
|
3583
|
+
*/
|
|
3584
|
+
layer(current, committed, amount) {
|
|
3585
|
+
let spill = null;
|
|
3586
|
+
if (committed) {
|
|
3587
|
+
const s = this.spillOf;
|
|
3588
|
+
if (s && s.color.id === committed.color.id && s.painted.id === committed.painted.id) spill = s.out;
|
|
3589
|
+
else {
|
|
3590
|
+
spill = computeSpill(committed.color, committed.painted, { radius: BACKLIGHT_SPILL.radius, strength: BACKLIGHT_SPILL.strength * amount });
|
|
3591
|
+
this.spillOf = { color: committed.color, painted: committed.painted, out: spill };
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
if (!spill) return current.light;
|
|
3595
|
+
const parts = [...current.light.tiles.values(), ...spill.tiles.values()];
|
|
3596
|
+
if (this.out && sameRefs(this.out.parts, parts)) return this.out.surface;
|
|
3597
|
+
const keys = /* @__PURE__ */ new Set([...current.light.tiles.keys(), ...spill.tiles.keys()]);
|
|
3598
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
3599
|
+
for (const key of keys) {
|
|
3600
|
+
const a = current.light.tiles.get(key);
|
|
3601
|
+
const b = spill.tiles.get(key);
|
|
3602
|
+
const t = !a ? b : !b ? a : addTiles(a, b);
|
|
3603
|
+
if (t) tiles.set(key, t);
|
|
3604
|
+
}
|
|
3605
|
+
const surface = new TiledSurface(4, tiles, 0);
|
|
3606
|
+
this.out = { parts, surface };
|
|
3607
|
+
return surface;
|
|
3608
|
+
}
|
|
3609
|
+
};
|
|
3610
|
+
var full4 = (t) => {
|
|
3611
|
+
if (t.data.length === TILE * TILE * 4) return t.data;
|
|
3612
|
+
const out = new Uint8ClampedArray(TILE * TILE * 4);
|
|
3613
|
+
const u = t.uniform ?? 0;
|
|
3614
|
+
const bytes = [u >>> 24 & 255, u >>> 16 & 255, u >>> 8 & 255, u & 255];
|
|
3615
|
+
for (let i = 0; i < out.length; i += 4) out.set(bytes, i);
|
|
3616
|
+
return out;
|
|
3617
|
+
};
|
|
3618
|
+
function addTiles(a, b) {
|
|
3619
|
+
const x = full4(a);
|
|
3620
|
+
const y = full4(b);
|
|
3621
|
+
const out = new Uint8ClampedArray(TILE * TILE * 4);
|
|
3622
|
+
for (let i = 0; i < out.length; i += 4) {
|
|
3623
|
+
const ay = y[i + 3] / 255;
|
|
3624
|
+
const r = x[i] + y[i] * ay;
|
|
3625
|
+
const g = x[i + 1] + y[i + 1] * ay;
|
|
3626
|
+
const bl = x[i + 2] + y[i + 2] * ay;
|
|
3627
|
+
if (r < 0.5 && g < 0.5 && bl < 0.5) continue;
|
|
3628
|
+
out[i] = Math.round(r);
|
|
3629
|
+
out[i + 1] = Math.round(g);
|
|
3630
|
+
out[i + 2] = Math.round(bl);
|
|
3631
|
+
out[i + 3] = 255;
|
|
3632
|
+
}
|
|
3633
|
+
return finishTile(out, 4, 0);
|
|
3634
|
+
}
|
|
3635
|
+
function computeTile(paper, grain, amount, rect, layers) {
|
|
3636
|
+
const n = rect.width * rect.height;
|
|
3637
|
+
const [p0, p1, p2] = hexToRgb8(paper.color);
|
|
3638
|
+
const pb = [p0, p1, p2];
|
|
3639
|
+
const pl = [Math.max(1e-4, LIN[p0]), Math.max(1e-4, LIN[p1]), Math.max(1e-4, LIN[p2])];
|
|
3640
|
+
const [c0, c1, c2] = hexToRgb8(paper.backlightColor ?? DEFAULT_BACKLIGHT_COLOR);
|
|
3641
|
+
const t0 = new Float32Array(n).fill(amount * LIN[c0]);
|
|
3642
|
+
const t1 = new Float32Array(n).fill(amount * LIN[c1]);
|
|
3643
|
+
const t2 = new Float32Array(n).fill(amount * LIN[c2]);
|
|
3644
|
+
const depth = grain.flat ? 0 : paper.depth;
|
|
3645
|
+
if (depth > 0) {
|
|
3646
|
+
for (let y = 0, i = 0; y < rect.height; y++)
|
|
3647
|
+
for (let x = 0; x < rect.width; x++, i++) {
|
|
3648
|
+
const g = 1 + VALLEY_GAIN * depth * (0.5 - grain.at(rect.x + x + 0.5, rect.y + y + 0.5));
|
|
3649
|
+
t0[i] *= g;
|
|
3650
|
+
t1[i] *= g;
|
|
3651
|
+
t2[i] *= g;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
const clear = new Float32Array(n).fill(1);
|
|
3655
|
+
for (const l of layers) {
|
|
3656
|
+
const lr = { x: rect.x - l.offset.x, y: rect.y - l.offset.y, width: rect.width, height: rect.height };
|
|
3657
|
+
if (!l.surface.tiles.size) continue;
|
|
3658
|
+
const px = readRegion(l.surface, lr);
|
|
3659
|
+
const h = l.thick?.height.tiles.size ? readRegion(l.thick.height, lr) : null;
|
|
3660
|
+
const m = h && l.thick?.material.tiles.size ? readRegion(l.thick.material, lr) : null;
|
|
3661
|
+
const op = l.opacity;
|
|
3662
|
+
for (let i = 0; i < n; i++) {
|
|
3663
|
+
const a8 = px[i * 4 + 3];
|
|
3664
|
+
if (!a8 && !(h && h[i])) continue;
|
|
3665
|
+
const a = a8 / 255;
|
|
3666
|
+
let aMin = 0;
|
|
3667
|
+
const tr = [1, 1, 1];
|
|
3668
|
+
for (let c = 0; c < 3; c++) {
|
|
3669
|
+
const e = a * px[i * 4 + c] + (1 - a) * pb[c];
|
|
3670
|
+
aMin = Math.max(aMin, needAlpha(e, pb[c]));
|
|
3671
|
+
tr[c] = Math.min(1, LIN[Math.round(e)] / pl[c]);
|
|
3672
|
+
}
|
|
3673
|
+
const hide = aMin < 1 && a > aMin ? (a - aMin) / (1 - aMin) : 0;
|
|
3674
|
+
let k = 1 - hide;
|
|
3675
|
+
if (h && h[i]) {
|
|
3676
|
+
const ma = m ? m[i * 4 + 3] : 0;
|
|
3677
|
+
const clarity = ma ? (255 - ma) / 254 : 0;
|
|
3678
|
+
k *= 1 - Math.min(1, h[i] / BLOCK_HEIGHT) * (1 - clarity);
|
|
3679
|
+
}
|
|
3680
|
+
t0[i] *= 1 - op + op * tr[0] * k;
|
|
3681
|
+
t1[i] *= 1 - op + op * tr[1] * k;
|
|
3682
|
+
t2[i] *= 1 - op + op * tr[2] * k;
|
|
3683
|
+
clear[i] *= 1 - op * Math.max(a, h && h[i] ? 1 : 0);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
const light = new Uint8ClampedArray(TILE * TILE * 4);
|
|
3687
|
+
const color = new Uint8ClampedArray(TILE * TILE * 4);
|
|
3688
|
+
const painted = new Uint8ClampedArray(TILE * TILE);
|
|
3689
|
+
for (let y = 0, i = 0; y < rect.height; y++) {
|
|
3690
|
+
for (let x = 0; x < rect.width; x++, i++) {
|
|
3691
|
+
const o = (y * TILE + x) * 4;
|
|
3692
|
+
const add = [Math.min(1, t0[i]), Math.min(1, t1[i]), Math.min(1, t2[i])];
|
|
3693
|
+
const mx = Math.max(add[0], add[1], add[2]);
|
|
3694
|
+
if (mx > 0.5 / 255) {
|
|
3695
|
+
light[o] = Math.round(add[0] * 255);
|
|
3696
|
+
light[o + 1] = Math.round(add[1] * 255);
|
|
3697
|
+
light[o + 2] = Math.round(add[2] * 255);
|
|
3698
|
+
light[o + 3] = 255;
|
|
3699
|
+
}
|
|
3700
|
+
const pv = Math.round((1 - clear[i]) * 255);
|
|
3701
|
+
if (pv && mx > 0.5 / 255) {
|
|
3702
|
+
painted[y * TILE + x] = pv;
|
|
3703
|
+
color[o] = Math.round(linearToSrgb(add[0]) * 255);
|
|
3704
|
+
color[o + 1] = Math.round(linearToSrgb(add[1]) * 255);
|
|
3705
|
+
color[o + 2] = Math.round(linearToSrgb(add[2]) * 255);
|
|
3706
|
+
color[o + 3] = 255;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return { light: finishTile(light, 4, 0), color: finishTile(color, 4, 0), painted: finishTile(painted, 1, 0) };
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3480
3713
|
// src/paint-editor/replay.ts
|
|
3481
3714
|
function replayItems(doc) {
|
|
3482
3715
|
const items = [];
|
|
@@ -3867,6 +4100,9 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
3867
4100
|
/** Light paint: each layer's spill settings (saved with the document, not undo steps; missing = the defaults). */
|
|
3868
4101
|
lightSpills = {};
|
|
3869
4102
|
lightCaches = /* @__PURE__ */ new Map();
|
|
4103
|
+
/** Backlit paper: the light through the paint as drawn (live strokes too), and as committed (its spill). */
|
|
4104
|
+
backlightView = new BacklightCache();
|
|
4105
|
+
backlightCommitted = new BacklightCache();
|
|
3870
4106
|
/** Live effect paint: playing and the frame rate cap (editor state), and the time the view shows. */
|
|
3871
4107
|
effectPlaybackState = { playing: true, fps: 30 };
|
|
3872
4108
|
effectTime = 0;
|
|
@@ -3928,6 +4164,12 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
3928
4164
|
this.doc = { ...this.doc, layers };
|
|
3929
4165
|
this.emit(rects.length ? rects : "all");
|
|
3930
4166
|
},
|
|
4167
|
+
layerLight: (id) => this.doc.light?.[id] ?? null,
|
|
4168
|
+
updateLights: (lights) => {
|
|
4169
|
+
let light = this.doc.light;
|
|
4170
|
+
for (const [id, surface] of lights) if (findLayer(this.doc.layers, id)) light = withLayerLight(light, id, surface);
|
|
4171
|
+
this.doc = { ...this.doc, ...light ? { light } : {} };
|
|
4172
|
+
},
|
|
3931
4173
|
changed: () => {
|
|
3932
4174
|
}
|
|
3933
4175
|
},
|
|
@@ -5193,7 +5435,6 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5193
5435
|
const brush = this.strokeBrush(init);
|
|
5194
5436
|
const effect = this.strokeEffect(init, brush);
|
|
5195
5437
|
if (brush.light.amount > 0) {
|
|
5196
|
-
if (brush.engine === "wet") throw new PaintEditorError(`The ${brush.name} brush paints wet media, which doesn't take light yet (glowing watercolour is planned in #494); paint light with a dab or impasto light brush (list_brushes category light), or set light.amount to 0 (set_brush).`);
|
|
5197
5438
|
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 (list_brushes category light), or set light.amount to 0 (set_brush).`);
|
|
5198
5439
|
}
|
|
5199
5440
|
if (doc.liveStrokes?.[layer.id]) {
|
|
@@ -5376,7 +5617,8 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5376
5617
|
const thick = ch ? { ...this.doc.thick ?? {}, [l.id]: ch } : this.doc.thick;
|
|
5377
5618
|
const sprayed = s.stickers();
|
|
5378
5619
|
const stickers = sprayed ? { ...this.doc.stickers ?? {}, [l.id]: [...this.doc.stickers?.[l.id] ?? [], ...sprayed] } : this.doc.stickers;
|
|
5379
|
-
const
|
|
5620
|
+
const wetLight = wetStroke && this.wet.world?.hasLane(l.id) ? this.wet.world.lightDisplay(l.id) : null;
|
|
5621
|
+
const light = withLayerLight(this.doc.light, l.id, s.lightChannel() ?? wetLight);
|
|
5380
5622
|
this.commit(`${brush.name} stroke`, this.withWetEvents(withLiveStroke({ ...this.doc, layers: mapLayer(this.doc.layers, l.id, () => ({ ...l, surface })), strokes: [...this.doc.strokes, stroke], ...thick ? { thick } : {}, ...stickers ? { stickers } : {}, ...light ? { light } : {} }, stroke)), wetStroke ? before : this.doc);
|
|
5381
5623
|
}
|
|
5382
5624
|
};
|
|
@@ -5410,13 +5652,14 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5410
5652
|
const offset = s.init.wet ? { x: 0, y: 0 } : l.offset;
|
|
5411
5653
|
const ch = s.thickChannels();
|
|
5412
5654
|
const sprayed = s.stickers();
|
|
5655
|
+
const lit = s.lightChannel() ?? (s.init.wet && this.wet.world?.hasLane(l.id) ? this.wet.world.lightDisplay(l.id) : null);
|
|
5413
5656
|
out = withLiveStroke({
|
|
5414
5657
|
...out,
|
|
5415
5658
|
layers: mapLayer(out.layers, l.id, () => ({ ...l, surface, offset })),
|
|
5416
5659
|
strokes: [...out.strokes, stroke],
|
|
5417
5660
|
...ch ? { thick: { ...out.thick ?? {}, [l.id]: ch } } : {},
|
|
5418
5661
|
...sprayed ? { stickers: { ...out.stickers ?? {}, [l.id]: [...out.stickers?.[l.id] ?? [], ...sprayed] } } : {},
|
|
5419
|
-
...
|
|
5662
|
+
...lit ? { light: withLayerLight(out.light, l.id, lit) } : {}
|
|
5420
5663
|
}, stroke);
|
|
5421
5664
|
}
|
|
5422
5665
|
};
|
|
@@ -5565,6 +5808,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5565
5808
|
clearLight(layerId) {
|
|
5566
5809
|
this.requireIdle("clearing light");
|
|
5567
5810
|
const l = this.paintTarget(layerId, "clear the light of");
|
|
5811
|
+
if (this.wet.bakedLight(l.id)) this.dryLayersFirst([l.id]);
|
|
5568
5812
|
const t = this.lightWithout([l.id]);
|
|
5569
5813
|
if (!t.light) return { layerId: l.id, cleared: false };
|
|
5570
5814
|
this.commit("Clear light", { ...this.doc, light: t.light });
|
|
@@ -5941,8 +6185,39 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5941
6185
|
}
|
|
5942
6186
|
return out;
|
|
5943
6187
|
}
|
|
6188
|
+
/** Visible pixel layers bottom first, as the backlight reads them (group opacities multiplied in). */
|
|
6189
|
+
backlitLayers(layers, committed, opacity = 1, out = []) {
|
|
6190
|
+
for (const l of layers) {
|
|
6191
|
+
if (!l.visible) continue;
|
|
6192
|
+
if (l.type === "group") this.backlitLayers(l.children, committed, opacity * l.opacity, out);
|
|
6193
|
+
else if (isPixelLayer(l)) {
|
|
6194
|
+
const thick = !committed && this.liveThick?.layerId === l.id ? this.liveThick.channels : this.doc.thick?.[l.id];
|
|
6195
|
+
out.push({ surface: l.surface, offset: l.offset, opacity: opacity * l.opacity, ...thick ? { thick } : {} });
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
return out;
|
|
6199
|
+
}
|
|
6200
|
+
/**
|
|
6201
|
+
* Backlit paper: the light that makes it through `base` (the layers as drawn), with the spill of
|
|
6202
|
+
* the light through the committed painting, as a Linear Dodge (Add) layer for the top of the
|
|
6203
|
+
* stack; `null` when the paper has no backlight.
|
|
6204
|
+
*/
|
|
6205
|
+
backlightLayer(base) {
|
|
6206
|
+
const paper = this.doc.paper;
|
|
6207
|
+
const amount = backlightOf(paper);
|
|
6208
|
+
if (!(amount > 0)) return null;
|
|
6209
|
+
const { width, height } = this.doc.canvas;
|
|
6210
|
+
const grain = paperGrain(paper);
|
|
6211
|
+
const current = this.backlightView.surface(paper, grain, width, height, this.backlitLayers(base, false), null);
|
|
6212
|
+
const committed = base === this.doc.layers ? current : this.backlightCommitted.surface(paper, grain, width, height, this.backlitLayers(this.doc.layers, true), null);
|
|
6213
|
+
const surface = this.backlightView.layer(current, committed, amount);
|
|
6214
|
+
return { type: "pixel", id: BACKLIGHT_LAYER_ID, name: "Backlight", visible: true, opacity: 1, blend: "linear-dodge", surface, offset: { x: 0, y: 0 } };
|
|
6215
|
+
}
|
|
5944
6216
|
renderLayers(options, view = false) {
|
|
5945
|
-
|
|
6217
|
+
const base = (options.wet ?? "display") === "dried" ? this.driedLayers() : this.currentLayers();
|
|
6218
|
+
let layers = this.lightLayers(this.litLayers(base));
|
|
6219
|
+
const backlight = options.paper !== false && !options.layerIds ? this.backlightLayer(base) : null;
|
|
6220
|
+
if (backlight) layers = [...layers, backlight];
|
|
5946
6221
|
if (options.layerIds) {
|
|
5947
6222
|
const picked = [];
|
|
5948
6223
|
for (const id of options.layerIds) picked.push(findLayer(layers, id)?.layer ?? this.requireLayer(id));
|
|
@@ -6032,7 +6307,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6032
6307
|
const light = {};
|
|
6033
6308
|
for (const [id, t] of Object.entries(this.doc.light ?? {})) {
|
|
6034
6309
|
if (!findLayer(this.doc.layers, id)) continue;
|
|
6035
|
-
const saved = lightToJSON(t);
|
|
6310
|
+
const saved = lightToJSON(this.wet.bakedLight(id) ?? t);
|
|
6036
6311
|
if (saved) light[id] = saved;
|
|
6037
6312
|
}
|
|
6038
6313
|
if (Object.keys(light).length) ext.light = light;
|
|
@@ -6218,7 +6493,9 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6218
6493
|
* pixels), and the composite as the merged image.
|
|
6219
6494
|
*/
|
|
6220
6495
|
exportPsd() {
|
|
6221
|
-
const
|
|
6496
|
+
const driedBase = this.driedLayers(this.doc.layers);
|
|
6497
|
+
const backlight = this.backlightLayer(driedBase);
|
|
6498
|
+
const dried = [...this.lightLayers(this.litLayers(driedBase)), ...backlight ? [backlight] : []];
|
|
6222
6499
|
const { width, height, dpi } = this.doc.canvas;
|
|
6223
6500
|
const paperPixels = this.compositePixels({ layerIds: [], paper: true });
|
|
6224
6501
|
const paperLayer = {
|
|
@@ -6249,6 +6526,9 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
6249
6526
|
const one = names.length === 1;
|
|
6250
6527
|
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).`);
|
|
6251
6528
|
}
|
|
6529
|
+
if (backlight) {
|
|
6530
|
+
result.warnings.push('Backlit paper is written as pixels: the light that comes through the paint is a "Backlight" Linear Dodge (Add) layer at the top. Opening the PSD again gives a plain pixel layer, not a backlight.');
|
|
6531
|
+
}
|
|
6252
6532
|
if (Object.values(this.doc.light ?? {}).some((l) => l.tiles.size)) {
|
|
6253
6533
|
result.warnings.push('Light paint is written as pixels: each layer with its glow, and its spill as a "\u2039layer\u203A light" Linear Dodge (Add) layer above it. Opening the PSD again gives plain pixel layers, not editable light.');
|
|
6254
6534
|
}
|
|
@@ -7265,6 +7545,7 @@ function withLayerLight(light, layerId, next) {
|
|
|
7265
7545
|
else delete out[layerId];
|
|
7266
7546
|
return out;
|
|
7267
7547
|
}
|
|
7548
|
+
var BACKLIGHT_LAYER_ID = "__backlight";
|
|
7268
7549
|
function insertAfter(layers, id, node) {
|
|
7269
7550
|
const i = layers.findIndex((l) => l.id === id);
|
|
7270
7551
|
if (i >= 0) return [...layers.slice(0, i + 1), node, ...layers.slice(i + 1)];
|
|
@@ -8259,7 +8540,7 @@ function createPaintWetTools(c) {
|
|
|
8259
8540
|
{
|
|
8260
8541
|
name: "advance_wet_paint",
|
|
8261
8542
|
title: "Let the wet paint flow",
|
|
8262
|
-
description: "Lets time pass for wet paint: the water spreads, runs downhill on a tilted canvas, granulates, blooms and evaporates, and pigment settles. Give steps (simulation steps; at the default 120 per second, 120 steps is one second) or seconds (default 1). Not an undo step, as time passing never is. Returns the steps run, the clock, stillWet and each wet layer\u2019s state. Paint dries by itself after a few thousand steps; dry_paint dries it at once.",
|
|
8543
|
+
description: "Lets time pass for wet paint: the water spreads, runs downhill on a tilted canvas, granulates, blooms and evaporates, and pigment settles. Give steps (simulation steps; at the default 120 per second, 120 steps is one second) or seconds (default 1). Not an undo step, as time passing never is. Returns the steps run, the clock, stillWet and each wet layer\u2019s state. Paint dries by itself after a few thousand steps; dry_paint dries it at once. Wet light paint (wet brushes with light.amount) flows with the pigment and settles where it dries into the layer\u2019s light (get_light).",
|
|
8263
8544
|
inputSchema: {
|
|
8264
8545
|
type: "object",
|
|
8265
8546
|
properties: {
|
|
@@ -8709,7 +8990,7 @@ var IMPASTO_BRUSH = {
|
|
|
8709
8990
|
};
|
|
8710
8991
|
var LIGHT_BRUSH = {
|
|
8711
8992
|
type: "object",
|
|
8712
|
-
description: "Light paint: the light the brush lays with its colour, glowing in the paint's own colour and spilling onto its surroundings (set_light_spill). Dab, particle, bristle, Mixer Brush and
|
|
8993
|
+
description: "Light paint: the light the brush lays with its colour, glowing in the paint's own colour and spilling onto its surroundings (set_light_spill). Dab, particle, bristle, Mixer Brush, impasto and wet paint brushes take it (wet light flows, pools, granulates and blooms with the pigment until the paint dries, then is ordinary light paint); wet tools other than paint lay none, and sticker brushes refuse light above 0. Missing fields keep their current values.",
|
|
8713
8994
|
properties: {
|
|
8714
8995
|
amount: { type: "number", minimum: 0, maximum: 1, description: "Light each dab lays, scaled by its coverage and flow (0 none, default; 1 full)." },
|
|
8715
8996
|
pressure: { type: "boolean", description: "Pressure scales the light (default false)." }
|
|
@@ -8940,7 +9221,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
8940
9221
|
add({
|
|
8941
9222
|
name: "get_paper",
|
|
8942
9223
|
title: "Get the paper",
|
|
8943
|
-
description: "Reads every setting of the current paper (preset, pattern, seed, colour, scale, tiling, depth, shading, dry and wet tuning, absorption, permeability, roughness).",
|
|
9224
|
+
description: "Reads every setting of the current paper (preset, pattern, seed, colour, scale, tiling, depth, shading, dry and wet tuning, absorption, permeability, roughness, and metallic, backlight and backlightColor when set).",
|
|
8944
9225
|
inputSchema: EMPTY3,
|
|
8945
9226
|
annotations: { title: "Get the paper", ...READ6 },
|
|
8946
9227
|
handler: () => JSON.parse(JSON.stringify(c.document.paper))
|
|
@@ -8956,7 +9237,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
8956
9237
|
add({
|
|
8957
9238
|
name: "set_paper",
|
|
8958
9239
|
title: "Set the paper",
|
|
8959
|
-
description: "Changes the paper under the whole painting: a preset from list_papers and / or its settings. Grain depth decides how much grain-responsive brushes (pencil, pastel, charcoal) skip the valleys; changing it re-renders the paper but not strokes already painted. One undo step. Returns the paper.",
|
|
9240
|
+
description: "Changes the paper under the whole painting: a preset from list_papers and / or its settings. Grain depth decides how much grain-responsive brushes (pencil, pastel, charcoal) skip the valleys; changing it re-renders the paper but not strokes already painted. backlight lights the paper from behind (the stained-glass preset has it on). One undo step. Returns the paper.",
|
|
8960
9241
|
inputSchema: {
|
|
8961
9242
|
type: "object",
|
|
8962
9243
|
properties: {
|
|
@@ -8974,6 +9255,8 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
8974
9255
|
permeability: { type: "number", minimum: 0, maximum: 1, description: "Wet media: how freely water flows over the fibres (low: more ragged, slower spreading)." },
|
|
8975
9256
|
roughness: { type: "number", minimum: 0, maximum: 1, description: "Wet media: how much the grain pins wet edges and catches granulating pigment." },
|
|
8976
9257
|
metallic: { type: "number", minimum: 0, maximum: 1, description: "Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light (see set_lighting). 0 by default." },
|
|
9258
|
+
backlight: { type: "number", minimum: 0, maximum: 1, description: "Backlit paper (a lightbox, stained glass): light from behind the paper, 0 (none, the default) .. 1. Transparent glazes (watercolour, airbrush, thin marker) glow in their own colour; opaque paint (gouache, oil, solid ink) and thick paint block it; the light through paint spills softly around it. Nothing is repainted." },
|
|
9259
|
+
backlightColor: { ...COLOR3, description: "The backlight\u2019s colour #rrggbb (default #fff1d6, warm white)." },
|
|
8977
9260
|
name: { type: "string", minLength: 1, description: "Display name." }
|
|
8978
9261
|
},
|
|
8979
9262
|
additionalProperties: false
|
|
@@ -9148,7 +9431,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
9148
9431
|
add({
|
|
9149
9432
|
name: "list_brushes",
|
|
9150
9433
|
title: "List brushes",
|
|
9151
|
-
description: "Lists the brush library: built-in dry media (pencils, pastels, charcoal, airbrush, marker, ink, erasers), paint brushes (dirty round, multi-colour flat, palette dabber), blending tools (blender, smudge, pigment smudge, Mixer Brush), light paint (category light: glow gel, neon marker, light airbrush, spark trail; light is the amount they lay) and saved user brushes, with id, name, category, engine, size, grain response, rotation mode, whether it can pick up paint (wetCapable), its pick-up ratio, dirty and multi-colour mode, plus the active brush id.",
|
|
9434
|
+
description: "Lists the brush library: built-in dry media (pencils, pastels, charcoal, airbrush, marker, ink, erasers), paint brushes (dirty round, multi-colour flat, palette dabber), blending tools (blender, smudge, pigment smudge, Mixer Brush), light paint (category light: glow gel, neon marker, light airbrush, spark trail, glow wash; light is the amount they lay) and saved user brushes, with id, name, category, engine, size, grain response, rotation mode, whether it can pick up paint (wetCapable), its pick-up ratio, dirty and multi-colour mode, plus the active brush id.",
|
|
9152
9435
|
inputSchema: EMPTY3,
|
|
9153
9436
|
annotations: { title: "List brushes", ...READ6 },
|
|
9154
9437
|
handler: () => {
|
|
@@ -9332,7 +9615,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
9332
9615
|
add({
|
|
9333
9616
|
name: "paint_stroke",
|
|
9334
9617
|
title: "Paint a brush stroke",
|
|
9335
|
-
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) picks up and mixes paint already on the layer, the blender mixes nearby colours, the smudge drags paint, and dab brushes with behavior.pickup lift and carry paint, and wet brushes (engine wet: watercolour, gouache, add water, dry, blow, wet blend, wet smudge, blot) paint on the layer\u2019s wet layer, where the paint keeps moving until it dries (the result says wet: true while it is; see advance_wet_paint and dry_paint; a dry brush on wet paint dries it first; wet oil brushes blend into the wet oil they meet), impasto brushes (engine impasto: impasto brush, paint tube, palette knife, roller, gloop pen, metallic paint) lay thick paint whose relief is lit by set_lighting, particle and bristle brushes draw procedural marks, and sticker brushes spray editable stickers onto an empty or sticker layer (list_stickers). Guide stencils make strokes that start near them follow them; mask stencils keep paint inside them. 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), for wet brushes wet (paint still wet) and, for brushes that pick up paint, mixerLoad: what the brush carries after the stroke.",
|
|
9618
|
+
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) picks up and mixes paint already on the layer, the blender mixes nearby colours, the smudge drags paint, and dab brushes with behavior.pickup lift and carry paint, and wet brushes (engine wet: watercolour, gouache, add water, dry, blow, wet blend, wet smudge, blot) paint on the layer\u2019s wet layer, where the paint keeps moving until it dries (the result says wet: true while it is; see advance_wet_paint and dry_paint; a dry brush on wet paint dries it first; wet oil brushes blend into the wet oil they meet; with light.amount, as glow-wash has, their light flows with the paint), impasto brushes (engine impasto: impasto brush, paint tube, palette knife, roller, gloop pen, metallic paint) lay thick paint whose relief is lit by set_lighting, particle and bristle brushes draw procedural marks, and sticker brushes spray editable stickers onto an empty or sticker layer (list_stickers). Guide stencils make strokes that start near them follow them; mask stencils keep paint inside them. 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), for wet brushes wet (paint still wet) and, for brushes that pick up paint, mixerLoad: what the brush carries after the stroke.",
|
|
9336
9619
|
inputSchema: {
|
|
9337
9620
|
type: "object",
|
|
9338
9621
|
properties: {
|
|
@@ -10054,7 +10337,8 @@ var PAPER_SLIDERS = [
|
|
|
10054
10337
|
{ path: "absorption", label: "Absorption", min: 0, max: 1, step: 0.01 },
|
|
10055
10338
|
{ path: "permeability", label: "Permeability", min: 0, max: 1, step: 0.01 },
|
|
10056
10339
|
{ path: "roughness", label: "Roughness", min: 0, max: 1, step: 0.01 },
|
|
10057
|
-
{ path: "metallic", label: "Metallic", min: 0, max: 1, step: 0.01 }
|
|
10340
|
+
{ path: "metallic", label: "Metallic", min: 0, max: 1, step: 0.01 },
|
|
10341
|
+
{ path: "backlight", label: "Backlight", min: 0, max: 1, step: 0.01 }
|
|
10058
10342
|
];
|
|
10059
10343
|
function readPath(obj, path) {
|
|
10060
10344
|
let v = obj;
|
|
@@ -10110,7 +10394,8 @@ var PICKUP_SLIDERS = [
|
|
|
10110
10394
|
];
|
|
10111
10395
|
function brushEngineControls(brush) {
|
|
10112
10396
|
const c = engineControls(brush);
|
|
10113
|
-
|
|
10397
|
+
const wetPaint = brush.engine === "wet" && brush.wet?.tool === "paint";
|
|
10398
|
+
if (!c || !(wetPaint || ["dab", "mixer", "particle", "bristle", "impasto"].includes(brush.engine))) return c;
|
|
10114
10399
|
const extra = brush.engine === "impasto" ? [{ path: "impasto.rim", label: "Rim", min: 0, max: 1, step: 0.01 }, { path: "impasto.clarity", label: "Clarity", min: 0, max: 1, step: 0.01 }] : [];
|
|
10115
10400
|
return { ...c, sliders: [...c.sliders, LIGHT_SLIDER, ...extra], light: true };
|
|
10116
10401
|
}
|
|
@@ -10644,6 +10929,7 @@ function createPaintEditorDocument(controller, options = {}) {
|
|
|
10644
10929
|
});
|
|
10645
10930
|
}
|
|
10646
10931
|
export {
|
|
10932
|
+
BACKLIGHT_SPILL,
|
|
10647
10933
|
BLEND_MENU_GROUPS,
|
|
10648
10934
|
BLEND_MODES,
|
|
10649
10935
|
BRUSH_ID,
|
|
@@ -10651,6 +10937,7 @@ export {
|
|
|
10651
10937
|
BRUSH_SLIDERS,
|
|
10652
10938
|
BUILTIN_BRUSHES,
|
|
10653
10939
|
BristleStroke,
|
|
10940
|
+
DEFAULT_BACKLIGHT_COLOR,
|
|
10654
10941
|
DEFAULT_BEHAVIOR,
|
|
10655
10942
|
DEFAULT_BRISTLE,
|
|
10656
10943
|
DEFAULT_BRUSH_ID,
|
|
@@ -10776,6 +11063,7 @@ export {
|
|
|
10776
11063
|
addPaintGradientStop,
|
|
10777
11064
|
affineCopy,
|
|
10778
11065
|
applyAffine,
|
|
11066
|
+
backlightOf,
|
|
10779
11067
|
brushEngineControls,
|
|
10780
11068
|
brushLoadColor,
|
|
10781
11069
|
brushLoadForStroke,
|
|
@@ -40,6 +40,13 @@ export interface PaperSettings {
|
|
|
40
40
|
roughness: number;
|
|
41
41
|
/** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
|
|
42
42
|
metallic?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Backlit paper (a lightbox, stained glass): light from behind the paper, 0..1 (default 0: none).
|
|
45
|
+
* Transparent glazes glow in their own colour; opaque and thick paint block it (`backlight.ts`).
|
|
46
|
+
*/
|
|
47
|
+
backlight?: number;
|
|
48
|
+
/** The backlight's colour #rrggbb (default warm white, `#fff1d6`). */
|
|
49
|
+
backlightColor?: string;
|
|
43
50
|
/**
|
|
44
51
|
* Where the grain's (0, 0) sits in document pixels (missing = the top-left). `extendCanvas` moves
|
|
45
52
|
* it with the paint, so the texture stays under what was painted on it.
|
|
@@ -40,6 +40,13 @@ export interface PaperSettings {
|
|
|
40
40
|
roughness: number;
|
|
41
41
|
/** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
|
|
42
42
|
metallic?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Backlit paper (a lightbox, stained glass): light from behind the paper, 0..1 (default 0: none).
|
|
45
|
+
* Transparent glazes glow in their own colour; opaque and thick paint block it (`backlight.ts`).
|
|
46
|
+
*/
|
|
47
|
+
backlight?: number;
|
|
48
|
+
/** The backlight's colour #rrggbb (default warm white, `#fff1d6`). */
|
|
49
|
+
backlightColor?: string;
|
|
43
50
|
/**
|
|
44
51
|
* Where the grain's (0, 0) sits in document pixels (missing = the top-left). `extendCanvas` moves
|
|
45
52
|
* it with the paint, so the texture stays under what was painted on it.
|