@pixerate/editor 0.93.0 → 0.95.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 +3 -0
- package/dist/{chunk-ZOOEX3WT.cjs → chunk-CRMAXIUV.cjs} +369 -7
- package/dist/chunk-CRMAXIUV.cjs.map +1 -0
- package/dist/chunk-E2ILRYTJ.js +3695 -0
- package/dist/chunk-E2ILRYTJ.js.map +1 -0
- package/dist/{chunk-D4ZCU72J.js → chunk-HEW6KDNI.js} +366 -4
- package/dist/chunk-HEW6KDNI.js.map +1 -0
- package/dist/{chunk-ISDCJHTO.js → chunk-IB53LVGV.js} +238 -285
- package/dist/chunk-IB53LVGV.js.map +1 -0
- package/dist/chunk-T6H3JDGA.cjs +3695 -0
- package/dist/chunk-T6H3JDGA.cjs.map +1 -0
- package/dist/{chunk-4F3YDIWW.cjs → chunk-ZZOTHJ4K.cjs} +264 -311
- package/dist/chunk-ZZOTHJ4K.cjs.map +1 -0
- package/dist/image-editor/index.cjs +1252 -588
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +164 -34
- package/dist/image-editor/index.d.ts +164 -34
- package/dist/image-editor/index.js +913 -249
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +22 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +117 -2
- package/dist/paint/index.d.ts +117 -2
- package/dist/paint/index.js +32 -13
- package/dist/raster/index.cjs +83 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +99 -348
- package/dist/raster/index.d.ts +99 -348
- package/dist/raster/index.js +83 -3
- package/dist/{surface-fJhkmNQn.d.cts → surface-CgAiXVI3.d.cts} +23 -11
- package/dist/{surface-fJhkmNQn.d.ts → surface-CgAiXVI3.d.ts} +23 -11
- package/dist/zlib-B3LIoxF-.d.cts +796 -0
- package/dist/zlib-eiTEEj1z.d.ts +796 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +82 -7
- package/dist/chunk-4F3YDIWW.cjs.map +0 -1
- package/dist/chunk-D4ZCU72J.js.map +0 -1
- package/dist/chunk-H543GD6T.cjs +0 -1696
- package/dist/chunk-H543GD6T.cjs.map +0 -1
- package/dist/chunk-ISDCJHTO.js.map +0 -1
- package/dist/chunk-P5TPUNBZ.js +0 -1696
- package/dist/chunk-P5TPUNBZ.js.map +0 -1
- package/dist/chunk-ZOOEX3WT.cjs.map +0 -1
|
@@ -5,17 +5,25 @@ import {
|
|
|
5
5
|
createCaptureTool
|
|
6
6
|
} from "../chunk-REOJJIY3.js";
|
|
7
7
|
import {
|
|
8
|
+
DEFAULT_GLOBAL_LIGHT,
|
|
8
9
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
10
|
+
LAYER_EFFECT_KINDS,
|
|
11
|
+
LayerEffectError,
|
|
9
12
|
RasterCompositor,
|
|
10
13
|
ZlibError,
|
|
11
14
|
adler32,
|
|
12
15
|
base64ToBytes,
|
|
13
16
|
blendTile,
|
|
17
|
+
builtinPattern,
|
|
18
|
+
builtinPatternIds,
|
|
14
19
|
bytesToBase64,
|
|
15
20
|
changedTiles,
|
|
21
|
+
curveLut,
|
|
16
22
|
decodePng,
|
|
23
|
+
defaultLayerEffect,
|
|
17
24
|
deflate,
|
|
18
25
|
deflateRaw,
|
|
26
|
+
effectsMargin,
|
|
19
27
|
encodePng,
|
|
20
28
|
findLayer,
|
|
21
29
|
flattenLayers,
|
|
@@ -23,44 +31,52 @@ import {
|
|
|
23
31
|
inflate,
|
|
24
32
|
inflateRaw,
|
|
25
33
|
insertLayer,
|
|
34
|
+
isLayerEffectKind,
|
|
26
35
|
isLayerLocked,
|
|
27
36
|
isPng,
|
|
37
|
+
layerEffectLabel,
|
|
28
38
|
mapLayer,
|
|
39
|
+
mergeLayerEffect,
|
|
40
|
+
newEffectId,
|
|
41
|
+
normalizeLayerEffects,
|
|
29
42
|
parseDataUrl,
|
|
30
|
-
pngDataUrl
|
|
31
|
-
|
|
43
|
+
pngDataUrl,
|
|
44
|
+
validateLayerEffects
|
|
45
|
+
} from "../chunk-E2ILRYTJ.js";
|
|
32
46
|
import {
|
|
33
47
|
CoverageMap,
|
|
34
48
|
DEFAULT_BRUSH,
|
|
49
|
+
DEFAULT_MIXER,
|
|
35
50
|
DabWalker,
|
|
51
|
+
EMPTY_MIXER_LOAD,
|
|
36
52
|
MAX_BRUSH_SIZE,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
MIXER_DRAIN,
|
|
54
|
+
MIXER_PICKUP_RATE,
|
|
55
|
+
MIXING_MODELS,
|
|
56
|
+
MixerStroke,
|
|
41
57
|
StrokeCoverage,
|
|
42
58
|
applyStrokeToTile,
|
|
59
|
+
cleanMixer,
|
|
43
60
|
coverageTileCoord,
|
|
44
|
-
createPigment,
|
|
45
61
|
createPigmentTools,
|
|
46
62
|
dabCoverage,
|
|
47
63
|
dabRect,
|
|
48
|
-
|
|
49
|
-
lerpPigmentInto,
|
|
50
|
-
mixPigments,
|
|
64
|
+
loadMixer,
|
|
51
65
|
normalizeBrush,
|
|
66
|
+
normalizeMixer,
|
|
52
67
|
rasterizeDab,
|
|
53
|
-
reflectanceToRgb,
|
|
54
|
-
rgbToReflectance,
|
|
55
|
-
setPigmentColor,
|
|
56
68
|
smoothPoints,
|
|
57
69
|
tipFalloff,
|
|
58
70
|
tipReach,
|
|
59
71
|
tipShape,
|
|
60
72
|
walkDabs
|
|
61
|
-
} from "../chunk-
|
|
73
|
+
} from "../chunk-IB53LVGV.js";
|
|
62
74
|
import {
|
|
63
75
|
BLEND_MODES,
|
|
76
|
+
PIGMENT_BANDS,
|
|
77
|
+
PIGMENT_LAMBDA_MIN,
|
|
78
|
+
PIGMENT_LAMBDA_STEP,
|
|
79
|
+
PIGMENT_WAVELENGTHS,
|
|
64
80
|
SurfaceDraft,
|
|
65
81
|
TILE,
|
|
66
82
|
TILE_SHIFT,
|
|
@@ -73,16 +89,25 @@ import {
|
|
|
73
89
|
canvasCompositeOperation,
|
|
74
90
|
collectTiles,
|
|
75
91
|
contentBounds,
|
|
92
|
+
createPigment,
|
|
76
93
|
emptySurface,
|
|
77
94
|
fillRect,
|
|
78
95
|
finishTile,
|
|
79
96
|
isBlendMode,
|
|
80
97
|
isSeparableBlendMode,
|
|
98
|
+
lerpPigment,
|
|
99
|
+
lerpPigmentInto,
|
|
100
|
+
mixPigment8,
|
|
101
|
+
mixPigmentRgb,
|
|
102
|
+
mixPigments,
|
|
81
103
|
normalizeBlendMode,
|
|
82
104
|
packRgba,
|
|
83
105
|
readPixel,
|
|
84
106
|
readRegion,
|
|
107
|
+
reflectanceToRgb,
|
|
108
|
+
rgbToReflectance,
|
|
85
109
|
setLum,
|
|
110
|
+
setPigmentColor,
|
|
86
111
|
solidSurface,
|
|
87
112
|
surfaceBytes,
|
|
88
113
|
surfaceFromPixels,
|
|
@@ -94,7 +119,7 @@ import {
|
|
|
94
119
|
translateSurface,
|
|
95
120
|
uniformTile,
|
|
96
121
|
writeRegion
|
|
97
|
-
} from "../chunk-
|
|
122
|
+
} from "../chunk-HEW6KDNI.js";
|
|
98
123
|
|
|
99
124
|
// src/image-editor/color.ts
|
|
100
125
|
var LEGACY_PROCESS_VERSION = 1;
|
|
@@ -1496,59 +1521,6 @@ function normalizeAdjustmentLayers(raw) {
|
|
|
1496
1521
|
}
|
|
1497
1522
|
return out;
|
|
1498
1523
|
}
|
|
1499
|
-
function curveLut(points, size = ADJUSTMENT_LUT_SIZE) {
|
|
1500
|
-
const pts = points.filter((p) => Number.isFinite(p.input) && Number.isFinite(p.output)).map((p) => [p.input, p.output]).sort((a, b) => a[0] - b[0]);
|
|
1501
|
-
const dedup = [];
|
|
1502
|
-
for (const p of pts) {
|
|
1503
|
-
if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;
|
|
1504
|
-
dedup.push(p);
|
|
1505
|
-
}
|
|
1506
|
-
const out = new Float32Array(size);
|
|
1507
|
-
const n = dedup.length;
|
|
1508
|
-
if (n < 2) {
|
|
1509
|
-
for (let k = 0; k < size; k++) out[k] = k / (size - 1);
|
|
1510
|
-
return out;
|
|
1511
|
-
}
|
|
1512
|
-
const x = dedup.map((p) => p[0]);
|
|
1513
|
-
const y = dedup.map((p) => p[1]);
|
|
1514
|
-
const m2 = new Float64Array(n);
|
|
1515
|
-
if (n > 2) {
|
|
1516
|
-
const c = new Float64Array(n);
|
|
1517
|
-
const d = new Float64Array(n);
|
|
1518
|
-
for (let i = 1; i < n - 1; i++) {
|
|
1519
|
-
const h0 = x[i] - x[i - 1];
|
|
1520
|
-
const h1 = x[i + 1] - x[i];
|
|
1521
|
-
const a = h0 / 6;
|
|
1522
|
-
const b = (h0 + h1) / 3;
|
|
1523
|
-
const cc = h1 / 6;
|
|
1524
|
-
const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;
|
|
1525
|
-
const denom = b - a * c[i - 1];
|
|
1526
|
-
c[i] = cc / denom;
|
|
1527
|
-
d[i] = (r - a * d[i - 1]) / denom;
|
|
1528
|
-
}
|
|
1529
|
-
for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];
|
|
1530
|
-
}
|
|
1531
|
-
let seg = 0;
|
|
1532
|
-
for (let k = 0; k < size; k++) {
|
|
1533
|
-
const xv = k / (size - 1);
|
|
1534
|
-
if (xv <= x[0]) {
|
|
1535
|
-
out[k] = clamp01(y[0]);
|
|
1536
|
-
continue;
|
|
1537
|
-
}
|
|
1538
|
-
if (xv >= x[n - 1]) {
|
|
1539
|
-
out[k] = clamp01(y[n - 1]);
|
|
1540
|
-
continue;
|
|
1541
|
-
}
|
|
1542
|
-
while (seg < n - 2 && xv > x[seg + 1]) seg++;
|
|
1543
|
-
const x0 = x[seg], x1 = x[seg + 1];
|
|
1544
|
-
const h = x1 - x0;
|
|
1545
|
-
const a = (x1 - xv) / h;
|
|
1546
|
-
const b = (xv - x0) / h;
|
|
1547
|
-
const v = a * y[seg] + b * y[seg + 1] + ((a * a * a - a) * m2[seg] + (b * b * b - b) * m2[seg + 1]) * (h * h) / 6;
|
|
1548
|
-
out[k] = clamp01(v);
|
|
1549
|
-
}
|
|
1550
|
-
return out;
|
|
1551
|
-
}
|
|
1552
1524
|
function curveLutFromLevels(points) {
|
|
1553
1525
|
return curveLut(points.map((p) => ({ input: p.input / 255, output: p.output / 255 })));
|
|
1554
1526
|
}
|
|
@@ -1958,6 +1930,10 @@ function applyLayer(c, layer, op, coverage = 1) {
|
|
|
1958
1930
|
}
|
|
1959
1931
|
const cb0 = c[0], cb1 = c[1], cb2 = c[2];
|
|
1960
1932
|
op(c);
|
|
1933
|
+
if (blend === "pigment") {
|
|
1934
|
+
mixPigmentRgb([clamp01(cb0), clamp01(cb1), clamp01(cb2)], [clamp01(c[0]), clamp01(c[1]), clamp01(c[2])], alpha, c);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1961
1937
|
const b = blend === "normal" ? c : blendRgb(blend, [cb0, cb1, cb2], c);
|
|
1962
1938
|
c[0] = (1 - alpha) * cb0 + alpha * b[0];
|
|
1963
1939
|
c[1] = (1 - alpha) * cb1 + alpha * b[1];
|
|
@@ -2700,7 +2676,7 @@ function developTarget(layers, activeLayerId) {
|
|
|
2700
2676
|
}
|
|
2701
2677
|
return pixels.find((l) => l.source === "document") ?? pixels.find((l) => l.id === BACKGROUND_LAYER_ID) ?? pixels[0] ?? null;
|
|
2702
2678
|
}
|
|
2703
|
-
var isPlain = (l) => l.visible && l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask && !l.locks;
|
|
2679
|
+
var isPlain = (l) => l.visible && l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask && !l.locks && !l.effects;
|
|
2704
2680
|
function legacyView(stack, width, height, activeLayerId = null) {
|
|
2705
2681
|
const { layers } = stack;
|
|
2706
2682
|
const target = developTarget(layers, activeLayerId);
|
|
@@ -2740,7 +2716,7 @@ function isLegacyShape(stack) {
|
|
|
2740
2716
|
const bg = layers[0];
|
|
2741
2717
|
if (!bg || bg.type !== "pixel" || bg.source !== "document") return false;
|
|
2742
2718
|
if (bg.id !== BACKGROUND_LAYER_ID || bg.name !== BACKGROUND_LAYER_NAME) return false;
|
|
2743
|
-
if (!bg.visible || bg.opacity !== 1 || bg.blend !== "normal" || bg.clipped || bg.locks) return false;
|
|
2719
|
+
if (!bg.visible || bg.opacity !== 1 || bg.blend !== "normal" || bg.clipped || bg.locks || bg.effects) return false;
|
|
2744
2720
|
if ((bg.fillOpacity ?? 1) !== 1 || bg.offset.x !== 0 || bg.offset.y !== 0) return false;
|
|
2745
2721
|
const filters = bg.filters ?? [];
|
|
2746
2722
|
if (filters.length < 1 || filters.length > 2) return false;
|
|
@@ -2756,7 +2732,7 @@ function isLegacyShape(stack) {
|
|
|
2756
2732
|
let adjustments = 0;
|
|
2757
2733
|
for (; i < layers.length && layers[i].type === "adjustment"; i++) {
|
|
2758
2734
|
const l = layers[i];
|
|
2759
|
-
if (l.clipped || l.locks || l.mask?.source) return false;
|
|
2735
|
+
if (l.clipped || l.locks || l.mask?.source || l.effects) return false;
|
|
2760
2736
|
adjustments++;
|
|
2761
2737
|
}
|
|
2762
2738
|
for (; i < layers.length; i++) {
|
|
@@ -3262,7 +3238,9 @@ var ImageEditorRenderer = class {
|
|
|
3262
3238
|
return this.depthMaps.get(`${source.maskSource}|${width}x${height}`) ?? null;
|
|
3263
3239
|
},
|
|
3264
3240
|
rasterizeAnnotation: (annotation, rect) => this.rasterizeAnnotation(annotation, rect),
|
|
3265
|
-
version: `${host?.version() ?? ""}:${this.assetVersion}
|
|
3241
|
+
version: `${host?.version() ?? ""}:${this.assetVersion}`,
|
|
3242
|
+
globalLight: host?.globalLight?.(),
|
|
3243
|
+
pattern: host?.pattern ? (id) => host.pattern(id) : void 0
|
|
3266
3244
|
};
|
|
3267
3245
|
}
|
|
3268
3246
|
/** Loads what the compositor reads synchronously: depth maps (at canvas size) and annotation images. */
|
|
@@ -4535,8 +4513,29 @@ function baseFields(layer, ctx) {
|
|
|
4535
4513
|
if (layer.clipped) out.clipped = true;
|
|
4536
4514
|
if (layer.mask) out.mask = serializeMask(layer.mask, `${layer.id}-mask`, ctx);
|
|
4537
4515
|
if (layer.locks && Object.values(layer.locks).some(Boolean)) out.locks = { ...layer.locks };
|
|
4516
|
+
if (layer.effects) out.effects = serializeEffects(layer.effects);
|
|
4538
4517
|
return out;
|
|
4539
4518
|
}
|
|
4519
|
+
function serializeEffects(effects) {
|
|
4520
|
+
const out = { enabled: effects.enabled, items: JSON.parse(JSON.stringify(effects.items)) };
|
|
4521
|
+
if (effects.scale !== void 0 && effects.scale !== 1) out.scale = effects.scale;
|
|
4522
|
+
if (effects.psd) out.psd = { key: effects.psd.key, data: bytesToBase64(effects.psd.data) };
|
|
4523
|
+
return out;
|
|
4524
|
+
}
|
|
4525
|
+
function deserializeEffects(raw) {
|
|
4526
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
4527
|
+
const r = raw;
|
|
4528
|
+
let psd;
|
|
4529
|
+
const p = r.psd;
|
|
4530
|
+
if (p && typeof p.key === "string" && typeof p.data === "string") {
|
|
4531
|
+
try {
|
|
4532
|
+
psd = { key: p.key, data: base64ToBytes(p.data) };
|
|
4533
|
+
} catch {
|
|
4534
|
+
psd = void 0;
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
return normalizeLayerEffects({ ...r, psd });
|
|
4538
|
+
}
|
|
4540
4539
|
function serializeMask(mask, hint, ctx) {
|
|
4541
4540
|
const out = { defaultValue: mask.defaultValue };
|
|
4542
4541
|
if (!mask.source) {
|
|
@@ -4739,6 +4738,8 @@ function deserializeLayer(raw, ctx) {
|
|
|
4739
4738
|
for (const k of ["pixels", "transparency", "position", "all"]) if (r.locks[k] === true) locks[k] = true;
|
|
4740
4739
|
if (Object.keys(locks).length) base.locks = locks;
|
|
4741
4740
|
}
|
|
4741
|
+
const effects = deserializeEffects(r.effects);
|
|
4742
|
+
if (effects && r.type !== "adjustment") base.effects = effects;
|
|
4742
4743
|
switch (r.type) {
|
|
4743
4744
|
case "pixel": {
|
|
4744
4745
|
const layer = {
|
|
@@ -5021,6 +5022,77 @@ function removeLayerFilter(layers, id, filterId) {
|
|
|
5021
5022
|
else delete next.filters;
|
|
5022
5023
|
return mapLayer(layers, id, () => next);
|
|
5023
5024
|
}
|
|
5025
|
+
function requireEffectLayer(layers, id, action) {
|
|
5026
|
+
const layer = requireLayer(layers, id).layer;
|
|
5027
|
+
if (layer.type === "adjustment") fail(`Layer "${layer.name || id}" is an adjustment layer; effects go on pixel, annotation and group layers.`);
|
|
5028
|
+
requireUnlocked(layer, "all", action);
|
|
5029
|
+
return layer;
|
|
5030
|
+
}
|
|
5031
|
+
function layerEffectsOf(layer) {
|
|
5032
|
+
return layer.effects ?? { enabled: true, items: [] };
|
|
5033
|
+
}
|
|
5034
|
+
function guard(fn) {
|
|
5035
|
+
try {
|
|
5036
|
+
return fn();
|
|
5037
|
+
} catch (e) {
|
|
5038
|
+
if (e instanceof LayerEffectError) fail(e.message);
|
|
5039
|
+
throw e;
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
function setLayerEffects(layers, id, effects) {
|
|
5043
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
5044
|
+
const next = { ...layer };
|
|
5045
|
+
if (effects && (effects.items.length || effects.psd || effects.scale !== void 0 || !effects.enabled)) {
|
|
5046
|
+
guard(() => validateLayerEffects(effects));
|
|
5047
|
+
next.effects = effects;
|
|
5048
|
+
} else delete next.effects;
|
|
5049
|
+
return mapLayer(layers, id, () => next);
|
|
5050
|
+
}
|
|
5051
|
+
function addLayerEffect(layers, id, kind, params = {}, index) {
|
|
5052
|
+
if (!isLayerEffectKind(kind)) fail(`Unknown effect kind "${String(kind)}". Kinds: ${LAYER_EFFECT_KINDS.join(", ")}.`);
|
|
5053
|
+
const layer = requireEffectLayer(layers, id, "add effects");
|
|
5054
|
+
const current = layerEffectsOf(layer);
|
|
5055
|
+
const ids = new Set(current.items.map((e) => e.id));
|
|
5056
|
+
let effectId = newEffectId();
|
|
5057
|
+
while (ids.has(effectId)) effectId = newEffectId();
|
|
5058
|
+
const { id: _ignored, kind: _kind, ...rest } = params;
|
|
5059
|
+
const effect = guard(() => mergeLayerEffect(defaultLayerEffect(kind, effectId), rest));
|
|
5060
|
+
const items = [...current.items];
|
|
5061
|
+
const at = index === void 0 ? items.length : Math.max(0, Math.min(items.length, Math.floor(index)));
|
|
5062
|
+
items.splice(at, 0, effect);
|
|
5063
|
+
return { layers: setLayerEffects(layers, id, { ...current, items }), effect };
|
|
5064
|
+
}
|
|
5065
|
+
function requireEffect(layer, effectId) {
|
|
5066
|
+
const items = layer.effects?.items ?? [];
|
|
5067
|
+
const i = items.findIndex((e) => e.id === effectId);
|
|
5068
|
+
if (i === -1) fail(`Layer "${layer.name || layer.id}" has no effect "${effectId}". Effects: ${items.map((e) => `${e.id} (${e.kind})`).join(", ") || "none"}; call list_layer_effects.`);
|
|
5069
|
+
return i;
|
|
5070
|
+
}
|
|
5071
|
+
function updateLayerEffect(layers, id, effectId, changes) {
|
|
5072
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
5073
|
+
const i = requireEffect(layer, effectId);
|
|
5074
|
+
const current = layer.effects;
|
|
5075
|
+
const items = [...current.items];
|
|
5076
|
+
items[i] = guard(() => mergeLayerEffect(items[i], changes));
|
|
5077
|
+
return setLayerEffects(layers, id, { ...current, items });
|
|
5078
|
+
}
|
|
5079
|
+
function removeLayerEffect(layers, id, effectId) {
|
|
5080
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
5081
|
+
requireEffect(layer, effectId);
|
|
5082
|
+
const current = layer.effects;
|
|
5083
|
+
return setLayerEffects(layers, id, { ...current, items: current.items.filter((e) => e.id !== effectId) });
|
|
5084
|
+
}
|
|
5085
|
+
function setLayerEffectsEnabled(layers, id, enabled, scale) {
|
|
5086
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
5087
|
+
if (scale !== void 0 && !(Number.isFinite(scale) && scale >= 0.01 && scale <= 10)) fail(`scale must be from 0.01 to 10 (1 = 100 %), got ${scale}.`);
|
|
5088
|
+
const current = layerEffectsOf(layer);
|
|
5089
|
+
const next = { ...current, enabled };
|
|
5090
|
+
if (scale !== void 0) {
|
|
5091
|
+
if (scale === 1) delete next.scale;
|
|
5092
|
+
else next.scale = scale;
|
|
5093
|
+
}
|
|
5094
|
+
return setLayerEffects(layers, id, next);
|
|
5095
|
+
}
|
|
5024
5096
|
function parseBlend(value, allowPassThrough = false) {
|
|
5025
5097
|
if (value === void 0) return void 0;
|
|
5026
5098
|
if (value === "pass-through" && allowPassThrough) return value;
|
|
@@ -7127,22 +7199,53 @@ var PaintStrokeSession = class {
|
|
|
7127
7199
|
constructor(controller, target, stroke, apply) {
|
|
7128
7200
|
this.controller = controller;
|
|
7129
7201
|
this.target = target;
|
|
7130
|
-
this.stroke = stroke;
|
|
7131
7202
|
this.apply = apply;
|
|
7132
7203
|
this.before = controller._captureForStroke();
|
|
7133
7204
|
this.base = target.surface;
|
|
7134
7205
|
this.live = new Map(this.base.tiles);
|
|
7206
|
+
if (stroke instanceof StrokeCoverage) {
|
|
7207
|
+
this.stroke = stroke;
|
|
7208
|
+
this.mixer = null;
|
|
7209
|
+
} else {
|
|
7210
|
+
this.stroke = null;
|
|
7211
|
+
this.mixer = stroke.create(this.mixerPixels());
|
|
7212
|
+
this.mixerInit = stroke;
|
|
7213
|
+
}
|
|
7135
7214
|
}
|
|
7136
7215
|
controller;
|
|
7137
7216
|
target;
|
|
7138
|
-
stroke;
|
|
7139
7217
|
apply;
|
|
7140
7218
|
before;
|
|
7141
7219
|
base;
|
|
7142
7220
|
live;
|
|
7143
7221
|
touched = /* @__PURE__ */ new Set();
|
|
7222
|
+
stroke;
|
|
7223
|
+
mixer;
|
|
7224
|
+
mixerInit = null;
|
|
7225
|
+
/** Mixer tiles written since the last update: they get new tile objects so the compositor sees them change. */
|
|
7226
|
+
fresh = /* @__PURE__ */ new Set();
|
|
7144
7227
|
last = null;
|
|
7145
7228
|
closed = false;
|
|
7229
|
+
/** The live pixels a mixer stroke reads and writes; base tiles are copied on first write. */
|
|
7230
|
+
mixerPixels() {
|
|
7231
|
+
const a = this.apply;
|
|
7232
|
+
return {
|
|
7233
|
+
offset: a.offset,
|
|
7234
|
+
lockTransparency: a.lockTransparency,
|
|
7235
|
+
selection: a.selection,
|
|
7236
|
+
readTile: (tx, ty) => this.live.get(tileKey(tx, ty))?.data ?? (this.base.defaultValue ? defaultTile(this.base) : null),
|
|
7237
|
+
writeTile: (tx, ty) => {
|
|
7238
|
+
const key = tileKey(tx, ty);
|
|
7239
|
+
if (!this.touched.has(key)) {
|
|
7240
|
+
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
7241
|
+
this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
|
|
7242
|
+
this.touched.add(key);
|
|
7243
|
+
}
|
|
7244
|
+
this.fresh.add(key);
|
|
7245
|
+
return this.live.get(key).data;
|
|
7246
|
+
}
|
|
7247
|
+
};
|
|
7248
|
+
}
|
|
7146
7249
|
get layerId() {
|
|
7147
7250
|
return this.target.id;
|
|
7148
7251
|
}
|
|
@@ -7150,11 +7253,18 @@ var PaintStrokeSession = class {
|
|
|
7150
7253
|
push(points) {
|
|
7151
7254
|
if (this.closed || !points.length) return;
|
|
7152
7255
|
this.last = points[points.length - 1];
|
|
7153
|
-
this.update(this.stroke.push(points));
|
|
7256
|
+
this.update(this.mixer ? this.mixer.push(points) : this.stroke.push(points));
|
|
7154
7257
|
}
|
|
7155
7258
|
update(rects) {
|
|
7156
7259
|
if (!rects.length) return;
|
|
7157
7260
|
const ch = this.base.channels;
|
|
7261
|
+
if (this.mixer) {
|
|
7262
|
+
for (const key of this.fresh) this.live.set(key, { data: this.live.get(key).data, uniform: null });
|
|
7263
|
+
this.fresh.clear();
|
|
7264
|
+
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
7265
|
+
return;
|
|
7266
|
+
}
|
|
7267
|
+
const stroke = this.stroke;
|
|
7158
7268
|
const regions = /* @__PURE__ */ new Map();
|
|
7159
7269
|
for (const r of rects) {
|
|
7160
7270
|
for (const t of tilesInRect({ x: r.x - this.apply.offset.x, y: r.y - this.apply.offset.y, width: r.width, height: r.height })) {
|
|
@@ -7167,7 +7277,7 @@ var PaintStrokeSession = class {
|
|
|
7167
7277
|
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
7168
7278
|
const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
|
|
7169
7279
|
this.touched.add(key);
|
|
7170
|
-
const data = applyStrokeToTile(
|
|
7280
|
+
const data = applyStrokeToTile(stroke.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
|
|
7171
7281
|
this.live.set(key, { data, uniform: null });
|
|
7172
7282
|
}
|
|
7173
7283
|
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
@@ -7175,7 +7285,8 @@ var PaintStrokeSession = class {
|
|
|
7175
7285
|
/** Finishes the stroke (one undo step) and returns what changed. */
|
|
7176
7286
|
end() {
|
|
7177
7287
|
if (this.closed) throw new LayerOperationError("This stroke has already ended.");
|
|
7178
|
-
this.
|
|
7288
|
+
const last = this.last ?? void 0;
|
|
7289
|
+
this.update(this.mixer ? this.mixer.finish(last) : this.stroke.finish(last));
|
|
7179
7290
|
this.closed = true;
|
|
7180
7291
|
const ch = this.base.channels;
|
|
7181
7292
|
const tiles = new Map(this.base.tiles);
|
|
@@ -7189,9 +7300,12 @@ var PaintStrokeSession = class {
|
|
|
7189
7300
|
if (!sameTile(prev, tile)) changed = true;
|
|
7190
7301
|
}
|
|
7191
7302
|
const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
|
|
7303
|
+
const mixerLoad = this.mixer ? this.mixerInit.end(this.mixer.load) : void 0;
|
|
7192
7304
|
this.controller._strokeCommit(this.before, this.target.id, this.target.channel, surface);
|
|
7193
|
-
const b = this.stroke.coverage.bounds;
|
|
7194
|
-
|
|
7305
|
+
const b = this.mixer ? this.mixer.bounds : this.stroke.coverage.bounds;
|
|
7306
|
+
const result = { layerId: this.target.id, channel: this.target.channel, bounds: changed ? b : null, dabs: (this.mixer ?? this.stroke).dabCount };
|
|
7307
|
+
if (mixerLoad) result.mixerLoad = mixerLoad;
|
|
7308
|
+
return result;
|
|
7195
7309
|
}
|
|
7196
7310
|
/** Abandons the stroke and restores the layer. */
|
|
7197
7311
|
cancel() {
|
|
@@ -7305,7 +7419,8 @@ var ImageEditorController = class {
|
|
|
7305
7419
|
this.renderer.layerSources = {
|
|
7306
7420
|
surfaceOf: (layer) => this.surfaceOf(layer),
|
|
7307
7421
|
sourceIsOpaque: () => this.sourceIsOpaque(),
|
|
7308
|
-
version: () => this.sourceVersion()
|
|
7422
|
+
version: () => this.sourceVersion(),
|
|
7423
|
+
globalLight: () => this.state.globalLight
|
|
7309
7424
|
};
|
|
7310
7425
|
this.state = {
|
|
7311
7426
|
sourceUrl: typeof options.image === "string" ? options.image : null,
|
|
@@ -7326,7 +7441,7 @@ var ImageEditorController = class {
|
|
|
7326
7441
|
activeLayerId: null,
|
|
7327
7442
|
activeTarget: "pixels",
|
|
7328
7443
|
outputOpacity: 1,
|
|
7329
|
-
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff" },
|
|
7444
|
+
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD },
|
|
7330
7445
|
fit: options.fit ?? "margin"
|
|
7331
7446
|
};
|
|
7332
7447
|
if (options.image instanceof Object && "width" in options.image) {
|
|
@@ -7459,6 +7574,7 @@ var ImageEditorController = class {
|
|
|
7459
7574
|
}
|
|
7460
7575
|
};
|
|
7461
7576
|
if (s.legacyOutputBlur !== void 0) entry.legacyOutputBlur = s.legacyOutputBlur;
|
|
7577
|
+
if (s.globalLight) entry.globalLight = { ...s.globalLight };
|
|
7462
7578
|
return entry;
|
|
7463
7579
|
}
|
|
7464
7580
|
restoreHistoryEntry(entry) {
|
|
@@ -7471,6 +7587,8 @@ var ImageEditorController = class {
|
|
|
7471
7587
|
s.outputOpacity = entry.outputOpacity;
|
|
7472
7588
|
if (entry.legacyOutputBlur !== void 0) s.legacyOutputBlur = entry.legacyOutputBlur;
|
|
7473
7589
|
else delete s.legacyOutputBlur;
|
|
7590
|
+
if (entry.globalLight) s.globalLight = { ...entry.globalLight };
|
|
7591
|
+
else delete s.globalLight;
|
|
7474
7592
|
s.selection = entry.selection;
|
|
7475
7593
|
if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
|
|
7476
7594
|
this.imageElement = entry.source.element;
|
|
@@ -7692,7 +7810,7 @@ var ImageEditorController = class {
|
|
|
7692
7810
|
}
|
|
7693
7811
|
compositorSources() {
|
|
7694
7812
|
const { width, height } = this.naturalSize;
|
|
7695
|
-
return { width, height, surfaceOf: (l) => this.surfaceOf(l), version: this.sourceVersion() };
|
|
7813
|
+
return { width, height, surfaceOf: (l) => this.surfaceOf(l), version: this.sourceVersion(), globalLight: this.state.globalLight };
|
|
7696
7814
|
}
|
|
7697
7815
|
sourceIds = /* @__PURE__ */ new WeakMap();
|
|
7698
7816
|
nextSourceId = 1;
|
|
@@ -8104,6 +8222,55 @@ var ImageEditorController = class {
|
|
|
8104
8222
|
this.commitLayers(next);
|
|
8105
8223
|
return findLayer(next, id).layer.filters ?? [];
|
|
8106
8224
|
}
|
|
8225
|
+
// --- Layer effects ---
|
|
8226
|
+
/** A layer's effects (an empty, enabled list when it has none). */
|
|
8227
|
+
getLayerEffects(id) {
|
|
8228
|
+
return layerEffectsOf(requireLayer(this.state.layers, id).layer);
|
|
8229
|
+
}
|
|
8230
|
+
/**
|
|
8231
|
+
* Adds an effect of `kind` with Photoshop's defaults and `params` applied (one undo step), at
|
|
8232
|
+
* `index` in the layer's effect list (default the end). Returns the new effect and the effects.
|
|
8233
|
+
*/
|
|
8234
|
+
addLayerEffect(id, kind, params = {}, index) {
|
|
8235
|
+
const res = addLayerEffect(this.state.layers, id, kind, params, index);
|
|
8236
|
+
this.commitLayers(res.layers);
|
|
8237
|
+
return { effect: res.effect, effects: this.getLayerEffects(id) };
|
|
8238
|
+
}
|
|
8239
|
+
/** Changes one effect (one undo step, or none with `commitToHistory = false` during a drag). Returns the effects. */
|
|
8240
|
+
updateLayerEffect(id, effectId, changes, commitToHistory = true) {
|
|
8241
|
+
this.commitLayers(updateLayerEffect(this.state.layers, id, effectId, changes), void 0, commitToHistory);
|
|
8242
|
+
return this.getLayerEffects(id);
|
|
8243
|
+
}
|
|
8244
|
+
/** Removes one effect (one undo step). Returns the remaining effects. */
|
|
8245
|
+
removeLayerEffect(id, effectId) {
|
|
8246
|
+
this.commitLayers(removeLayerEffect(this.state.layers, id, effectId));
|
|
8247
|
+
return this.getLayerEffects(id);
|
|
8248
|
+
}
|
|
8249
|
+
/** Turns a layer's effects on or off as a whole and, when given, sets Scale Effects (one undo step). */
|
|
8250
|
+
setLayerEffectsEnabled(id, enabled, scale, commitToHistory = true) {
|
|
8251
|
+
this.commitLayers(setLayerEffectsEnabled(this.state.layers, id, enabled, scale), void 0, commitToHistory);
|
|
8252
|
+
return this.getLayerEffects(id);
|
|
8253
|
+
}
|
|
8254
|
+
/** Replaces a layer's effects (one undo step; `null` removes them). Returns the effects. */
|
|
8255
|
+
setLayerEffects(id, effects, commitToHistory = true) {
|
|
8256
|
+
this.commitLayers(setLayerEffects(this.state.layers, id, effects), void 0, commitToHistory);
|
|
8257
|
+
return this.getLayerEffects(id);
|
|
8258
|
+
}
|
|
8259
|
+
/** The light effects with "use global light" follow. */
|
|
8260
|
+
getGlobalLight() {
|
|
8261
|
+
return { ...this.state.globalLight ?? DEFAULT_GLOBAL_LIGHT };
|
|
8262
|
+
}
|
|
8263
|
+
/** Sets the global light (one undo step). `angle` -360..360°, `altitude` 0..90°. Returns it. */
|
|
8264
|
+
setGlobalLight(light, commitToHistory = true) {
|
|
8265
|
+
const next = { ...this.getGlobalLight(), ...light };
|
|
8266
|
+
if (!(Number.isFinite(next.angle) && next.angle >= -360 && next.angle <= 360)) throw new LayerOperationError(`angle must be from -360 to 360 degrees, got ${next.angle}.`);
|
|
8267
|
+
if (!(Number.isFinite(next.altitude) && next.altitude >= 0 && next.altitude <= 90)) throw new LayerOperationError(`altitude must be from 0 to 90 degrees, got ${next.altitude}.`);
|
|
8268
|
+
if (commitToHistory) this.recordHistory();
|
|
8269
|
+
if (next.angle === DEFAULT_GLOBAL_LIGHT.angle && next.altitude === DEFAULT_GLOBAL_LIGHT.altitude) delete this.state.globalLight;
|
|
8270
|
+
else this.state.globalLight = next;
|
|
8271
|
+
this.notify();
|
|
8272
|
+
return this.getGlobalLight();
|
|
8273
|
+
}
|
|
8107
8274
|
/** Composites `layers` (in isolation) over `rect` into a surface, loading depth maps first. */
|
|
8108
8275
|
async compositeToSurface(layers, rect) {
|
|
8109
8276
|
const { width, height } = this.naturalSize;
|
|
@@ -8133,7 +8300,8 @@ var ImageEditorController = class {
|
|
|
8133
8300
|
}
|
|
8134
8301
|
/**
|
|
8135
8302
|
* Merge Down: composites a layer onto the pixel layer directly below it (which keeps its name,
|
|
8136
|
-
* opacity and blend; masks, fill and
|
|
8303
|
+
* opacity and blend; masks, fill, filters and both layers' effects are baked), as one undo step.
|
|
8304
|
+
* Returns the merged id.
|
|
8137
8305
|
*/
|
|
8138
8306
|
async mergeDown(id) {
|
|
8139
8307
|
const loc = requireLayer(this.state.layers, id);
|
|
@@ -8145,7 +8313,9 @@ var ImageEditorController = class {
|
|
|
8145
8313
|
if (!upper.visible) throw new LayerOperationError(`Layer "${upper.name || id}" is hidden; show it or delete it instead of merging.`);
|
|
8146
8314
|
requireUnlocked(upper, "all", "merge it");
|
|
8147
8315
|
requireUnlocked(lower, "pixels", "merge into it");
|
|
8148
|
-
|
|
8316
|
+
let rect = unionRect(this.paintBounds(lower), upper.type === "adjustment" ? null : this.paintBounds(upper)) ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
8317
|
+
const reach = Math.max(effectsMargin(lower.effects), upper.type === "adjustment" ? 0 : effectsMargin(upper.effects));
|
|
8318
|
+
if (reach) rect = { x: rect.x - reach, y: rect.y - reach, width: rect.width + 2 * reach, height: rect.height + 2 * reach };
|
|
8149
8319
|
const base = { ...lower, blend: "normal", opacity: 1 };
|
|
8150
8320
|
delete base.clipped;
|
|
8151
8321
|
const surface = await this.compositeToSurface([base, upper], rect);
|
|
@@ -8154,6 +8324,7 @@ var ImageEditorController = class {
|
|
|
8154
8324
|
delete merged.fillOpacity;
|
|
8155
8325
|
delete merged.filters;
|
|
8156
8326
|
delete merged.source;
|
|
8327
|
+
delete merged.effects;
|
|
8157
8328
|
let next = mapLayer(this.state.layers, id, () => null);
|
|
8158
8329
|
next = mapLayer(next, lower.id, () => merged);
|
|
8159
8330
|
this.commitLayers(next, () => {
|
|
@@ -8261,12 +8432,38 @@ var ImageEditorController = class {
|
|
|
8261
8432
|
const next = {
|
|
8262
8433
|
brush: options.brush ? normalizeBrush({ ...cur.brush, ...options.brush }) : cur.brush,
|
|
8263
8434
|
foreground: options.foreground !== void 0 ? toHex2(parseColor(options.foreground)) : cur.foreground,
|
|
8264
|
-
background: options.background !== void 0 ? toHex2(parseColor(options.background)) : cur.background
|
|
8435
|
+
background: options.background !== void 0 ? toHex2(parseColor(options.background)) : cur.background,
|
|
8436
|
+
mixer: options.mixer ? normalizeMixer({ ...cur.mixer, ...options.mixer }) : cur.mixer,
|
|
8437
|
+
mixerLoad: cur.mixerLoad
|
|
8265
8438
|
};
|
|
8266
8439
|
this.state.paint = next;
|
|
8267
8440
|
this.notify();
|
|
8268
8441
|
return next;
|
|
8269
8442
|
}
|
|
8443
|
+
/**
|
|
8444
|
+
* Loads the Mixer Brush's reservoir full with `color` (default the foreground); the picked-up
|
|
8445
|
+
* colour is kept. View state, no undo. Returns the brush load.
|
|
8446
|
+
*/
|
|
8447
|
+
loadMixerBrush(color) {
|
|
8448
|
+
const c = parseColor(color ?? this.state.paint.foreground);
|
|
8449
|
+
const load = loadMixer(this.state.paint.mixerLoad, [c[0] / 255, c[1] / 255, c[2] / 255]);
|
|
8450
|
+
this.state.paint = { ...this.state.paint, mixerLoad: load };
|
|
8451
|
+
this.notify();
|
|
8452
|
+
return load;
|
|
8453
|
+
}
|
|
8454
|
+
/** Cleans the Mixer Brush: empties the pickup well (the reservoir is kept). Returns the brush load. */
|
|
8455
|
+
cleanMixerBrush() {
|
|
8456
|
+
const load = cleanMixer(this.state.paint.mixerLoad);
|
|
8457
|
+
this.state.paint = { ...this.state.paint, mixerLoad: load };
|
|
8458
|
+
this.notify();
|
|
8459
|
+
return load;
|
|
8460
|
+
}
|
|
8461
|
+
/** Empties the Mixer Brush entirely (no reservoir, nothing picked up). Returns the brush load. */
|
|
8462
|
+
emptyMixerBrush() {
|
|
8463
|
+
this.state.paint = { ...this.state.paint, mixerLoad: EMPTY_MIXER_LOAD };
|
|
8464
|
+
this.notify();
|
|
8465
|
+
return EMPTY_MIXER_LOAD;
|
|
8466
|
+
}
|
|
8270
8467
|
/** D: black foreground, white background. */
|
|
8271
8468
|
resetPaintColors() {
|
|
8272
8469
|
this.setPaintOptions({ foreground: "#000000", background: "#ffffff" });
|
|
@@ -8311,7 +8508,11 @@ var ImageEditorController = class {
|
|
|
8311
8508
|
*/
|
|
8312
8509
|
beginStroke(init = {}) {
|
|
8313
8510
|
const tool = init.tool ?? "brush";
|
|
8511
|
+
if (tool !== "brush" && tool !== "eraser" && tool !== "mixer") throw new LayerOperationError(`Unknown paint tool "${String(tool)}"; use brush, eraser or mixer.`);
|
|
8314
8512
|
const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : "paint");
|
|
8513
|
+
if (tool === "mixer" && target.channel === "mask") {
|
|
8514
|
+
throw new LayerOperationError("The Mixer Brush paints colour on pixels, not on masks; target pixels (channel pixels), or paint the mask with the brush.");
|
|
8515
|
+
}
|
|
8315
8516
|
const brush = normalizeBrush({ ...this.state.paint.brush, ...init.brush ?? {} });
|
|
8316
8517
|
const color = parseColor(init.color ?? this.state.paint.foreground);
|
|
8317
8518
|
const background = parseColor(init.background ?? this.state.paint.background);
|
|
@@ -8329,8 +8530,26 @@ var ImageEditorController = class {
|
|
|
8329
8530
|
selection: this.state.selection?.bounds ? this.state.selection : null,
|
|
8330
8531
|
offset: target.offset
|
|
8331
8532
|
};
|
|
8533
|
+
if (tool === "mixer") return new PaintStrokeSession(this, target, this.mixerStroke(init, brush), apply);
|
|
8332
8534
|
return new PaintStrokeSession(this, target, new StrokeCoverage(brush), apply);
|
|
8333
8535
|
}
|
|
8536
|
+
/** A Mixer Brush stroke's start: the settings, and the load (refilled when asked or set to). */
|
|
8537
|
+
mixerStroke(init, brush) {
|
|
8538
|
+
const settings = normalizeMixer({ ...this.state.paint.mixer, ...init.mixer ?? {} });
|
|
8539
|
+
let load = this.state.paint.mixerLoad;
|
|
8540
|
+
if (init.color !== void 0 || settings.loadEachStroke) {
|
|
8541
|
+
const c = parseColor(init.color ?? this.state.paint.foreground);
|
|
8542
|
+
load = loadMixer(load, [c[0] / 255, c[1] / 255, c[2] / 255]);
|
|
8543
|
+
}
|
|
8544
|
+
return {
|
|
8545
|
+
create: (pixels) => new MixerStroke(brush, settings, load, pixels),
|
|
8546
|
+
end: (after) => {
|
|
8547
|
+
const next = settings.cleanEachStroke ? cleanMixer(after) : after;
|
|
8548
|
+
this.state.paint = { ...this.state.paint, mixerLoad: next };
|
|
8549
|
+
return next;
|
|
8550
|
+
}
|
|
8551
|
+
};
|
|
8552
|
+
}
|
|
8334
8553
|
/** @internal Shows a stroke's live surface (no history). */
|
|
8335
8554
|
_strokeUpdate(layerId, channel, surface, rects) {
|
|
8336
8555
|
this.state.layers = mapLayer(this.state.layers, layerId, (l) => this.withSurface(l, channel, surface));
|
|
@@ -8354,7 +8573,7 @@ var ImageEditorController = class {
|
|
|
8354
8573
|
}
|
|
8355
8574
|
/**
|
|
8356
8575
|
* Paints a whole stroke in one call (agents, replays): `points` in document pixels with optional
|
|
8357
|
-
* pressure, through the brush or
|
|
8576
|
+
* pressure, through the brush, eraser or Mixer Brush. One undo step. Returns the layer, channel, the changed
|
|
8358
8577
|
* document bounds and the number of dabs.
|
|
8359
8578
|
*/
|
|
8360
8579
|
paintStroke(init) {
|
|
@@ -9363,6 +9582,11 @@ var ImageEditorController = class {
|
|
|
9363
9582
|
return baked;
|
|
9364
9583
|
};
|
|
9365
9584
|
const prepare = async (layer) => {
|
|
9585
|
+
const p = await prepareLayer(layer);
|
|
9586
|
+
if (p?.blend === "pigment") add("The Pigment blend mode has no PSD equivalent and was written as Normal", layer.name);
|
|
9587
|
+
return p;
|
|
9588
|
+
};
|
|
9589
|
+
const prepareLayer = async (layer) => {
|
|
9366
9590
|
if (layer.type === "group") {
|
|
9367
9591
|
const children = [];
|
|
9368
9592
|
for (const c of layer.children) {
|
|
@@ -9534,6 +9758,9 @@ var ImageEditorController = class {
|
|
|
9534
9758
|
if (typeof data.legacyOutputBlur === "number" && data.legacyOutputBlur >= 0) s.legacyOutputBlur = data.legacyOutputBlur;
|
|
9535
9759
|
else delete s.legacyOutputBlur;
|
|
9536
9760
|
s.activeLayerId = data.activeLayerId && findLayer(s.layers, data.activeLayerId) ? data.activeLayerId : null;
|
|
9761
|
+
const light = data.globalLight;
|
|
9762
|
+
if (light && Number.isFinite(light.angle) && Number.isFinite(light.altitude)) s.globalLight = { angle: light.angle, altitude: Math.min(90, Math.max(0, light.altitude)) };
|
|
9763
|
+
else delete s.globalLight;
|
|
9537
9764
|
if (!this.imageElement && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
|
|
9538
9765
|
s.imageDimensions = { width: data.width, height: data.height };
|
|
9539
9766
|
}
|
|
@@ -9547,6 +9774,7 @@ var ImageEditorController = class {
|
|
|
9547
9774
|
s.crop = data.crop ? { ...data.crop } : null;
|
|
9548
9775
|
s.transform = { ...data.transform };
|
|
9549
9776
|
s.activeLayerId = null;
|
|
9777
|
+
delete s.globalLight;
|
|
9550
9778
|
}
|
|
9551
9779
|
s.activeTarget = "pixels";
|
|
9552
9780
|
this.viewKey = [];
|
|
@@ -9556,7 +9784,7 @@ var ImageEditorController = class {
|
|
|
9556
9784
|
serializeState() {
|
|
9557
9785
|
const { width, height } = this.naturalSize;
|
|
9558
9786
|
const view = legacyView(this.stack(), width, height, null);
|
|
9559
|
-
if (view.exact) {
|
|
9787
|
+
if (view.exact && !this.state.globalLight) {
|
|
9560
9788
|
return {
|
|
9561
9789
|
// States without adjustment layers keep schema 1, so they round-trip unchanged.
|
|
9562
9790
|
version: view.adjustments.layers?.length ? 2 : 1,
|
|
@@ -9583,6 +9811,7 @@ var ImageEditorController = class {
|
|
|
9583
9811
|
opacity: this.state.outputOpacity
|
|
9584
9812
|
};
|
|
9585
9813
|
if (this.state.legacyOutputBlur !== void 0) out.legacyOutputBlur = this.state.legacyOutputBlur;
|
|
9814
|
+
if (this.state.globalLight) out.globalLight = { ...this.state.globalLight };
|
|
9586
9815
|
return out;
|
|
9587
9816
|
}
|
|
9588
9817
|
};
|
|
@@ -10820,6 +11049,7 @@ function describeLayer(layer, activeId, boundsOf) {
|
|
|
10820
11049
|
clipped: Boolean(layer.clipped),
|
|
10821
11050
|
locks: layer.locks ? { ...layer.locks } : {},
|
|
10822
11051
|
mask: maskSummary(layer.mask),
|
|
11052
|
+
effects: layer.effects ? { enabled: layer.effects.enabled, scale: layer.effects.scale ?? 1, items: layer.effects.items.map((e) => ({ id: e.id, kind: e.kind, enabled: e.enabled })) } : null,
|
|
10823
11053
|
active: layer.id === activeId
|
|
10824
11054
|
};
|
|
10825
11055
|
switch (layer.type) {
|
|
@@ -10879,7 +11109,7 @@ function createImageLayerTools(controller) {
|
|
|
10879
11109
|
const listLayers = {
|
|
10880
11110
|
name: "list_layers",
|
|
10881
11111
|
title: "List image layers",
|
|
10882
|
-
description: "Returns the layer stack bottom to top as a tree: id, type (pixel, adjustment, group, annotation), name, document bounds (pixel layers), visible, opacity, fillOpacity, blend, clipped, locks, a mask summary, filters (pixel layers), kind and params (adjustment layers), children (groups) and which layer is active. Also returns the active layer id and target (pixels or mask) and the canvas size.",
|
|
11112
|
+
description: "Returns the layer stack bottom to top as a tree: id, type (pixel, adjustment, group, annotation), name, document bounds (pixel layers), visible, opacity, fillOpacity, blend, clipped, locks, a mask summary, an effects summary (master switch, scale, effect ids and kinds; details from list_layer_effects), filters (pixel layers), kind and params (adjustment layers), children (groups) and which layer is active. Also returns the active layer id and target (pixels or mask) and the canvas size.",
|
|
10883
11113
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
10884
11114
|
annotations: { title: "List image layers", ...READ2 },
|
|
10885
11115
|
handler: () => {
|
|
@@ -11315,9 +11545,491 @@ function createImageLayerTools(controller) {
|
|
|
11315
11545
|
];
|
|
11316
11546
|
}
|
|
11317
11547
|
|
|
11318
|
-
// src/image-editor/
|
|
11548
|
+
// src/image-editor/effect-tools.ts
|
|
11549
|
+
var READ3 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11319
11550
|
var WRITE3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
11551
|
+
var WRITE_IDEMPOTENT3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11320
11552
|
var DESTRUCTIVE2 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11553
|
+
var LAYER_ID3 = { type: "string", description: "Layer id from list_layers (a pixel, annotation or group layer)." };
|
|
11554
|
+
var EFFECT_ID = { type: "string", description: "Effect id from list_layer_effects or add_layer_effect." };
|
|
11555
|
+
var BLEND_IDS = BLEND_MODES.map((m) => m.id);
|
|
11556
|
+
var DEFS = {
|
|
11557
|
+
color: { type: "string", pattern: "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$", description: "Colour as #rrggbb." },
|
|
11558
|
+
contour: {
|
|
11559
|
+
type: "object",
|
|
11560
|
+
description: "A transfer curve over the effect coverage: points { input, output } from 0 to 1 (at least 2), sorted by input. Linear is [{0,0},{1,1}].",
|
|
11561
|
+
properties: {
|
|
11562
|
+
name: { type: "string", description: 'Display name (e.g. "Cone", "Gaussian").' },
|
|
11563
|
+
points: {
|
|
11564
|
+
type: "array",
|
|
11565
|
+
minItems: 2,
|
|
11566
|
+
description: "Curve points.",
|
|
11567
|
+
items: {
|
|
11568
|
+
type: "object",
|
|
11569
|
+
properties: {
|
|
11570
|
+
input: { type: "number", minimum: 0, maximum: 1, description: "Coverage in (0..1)." },
|
|
11571
|
+
output: { type: "number", minimum: 0, maximum: 1, description: "Coverage out (0..1)." },
|
|
11572
|
+
corner: { type: "boolean", description: "Sharp corner instead of a smooth point (default false)." }
|
|
11573
|
+
},
|
|
11574
|
+
required: ["input", "output"],
|
|
11575
|
+
additionalProperties: false
|
|
11576
|
+
}
|
|
11577
|
+
}
|
|
11578
|
+
},
|
|
11579
|
+
required: ["name", "points"],
|
|
11580
|
+
additionalProperties: false
|
|
11581
|
+
},
|
|
11582
|
+
gradient: {
|
|
11583
|
+
type: "object",
|
|
11584
|
+
description: "A gradient: colour stops, optional opacity stops and its layout.",
|
|
11585
|
+
properties: {
|
|
11586
|
+
stops: {
|
|
11587
|
+
type: "array",
|
|
11588
|
+
minItems: 1,
|
|
11589
|
+
description: "Colour stops { position 0..1, color #rrggbb }.",
|
|
11590
|
+
items: {
|
|
11591
|
+
type: "object",
|
|
11592
|
+
properties: { position: { type: "number", minimum: 0, maximum: 1, description: "0..1 along the gradient." }, color: { $ref: "#/$defs/color" } },
|
|
11593
|
+
required: ["position", "color"],
|
|
11594
|
+
additionalProperties: false
|
|
11595
|
+
}
|
|
11596
|
+
},
|
|
11597
|
+
opacityStops: {
|
|
11598
|
+
type: "array",
|
|
11599
|
+
description: "Opacity stops { position 0..1, opacity 0..1 } (default opaque).",
|
|
11600
|
+
items: {
|
|
11601
|
+
type: "object",
|
|
11602
|
+
properties: {
|
|
11603
|
+
position: { type: "number", minimum: 0, maximum: 1, description: "0..1 along the gradient." },
|
|
11604
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1." }
|
|
11605
|
+
},
|
|
11606
|
+
required: ["position", "opacity"],
|
|
11607
|
+
additionalProperties: false
|
|
11608
|
+
}
|
|
11609
|
+
},
|
|
11610
|
+
style: { type: "string", enum: ["linear", "radial", "angle", "reflected", "diamond"], description: "Geometry." },
|
|
11611
|
+
angle: { type: "number", minimum: -360, maximum: 360, description: "Degrees, 90 = bottom to top." },
|
|
11612
|
+
scale: { type: "number", minimum: 0.1, maximum: 1.5, description: "1 = 100 %." },
|
|
11613
|
+
reverse: { type: "boolean", description: "Swap the ends." },
|
|
11614
|
+
align: { type: "boolean", description: "Lay out over the layer bounds (true) or the canvas." },
|
|
11615
|
+
offset: {
|
|
11616
|
+
type: "object",
|
|
11617
|
+
description: "Centre offset as fractions of the bounds.",
|
|
11618
|
+
properties: { x: { type: "number", minimum: -1, maximum: 1, description: "Fraction of the width." }, y: { type: "number", minimum: -1, maximum: 1, description: "Fraction of the height." } },
|
|
11619
|
+
required: ["x", "y"],
|
|
11620
|
+
additionalProperties: false
|
|
11621
|
+
}
|
|
11622
|
+
},
|
|
11623
|
+
required: ["stops", "style", "angle", "scale", "reverse", "align", "offset"],
|
|
11624
|
+
additionalProperties: false
|
|
11625
|
+
},
|
|
11626
|
+
phase: {
|
|
11627
|
+
type: "object",
|
|
11628
|
+
description: "Pattern origin offset in px.",
|
|
11629
|
+
properties: { x: { type: "number", description: "px." }, y: { type: "number", description: "px." } },
|
|
11630
|
+
required: ["x", "y"],
|
|
11631
|
+
additionalProperties: false
|
|
11632
|
+
},
|
|
11633
|
+
fill: {
|
|
11634
|
+
description: "What a stroke or glow paints with. Glows take color or gradient only.",
|
|
11635
|
+
anyOf: [
|
|
11636
|
+
{
|
|
11637
|
+
type: "object",
|
|
11638
|
+
properties: { type: { const: "color", description: "Solid colour." }, color: { $ref: "#/$defs/color" } },
|
|
11639
|
+
required: ["type", "color"],
|
|
11640
|
+
additionalProperties: false
|
|
11641
|
+
},
|
|
11642
|
+
{
|
|
11643
|
+
type: "object",
|
|
11644
|
+
properties: { type: { const: "gradient", description: "Gradient." }, gradient: { $ref: "#/$defs/gradient" } },
|
|
11645
|
+
required: ["type", "gradient"],
|
|
11646
|
+
additionalProperties: false
|
|
11647
|
+
},
|
|
11648
|
+
{
|
|
11649
|
+
type: "object",
|
|
11650
|
+
properties: {
|
|
11651
|
+
type: { const: "pattern", description: "Pattern (strokes only)." },
|
|
11652
|
+
patternId: { type: "string", description: "Pattern id from list_layer_effects.patterns." },
|
|
11653
|
+
name: { type: "string", description: "Display name." },
|
|
11654
|
+
scale: { type: "number", minimum: 0.01, maximum: 10, description: "1 = 100 %." },
|
|
11655
|
+
angle: { type: "number", minimum: -360, maximum: 360, description: "Rotation in degrees (default 0)." },
|
|
11656
|
+
link: { type: "boolean", description: "Tile from the layer (default true) or the canvas origin." },
|
|
11657
|
+
phase: { $ref: "#/$defs/phase" }
|
|
11658
|
+
},
|
|
11659
|
+
required: ["type", "patternId", "scale"],
|
|
11660
|
+
additionalProperties: false
|
|
11661
|
+
}
|
|
11662
|
+
]
|
|
11663
|
+
}
|
|
11664
|
+
};
|
|
11665
|
+
var nullable = (schema, description) => ({ anyOf: [schema, { type: "null" }], description });
|
|
11666
|
+
function settingsSchema(forUpdate) {
|
|
11667
|
+
const opt = (schema, description) => forUpdate ? nullable(schema, `${description} null removes it.`) : { ...schema, description };
|
|
11668
|
+
return {
|
|
11669
|
+
type: "object",
|
|
11670
|
+
description: forUpdate ? "Settings to change (only these; nested objects are replaced whole). Which settings a kind takes: see add_layer_effect." : "Settings to change from Photoshop's defaults (all optional). Unknown settings for the kind are refused with the list of its settings.",
|
|
11671
|
+
properties: {
|
|
11672
|
+
enabled: { type: "boolean", description: "All kinds: false keeps the effect but hides it (default true)." },
|
|
11673
|
+
blend: { type: "string", enum: BLEND_IDS, description: "All but bevel-emboss: blend mode (shadows multiply, glows screen, others normal)." },
|
|
11674
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "All but bevel-emboss: 0..1 (shadows / glows 0.75, satin 0.5, others 1)." },
|
|
11675
|
+
color: { $ref: "#/$defs/color", description: "drop-shadow, inner-shadow, satin, color-overlay: #rrggbb." },
|
|
11676
|
+
angle: { type: "number", minimum: -360, maximum: 360, description: "Shadows, satin, bevel-emboss: light direction in degrees, counter-clockwise from 3 o'clock (default 120; satin 19)." },
|
|
11677
|
+
useGlobalLight: { type: "boolean", description: "Shadows, bevel-emboss: follow the document global light instead of angle (default true)." },
|
|
11678
|
+
distance: { type: "number", minimum: 0, maximum: 3e4, description: "Shadows, satin: offset in px (default 5; satin 11)." },
|
|
11679
|
+
spread: { type: "number", minimum: 0, maximum: 1, description: "drop-shadow, outer-glow: 0..1 of the size made solid (default 0)." },
|
|
11680
|
+
choke: { type: "number", minimum: 0, maximum: 1, description: "inner-shadow, inner-glow: 0..1 of the size made solid (default 0)." },
|
|
11681
|
+
size: { type: "number", minimum: 0, maximum: 250, description: "Shadows, glows, satin, bevel-emboss: blur / size in px (default 5; satin 14); stroke: width in px (default 3)." },
|
|
11682
|
+
contour: opt({ $ref: "#/$defs/contour" }, "Shadows, glows, satin: coverage contour (default linear)."),
|
|
11683
|
+
antiAlias: { type: "boolean", description: "Shadows, glows, satin: smooth the contour (default false; satin true)." },
|
|
11684
|
+
noise: { type: "number", minimum: 0, maximum: 1, description: "Shadows, glows: speckle 0..1 (default 0)." },
|
|
11685
|
+
knocksOut: { type: "boolean", description: "drop-shadow: the layer hides the shadow beneath its see-through fill (default true)." },
|
|
11686
|
+
fill: { $ref: "#/$defs/fill", description: "outer-glow, inner-glow (color or gradient), stroke (color, gradient or pattern)." },
|
|
11687
|
+
technique: { type: "string", enum: ["softer", "precise", "smooth", "chisel-hard", "chisel-soft"], description: "Glows: softer | precise (default softer); bevel-emboss: smooth | chisel-hard | chisel-soft (default smooth)." },
|
|
11688
|
+
range: { type: "number", minimum: 0.01, maximum: 1, description: "Glows: the part of the glow the contour spans (default 0.5)." },
|
|
11689
|
+
jitter: { type: "number", minimum: 0, maximum: 1, description: "Glows: gradient jitter 0..1 (stored, not rendered; default 0)." },
|
|
11690
|
+
source: { type: "string", enum: ["edge", "center"], description: "inner-glow: glow from the edge (default) or the centre." },
|
|
11691
|
+
style: { type: "string", enum: ["inner-bevel", "outer-bevel", "emboss", "pillow-emboss", "stroke-emboss"], description: "bevel-emboss: style (default inner-bevel)." },
|
|
11692
|
+
depth: { type: "number", minimum: 0, maximum: 10, description: "bevel-emboss: depth, 1 = 100 % (default 1)." },
|
|
11693
|
+
direction: { type: "string", enum: ["up", "down"], description: "bevel-emboss: raised (up, default) or sunk (down)." },
|
|
11694
|
+
soften: { type: "number", minimum: 0, maximum: 16, description: "bevel-emboss: blur of the height in px (default 0)." },
|
|
11695
|
+
altitude: { type: "number", minimum: 0, maximum: 90, description: "bevel-emboss: light altitude in degrees (default 30)." },
|
|
11696
|
+
glossContour: opt({ $ref: "#/$defs/contour" }, "bevel-emboss: gloss contour over the shading (default linear)."),
|
|
11697
|
+
highlightColor: { $ref: "#/$defs/color", description: "bevel-emboss: highlight colour (default #ffffff)." },
|
|
11698
|
+
highlightBlend: { type: "string", enum: BLEND_IDS, description: "bevel-emboss: highlight mode (default screen)." },
|
|
11699
|
+
highlightOpacity: { type: "number", minimum: 0, maximum: 1, description: "bevel-emboss: highlight opacity (default 0.75)." },
|
|
11700
|
+
shadowColor: { $ref: "#/$defs/color", description: "bevel-emboss: shadow colour (default #000000)." },
|
|
11701
|
+
shadowBlend: { type: "string", enum: BLEND_IDS, description: "bevel-emboss: shadow mode (default multiply)." },
|
|
11702
|
+
shadowOpacity: { type: "number", minimum: 0, maximum: 1, description: "bevel-emboss: shadow opacity (default 0.75)." },
|
|
11703
|
+
contourElement: opt(
|
|
11704
|
+
{
|
|
11705
|
+
type: "object",
|
|
11706
|
+
properties: {
|
|
11707
|
+
contour: { $ref: "#/$defs/contour" },
|
|
11708
|
+
range: { type: "number", minimum: 0, maximum: 1, description: "Stored range (default 0.5)." },
|
|
11709
|
+
antiAlias: { type: "boolean", description: "Smooth the contour." }
|
|
11710
|
+
},
|
|
11711
|
+
required: ["contour", "range", "antiAlias"],
|
|
11712
|
+
additionalProperties: false
|
|
11713
|
+
},
|
|
11714
|
+
"bevel-emboss: the Contour element, shaping the bevel's height profile (default off)."
|
|
11715
|
+
),
|
|
11716
|
+
texture: opt(
|
|
11717
|
+
{
|
|
11718
|
+
type: "object",
|
|
11719
|
+
properties: {
|
|
11720
|
+
patternId: { type: "string", description: "Pattern id from list_layer_effects.patterns." },
|
|
11721
|
+
name: { type: "string", description: "Display name." },
|
|
11722
|
+
scale: { type: "number", minimum: 0.01, maximum: 10, description: "1 = 100 %." },
|
|
11723
|
+
depth: { type: "number", minimum: -10, maximum: 10, description: "1 = +100 %; negative carves the pattern in." },
|
|
11724
|
+
invert: { type: "boolean", description: "Invert the pattern." },
|
|
11725
|
+
link: { type: "boolean", description: "Tile from the layer (default true)." },
|
|
11726
|
+
phase: { $ref: "#/$defs/phase" }
|
|
11727
|
+
},
|
|
11728
|
+
required: ["patternId", "scale", "depth", "invert"],
|
|
11729
|
+
additionalProperties: false
|
|
11730
|
+
},
|
|
11731
|
+
"bevel-emboss: the Texture element, a pattern's luminance as extra height (default off)."
|
|
11732
|
+
),
|
|
11733
|
+
invert: { type: "boolean", description: "satin: invert (default true)." },
|
|
11734
|
+
gradient: { $ref: "#/$defs/gradient", description: "gradient-overlay: the gradient (default black to white, 90\xB0)." },
|
|
11735
|
+
dither: { type: "boolean", description: "gradient-overlay: dither (stored; default false)." },
|
|
11736
|
+
patternId: { type: "string", description: "pattern-overlay: pattern id from list_layer_effects.patterns (default checker)." },
|
|
11737
|
+
name: { type: "string", description: "pattern-overlay: pattern display name." },
|
|
11738
|
+
scale: { type: "number", minimum: 0.01, maximum: 10, description: "pattern-overlay: 1 = 100 % (default 1)." },
|
|
11739
|
+
link: { type: "boolean", description: "pattern-overlay: tile from the layer (default true) or the canvas origin." },
|
|
11740
|
+
phase: { $ref: "#/$defs/phase", description: "pattern-overlay: origin offset in px (default 0, 0)." },
|
|
11741
|
+
position: { type: "string", enum: ["outside", "inside", "center"], description: "stroke: where the stroke sits on the edge (default outside)." }
|
|
11742
|
+
},
|
|
11743
|
+
additionalProperties: false
|
|
11744
|
+
};
|
|
11745
|
+
}
|
|
11746
|
+
function patternList() {
|
|
11747
|
+
return builtinPatternIds().map((id) => {
|
|
11748
|
+
const p = builtinPattern(id);
|
|
11749
|
+
return { id, name: p.name, width: p.width, height: p.height, builtin: true };
|
|
11750
|
+
});
|
|
11751
|
+
}
|
|
11752
|
+
function describeLayerEffects(effects) {
|
|
11753
|
+
return {
|
|
11754
|
+
enabled: effects.enabled,
|
|
11755
|
+
scale: effects.scale ?? 1,
|
|
11756
|
+
margin: effectsMargin(effects),
|
|
11757
|
+
fromPsd: Boolean(effects.psd),
|
|
11758
|
+
effects: effects.items.map((e) => ({ ...JSON.parse(JSON.stringify(e)), label: layerEffectLabel(e.kind) }))
|
|
11759
|
+
};
|
|
11760
|
+
}
|
|
11761
|
+
function createImageEffectTools(controller) {
|
|
11762
|
+
const layerArg = (id) => {
|
|
11763
|
+
if (typeof id !== "string" || !id) throw new LayerOperationError("layerId is required (a layer id from list_layers).");
|
|
11764
|
+
requireLayer(controller.getLayers(), id);
|
|
11765
|
+
return id;
|
|
11766
|
+
};
|
|
11767
|
+
const result = (layerId, extra = {}) => ({ layerId, ...extra, ...describeLayerEffects(controller.getLayerEffects(layerId)) });
|
|
11768
|
+
const list = {
|
|
11769
|
+
name: "list_layer_effects",
|
|
11770
|
+
title: "List layer effects",
|
|
11771
|
+
description: "Returns a layer's effects: the master switch (enabled), Scale Effects (scale, 1 = 100 %), margin (px the effects reach beyond the shape), whether they came from a PSD, and every effect with its id, kind, label and all settings. Also returns the document global light and the pattern ids pattern effects can use.",
|
|
11772
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID3 }, required: ["layerId"], additionalProperties: false },
|
|
11773
|
+
annotations: { title: "List layer effects", ...READ3 },
|
|
11774
|
+
handler: ({ layerId }) => result(layerArg(layerId), { globalLight: controller.getGlobalLight(), patterns: patternList(), kinds: [...LAYER_EFFECT_KINDS] })
|
|
11775
|
+
};
|
|
11776
|
+
const add = {
|
|
11777
|
+
name: "add_layer_effect",
|
|
11778
|
+
title: "Add a layer effect",
|
|
11779
|
+
description: "Adds a live effect to a pixel, annotation or group layer with Photoshop's defaults, changed by settings: drop-shadow, inner-shadow, outer-glow, inner-glow, bevel-emboss, satin, color-overlay, gradient-overlay, pattern-overlay or stroke. Several of one kind are allowed (the first listed is on top). One undo step. Returns the new effect's id and the layer's effects.",
|
|
11780
|
+
inputSchema: {
|
|
11781
|
+
type: "object",
|
|
11782
|
+
$defs: DEFS,
|
|
11783
|
+
properties: {
|
|
11784
|
+
layerId: LAYER_ID3,
|
|
11785
|
+
kind: { type: "string", enum: [...LAYER_EFFECT_KINDS], description: "Effect kind." },
|
|
11786
|
+
settings: settingsSchema(false),
|
|
11787
|
+
index: { type: "integer", minimum: 0, description: "Position in the layer's effect list (default the end)." }
|
|
11788
|
+
},
|
|
11789
|
+
required: ["layerId", "kind"],
|
|
11790
|
+
additionalProperties: false
|
|
11791
|
+
},
|
|
11792
|
+
annotations: { title: "Add a layer effect", ...WRITE3 },
|
|
11793
|
+
handler: ({ layerId, kind, settings, index }) => {
|
|
11794
|
+
const id = layerArg(layerId);
|
|
11795
|
+
if (!isLayerEffectKind(kind)) throw new LayerOperationError(`kind must be one of ${LAYER_EFFECT_KINDS.join(", ")}.`);
|
|
11796
|
+
const { effect } = controller.addLayerEffect(id, kind, settings ?? {}, index);
|
|
11797
|
+
return result(id, { effectId: effect.id });
|
|
11798
|
+
}
|
|
11799
|
+
};
|
|
11800
|
+
const update = {
|
|
11801
|
+
name: "update_layer_effect",
|
|
11802
|
+
title: "Change a layer effect",
|
|
11803
|
+
description: "Changes settings of one effect (only the given ones; fill, gradient, contour and texture objects are replaced whole, null removes an optional contour or texture). The kind cannot change. Validates everything before changing anything. One undo step. Returns the layer's effects.",
|
|
11804
|
+
inputSchema: {
|
|
11805
|
+
type: "object",
|
|
11806
|
+
$defs: DEFS,
|
|
11807
|
+
properties: { layerId: LAYER_ID3, effectId: EFFECT_ID, settings: settingsSchema(true) },
|
|
11808
|
+
required: ["layerId", "effectId", "settings"],
|
|
11809
|
+
additionalProperties: false
|
|
11810
|
+
},
|
|
11811
|
+
annotations: { title: "Change a layer effect", ...WRITE_IDEMPOTENT3 },
|
|
11812
|
+
handler: ({ layerId, effectId, settings }) => {
|
|
11813
|
+
const id = layerArg(layerId);
|
|
11814
|
+
const changes = settings ?? {};
|
|
11815
|
+
if (!Object.keys(changes).length) throw new LayerOperationError("Give at least one setting to change.");
|
|
11816
|
+
controller.updateLayerEffect(id, effectId, changes);
|
|
11817
|
+
return result(id);
|
|
11818
|
+
}
|
|
11819
|
+
};
|
|
11820
|
+
const remove = {
|
|
11821
|
+
name: "remove_layer_effect",
|
|
11822
|
+
title: "Remove a layer effect",
|
|
11823
|
+
description: "Removes one effect from a layer (to hide it but keep its settings, set enabled false with update_layer_effect instead). One undo step. Returns the layer's remaining effects.",
|
|
11824
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID3, effectId: EFFECT_ID }, required: ["layerId", "effectId"], additionalProperties: false },
|
|
11825
|
+
annotations: { title: "Remove a layer effect", ...DESTRUCTIVE2 },
|
|
11826
|
+
handler: ({ layerId, effectId }) => {
|
|
11827
|
+
const id = layerArg(layerId);
|
|
11828
|
+
controller.removeLayerEffect(id, effectId);
|
|
11829
|
+
return result(id);
|
|
11830
|
+
}
|
|
11831
|
+
};
|
|
11832
|
+
const setEnabled = {
|
|
11833
|
+
name: "set_layer_effects_enabled",
|
|
11834
|
+
title: "Switch layer effects on or off",
|
|
11835
|
+
description: "Turns all of a layer's effects on or off at once (the master switch; each effect keeps its own enabled flag) and optionally sets Scale Effects, which multiplies every size, distance and pattern scale. One undo step. Returns the layer's effects.",
|
|
11836
|
+
inputSchema: {
|
|
11837
|
+
type: "object",
|
|
11838
|
+
properties: {
|
|
11839
|
+
layerId: LAYER_ID3,
|
|
11840
|
+
enabled: { type: "boolean", description: "true renders the enabled effects; false hides them all." },
|
|
11841
|
+
scale: { type: "number", minimum: 0.01, maximum: 10, description: "Scale Effects, 1 = 100 % (unchanged when left out)." }
|
|
11842
|
+
},
|
|
11843
|
+
required: ["layerId", "enabled"],
|
|
11844
|
+
additionalProperties: false
|
|
11845
|
+
},
|
|
11846
|
+
annotations: { title: "Switch layer effects on or off", ...WRITE_IDEMPOTENT3 },
|
|
11847
|
+
handler: ({ layerId, enabled, scale }) => {
|
|
11848
|
+
const id = layerArg(layerId);
|
|
11849
|
+
controller.setLayerEffectsEnabled(id, enabled, scale);
|
|
11850
|
+
return result(id);
|
|
11851
|
+
}
|
|
11852
|
+
};
|
|
11853
|
+
const light = {
|
|
11854
|
+
name: "set_global_light",
|
|
11855
|
+
title: "Set the global light",
|
|
11856
|
+
description: "Sets the document global light that shadows and bevels with useGlobalLight follow (Photoshop default: angle 120\xB0, altitude 30\xB0). One undo step. Returns the light.",
|
|
11857
|
+
inputSchema: {
|
|
11858
|
+
type: "object",
|
|
11859
|
+
properties: {
|
|
11860
|
+
angle: { type: "number", minimum: -360, maximum: 360, description: "Degrees, counter-clockwise from 3 o'clock: where the light comes from (unchanged when left out)." },
|
|
11861
|
+
altitude: { type: "number", minimum: 0, maximum: 90, description: "Degrees above the canvas, for bevels (unchanged when left out)." }
|
|
11862
|
+
},
|
|
11863
|
+
additionalProperties: false
|
|
11864
|
+
},
|
|
11865
|
+
annotations: { title: "Set the global light", ...WRITE_IDEMPOTENT3 },
|
|
11866
|
+
handler: ({ angle, altitude }) => {
|
|
11867
|
+
if (angle === void 0 && altitude === void 0) throw new LayerOperationError("Give angle, altitude or both.");
|
|
11868
|
+
const next = {};
|
|
11869
|
+
if (angle !== void 0) next.angle = angle;
|
|
11870
|
+
if (altitude !== void 0) next.altitude = altitude;
|
|
11871
|
+
return { globalLight: controller.setGlobalLight(next) };
|
|
11872
|
+
}
|
|
11873
|
+
};
|
|
11874
|
+
return [list, add, update, remove, setEnabled, light];
|
|
11875
|
+
}
|
|
11876
|
+
|
|
11877
|
+
// src/image-editor/layers/paint-ui.ts
|
|
11878
|
+
var plain2 = { kind: "plain" };
|
|
11879
|
+
var BRUSH_OPTION_CONTROLS = [
|
|
11880
|
+
{ id: "size", label: "Size", section: "layer", min: 1, max: 500, step: 1, default: 20, decimals: 0, track: plain2, unit: "px" },
|
|
11881
|
+
{ id: "hardness", label: "Hardness", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
11882
|
+
{ id: "opacity", label: "Opacity", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
11883
|
+
{ id: "flow", label: "Flow", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
11884
|
+
{ id: "smoothing", label: "Smoothing", section: "layer", min: 0, max: 0.95, step: 0.01, default: 0, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
11885
|
+
];
|
|
11886
|
+
var MIXER_OPTION_CONTROLS = [
|
|
11887
|
+
{ id: "wet", label: "Wet", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
11888
|
+
{ id: "load", label: "Load", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
11889
|
+
{ id: "mix", label: "Mix", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
11890
|
+
];
|
|
11891
|
+
var MIXER_MIXING_OPTIONS = [
|
|
11892
|
+
{ id: "rgb", label: "RGB", title: "Mix colours as RGB averages" },
|
|
11893
|
+
{ id: "pigment", label: "Pigment", title: "Mix colours like paint: blue and yellow make green" }
|
|
11894
|
+
];
|
|
11895
|
+
var PAINT_TOOLS = [
|
|
11896
|
+
{ id: "move", label: "Move", shortcut: "V" },
|
|
11897
|
+
{ id: "brush", label: "Brush", shortcut: "B" },
|
|
11898
|
+
{ id: "mixer", label: "Mixer", shortcut: "Shift+B" },
|
|
11899
|
+
{ id: "eraser", label: "Eraser", shortcut: "E" }
|
|
11900
|
+
];
|
|
11901
|
+
var isPaintTool = (tool) => tool === "move" || tool === "brush" || tool === "mixer" || tool === "eraser";
|
|
11902
|
+
function rgbUnitToHex(c) {
|
|
11903
|
+
return `#${c.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
11904
|
+
}
|
|
11905
|
+
function sizeStep(size) {
|
|
11906
|
+
return size < 10 ? 1 : size < 50 ? 5 : size < 100 ? 10 : size < 200 ? 25 : size < 500 ? 50 : 100;
|
|
11907
|
+
}
|
|
11908
|
+
var isTyping = (t) => {
|
|
11909
|
+
const el = t;
|
|
11910
|
+
return Boolean(el && (el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT"));
|
|
11911
|
+
};
|
|
11912
|
+
function handlePaintShortcut(controller, e) {
|
|
11913
|
+
if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
|
|
11914
|
+
const brush = controller.getState().paint.brush;
|
|
11915
|
+
const set = (b) => controller.setPaintOptions({ brush: b });
|
|
11916
|
+
const key = e.key;
|
|
11917
|
+
switch (key.toLowerCase()) {
|
|
11918
|
+
case "b":
|
|
11919
|
+
controller.setTool(e.shiftKey || key === "B" ? "mixer" : "brush");
|
|
11920
|
+
return true;
|
|
11921
|
+
case "e":
|
|
11922
|
+
controller.setTool("eraser");
|
|
11923
|
+
return true;
|
|
11924
|
+
case "v":
|
|
11925
|
+
controller.setTool("move");
|
|
11926
|
+
return true;
|
|
11927
|
+
case "d":
|
|
11928
|
+
controller.resetPaintColors();
|
|
11929
|
+
return true;
|
|
11930
|
+
case "x":
|
|
11931
|
+
controller.swapPaintColors();
|
|
11932
|
+
return true;
|
|
11933
|
+
}
|
|
11934
|
+
if (key === "[" || key === "{") {
|
|
11935
|
+
if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
|
|
11936
|
+
else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
|
|
11937
|
+
return true;
|
|
11938
|
+
}
|
|
11939
|
+
if (key === "]" || key === "}") {
|
|
11940
|
+
if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
|
|
11941
|
+
else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
|
|
11942
|
+
return true;
|
|
11943
|
+
}
|
|
11944
|
+
if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
|
|
11945
|
+
set({ opacity: key === "0" ? 1 : Number(key) / 10 });
|
|
11946
|
+
return true;
|
|
11947
|
+
}
|
|
11948
|
+
return false;
|
|
11949
|
+
}
|
|
11950
|
+
function pointerPressure(e) {
|
|
11951
|
+
if (e.pointerType === "pen" && typeof e.pressure === "number" && e.pressure > 0) return Math.min(1, e.pressure);
|
|
11952
|
+
return 1;
|
|
11953
|
+
}
|
|
11954
|
+
var PaintGesture = class {
|
|
11955
|
+
constructor(controller) {
|
|
11956
|
+
this.controller = controller;
|
|
11957
|
+
}
|
|
11958
|
+
controller;
|
|
11959
|
+
stroke = null;
|
|
11960
|
+
move = null;
|
|
11961
|
+
/** The last refusal (e.g. "layer is locked"), for the editor to show. */
|
|
11962
|
+
error = null;
|
|
11963
|
+
get active() {
|
|
11964
|
+
return Boolean(this.stroke || this.move);
|
|
11965
|
+
}
|
|
11966
|
+
down(sample) {
|
|
11967
|
+
this.error = null;
|
|
11968
|
+
const tool = this.controller.getState().activeTool;
|
|
11969
|
+
try {
|
|
11970
|
+
if (tool === "move") {
|
|
11971
|
+
const layer = this.controller.getActiveLayer();
|
|
11972
|
+
if (!layer) throw new Error("Select a layer to move in the Layers panel.");
|
|
11973
|
+
if (layer.type !== "pixel") throw new Error(`Only pixel layers move; "${layer.name}" is a ${layer.type} layer.`);
|
|
11974
|
+
if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
|
|
11975
|
+
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin: { ...layer.offset }, moved: false };
|
|
11976
|
+
return null;
|
|
11977
|
+
}
|
|
11978
|
+
if (tool === "brush" || tool === "eraser" || tool === "mixer") {
|
|
11979
|
+
this.stroke = this.controller.beginStroke({ tool });
|
|
11980
|
+
this.stroke.push([sample]);
|
|
11981
|
+
return null;
|
|
11982
|
+
}
|
|
11983
|
+
} catch (err) {
|
|
11984
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
11985
|
+
this.stroke = null;
|
|
11986
|
+
this.move = null;
|
|
11987
|
+
}
|
|
11988
|
+
return this.error;
|
|
11989
|
+
}
|
|
11990
|
+
/** Feeds pointer samples (all coalesced events of one move, in order). */
|
|
11991
|
+
moveTo(samples) {
|
|
11992
|
+
if (!samples.length) return;
|
|
11993
|
+
if (this.stroke) {
|
|
11994
|
+
this.stroke.push(samples);
|
|
11995
|
+
return;
|
|
11996
|
+
}
|
|
11997
|
+
const m = this.move;
|
|
11998
|
+
if (m) {
|
|
11999
|
+
const last = samples[samples.length - 1];
|
|
12000
|
+
const x = m.origin.x + Math.round(last.x - m.start.x);
|
|
12001
|
+
const y = m.origin.y + Math.round(last.y - m.start.y);
|
|
12002
|
+
try {
|
|
12003
|
+
this.controller.offsetLayer(m.id, { x, y }, !m.moved);
|
|
12004
|
+
m.moved = true;
|
|
12005
|
+
} catch (err) {
|
|
12006
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
12007
|
+
this.move = null;
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
12010
|
+
}
|
|
12011
|
+
up() {
|
|
12012
|
+
if (this.stroke) {
|
|
12013
|
+
try {
|
|
12014
|
+
this.stroke.end();
|
|
12015
|
+
} catch (err) {
|
|
12016
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
12017
|
+
}
|
|
12018
|
+
}
|
|
12019
|
+
this.stroke = null;
|
|
12020
|
+
this.move = null;
|
|
12021
|
+
}
|
|
12022
|
+
cancel() {
|
|
12023
|
+
this.stroke?.cancel();
|
|
12024
|
+
this.stroke = null;
|
|
12025
|
+
this.move = null;
|
|
12026
|
+
}
|
|
12027
|
+
};
|
|
12028
|
+
|
|
12029
|
+
// src/image-editor/paint-tools.ts
|
|
12030
|
+
var READ4 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
12031
|
+
var WRITE4 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
12032
|
+
var DESTRUCTIVE3 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11321
12033
|
var TARGET = {
|
|
11322
12034
|
layerId: { type: "string", description: "Layer to paint (default the active layer; see list_layers)." },
|
|
11323
12035
|
channel: { type: "string", enum: ["pixels", "mask"], description: "pixels (default for a pixel layer) or the layer's mask: black hides, white reveals." }
|
|
@@ -11336,10 +12048,30 @@ var BRUSH = {
|
|
|
11336
12048
|
smoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Point smoothing 0..0.95 (default 0)." },
|
|
11337
12049
|
pressureSize: { type: "boolean", description: "Point pressure scales the size (default true)." },
|
|
11338
12050
|
pressureOpacity: { type: "boolean", description: "Point pressure scales the opacity (default false)." },
|
|
11339
|
-
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "How paint combines with the layer (default normal)." }
|
|
12051
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "How paint combines with the layer (default normal). pigment mixes it in like real paint (blue into yellow gives green) instead of laying it over." }
|
|
11340
12052
|
},
|
|
11341
12053
|
additionalProperties: false
|
|
11342
12054
|
};
|
|
12055
|
+
var MIXER = {
|
|
12056
|
+
type: "object",
|
|
12057
|
+
description: "Mixer Brush settings (tool mixer only); missing fields use the current mixer options (wet 0.5, load 0.5, mix 0.5, loadEachStroke true, cleanEachStroke true, mixing rgb).",
|
|
12058
|
+
properties: {
|
|
12059
|
+
wet: { type: "number", minimum: 0, maximum: 1, description: "How much canvas colour each dab picks up, 0 (dry: paints only the reservoir) .. 1 (default 0.5)." },
|
|
12060
|
+
load: { type: "number", minimum: 0, maximum: 1, description: "How much paint the reservoir holds, 0 .. 1 (default 0.5); below 1 it drains per dab, so long strokes run dry." },
|
|
12061
|
+
mix: { type: "number", minimum: 0, maximum: 1, description: "How far each dab's paint moves from the reservoir towards the picked-up colour, 0 .. 1 (default 0.5)." },
|
|
12062
|
+
loadEachStroke: { type: "boolean", description: "Refill the reservoir with `color` (or the foreground) before the stroke (default true). Off, the brush keeps what is left of its last load." },
|
|
12063
|
+
cleanEachStroke: { type: "boolean", description: "Empty the pickup well after the stroke (default true). Off, the next stroke starts with the colour this one picked up." },
|
|
12064
|
+
mixing: { type: "string", enum: [...MIXING_MODELS], description: "rgb (default): RGB averages. pigment: mixes like paint (Kubelka-Munk), so blue dragged through yellow makes green." }
|
|
12065
|
+
},
|
|
12066
|
+
additionalProperties: false
|
|
12067
|
+
};
|
|
12068
|
+
function mixerLoadResult(load) {
|
|
12069
|
+
return {
|
|
12070
|
+
reservoir: load.reservoir ? rgbUnitToHex(load.reservoir) : null,
|
|
12071
|
+
amount: Math.round(load.amount * 1e3) / 1e3,
|
|
12072
|
+
pickup: load.pickup ? rgbUnitToHex(load.pickup) : null
|
|
12073
|
+
};
|
|
12074
|
+
}
|
|
11343
12075
|
function readTarget(args) {
|
|
11344
12076
|
return { layerId: args.layerId, channel: args.channel };
|
|
11345
12077
|
}
|
|
@@ -11347,11 +12079,11 @@ function createImagePaintTools(controller) {
|
|
|
11347
12079
|
const paintStroke = {
|
|
11348
12080
|
name: "paint_stroke",
|
|
11349
12081
|
title: "Paint a brush stroke",
|
|
11350
|
-
description: "Paints one stroke with the brush (or removes pixels with the eraser
|
|
12082
|
+
description: "Paints one stroke with the brush, smears and blends paint with the Mixer Brush (tool mixer: its reservoir mixes with colour picked up from the layer under each dab, in RGB or pigment mixing), or removes pixels with the eraser, through points in document pixels (optional pressure 0..1 each) on a pixel layer or a layer mask (the mixer paints pixels only). The selection limits the paint; a transparency-locked layer keeps its alpha (the eraser then paints the background colour). One undo step. Returns the layerId, channel, changed bounds and dab count, and for the mixer its mixerLoad after the stroke ({reservoir, amount, pickup}).",
|
|
11351
12083
|
inputSchema: {
|
|
11352
12084
|
type: "object",
|
|
11353
12085
|
properties: {
|
|
11354
|
-
tool: { type: "string", enum: ["brush", "eraser"], description: "brush (default) or
|
|
12086
|
+
tool: { type: "string", enum: ["brush", "eraser", "mixer"], description: "brush (default), eraser, or mixer (the Mixer Brush: picks up and blends the layer colour under each dab)." },
|
|
11355
12087
|
points: {
|
|
11356
12088
|
type: "array",
|
|
11357
12089
|
minItems: 1,
|
|
@@ -11368,21 +12100,66 @@ function createImagePaintTools(controller) {
|
|
|
11368
12100
|
additionalProperties: false
|
|
11369
12101
|
}
|
|
11370
12102
|
},
|
|
11371
|
-
color: {
|
|
12103
|
+
color: {
|
|
12104
|
+
type: "string",
|
|
12105
|
+
description: "Paint colour #rrggbb (default the foreground). On a mask its grey value: #000000 hides, #ffffff reveals. For the mixer, the colour its reservoir is loaded with before the stroke (given, it loads even with loadEachStroke false)."
|
|
12106
|
+
},
|
|
11372
12107
|
brush: BRUSH,
|
|
12108
|
+
mixer: MIXER,
|
|
11373
12109
|
...TARGET
|
|
11374
12110
|
},
|
|
11375
12111
|
required: ["points"],
|
|
11376
12112
|
additionalProperties: false
|
|
11377
12113
|
},
|
|
11378
|
-
annotations: { title: "Paint a brush stroke", ...
|
|
11379
|
-
handler: (args) =>
|
|
11380
|
-
tool
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
12114
|
+
annotations: { title: "Paint a brush stroke", ...WRITE4 },
|
|
12115
|
+
handler: (args) => {
|
|
12116
|
+
const tool = args.tool;
|
|
12117
|
+
if (args.mixer !== void 0 && tool !== "mixer") throw new LayerOperationError('mixer settings apply to the Mixer Brush only; pass tool "mixer", or drop mixer.');
|
|
12118
|
+
const result = controller.paintStroke({
|
|
12119
|
+
tool,
|
|
12120
|
+
points: args.points,
|
|
12121
|
+
color: args.color,
|
|
12122
|
+
brush: args.brush,
|
|
12123
|
+
mixer: args.mixer,
|
|
12124
|
+
...readTarget(args)
|
|
12125
|
+
});
|
|
12126
|
+
return result.mixerLoad ? { ...result, mixerLoad: mixerLoadResult(result.mixerLoad) } : result;
|
|
12127
|
+
}
|
|
12128
|
+
};
|
|
12129
|
+
const getMixer = {
|
|
12130
|
+
name: "get_mixer_brush",
|
|
12131
|
+
title: "Get the Mixer Brush",
|
|
12132
|
+
description: "Reads the Mixer Brush: its settings (wet, load, mix, loadEachStroke, cleanEachStroke, mixing) and what it carries now (reservoir #rrggbb or null, amount 0..1 left, pickup #rrggbb or null). Call it before a paint_stroke with tool mixer and loadEachStroke false, to know the brush's starting paint.",
|
|
12133
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
12134
|
+
annotations: { title: "Get the Mixer Brush", ...READ4 },
|
|
12135
|
+
handler: () => {
|
|
12136
|
+
const { mixer, mixerLoad } = controller.getState().paint;
|
|
12137
|
+
return { settings: { ...mixer }, load: mixerLoadResult(mixerLoad) };
|
|
12138
|
+
}
|
|
12139
|
+
};
|
|
12140
|
+
const loadMixerTool = {
|
|
12141
|
+
name: "load_mixer_brush",
|
|
12142
|
+
title: "Load the Mixer Brush",
|
|
12143
|
+
description: "Fills the Mixer Brush reservoir with a colour (amount 1); the picked-up colour is kept. View state, not an undo step. Use it with loadEachStroke false to carry one load across several paint_stroke calls. Returns the load {reservoir, amount, pickup}.",
|
|
12144
|
+
inputSchema: {
|
|
12145
|
+
type: "object",
|
|
12146
|
+
properties: { color: { type: "string", description: "Paint colour #rrggbb (default the foreground)." } },
|
|
12147
|
+
additionalProperties: false
|
|
12148
|
+
},
|
|
12149
|
+
annotations: { title: "Load the Mixer Brush", ...WRITE4, idempotentHint: true },
|
|
12150
|
+
handler: (args) => mixerLoadResult(controller.loadMixerBrush(args.color))
|
|
12151
|
+
};
|
|
12152
|
+
const cleanMixerTool = {
|
|
12153
|
+
name: "clean_mixer_brush",
|
|
12154
|
+
title: "Clean the Mixer Brush",
|
|
12155
|
+
description: "Cleans the Mixer Brush: empties the pickup well so the next stroke starts without canvas colour; with emptyReservoir also drops the loaded paint (the brush then only smears). View state, not an undo step. Returns the load {reservoir, amount, pickup}.",
|
|
12156
|
+
inputSchema: {
|
|
12157
|
+
type: "object",
|
|
12158
|
+
properties: { emptyReservoir: { type: "boolean", description: "Also drop the reservoir (default false)." } },
|
|
12159
|
+
additionalProperties: false
|
|
12160
|
+
},
|
|
12161
|
+
annotations: { title: "Clean the Mixer Brush", ...WRITE4, idempotentHint: true },
|
|
12162
|
+
handler: (args) => mixerLoadResult(args.emptyReservoir === true ? controller.emptyMixerBrush() : controller.cleanMixerBrush())
|
|
11386
12163
|
};
|
|
11387
12164
|
const fill = {
|
|
11388
12165
|
name: "fill_pixels",
|
|
@@ -11393,13 +12170,13 @@ function createImagePaintTools(controller) {
|
|
|
11393
12170
|
properties: {
|
|
11394
12171
|
color: { type: "string", description: "Fill colour #rrggbb or #rrggbbaa (default the foreground)." },
|
|
11395
12172
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Fill opacity 0..1 (default 1)." },
|
|
11396
|
-
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal)." },
|
|
12173
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal). pigment mixes the colour into the pixels like paint, opacity being the share of new paint." },
|
|
11397
12174
|
useSelection: { type: "boolean", description: "Limit to the selection when there is one (default true)." },
|
|
11398
12175
|
...TARGET
|
|
11399
12176
|
},
|
|
11400
12177
|
additionalProperties: false
|
|
11401
12178
|
},
|
|
11402
|
-
annotations: { title: "Fill pixels", ...
|
|
12179
|
+
annotations: { title: "Fill pixels", ...WRITE4 },
|
|
11403
12180
|
handler: (args) => controller.fillPixels({
|
|
11404
12181
|
color: args.color,
|
|
11405
12182
|
opacity: args.opacity,
|
|
@@ -11421,15 +12198,15 @@ function createImagePaintTools(controller) {
|
|
|
11421
12198
|
},
|
|
11422
12199
|
additionalProperties: false
|
|
11423
12200
|
},
|
|
11424
|
-
annotations: { title: "Clear pixels", ...
|
|
12201
|
+
annotations: { title: "Clear pixels", ...DESTRUCTIVE3 },
|
|
11425
12202
|
handler: (args) => controller.clearPixels({ opacity: args.opacity, useSelection: args.useSelection, ...readTarget(args) })
|
|
11426
12203
|
};
|
|
11427
|
-
return [paintStroke, fill, clear];
|
|
12204
|
+
return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool];
|
|
11428
12205
|
}
|
|
11429
12206
|
|
|
11430
12207
|
// src/image-editor/psd-tools.ts
|
|
11431
|
-
var
|
|
11432
|
-
var
|
|
12208
|
+
var READ5 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
12209
|
+
var DESTRUCTIVE4 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11433
12210
|
var DATA = {
|
|
11434
12211
|
type: "string",
|
|
11435
12212
|
minLength: 1,
|
|
@@ -11449,7 +12226,7 @@ function createImagePsdTools(controller) {
|
|
|
11449
12226
|
title: "Inspect a PSD",
|
|
11450
12227
|
description: "Reads a PSD / PSB without opening it: size, bit depth, colour mode, resolution, and every layer (bottom first, a group before its children) with its PSD kind, the layer type it would become and its status (mapped, approximated or dropped, with notes), plus the import warnings. Changes nothing. Call before import_psd to see what would be lost.",
|
|
11451
12228
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
11452
|
-
annotations: { title: "Inspect a PSD", ...
|
|
12229
|
+
annotations: { title: "Inspect a PSD", ...READ5 },
|
|
11453
12230
|
handler: ({ data }) => {
|
|
11454
12231
|
const res = importPsd(psdBytesFromBase64(data), { skipPixels: true });
|
|
11455
12232
|
return {
|
|
@@ -11467,7 +12244,7 @@ function createImagePsdTools(controller) {
|
|
|
11467
12244
|
title: "Open a PSD as the document",
|
|
11468
12245
|
description: "Opens a PSD / PSB as the document, replacing the current layers, crop and rotation (one undo step; undo brings the previous document back). Pixel, group and adjustment layers, masks, blend modes, opacity, fill, clipping, visibility and locks map directly; type, smart-object, fill and shape layers come in as their pixels; unsupported adjustments are left out. Returns the new layer tree with ids, the size and the warnings (each names the affected layers).",
|
|
11469
12246
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
11470
|
-
annotations: { title: "Open a PSD as the document", ...
|
|
12247
|
+
annotations: { title: "Open a PSD as the document", ...DESTRUCTIVE4 },
|
|
11471
12248
|
handler: ({ data }) => {
|
|
11472
12249
|
const res = controller.loadPsd(psdBytesFromBase64(data));
|
|
11473
12250
|
const s = controller.getState();
|
|
@@ -11486,7 +12263,7 @@ function createImagePsdTools(controller) {
|
|
|
11486
12263
|
title: "Export the document as a PSD",
|
|
11487
12264
|
description: "Writes the document as an 8-bit RGB PSD (a PSB above 30000 px) with real layers, groups, adjustment layers and masks, in the source frame (crop and rotation are not applied). Layer filters and depth masks are baked, annotation layers are rasterized (in a browser; elsewhere left out), and the merged image is the editor's composite. Changes nothing. Returns the file as base64, its size in bytes, whether it is a PSB, and warnings for everything that was changed or left out.",
|
|
11488
12265
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
11489
|
-
annotations: { title: "Export the document as a PSD", ...
|
|
12266
|
+
annotations: { title: "Export the document as a PSD", ...READ5 },
|
|
11490
12267
|
handler: async () => {
|
|
11491
12268
|
const res = await controller.toPsd();
|
|
11492
12269
|
return {
|
|
@@ -11740,6 +12517,7 @@ function createImageEditorToolProvider(controller, options = {}) {
|
|
|
11740
12517
|
...createImageSelectionTools(controller),
|
|
11741
12518
|
...createImageEditTools(controller),
|
|
11742
12519
|
...createImageLayerTools(controller),
|
|
12520
|
+
...createImageEffectTools(controller),
|
|
11743
12521
|
...createImagePaintTools(controller),
|
|
11744
12522
|
...createPigmentTools(),
|
|
11745
12523
|
...createImagePsdTools(controller)
|
|
@@ -12183,145 +12961,6 @@ async function runLayerMenuAction(controller, action) {
|
|
|
12183
12961
|
}
|
|
12184
12962
|
}
|
|
12185
12963
|
|
|
12186
|
-
// src/image-editor/layers/paint-ui.ts
|
|
12187
|
-
var plain2 = { kind: "plain" };
|
|
12188
|
-
var BRUSH_OPTION_CONTROLS = [
|
|
12189
|
-
{ id: "size", label: "Size", section: "layer", min: 1, max: 500, step: 1, default: 20, decimals: 0, track: plain2, unit: "px" },
|
|
12190
|
-
{ id: "hardness", label: "Hardness", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12191
|
-
{ id: "opacity", label: "Opacity", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12192
|
-
{ id: "flow", label: "Flow", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12193
|
-
{ id: "smoothing", label: "Smoothing", section: "layer", min: 0, max: 0.95, step: 0.01, default: 0, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
12194
|
-
];
|
|
12195
|
-
var PAINT_TOOLS = [
|
|
12196
|
-
{ id: "move", label: "Move", shortcut: "V" },
|
|
12197
|
-
{ id: "brush", label: "Brush", shortcut: "B" },
|
|
12198
|
-
{ id: "eraser", label: "Eraser", shortcut: "E" }
|
|
12199
|
-
];
|
|
12200
|
-
var isPaintTool = (tool) => tool === "move" || tool === "brush" || tool === "eraser";
|
|
12201
|
-
function sizeStep(size) {
|
|
12202
|
-
return size < 10 ? 1 : size < 50 ? 5 : size < 100 ? 10 : size < 200 ? 25 : size < 500 ? 50 : 100;
|
|
12203
|
-
}
|
|
12204
|
-
var isTyping = (t) => {
|
|
12205
|
-
const el = t;
|
|
12206
|
-
return Boolean(el && (el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT"));
|
|
12207
|
-
};
|
|
12208
|
-
function handlePaintShortcut(controller, e) {
|
|
12209
|
-
if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
|
|
12210
|
-
const brush = controller.getState().paint.brush;
|
|
12211
|
-
const set = (b) => controller.setPaintOptions({ brush: b });
|
|
12212
|
-
const key = e.key;
|
|
12213
|
-
switch (key.toLowerCase()) {
|
|
12214
|
-
case "b":
|
|
12215
|
-
controller.setTool("brush");
|
|
12216
|
-
return true;
|
|
12217
|
-
case "e":
|
|
12218
|
-
controller.setTool("eraser");
|
|
12219
|
-
return true;
|
|
12220
|
-
case "v":
|
|
12221
|
-
controller.setTool("move");
|
|
12222
|
-
return true;
|
|
12223
|
-
case "d":
|
|
12224
|
-
controller.resetPaintColors();
|
|
12225
|
-
return true;
|
|
12226
|
-
case "x":
|
|
12227
|
-
controller.swapPaintColors();
|
|
12228
|
-
return true;
|
|
12229
|
-
}
|
|
12230
|
-
if (key === "[" || key === "{") {
|
|
12231
|
-
if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
|
|
12232
|
-
else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
|
|
12233
|
-
return true;
|
|
12234
|
-
}
|
|
12235
|
-
if (key === "]" || key === "}") {
|
|
12236
|
-
if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
|
|
12237
|
-
else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
|
|
12238
|
-
return true;
|
|
12239
|
-
}
|
|
12240
|
-
if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
|
|
12241
|
-
set({ opacity: key === "0" ? 1 : Number(key) / 10 });
|
|
12242
|
-
return true;
|
|
12243
|
-
}
|
|
12244
|
-
return false;
|
|
12245
|
-
}
|
|
12246
|
-
function pointerPressure(e) {
|
|
12247
|
-
if (e.pointerType === "pen" && typeof e.pressure === "number" && e.pressure > 0) return Math.min(1, e.pressure);
|
|
12248
|
-
return 1;
|
|
12249
|
-
}
|
|
12250
|
-
var PaintGesture = class {
|
|
12251
|
-
constructor(controller) {
|
|
12252
|
-
this.controller = controller;
|
|
12253
|
-
}
|
|
12254
|
-
controller;
|
|
12255
|
-
stroke = null;
|
|
12256
|
-
move = null;
|
|
12257
|
-
/** The last refusal (e.g. "layer is locked"), for the editor to show. */
|
|
12258
|
-
error = null;
|
|
12259
|
-
get active() {
|
|
12260
|
-
return Boolean(this.stroke || this.move);
|
|
12261
|
-
}
|
|
12262
|
-
down(sample) {
|
|
12263
|
-
this.error = null;
|
|
12264
|
-
const tool = this.controller.getState().activeTool;
|
|
12265
|
-
try {
|
|
12266
|
-
if (tool === "move") {
|
|
12267
|
-
const layer = this.controller.getActiveLayer();
|
|
12268
|
-
if (!layer) throw new Error("Select a layer to move in the Layers panel.");
|
|
12269
|
-
if (layer.type !== "pixel") throw new Error(`Only pixel layers move; "${layer.name}" is a ${layer.type} layer.`);
|
|
12270
|
-
if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
|
|
12271
|
-
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin: { ...layer.offset }, moved: false };
|
|
12272
|
-
return null;
|
|
12273
|
-
}
|
|
12274
|
-
if (tool === "brush" || tool === "eraser") {
|
|
12275
|
-
this.stroke = this.controller.beginStroke({ tool });
|
|
12276
|
-
this.stroke.push([sample]);
|
|
12277
|
-
return null;
|
|
12278
|
-
}
|
|
12279
|
-
} catch (err) {
|
|
12280
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12281
|
-
this.stroke = null;
|
|
12282
|
-
this.move = null;
|
|
12283
|
-
}
|
|
12284
|
-
return this.error;
|
|
12285
|
-
}
|
|
12286
|
-
/** Feeds pointer samples (all coalesced events of one move, in order). */
|
|
12287
|
-
moveTo(samples) {
|
|
12288
|
-
if (!samples.length) return;
|
|
12289
|
-
if (this.stroke) {
|
|
12290
|
-
this.stroke.push(samples);
|
|
12291
|
-
return;
|
|
12292
|
-
}
|
|
12293
|
-
const m = this.move;
|
|
12294
|
-
if (m) {
|
|
12295
|
-
const last = samples[samples.length - 1];
|
|
12296
|
-
const x = m.origin.x + Math.round(last.x - m.start.x);
|
|
12297
|
-
const y = m.origin.y + Math.round(last.y - m.start.y);
|
|
12298
|
-
try {
|
|
12299
|
-
this.controller.offsetLayer(m.id, { x, y }, !m.moved);
|
|
12300
|
-
m.moved = true;
|
|
12301
|
-
} catch (err) {
|
|
12302
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12303
|
-
this.move = null;
|
|
12304
|
-
}
|
|
12305
|
-
}
|
|
12306
|
-
}
|
|
12307
|
-
up() {
|
|
12308
|
-
if (this.stroke) {
|
|
12309
|
-
try {
|
|
12310
|
-
this.stroke.end();
|
|
12311
|
-
} catch (err) {
|
|
12312
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12313
|
-
}
|
|
12314
|
-
}
|
|
12315
|
-
this.stroke = null;
|
|
12316
|
-
this.move = null;
|
|
12317
|
-
}
|
|
12318
|
-
cancel() {
|
|
12319
|
-
this.stroke?.cancel();
|
|
12320
|
-
this.stroke = null;
|
|
12321
|
-
this.move = null;
|
|
12322
|
-
}
|
|
12323
|
-
};
|
|
12324
|
-
|
|
12325
12964
|
// src/image-editor/psd/ui.ts
|
|
12326
12965
|
var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
|
|
12327
12966
|
function blobBytes(file) {
|
|
@@ -12372,6 +13011,7 @@ export {
|
|
|
12372
13011
|
DEFAULT_ADJUSTMENTS,
|
|
12373
13012
|
DEFAULT_BRUSH,
|
|
12374
13013
|
DEFAULT_DEPTH_MASK,
|
|
13014
|
+
DEFAULT_MIXER,
|
|
12375
13015
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
12376
13016
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
12377
13017
|
DEFAULT_TRANSFORM,
|
|
@@ -12379,6 +13019,7 @@ export {
|
|
|
12379
13019
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
12380
13020
|
DabWalker,
|
|
12381
13021
|
EMPTY_DISTANCE,
|
|
13022
|
+
EMPTY_MIXER_LOAD,
|
|
12382
13023
|
GUIDED_FILTER_EPS,
|
|
12383
13024
|
HISTOGRAM_BINS,
|
|
12384
13025
|
HUE_RANGE_LABELS,
|
|
@@ -12398,6 +13039,12 @@ export {
|
|
|
12398
13039
|
MAX_KELVIN,
|
|
12399
13040
|
MIN_KELVIN,
|
|
12400
13041
|
MIRED_PER_TEMPERATURE_UNIT,
|
|
13042
|
+
MIXER_DRAIN,
|
|
13043
|
+
MIXER_MIXING_OPTIONS,
|
|
13044
|
+
MIXER_OPTION_CONTROLS,
|
|
13045
|
+
MIXER_PICKUP_RATE,
|
|
13046
|
+
MIXING_MODELS,
|
|
13047
|
+
MixerStroke,
|
|
12401
13048
|
PAINT_TOOLS,
|
|
12402
13049
|
PIGMENT_BANDS,
|
|
12403
13050
|
PIGMENT_LAMBDA_MIN,
|
|
@@ -12427,6 +13074,7 @@ export {
|
|
|
12427
13074
|
XYZ_D50_TO_SRGB,
|
|
12428
13075
|
ZlibError,
|
|
12429
13076
|
activeAdjustmentLayers,
|
|
13077
|
+
addLayerEffect,
|
|
12430
13078
|
addLayerFilter,
|
|
12431
13079
|
addToHistogram,
|
|
12432
13080
|
adjustmentKindLabel,
|
|
@@ -12472,6 +13120,7 @@ export {
|
|
|
12472
13120
|
changedTiles,
|
|
12473
13121
|
clampControlValue,
|
|
12474
13122
|
clampCropToImage,
|
|
13123
|
+
cleanMixer,
|
|
12475
13124
|
collectTiles,
|
|
12476
13125
|
colorBalanceChannel,
|
|
12477
13126
|
colorRange,
|
|
@@ -12490,6 +13139,7 @@ export {
|
|
|
12490
13139
|
createImageCaptureSurface,
|
|
12491
13140
|
createImageEditTools,
|
|
12492
13141
|
createImageEditorToolProvider,
|
|
13142
|
+
createImageEffectTools,
|
|
12493
13143
|
createImageLayerTools,
|
|
12494
13144
|
createImagePaintTools,
|
|
12495
13145
|
createImagePsdTools,
|
|
@@ -12516,7 +13166,9 @@ export {
|
|
|
12516
13166
|
depthMaskAlpha,
|
|
12517
13167
|
depthRasterMask,
|
|
12518
13168
|
describeLayer,
|
|
13169
|
+
describeLayerEffects,
|
|
12519
13170
|
describeSelection,
|
|
13171
|
+
deserializeEffects,
|
|
12520
13172
|
deserializeLayer,
|
|
12521
13173
|
deserializeLayers,
|
|
12522
13174
|
deserializeSelection,
|
|
@@ -12601,6 +13253,7 @@ export {
|
|
|
12601
13253
|
isSpatialFilter,
|
|
12602
13254
|
kelvinToTemperature,
|
|
12603
13255
|
layerBadge,
|
|
13256
|
+
layerEffectsOf,
|
|
12604
13257
|
layerLabel,
|
|
12605
13258
|
layerMaskCoverage,
|
|
12606
13259
|
layerMaskFromSelection,
|
|
@@ -12620,13 +13273,17 @@ export {
|
|
|
12620
13273
|
linearSrgbToXy,
|
|
12621
13274
|
linearToSrgb,
|
|
12622
13275
|
loadImageEditorDocument,
|
|
13276
|
+
loadMixer,
|
|
12623
13277
|
magicWand,
|
|
12624
13278
|
mapCropHandle,
|
|
12625
13279
|
mapLayer,
|
|
12626
13280
|
maskSummary,
|
|
12627
13281
|
migrateSerializedImageEditorState,
|
|
12628
13282
|
migrateToLayeredState,
|
|
13283
|
+
mixPigment8,
|
|
13284
|
+
mixPigmentRgb,
|
|
12629
13285
|
mixPigments,
|
|
13286
|
+
mixerLoadResult,
|
|
12630
13287
|
modifySelection,
|
|
12631
13288
|
moveCurvePoint,
|
|
12632
13289
|
moveLayer,
|
|
@@ -12641,6 +13298,7 @@ export {
|
|
|
12641
13298
|
normalizeBrush,
|
|
12642
13299
|
normalizeFilter,
|
|
12643
13300
|
normalizeLayerMask,
|
|
13301
|
+
normalizeMixer,
|
|
12644
13302
|
normalizeProcessVersion,
|
|
12645
13303
|
normalizeRect,
|
|
12646
13304
|
normalizeRefineEdgeParams,
|
|
@@ -12686,6 +13344,7 @@ export {
|
|
|
12686
13344
|
removeAlpha,
|
|
12687
13345
|
removeCurvePoint,
|
|
12688
13346
|
removeLayer,
|
|
13347
|
+
removeLayerEffect,
|
|
12689
13348
|
removeLayerFilter,
|
|
12690
13349
|
renderLayeredBase,
|
|
12691
13350
|
requireLayer,
|
|
@@ -12697,6 +13356,7 @@ export {
|
|
|
12697
13356
|
rgbToGray,
|
|
12698
13357
|
rgbToHsl,
|
|
12699
13358
|
rgbToReflectance,
|
|
13359
|
+
rgbUnitToHex,
|
|
12700
13360
|
runLayerMenuAction,
|
|
12701
13361
|
sampleLut,
|
|
12702
13362
|
sampleToneLut,
|
|
@@ -12708,11 +13368,14 @@ export {
|
|
|
12708
13368
|
selectionToCoverage,
|
|
12709
13369
|
selectionToPixels,
|
|
12710
13370
|
selectionValueAt,
|
|
13371
|
+
serializeEffects,
|
|
12711
13372
|
serializeLayer,
|
|
12712
13373
|
serializeLayers,
|
|
12713
13374
|
serializeLayersExternal,
|
|
12714
13375
|
serializeSelection,
|
|
12715
13376
|
setControlPath,
|
|
13377
|
+
setLayerEffects,
|
|
13378
|
+
setLayerEffectsEnabled,
|
|
12716
13379
|
setLayerMask,
|
|
12717
13380
|
setLayerProperties,
|
|
12718
13381
|
setLum,
|
|
@@ -12746,6 +13409,7 @@ export {
|
|
|
12746
13409
|
uniformTile,
|
|
12747
13410
|
unionRect,
|
|
12748
13411
|
unprojectCanvasPoint,
|
|
13412
|
+
updateLayerEffect,
|
|
12749
13413
|
updateLayerFilter,
|
|
12750
13414
|
updateLayerMaskProps,
|
|
12751
13415
|
uvToXy,
|