@pixerate/editor 0.94.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 +1 -0
- package/dist/{chunk-ZOOEX3WT.cjs → chunk-CRMAXIUV.cjs} +369 -7
- package/dist/chunk-CRMAXIUV.cjs.map +1 -0
- package/dist/{chunk-747CGOT2.js → chunk-E2ILRYTJ.js} +12 -2
- 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-73L6TROJ.cjs → chunk-T6H3JDGA.cjs} +54 -44
- 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 +705 -501
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +86 -22
- package/dist/image-editor/index.d.ts +86 -22
- package/dist/image-editor/index.js +383 -179
- 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 +7 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +4 -4
- package/dist/raster/index.d.ts +4 -4
- package/dist/raster/index.js +6 -2
- package/dist/{surface-CuBg2c6k.d.cts → surface-CgAiXVI3.d.cts} +23 -11
- package/dist/{surface-CuBg2c6k.d.ts → surface-CgAiXVI3.d.ts} +23 -11
- package/dist/{zlib-BJ6U7LuB.d.cts → zlib-B3LIoxF-.d.cts} +3 -2
- package/dist/{zlib-BjQkpMFi.d.ts → zlib-eiTEEj1z.d.ts} +3 -2
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +26 -2
- package/dist/chunk-4F3YDIWW.cjs.map +0 -1
- package/dist/chunk-73L6TROJ.cjs.map +0 -1
- package/dist/chunk-747CGOT2.js.map +0 -1
- package/dist/chunk-D4ZCU72J.js.map +0 -1
- package/dist/chunk-ISDCJHTO.js.map +0 -1
- package/dist/chunk-ZOOEX3WT.cjs.map +0 -1
|
@@ -42,39 +42,41 @@ import {
|
|
|
42
42
|
parseDataUrl,
|
|
43
43
|
pngDataUrl,
|
|
44
44
|
validateLayerEffects
|
|
45
|
-
} from "../chunk-
|
|
45
|
+
} from "../chunk-E2ILRYTJ.js";
|
|
46
46
|
import {
|
|
47
47
|
CoverageMap,
|
|
48
48
|
DEFAULT_BRUSH,
|
|
49
|
+
DEFAULT_MIXER,
|
|
49
50
|
DabWalker,
|
|
51
|
+
EMPTY_MIXER_LOAD,
|
|
50
52
|
MAX_BRUSH_SIZE,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
MIXER_DRAIN,
|
|
54
|
+
MIXER_PICKUP_RATE,
|
|
55
|
+
MIXING_MODELS,
|
|
56
|
+
MixerStroke,
|
|
55
57
|
StrokeCoverage,
|
|
56
58
|
applyStrokeToTile,
|
|
59
|
+
cleanMixer,
|
|
57
60
|
coverageTileCoord,
|
|
58
|
-
createPigment,
|
|
59
61
|
createPigmentTools,
|
|
60
62
|
dabCoverage,
|
|
61
63
|
dabRect,
|
|
62
|
-
|
|
63
|
-
lerpPigmentInto,
|
|
64
|
-
mixPigments,
|
|
64
|
+
loadMixer,
|
|
65
65
|
normalizeBrush,
|
|
66
|
+
normalizeMixer,
|
|
66
67
|
rasterizeDab,
|
|
67
|
-
reflectanceToRgb,
|
|
68
|
-
rgbToReflectance,
|
|
69
|
-
setPigmentColor,
|
|
70
68
|
smoothPoints,
|
|
71
69
|
tipFalloff,
|
|
72
70
|
tipReach,
|
|
73
71
|
tipShape,
|
|
74
72
|
walkDabs
|
|
75
|
-
} from "../chunk-
|
|
73
|
+
} from "../chunk-IB53LVGV.js";
|
|
76
74
|
import {
|
|
77
75
|
BLEND_MODES,
|
|
76
|
+
PIGMENT_BANDS,
|
|
77
|
+
PIGMENT_LAMBDA_MIN,
|
|
78
|
+
PIGMENT_LAMBDA_STEP,
|
|
79
|
+
PIGMENT_WAVELENGTHS,
|
|
78
80
|
SurfaceDraft,
|
|
79
81
|
TILE,
|
|
80
82
|
TILE_SHIFT,
|
|
@@ -87,16 +89,25 @@ import {
|
|
|
87
89
|
canvasCompositeOperation,
|
|
88
90
|
collectTiles,
|
|
89
91
|
contentBounds,
|
|
92
|
+
createPigment,
|
|
90
93
|
emptySurface,
|
|
91
94
|
fillRect,
|
|
92
95
|
finishTile,
|
|
93
96
|
isBlendMode,
|
|
94
97
|
isSeparableBlendMode,
|
|
98
|
+
lerpPigment,
|
|
99
|
+
lerpPigmentInto,
|
|
100
|
+
mixPigment8,
|
|
101
|
+
mixPigmentRgb,
|
|
102
|
+
mixPigments,
|
|
95
103
|
normalizeBlendMode,
|
|
96
104
|
packRgba,
|
|
97
105
|
readPixel,
|
|
98
106
|
readRegion,
|
|
107
|
+
reflectanceToRgb,
|
|
108
|
+
rgbToReflectance,
|
|
99
109
|
setLum,
|
|
110
|
+
setPigmentColor,
|
|
100
111
|
solidSurface,
|
|
101
112
|
surfaceBytes,
|
|
102
113
|
surfaceFromPixels,
|
|
@@ -108,7 +119,7 @@ import {
|
|
|
108
119
|
translateSurface,
|
|
109
120
|
uniformTile,
|
|
110
121
|
writeRegion
|
|
111
|
-
} from "../chunk-
|
|
122
|
+
} from "../chunk-HEW6KDNI.js";
|
|
112
123
|
|
|
113
124
|
// src/image-editor/color.ts
|
|
114
125
|
var LEGACY_PROCESS_VERSION = 1;
|
|
@@ -1919,6 +1930,10 @@ function applyLayer(c, layer, op, coverage = 1) {
|
|
|
1919
1930
|
}
|
|
1920
1931
|
const cb0 = c[0], cb1 = c[1], cb2 = c[2];
|
|
1921
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
|
+
}
|
|
1922
1937
|
const b = blend === "normal" ? c : blendRgb(blend, [cb0, cb1, cb2], c);
|
|
1923
1938
|
c[0] = (1 - alpha) * cb0 + alpha * b[0];
|
|
1924
1939
|
c[1] = (1 - alpha) * cb1 + alpha * b[1];
|
|
@@ -7184,22 +7199,53 @@ var PaintStrokeSession = class {
|
|
|
7184
7199
|
constructor(controller, target, stroke, apply) {
|
|
7185
7200
|
this.controller = controller;
|
|
7186
7201
|
this.target = target;
|
|
7187
|
-
this.stroke = stroke;
|
|
7188
7202
|
this.apply = apply;
|
|
7189
7203
|
this.before = controller._captureForStroke();
|
|
7190
7204
|
this.base = target.surface;
|
|
7191
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
|
+
}
|
|
7192
7214
|
}
|
|
7193
7215
|
controller;
|
|
7194
7216
|
target;
|
|
7195
|
-
stroke;
|
|
7196
7217
|
apply;
|
|
7197
7218
|
before;
|
|
7198
7219
|
base;
|
|
7199
7220
|
live;
|
|
7200
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();
|
|
7201
7227
|
last = null;
|
|
7202
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
|
+
}
|
|
7203
7249
|
get layerId() {
|
|
7204
7250
|
return this.target.id;
|
|
7205
7251
|
}
|
|
@@ -7207,11 +7253,18 @@ var PaintStrokeSession = class {
|
|
|
7207
7253
|
push(points) {
|
|
7208
7254
|
if (this.closed || !points.length) return;
|
|
7209
7255
|
this.last = points[points.length - 1];
|
|
7210
|
-
this.update(this.stroke.push(points));
|
|
7256
|
+
this.update(this.mixer ? this.mixer.push(points) : this.stroke.push(points));
|
|
7211
7257
|
}
|
|
7212
7258
|
update(rects) {
|
|
7213
7259
|
if (!rects.length) return;
|
|
7214
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;
|
|
7215
7268
|
const regions = /* @__PURE__ */ new Map();
|
|
7216
7269
|
for (const r of rects) {
|
|
7217
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 })) {
|
|
@@ -7224,7 +7277,7 @@ var PaintStrokeSession = class {
|
|
|
7224
7277
|
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
7225
7278
|
const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
|
|
7226
7279
|
this.touched.add(key);
|
|
7227
|
-
const data = applyStrokeToTile(
|
|
7280
|
+
const data = applyStrokeToTile(stroke.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
|
|
7228
7281
|
this.live.set(key, { data, uniform: null });
|
|
7229
7282
|
}
|
|
7230
7283
|
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
@@ -7232,7 +7285,8 @@ var PaintStrokeSession = class {
|
|
|
7232
7285
|
/** Finishes the stroke (one undo step) and returns what changed. */
|
|
7233
7286
|
end() {
|
|
7234
7287
|
if (this.closed) throw new LayerOperationError("This stroke has already ended.");
|
|
7235
|
-
this.
|
|
7288
|
+
const last = this.last ?? void 0;
|
|
7289
|
+
this.update(this.mixer ? this.mixer.finish(last) : this.stroke.finish(last));
|
|
7236
7290
|
this.closed = true;
|
|
7237
7291
|
const ch = this.base.channels;
|
|
7238
7292
|
const tiles = new Map(this.base.tiles);
|
|
@@ -7246,9 +7300,12 @@ var PaintStrokeSession = class {
|
|
|
7246
7300
|
if (!sameTile(prev, tile)) changed = true;
|
|
7247
7301
|
}
|
|
7248
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;
|
|
7249
7304
|
this.controller._strokeCommit(this.before, this.target.id, this.target.channel, surface);
|
|
7250
|
-
const b = this.stroke.coverage.bounds;
|
|
7251
|
-
|
|
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;
|
|
7252
7309
|
}
|
|
7253
7310
|
/** Abandons the stroke and restores the layer. */
|
|
7254
7311
|
cancel() {
|
|
@@ -7384,7 +7441,7 @@ var ImageEditorController = class {
|
|
|
7384
7441
|
activeLayerId: null,
|
|
7385
7442
|
activeTarget: "pixels",
|
|
7386
7443
|
outputOpacity: 1,
|
|
7387
|
-
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff" },
|
|
7444
|
+
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD },
|
|
7388
7445
|
fit: options.fit ?? "margin"
|
|
7389
7446
|
};
|
|
7390
7447
|
if (options.image instanceof Object && "width" in options.image) {
|
|
@@ -8375,12 +8432,38 @@ var ImageEditorController = class {
|
|
|
8375
8432
|
const next = {
|
|
8376
8433
|
brush: options.brush ? normalizeBrush({ ...cur.brush, ...options.brush }) : cur.brush,
|
|
8377
8434
|
foreground: options.foreground !== void 0 ? toHex2(parseColor(options.foreground)) : cur.foreground,
|
|
8378
|
-
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
|
|
8379
8438
|
};
|
|
8380
8439
|
this.state.paint = next;
|
|
8381
8440
|
this.notify();
|
|
8382
8441
|
return next;
|
|
8383
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
|
+
}
|
|
8384
8467
|
/** D: black foreground, white background. */
|
|
8385
8468
|
resetPaintColors() {
|
|
8386
8469
|
this.setPaintOptions({ foreground: "#000000", background: "#ffffff" });
|
|
@@ -8425,7 +8508,11 @@ var ImageEditorController = class {
|
|
|
8425
8508
|
*/
|
|
8426
8509
|
beginStroke(init = {}) {
|
|
8427
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.`);
|
|
8428
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
|
+
}
|
|
8429
8516
|
const brush = normalizeBrush({ ...this.state.paint.brush, ...init.brush ?? {} });
|
|
8430
8517
|
const color = parseColor(init.color ?? this.state.paint.foreground);
|
|
8431
8518
|
const background = parseColor(init.background ?? this.state.paint.background);
|
|
@@ -8443,8 +8530,26 @@ var ImageEditorController = class {
|
|
|
8443
8530
|
selection: this.state.selection?.bounds ? this.state.selection : null,
|
|
8444
8531
|
offset: target.offset
|
|
8445
8532
|
};
|
|
8533
|
+
if (tool === "mixer") return new PaintStrokeSession(this, target, this.mixerStroke(init, brush), apply);
|
|
8446
8534
|
return new PaintStrokeSession(this, target, new StrokeCoverage(brush), apply);
|
|
8447
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
|
+
}
|
|
8448
8553
|
/** @internal Shows a stroke's live surface (no history). */
|
|
8449
8554
|
_strokeUpdate(layerId, channel, surface, rects) {
|
|
8450
8555
|
this.state.layers = mapLayer(this.state.layers, layerId, (l) => this.withSurface(l, channel, surface));
|
|
@@ -8468,7 +8573,7 @@ var ImageEditorController = class {
|
|
|
8468
8573
|
}
|
|
8469
8574
|
/**
|
|
8470
8575
|
* Paints a whole stroke in one call (agents, replays): `points` in document pixels with optional
|
|
8471
|
-
* pressure, through the brush or
|
|
8576
|
+
* pressure, through the brush, eraser or Mixer Brush. One undo step. Returns the layer, channel, the changed
|
|
8472
8577
|
* document bounds and the number of dabs.
|
|
8473
8578
|
*/
|
|
8474
8579
|
paintStroke(init) {
|
|
@@ -9477,6 +9582,11 @@ var ImageEditorController = class {
|
|
|
9477
9582
|
return baked;
|
|
9478
9583
|
};
|
|
9479
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) => {
|
|
9480
9590
|
if (layer.type === "group") {
|
|
9481
9591
|
const children = [];
|
|
9482
9592
|
for (const c of layer.children) {
|
|
@@ -11764,7 +11874,160 @@ function createImageEffectTools(controller) {
|
|
|
11764
11874
|
return [list, add, update, remove, setEnabled, light];
|
|
11765
11875
|
}
|
|
11766
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
|
+
|
|
11767
12029
|
// src/image-editor/paint-tools.ts
|
|
12030
|
+
var READ4 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11768
12031
|
var WRITE4 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
11769
12032
|
var DESTRUCTIVE3 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11770
12033
|
var TARGET = {
|
|
@@ -11785,10 +12048,30 @@ var BRUSH = {
|
|
|
11785
12048
|
smoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Point smoothing 0..0.95 (default 0)." },
|
|
11786
12049
|
pressureSize: { type: "boolean", description: "Point pressure scales the size (default true)." },
|
|
11787
12050
|
pressureOpacity: { type: "boolean", description: "Point pressure scales the opacity (default false)." },
|
|
11788
|
-
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." }
|
|
11789
12052
|
},
|
|
11790
12053
|
additionalProperties: false
|
|
11791
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
|
+
}
|
|
11792
12075
|
function readTarget(args) {
|
|
11793
12076
|
return { layerId: args.layerId, channel: args.channel };
|
|
11794
12077
|
}
|
|
@@ -11796,11 +12079,11 @@ function createImagePaintTools(controller) {
|
|
|
11796
12079
|
const paintStroke = {
|
|
11797
12080
|
name: "paint_stroke",
|
|
11798
12081
|
title: "Paint a brush stroke",
|
|
11799
|
-
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}).",
|
|
11800
12083
|
inputSchema: {
|
|
11801
12084
|
type: "object",
|
|
11802
12085
|
properties: {
|
|
11803
|
-
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)." },
|
|
11804
12087
|
points: {
|
|
11805
12088
|
type: "array",
|
|
11806
12089
|
minItems: 1,
|
|
@@ -11817,21 +12100,66 @@ function createImagePaintTools(controller) {
|
|
|
11817
12100
|
additionalProperties: false
|
|
11818
12101
|
}
|
|
11819
12102
|
},
|
|
11820
|
-
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
|
+
},
|
|
11821
12107
|
brush: BRUSH,
|
|
12108
|
+
mixer: MIXER,
|
|
11822
12109
|
...TARGET
|
|
11823
12110
|
},
|
|
11824
12111
|
required: ["points"],
|
|
11825
12112
|
additionalProperties: false
|
|
11826
12113
|
},
|
|
11827
12114
|
annotations: { title: "Paint a brush stroke", ...WRITE4 },
|
|
11828
|
-
handler: (args) =>
|
|
11829
|
-
tool
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
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())
|
|
11835
12163
|
};
|
|
11836
12164
|
const fill = {
|
|
11837
12165
|
name: "fill_pixels",
|
|
@@ -11842,7 +12170,7 @@ function createImagePaintTools(controller) {
|
|
|
11842
12170
|
properties: {
|
|
11843
12171
|
color: { type: "string", description: "Fill colour #rrggbb or #rrggbbaa (default the foreground)." },
|
|
11844
12172
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Fill opacity 0..1 (default 1)." },
|
|
11845
|
-
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." },
|
|
11846
12174
|
useSelection: { type: "boolean", description: "Limit to the selection when there is one (default true)." },
|
|
11847
12175
|
...TARGET
|
|
11848
12176
|
},
|
|
@@ -11873,11 +12201,11 @@ function createImagePaintTools(controller) {
|
|
|
11873
12201
|
annotations: { title: "Clear pixels", ...DESTRUCTIVE3 },
|
|
11874
12202
|
handler: (args) => controller.clearPixels({ opacity: args.opacity, useSelection: args.useSelection, ...readTarget(args) })
|
|
11875
12203
|
};
|
|
11876
|
-
return [paintStroke, fill, clear];
|
|
12204
|
+
return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool];
|
|
11877
12205
|
}
|
|
11878
12206
|
|
|
11879
12207
|
// src/image-editor/psd-tools.ts
|
|
11880
|
-
var
|
|
12208
|
+
var READ5 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11881
12209
|
var DESTRUCTIVE4 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11882
12210
|
var DATA = {
|
|
11883
12211
|
type: "string",
|
|
@@ -11898,7 +12226,7 @@ function createImagePsdTools(controller) {
|
|
|
11898
12226
|
title: "Inspect a PSD",
|
|
11899
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.",
|
|
11900
12228
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
11901
|
-
annotations: { title: "Inspect a PSD", ...
|
|
12229
|
+
annotations: { title: "Inspect a PSD", ...READ5 },
|
|
11902
12230
|
handler: ({ data }) => {
|
|
11903
12231
|
const res = importPsd(psdBytesFromBase64(data), { skipPixels: true });
|
|
11904
12232
|
return {
|
|
@@ -11935,7 +12263,7 @@ function createImagePsdTools(controller) {
|
|
|
11935
12263
|
title: "Export the document as a PSD",
|
|
11936
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.",
|
|
11937
12265
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
11938
|
-
annotations: { title: "Export the document as a PSD", ...
|
|
12266
|
+
annotations: { title: "Export the document as a PSD", ...READ5 },
|
|
11939
12267
|
handler: async () => {
|
|
11940
12268
|
const res = await controller.toPsd();
|
|
11941
12269
|
return {
|
|
@@ -12633,145 +12961,6 @@ async function runLayerMenuAction(controller, action) {
|
|
|
12633
12961
|
}
|
|
12634
12962
|
}
|
|
12635
12963
|
|
|
12636
|
-
// src/image-editor/layers/paint-ui.ts
|
|
12637
|
-
var plain2 = { kind: "plain" };
|
|
12638
|
-
var BRUSH_OPTION_CONTROLS = [
|
|
12639
|
-
{ id: "size", label: "Size", section: "layer", min: 1, max: 500, step: 1, default: 20, decimals: 0, track: plain2, unit: "px" },
|
|
12640
|
-
{ id: "hardness", label: "Hardness", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12641
|
-
{ id: "opacity", label: "Opacity", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12642
|
-
{ id: "flow", label: "Flow", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12643
|
-
{ id: "smoothing", label: "Smoothing", section: "layer", min: 0, max: 0.95, step: 0.01, default: 0, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
12644
|
-
];
|
|
12645
|
-
var PAINT_TOOLS = [
|
|
12646
|
-
{ id: "move", label: "Move", shortcut: "V" },
|
|
12647
|
-
{ id: "brush", label: "Brush", shortcut: "B" },
|
|
12648
|
-
{ id: "eraser", label: "Eraser", shortcut: "E" }
|
|
12649
|
-
];
|
|
12650
|
-
var isPaintTool = (tool) => tool === "move" || tool === "brush" || tool === "eraser";
|
|
12651
|
-
function sizeStep(size) {
|
|
12652
|
-
return size < 10 ? 1 : size < 50 ? 5 : size < 100 ? 10 : size < 200 ? 25 : size < 500 ? 50 : 100;
|
|
12653
|
-
}
|
|
12654
|
-
var isTyping = (t) => {
|
|
12655
|
-
const el = t;
|
|
12656
|
-
return Boolean(el && (el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT"));
|
|
12657
|
-
};
|
|
12658
|
-
function handlePaintShortcut(controller, e) {
|
|
12659
|
-
if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
|
|
12660
|
-
const brush = controller.getState().paint.brush;
|
|
12661
|
-
const set = (b) => controller.setPaintOptions({ brush: b });
|
|
12662
|
-
const key = e.key;
|
|
12663
|
-
switch (key.toLowerCase()) {
|
|
12664
|
-
case "b":
|
|
12665
|
-
controller.setTool("brush");
|
|
12666
|
-
return true;
|
|
12667
|
-
case "e":
|
|
12668
|
-
controller.setTool("eraser");
|
|
12669
|
-
return true;
|
|
12670
|
-
case "v":
|
|
12671
|
-
controller.setTool("move");
|
|
12672
|
-
return true;
|
|
12673
|
-
case "d":
|
|
12674
|
-
controller.resetPaintColors();
|
|
12675
|
-
return true;
|
|
12676
|
-
case "x":
|
|
12677
|
-
controller.swapPaintColors();
|
|
12678
|
-
return true;
|
|
12679
|
-
}
|
|
12680
|
-
if (key === "[" || key === "{") {
|
|
12681
|
-
if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
|
|
12682
|
-
else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
|
|
12683
|
-
return true;
|
|
12684
|
-
}
|
|
12685
|
-
if (key === "]" || key === "}") {
|
|
12686
|
-
if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
|
|
12687
|
-
else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
|
|
12688
|
-
return true;
|
|
12689
|
-
}
|
|
12690
|
-
if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
|
|
12691
|
-
set({ opacity: key === "0" ? 1 : Number(key) / 10 });
|
|
12692
|
-
return true;
|
|
12693
|
-
}
|
|
12694
|
-
return false;
|
|
12695
|
-
}
|
|
12696
|
-
function pointerPressure(e) {
|
|
12697
|
-
if (e.pointerType === "pen" && typeof e.pressure === "number" && e.pressure > 0) return Math.min(1, e.pressure);
|
|
12698
|
-
return 1;
|
|
12699
|
-
}
|
|
12700
|
-
var PaintGesture = class {
|
|
12701
|
-
constructor(controller) {
|
|
12702
|
-
this.controller = controller;
|
|
12703
|
-
}
|
|
12704
|
-
controller;
|
|
12705
|
-
stroke = null;
|
|
12706
|
-
move = null;
|
|
12707
|
-
/** The last refusal (e.g. "layer is locked"), for the editor to show. */
|
|
12708
|
-
error = null;
|
|
12709
|
-
get active() {
|
|
12710
|
-
return Boolean(this.stroke || this.move);
|
|
12711
|
-
}
|
|
12712
|
-
down(sample) {
|
|
12713
|
-
this.error = null;
|
|
12714
|
-
const tool = this.controller.getState().activeTool;
|
|
12715
|
-
try {
|
|
12716
|
-
if (tool === "move") {
|
|
12717
|
-
const layer = this.controller.getActiveLayer();
|
|
12718
|
-
if (!layer) throw new Error("Select a layer to move in the Layers panel.");
|
|
12719
|
-
if (layer.type !== "pixel") throw new Error(`Only pixel layers move; "${layer.name}" is a ${layer.type} layer.`);
|
|
12720
|
-
if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
|
|
12721
|
-
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin: { ...layer.offset }, moved: false };
|
|
12722
|
-
return null;
|
|
12723
|
-
}
|
|
12724
|
-
if (tool === "brush" || tool === "eraser") {
|
|
12725
|
-
this.stroke = this.controller.beginStroke({ tool });
|
|
12726
|
-
this.stroke.push([sample]);
|
|
12727
|
-
return null;
|
|
12728
|
-
}
|
|
12729
|
-
} catch (err) {
|
|
12730
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12731
|
-
this.stroke = null;
|
|
12732
|
-
this.move = null;
|
|
12733
|
-
}
|
|
12734
|
-
return this.error;
|
|
12735
|
-
}
|
|
12736
|
-
/** Feeds pointer samples (all coalesced events of one move, in order). */
|
|
12737
|
-
moveTo(samples) {
|
|
12738
|
-
if (!samples.length) return;
|
|
12739
|
-
if (this.stroke) {
|
|
12740
|
-
this.stroke.push(samples);
|
|
12741
|
-
return;
|
|
12742
|
-
}
|
|
12743
|
-
const m = this.move;
|
|
12744
|
-
if (m) {
|
|
12745
|
-
const last = samples[samples.length - 1];
|
|
12746
|
-
const x = m.origin.x + Math.round(last.x - m.start.x);
|
|
12747
|
-
const y = m.origin.y + Math.round(last.y - m.start.y);
|
|
12748
|
-
try {
|
|
12749
|
-
this.controller.offsetLayer(m.id, { x, y }, !m.moved);
|
|
12750
|
-
m.moved = true;
|
|
12751
|
-
} catch (err) {
|
|
12752
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12753
|
-
this.move = null;
|
|
12754
|
-
}
|
|
12755
|
-
}
|
|
12756
|
-
}
|
|
12757
|
-
up() {
|
|
12758
|
-
if (this.stroke) {
|
|
12759
|
-
try {
|
|
12760
|
-
this.stroke.end();
|
|
12761
|
-
} catch (err) {
|
|
12762
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12763
|
-
}
|
|
12764
|
-
}
|
|
12765
|
-
this.stroke = null;
|
|
12766
|
-
this.move = null;
|
|
12767
|
-
}
|
|
12768
|
-
cancel() {
|
|
12769
|
-
this.stroke?.cancel();
|
|
12770
|
-
this.stroke = null;
|
|
12771
|
-
this.move = null;
|
|
12772
|
-
}
|
|
12773
|
-
};
|
|
12774
|
-
|
|
12775
12964
|
// src/image-editor/psd/ui.ts
|
|
12776
12965
|
var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
|
|
12777
12966
|
function blobBytes(file) {
|
|
@@ -12822,6 +13011,7 @@ export {
|
|
|
12822
13011
|
DEFAULT_ADJUSTMENTS,
|
|
12823
13012
|
DEFAULT_BRUSH,
|
|
12824
13013
|
DEFAULT_DEPTH_MASK,
|
|
13014
|
+
DEFAULT_MIXER,
|
|
12825
13015
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
12826
13016
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
12827
13017
|
DEFAULT_TRANSFORM,
|
|
@@ -12829,6 +13019,7 @@ export {
|
|
|
12829
13019
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
12830
13020
|
DabWalker,
|
|
12831
13021
|
EMPTY_DISTANCE,
|
|
13022
|
+
EMPTY_MIXER_LOAD,
|
|
12832
13023
|
GUIDED_FILTER_EPS,
|
|
12833
13024
|
HISTOGRAM_BINS,
|
|
12834
13025
|
HUE_RANGE_LABELS,
|
|
@@ -12848,6 +13039,12 @@ export {
|
|
|
12848
13039
|
MAX_KELVIN,
|
|
12849
13040
|
MIN_KELVIN,
|
|
12850
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,
|
|
12851
13048
|
PAINT_TOOLS,
|
|
12852
13049
|
PIGMENT_BANDS,
|
|
12853
13050
|
PIGMENT_LAMBDA_MIN,
|
|
@@ -12923,6 +13120,7 @@ export {
|
|
|
12923
13120
|
changedTiles,
|
|
12924
13121
|
clampControlValue,
|
|
12925
13122
|
clampCropToImage,
|
|
13123
|
+
cleanMixer,
|
|
12926
13124
|
collectTiles,
|
|
12927
13125
|
colorBalanceChannel,
|
|
12928
13126
|
colorRange,
|
|
@@ -13075,13 +13273,17 @@ export {
|
|
|
13075
13273
|
linearSrgbToXy,
|
|
13076
13274
|
linearToSrgb,
|
|
13077
13275
|
loadImageEditorDocument,
|
|
13276
|
+
loadMixer,
|
|
13078
13277
|
magicWand,
|
|
13079
13278
|
mapCropHandle,
|
|
13080
13279
|
mapLayer,
|
|
13081
13280
|
maskSummary,
|
|
13082
13281
|
migrateSerializedImageEditorState,
|
|
13083
13282
|
migrateToLayeredState,
|
|
13283
|
+
mixPigment8,
|
|
13284
|
+
mixPigmentRgb,
|
|
13084
13285
|
mixPigments,
|
|
13286
|
+
mixerLoadResult,
|
|
13085
13287
|
modifySelection,
|
|
13086
13288
|
moveCurvePoint,
|
|
13087
13289
|
moveLayer,
|
|
@@ -13096,6 +13298,7 @@ export {
|
|
|
13096
13298
|
normalizeBrush,
|
|
13097
13299
|
normalizeFilter,
|
|
13098
13300
|
normalizeLayerMask,
|
|
13301
|
+
normalizeMixer,
|
|
13099
13302
|
normalizeProcessVersion,
|
|
13100
13303
|
normalizeRect,
|
|
13101
13304
|
normalizeRefineEdgeParams,
|
|
@@ -13153,6 +13356,7 @@ export {
|
|
|
13153
13356
|
rgbToGray,
|
|
13154
13357
|
rgbToHsl,
|
|
13155
13358
|
rgbToReflectance,
|
|
13359
|
+
rgbUnitToHex,
|
|
13156
13360
|
runLayerMenuAction,
|
|
13157
13361
|
sampleLut,
|
|
13158
13362
|
sampleToneLut,
|