@pixerate/editor 0.103.0 → 0.104.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 +2 -0
- package/dist/paint-editor/index.cjs +1209 -122
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +308 -2
- package/dist/paint-editor/index.d.ts +308 -2
- package/dist/paint-editor/index.js +1162 -75
- package/dist/paint-editor/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +14 -5
- package/skills/pixerate-paint-editor/SKILL.md +49 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9;
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -14,6 +14,9 @@ var _chunkVSMM7B3Pcjs = require('../chunk-VSMM7B3P.cjs');
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
17
20
|
var _chunkWSTEXEKDcjs = require('../chunk-WSTEXEKD.cjs');
|
|
18
21
|
require('../chunk-MUUXNRU4.cjs');
|
|
19
22
|
|
|
@@ -46,6 +49,7 @@ var _chunkPV42JITTcjs = require('../chunk-PV42JITT.cjs');
|
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
|
|
52
|
+
|
|
49
53
|
var _chunkCRMAXIUVcjs = require('../chunk-CRMAXIUV.cjs');
|
|
50
54
|
require('../chunk-DWGZUSES.cjs');
|
|
51
55
|
|
|
@@ -855,7 +859,7 @@ var DryStroke = (_class5 = class {
|
|
|
855
859
|
this.brush = brush;
|
|
856
860
|
this.options = options;
|
|
857
861
|
this.smoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
|
|
858
|
-
this.copies = _optionalChain([options, 'access',
|
|
862
|
+
this.copies = _optionalChain([options, 'access', _2 => _2.copies, 'optionalAccess', _3 => _3.length]) ? options.copies : [IDENTITY_COPY];
|
|
859
863
|
this.walkers = this.copies.map((c, i) => new PenDabWalker(brush, (_nullishCoalesce(options.seed, () => ( 0))) + i * 7919 | 0, c.linear));
|
|
860
864
|
this.depth = options.grain && !options.grain.flat ? Math.min(1, Math.max(0, options.paperDepth * brush.grain)) : 0;
|
|
861
865
|
}
|
|
@@ -991,11 +995,11 @@ var PaintStrokeSession = (_class6 = class {
|
|
|
991
995
|
offset: this.init.offset,
|
|
992
996
|
lockTransparency: this.init.lockTransparency,
|
|
993
997
|
selection: null,
|
|
994
|
-
readTile: (tx, ty) => _nullishCoalesce(_optionalChain([this, 'access',
|
|
998
|
+
readTile: (tx, ty) => _nullishCoalesce(_optionalChain([this, 'access', _4 => _4.live, 'access', _5 => _5.get, 'call', _6 => _6(_chunkCRMAXIUVcjs.tileKey.call(void 0, tx, ty)), 'optionalAccess', _7 => _7.data]), () => ( null)),
|
|
995
999
|
writeTile: (tx, ty) => {
|
|
996
1000
|
const key = _chunkCRMAXIUVcjs.tileKey.call(void 0, tx, ty);
|
|
997
1001
|
if (!this.touched.has(key)) {
|
|
998
|
-
const src = _nullishCoalesce(_optionalChain([this, 'access',
|
|
1002
|
+
const src = _nullishCoalesce(_optionalChain([this, 'access', _8 => _8.base, 'access', _9 => _9.tiles, 'access', _10 => _10.get, 'call', _11 => _11(key), 'optionalAccess', _12 => _12.data]), () => ( defaultTile(this.base)));
|
|
999
1003
|
this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
|
|
1000
1004
|
this.touched.add(key);
|
|
1001
1005
|
}
|
|
@@ -1010,6 +1014,8 @@ var PaintStrokeSession = (_class6 = class {
|
|
|
1010
1014
|
/** Adds raw pen samples (document pixels). */
|
|
1011
1015
|
push(points) {
|
|
1012
1016
|
if (this.closed || !points.length) return;
|
|
1017
|
+
const filter = this.init.inputFilter;
|
|
1018
|
+
if (filter) points = points.map(filter);
|
|
1013
1019
|
for (const p of points) this.points.push(p);
|
|
1014
1020
|
this.lastRaw = points[points.length - 1];
|
|
1015
1021
|
if (this.dry) this.update(this.dry.push(points));
|
|
@@ -1042,8 +1048,8 @@ var PaintStrokeSession = (_class6 = class {
|
|
|
1042
1048
|
}
|
|
1043
1049
|
}
|
|
1044
1050
|
for (const [key, { tx, ty, r }] of regions) {
|
|
1045
|
-
const src = _nullishCoalesce(_optionalChain([this, 'access',
|
|
1046
|
-
const prev = this.touched.has(key) ? _optionalChain([this, 'access',
|
|
1051
|
+
const src = _nullishCoalesce(_optionalChain([this, 'access', _13 => _13.base, 'access', _14 => _14.tiles, 'access', _15 => _15.get, 'call', _16 => _16(key), 'optionalAccess', _17 => _17.data]), () => ( defaultTile(this.base)));
|
|
1052
|
+
const prev = this.touched.has(key) ? _optionalChain([this, 'access', _18 => _18.live, 'access', _19 => _19.get, 'call', _20 => _20(key), 'optionalAccess', _21 => _21.data]) : void 0;
|
|
1047
1053
|
this.touched.add(key);
|
|
1048
1054
|
const data = _chunkPV42JITTcjs.applyStrokeToTile.call(void 0, this.dry.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
|
|
1049
1055
|
this.live.set(key, { data, uniform: null });
|
|
@@ -1192,7 +1198,7 @@ function varyStroke(stroke, m, v) {
|
|
|
1192
1198
|
});
|
|
1193
1199
|
const brush = s !== 1 ? normalizePaintBrush({ ...stroke.brush, tip: { ...stroke.brush.tip, size: stroke.brush.tip.size * s } }) : stroke.brush;
|
|
1194
1200
|
const out = { ...stroke, brush, points };
|
|
1195
|
-
if (_optionalChain([stroke, 'access',
|
|
1201
|
+
if (_optionalChain([stroke, 'access', _22 => _22.copies, 'optionalAccess', _23 => _23.length])) {
|
|
1196
1202
|
const inv = invertAffine(m);
|
|
1197
1203
|
out.copies = stroke.copies.map((c) => composeAffine(m, composeAffine(c, inv)));
|
|
1198
1204
|
}
|
|
@@ -1307,7 +1313,7 @@ function downsampleRgba(data, width, height, tw, th) {
|
|
|
1307
1313
|
// src/paint-editor/serialize.ts
|
|
1308
1314
|
var PAINT_FORMAT = "pixerate-paint";
|
|
1309
1315
|
var PAINT_SCHEMA_VERSION = 1;
|
|
1310
|
-
var KNOWN = /* @__PURE__ */ new Set(["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId", "strokes"]);
|
|
1316
|
+
var KNOWN = /* @__PURE__ */ new Set(["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId", "strokes", "guides", "swatches"]);
|
|
1311
1317
|
function documentExtensions(raw) {
|
|
1312
1318
|
const out = {};
|
|
1313
1319
|
for (const [k, v] of Object.entries(raw)) if (!KNOWN.has(k)) out[k] = v;
|
|
@@ -1325,6 +1331,8 @@ function header(state, options) {
|
|
|
1325
1331
|
activeLayerId: state.activeLayerId
|
|
1326
1332
|
};
|
|
1327
1333
|
if (options.strokes !== false && state.strokes.length) out.strokes = JSON.parse(JSON.stringify(state.strokes));
|
|
1334
|
+
if (options.guides !== void 0) out.guides = JSON.parse(JSON.stringify(options.guides));
|
|
1335
|
+
if (options.swatches !== void 0) out.swatches = JSON.parse(JSON.stringify(options.swatches));
|
|
1328
1336
|
return out;
|
|
1329
1337
|
}
|
|
1330
1338
|
function serializePaintDocument(state, options = {}) {
|
|
@@ -1340,7 +1348,7 @@ function paintLayersOnly(layers, problems) {
|
|
|
1340
1348
|
for (const l of layers) {
|
|
1341
1349
|
if (l.type === "pixel") {
|
|
1342
1350
|
const { filters, source, ...rest } = l;
|
|
1343
|
-
if (_optionalChain([filters, 'optionalAccess',
|
|
1351
|
+
if (_optionalChain([filters, 'optionalAccess', _24 => _24.length])) problems.push(`Layer "${l.name}": its filters are an image editor feature and were dropped.`);
|
|
1344
1352
|
if (source) problems.push(`Layer "${l.name}" refers to an image editor source image; it loads empty.`);
|
|
1345
1353
|
out.push(rest);
|
|
1346
1354
|
} else if (l.type === "group") out.push({ ...l, children: paintLayersOnly(l.children, problems) });
|
|
@@ -1371,8 +1379,8 @@ function deserializePaintDocument(raw) {
|
|
|
1371
1379
|
const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
|
|
1372
1380
|
const layers = paintLayersOnly(_chunkVSMM7B3Pcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
|
|
1373
1381
|
const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
|
|
1374
|
-
const activeLayerId = wanted && _chunkWSTEXEKDcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.flattenLayers.call(void 0, layers), 'access',
|
|
1375
|
-
return { state: { canvas, paper, layers, activeLayerId, strokes: readStrokes(r.strokes, problems) }, extensions: documentExtensions(r), problems };
|
|
1382
|
+
const activeLayerId = wanted && _chunkWSTEXEKDcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.flattenLayers.call(void 0, layers), 'access', _25 => _25.filter, 'call', _26 => _26((l) => l.type === "pixel"), 'access', _27 => _27.pop, 'call', _28 => _28(), 'optionalAccess', _29 => _29.id]), () => ( null));
|
|
1383
|
+
return { state: { canvas, paper, layers, activeLayerId, strokes: readStrokes(r.strokes, problems) }, extensions: documentExtensions(r), guides: r.guides, swatches: r.swatches, problems };
|
|
1376
1384
|
}
|
|
1377
1385
|
async function deserializePaintDocumentAsync(raw, store) {
|
|
1378
1386
|
if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
|
|
@@ -1562,6 +1570,411 @@ function renderBrushThumbnail(brush, options) {
|
|
|
1562
1570
|
return { width, height, data };
|
|
1563
1571
|
}
|
|
1564
1572
|
|
|
1573
|
+
// src/paint-editor/guides.ts
|
|
1574
|
+
var SYMMETRY_MODES = ["off", "vertical", "horizontal", "dual", "diagonal", "kaleidoscope"];
|
|
1575
|
+
function defaultGuides(canvas) {
|
|
1576
|
+
return {
|
|
1577
|
+
symmetry: { mode: "off", center: { x: canvas.width / 2, y: canvas.height / 2 }, rotation: 0, segments: 6, mirror: true },
|
|
1578
|
+
perspective: { enabled: false, points: [], snap: true },
|
|
1579
|
+
tracing: null,
|
|
1580
|
+
references: []
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
var MAX_COORDINATE = 1e6;
|
|
1584
|
+
var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
1585
|
+
var coord = (v, d) => finite2(v) && Math.abs(v) <= MAX_COORDINATE ? v : d;
|
|
1586
|
+
function normalizeSymmetry(raw, base) {
|
|
1587
|
+
const r = raw;
|
|
1588
|
+
const c = _nullishCoalesce(r.center, () => ( {}));
|
|
1589
|
+
return {
|
|
1590
|
+
mode: SYMMETRY_MODES.includes(r.mode) ? r.mode : base.mode,
|
|
1591
|
+
center: { x: coord(c.x, base.center.x), y: coord(c.y, base.center.y) },
|
|
1592
|
+
rotation: finite2(r.rotation) ? (r.rotation % 360 + 360) % 360 : base.rotation,
|
|
1593
|
+
segments: finite2(r.segments) ? Math.min(32, Math.max(2, Math.round(r.segments))) : base.segments,
|
|
1594
|
+
mirror: typeof r.mirror === "boolean" ? r.mirror : base.mirror
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
function reflectAbout(c, a) {
|
|
1598
|
+
const dx = Math.cos(a);
|
|
1599
|
+
const dy = Math.sin(a);
|
|
1600
|
+
const m00 = 2 * dx * dx - 1;
|
|
1601
|
+
const m01 = 2 * dx * dy;
|
|
1602
|
+
const m11 = 2 * dy * dy - 1;
|
|
1603
|
+
return [m00, m01, m01, m11, c.x - (m00 * c.x + m01 * c.y), c.y - (m01 * c.x + m11 * c.y)];
|
|
1604
|
+
}
|
|
1605
|
+
function rotateAbout(c, a) {
|
|
1606
|
+
const cos = Math.cos(a);
|
|
1607
|
+
const sin = Math.sin(a);
|
|
1608
|
+
return [cos, -sin, sin, cos, c.x - (cos * c.x - sin * c.y), c.y - (sin * c.x + cos * c.y)];
|
|
1609
|
+
}
|
|
1610
|
+
var rad = (deg) => deg * Math.PI / 180;
|
|
1611
|
+
function symmetryAxisAngles(g) {
|
|
1612
|
+
if (g.mode === "off") return [];
|
|
1613
|
+
if (g.mode === "kaleidoscope") return Array.from({ length: g.segments }, (_, k) => rad(g.rotation) + k * Math.PI / g.segments);
|
|
1614
|
+
const initial = g.mode === "horizontal" ? 0 : g.mode === "diagonal" ? 45 : 90;
|
|
1615
|
+
const a = rad(initial + g.rotation);
|
|
1616
|
+
return g.mode === "dual" ? [a, a + Math.PI / 2] : [a];
|
|
1617
|
+
}
|
|
1618
|
+
function symmetryCopies(g) {
|
|
1619
|
+
const c = g.center;
|
|
1620
|
+
switch (g.mode) {
|
|
1621
|
+
case "off":
|
|
1622
|
+
return [];
|
|
1623
|
+
case "vertical":
|
|
1624
|
+
case "horizontal":
|
|
1625
|
+
case "diagonal":
|
|
1626
|
+
return symmetryAxisAngles(g).map((a) => reflectAbout(c, a));
|
|
1627
|
+
case "dual": {
|
|
1628
|
+
const [a, b] = symmetryAxisAngles(g);
|
|
1629
|
+
return [reflectAbout(c, a), reflectAbout(c, b), rotateAbout(c, Math.PI)];
|
|
1630
|
+
}
|
|
1631
|
+
case "kaleidoscope": {
|
|
1632
|
+
const out = [];
|
|
1633
|
+
const n = g.segments;
|
|
1634
|
+
for (let k = 1; k < n; k++) out.push(rotateAbout(c, 2 * Math.PI * k / n));
|
|
1635
|
+
if (g.mirror) {
|
|
1636
|
+
const first = reflectAbout(c, rad(g.rotation));
|
|
1637
|
+
for (let k = 0; k < n; k++) out.push(composeAffine(rotateAbout(c, 2 * Math.PI * k / n), first));
|
|
1638
|
+
}
|
|
1639
|
+
return out;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function symmetryAxisSegments(g, size) {
|
|
1644
|
+
const out = [];
|
|
1645
|
+
for (const a of symmetryAxisAngles(g)) {
|
|
1646
|
+
const d = [Math.cos(a), Math.sin(a)];
|
|
1647
|
+
const origin = [g.center.x, g.center.y];
|
|
1648
|
+
const extent = [size.width, size.height];
|
|
1649
|
+
let lo = -Infinity;
|
|
1650
|
+
let hi = Infinity;
|
|
1651
|
+
let ok = true;
|
|
1652
|
+
for (let i = 0; i < 2; i++) {
|
|
1653
|
+
if (Math.abs(d[i]) < 1e-12) {
|
|
1654
|
+
if (origin[i] < 0 || origin[i] > extent[i]) ok = false;
|
|
1655
|
+
} else {
|
|
1656
|
+
const t0 = -origin[i] / d[i];
|
|
1657
|
+
const t1 = (extent[i] - origin[i]) / d[i];
|
|
1658
|
+
lo = Math.max(lo, Math.min(t0, t1));
|
|
1659
|
+
hi = Math.min(hi, Math.max(t0, t1));
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (!ok || !Number.isFinite(lo) || !Number.isFinite(hi) || hi < lo) continue;
|
|
1663
|
+
out.push([{ x: g.center.x + lo * d[0], y: g.center.y + lo * d[1] }, { x: g.center.x + hi * d[0], y: g.center.y + hi * d[1] }]);
|
|
1664
|
+
}
|
|
1665
|
+
return out;
|
|
1666
|
+
}
|
|
1667
|
+
function normalizePerspective(raw, base) {
|
|
1668
|
+
const r = raw;
|
|
1669
|
+
let points = base.points;
|
|
1670
|
+
if (Array.isArray(r.points)) {
|
|
1671
|
+
if (r.points.length > 3) throw new Error(`A perspective guide has one to three vanishing points; got ${r.points.length}.`);
|
|
1672
|
+
points = r.points.map((p, i) => {
|
|
1673
|
+
const q = _nullishCoalesce(p, () => ( {}));
|
|
1674
|
+
if (!finite2(q.x) || !finite2(q.y) || Math.abs(q.x) > MAX_COORDINATE || Math.abs(q.y) > MAX_COORDINATE) {
|
|
1675
|
+
throw new Error(`points[${i}] needs finite x and y within \xB1${MAX_COORDINATE} document pixels.`);
|
|
1676
|
+
}
|
|
1677
|
+
return { x: q.x, y: q.y };
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const enabled = typeof r.enabled === "boolean" ? r.enabled : base.enabled;
|
|
1681
|
+
if (enabled && !points.length) throw new Error("An enabled perspective guide needs at least one vanishing point; pass points.");
|
|
1682
|
+
return { enabled, points, snap: typeof r.snap === "boolean" ? r.snap : base.snap };
|
|
1683
|
+
}
|
|
1684
|
+
function perspectiveDirections(g, p) {
|
|
1685
|
+
const out = [];
|
|
1686
|
+
for (const v of g.points) {
|
|
1687
|
+
const dx = v.x - p.x;
|
|
1688
|
+
const dy = v.y - p.y;
|
|
1689
|
+
const len = Math.hypot(dx, dy);
|
|
1690
|
+
if (len > 1e-6) out.push({ x: dx / len, y: dy / len });
|
|
1691
|
+
}
|
|
1692
|
+
if (g.points.length <= 2) out.push({ x: 0, y: 1 });
|
|
1693
|
+
if (g.points.length <= 1) out.push({ x: 1, y: 0 });
|
|
1694
|
+
return out;
|
|
1695
|
+
}
|
|
1696
|
+
var SNAP_DECIDE_DISTANCE = 6;
|
|
1697
|
+
var PerspectiveSnapper = (_class7 = class {
|
|
1698
|
+
constructor(guide) {;_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);
|
|
1699
|
+
this.guide = guide;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
__init27() {this.start = null}
|
|
1703
|
+
__init28() {this.dir = null}
|
|
1704
|
+
/** The line the stroke follows, once chosen. */
|
|
1705
|
+
get line() {
|
|
1706
|
+
return this.start && this.dir ? { from: this.start, direction: this.dir } : null;
|
|
1707
|
+
}
|
|
1708
|
+
snap(p) {
|
|
1709
|
+
if (!this.guide.enabled || !this.guide.snap || !this.guide.points.length) return p;
|
|
1710
|
+
if (!this.start) {
|
|
1711
|
+
this.start = { x: p.x, y: p.y };
|
|
1712
|
+
return p;
|
|
1713
|
+
}
|
|
1714
|
+
const s = this.start;
|
|
1715
|
+
if (!this.dir) {
|
|
1716
|
+
const dx = p.x - s.x;
|
|
1717
|
+
const dy = p.y - s.y;
|
|
1718
|
+
const len = Math.hypot(dx, dy);
|
|
1719
|
+
if (len < SNAP_DECIDE_DISTANCE) return { ...p, x: s.x, y: s.y };
|
|
1720
|
+
let best = null;
|
|
1721
|
+
let bestDot = -1;
|
|
1722
|
+
for (const d2 of perspectiveDirections(this.guide, s)) {
|
|
1723
|
+
const dot = Math.abs((dx * d2.x + dy * d2.y) / len);
|
|
1724
|
+
if (dot > bestDot) {
|
|
1725
|
+
bestDot = dot;
|
|
1726
|
+
best = d2;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
this.dir = best;
|
|
1730
|
+
if (!best) return p;
|
|
1731
|
+
}
|
|
1732
|
+
const d = this.dir;
|
|
1733
|
+
const t = (p.x - s.x) * d.x + (p.y - s.y) * d.y;
|
|
1734
|
+
return { ...p, x: s.x + d.x * t, y: s.y + d.y * t };
|
|
1735
|
+
}
|
|
1736
|
+
}, _class7);
|
|
1737
|
+
function perspectiveGuideLines(g, size, count = 12) {
|
|
1738
|
+
const out = [];
|
|
1739
|
+
const far = Math.hypot(size.width, size.height) * 4;
|
|
1740
|
+
for (const v of g.points) {
|
|
1741
|
+
const cx = size.width / 2 - v.x;
|
|
1742
|
+
const cy = size.height / 2 - v.y;
|
|
1743
|
+
const inside = v.x >= 0 && v.y >= 0 && v.x <= size.width && v.y <= size.height;
|
|
1744
|
+
const mid = Math.atan2(cy, cx);
|
|
1745
|
+
const spread = inside ? Math.PI : Math.min(Math.PI, 2 * Math.atan2(Math.hypot(size.width, size.height) / 2, Math.hypot(cx, cy)));
|
|
1746
|
+
for (let i = 0; i < count; i++) {
|
|
1747
|
+
const a = inside ? 2 * Math.PI * i / count : mid - spread / 2 + spread * (i + 0.5) / count;
|
|
1748
|
+
out.push([{ ...v }, { x: v.x + Math.cos(a) * far, y: v.y + Math.sin(a) * far }]);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
return out;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// src/paint-editor/color.ts
|
|
1755
|
+
var HEX2 = /^#?([0-9a-f]{6})$/i;
|
|
1756
|
+
function parseHexColor(value, what = "color") {
|
|
1757
|
+
const m = typeof value === "string" ? HEX2.exec(value.trim()) : null;
|
|
1758
|
+
if (!m) throw new Error(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
|
|
1759
|
+
const n = parseInt(m[1], 16);
|
|
1760
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
1761
|
+
}
|
|
1762
|
+
function rgbToHexColor(r, g, b) {
|
|
1763
|
+
return "#" + [r, g, b].map((v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0")).join("");
|
|
1764
|
+
}
|
|
1765
|
+
function rgbToHsv(r, g, b) {
|
|
1766
|
+
const R = r / 255;
|
|
1767
|
+
const G = g / 255;
|
|
1768
|
+
const B = b / 255;
|
|
1769
|
+
const max = Math.max(R, G, B);
|
|
1770
|
+
const min = Math.min(R, G, B);
|
|
1771
|
+
const d = max - min;
|
|
1772
|
+
let h = 0;
|
|
1773
|
+
if (d > 0) {
|
|
1774
|
+
if (max === R) h = 60 * ((G - B) / d % 6);
|
|
1775
|
+
else if (max === G) h = 60 * ((B - R) / d + 2);
|
|
1776
|
+
else h = 60 * ((R - G) / d + 4);
|
|
1777
|
+
}
|
|
1778
|
+
if (h < 0) h += 360;
|
|
1779
|
+
return { h, s: max === 0 ? 0 : d / max, v: max };
|
|
1780
|
+
}
|
|
1781
|
+
function hsvToRgb(h, s, v) {
|
|
1782
|
+
const hh = (h % 360 + 360) % 360 / 60;
|
|
1783
|
+
const c = v * s;
|
|
1784
|
+
const x = c * (1 - Math.abs(hh % 2 - 1));
|
|
1785
|
+
const m = v - c;
|
|
1786
|
+
const [r, g, b] = hh < 1 ? [c, x, 0] : hh < 2 ? [x, c, 0] : hh < 3 ? [0, c, x] : hh < 4 ? [0, x, c] : hh < 5 ? [x, 0, c] : [c, 0, x];
|
|
1787
|
+
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
|
1788
|
+
}
|
|
1789
|
+
function rgbToHsl(r, g, b) {
|
|
1790
|
+
const { h } = rgbToHsv(r, g, b);
|
|
1791
|
+
const max = Math.max(r, g, b) / 255;
|
|
1792
|
+
const min = Math.min(r, g, b) / 255;
|
|
1793
|
+
const l = (max + min) / 2;
|
|
1794
|
+
const d = max - min;
|
|
1795
|
+
return { h, s: d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1)), l };
|
|
1796
|
+
}
|
|
1797
|
+
function hslToRgb(h, s, l) {
|
|
1798
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
1799
|
+
const v = l + c / 2;
|
|
1800
|
+
return hsvToRgb(h, v === 0 ? 0 : c / v, v);
|
|
1801
|
+
}
|
|
1802
|
+
var RYB_STOPS = [
|
|
1803
|
+
[0, 0],
|
|
1804
|
+
[60, 30],
|
|
1805
|
+
[120, 60],
|
|
1806
|
+
[180, 120],
|
|
1807
|
+
[240, 240],
|
|
1808
|
+
[300, 280],
|
|
1809
|
+
[360, 360]
|
|
1810
|
+
];
|
|
1811
|
+
function piecewise(v, from) {
|
|
1812
|
+
const x = (v % 360 + 360) % 360;
|
|
1813
|
+
const to = from === 0 ? 1 : 0;
|
|
1814
|
+
for (let i = 1; i < RYB_STOPS.length; i++) {
|
|
1815
|
+
const a = RYB_STOPS[i - 1];
|
|
1816
|
+
const b = RYB_STOPS[i];
|
|
1817
|
+
if (x <= b[from]) return a[to] + (x - a[from]) / (b[from] - a[from]) * (b[to] - a[to]);
|
|
1818
|
+
}
|
|
1819
|
+
return x;
|
|
1820
|
+
}
|
|
1821
|
+
function wheelToHue(angle, wheel) {
|
|
1822
|
+
return wheel === "rgb" ? (angle % 360 + 360) % 360 : piecewise(angle, 0);
|
|
1823
|
+
}
|
|
1824
|
+
function hueToWheel(hue, wheel) {
|
|
1825
|
+
return wheel === "rgb" ? (hue % 360 + 360) % 360 : piecewise(hue, 1);
|
|
1826
|
+
}
|
|
1827
|
+
var HARMONY_KINDS = ["complementary", "split-complementary", "analogous", "triadic", "tetradic", "square", "monochromatic"];
|
|
1828
|
+
function colorHarmony(base, kind, options = {}) {
|
|
1829
|
+
const wheel = _nullishCoalesce(options.wheel, () => ( "ryb"));
|
|
1830
|
+
const [r, g, b] = parseHexColor(base, "base");
|
|
1831
|
+
const hsv = rgbToHsv(r, g, b);
|
|
1832
|
+
const start = hueToWheel(hsv.h, wheel);
|
|
1833
|
+
const at = (angle) => {
|
|
1834
|
+
const a = (angle % 360 + 360) % 360;
|
|
1835
|
+
const [rr, gg, bb] = hsvToRgb(wheelToHue(a, wheel), hsv.s, hsv.v);
|
|
1836
|
+
return { color: rgbToHexColor(rr, gg, bb), angle: Math.round(a * 10) / 10 };
|
|
1837
|
+
};
|
|
1838
|
+
const spread = _nullishCoalesce(options.spread, () => ( 30));
|
|
1839
|
+
const offsets = {
|
|
1840
|
+
complementary: [0, 180],
|
|
1841
|
+
"split-complementary": [0, 180 - spread, 180 + spread],
|
|
1842
|
+
analogous: [0, -spread, spread],
|
|
1843
|
+
triadic: [0, 120, 240],
|
|
1844
|
+
tetradic: [0, 60, 180, 240],
|
|
1845
|
+
square: [0, 90, 180, 270]
|
|
1846
|
+
};
|
|
1847
|
+
if (kind === "monochromatic") {
|
|
1848
|
+
const hsl = rgbToHsl(r, g, b);
|
|
1849
|
+
const steps = [0, -0.3, -0.15, 0.15, 0.3];
|
|
1850
|
+
return steps.map((d) => {
|
|
1851
|
+
const [rr, gg, bb] = d === 0 ? [r, g, b] : hslToRgb(hsl.h, hsl.s, Math.min(0.97, Math.max(0.03, hsl.l + d)));
|
|
1852
|
+
return { color: rgbToHexColor(rr, gg, bb), angle: Math.round(start * 10) / 10 };
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
return offsets[kind].map((o, i) => i === 0 ? { color: rgbToHexColor(r, g, b), angle: Math.round(start * 10) / 10 } : at(start + o));
|
|
1856
|
+
}
|
|
1857
|
+
var DEFAULT_SWATCHES = [
|
|
1858
|
+
{ color: "#f7f5ee", name: "Warm white" },
|
|
1859
|
+
{ color: "#f3d23a", name: "Lemon yellow" },
|
|
1860
|
+
{ color: "#c8912f", name: "Yellow ochre" },
|
|
1861
|
+
{ color: "#d23a2a", name: "Warm red" },
|
|
1862
|
+
{ color: "#9e1f3d", name: "Crimson" },
|
|
1863
|
+
{ color: "#8a4a2b", name: "Burnt earth" },
|
|
1864
|
+
{ color: "#2f3f9e", name: "Deep blue" },
|
|
1865
|
+
{ color: "#1d6f8f", name: "Cool blue" },
|
|
1866
|
+
{ color: "#4f6e2c", name: "Leaf green" },
|
|
1867
|
+
{ color: "#25221f", name: "Warm black" }
|
|
1868
|
+
];
|
|
1869
|
+
|
|
1870
|
+
// src/paint-editor/mixing-pad.ts
|
|
1871
|
+
var MIXING_PAD_WIDTH = 360;
|
|
1872
|
+
var MIXING_PAD_HEIGHT = 220;
|
|
1873
|
+
var PAD_PAINT = normalizePaintBrush({
|
|
1874
|
+
id: "pad-paint",
|
|
1875
|
+
name: "Pad paint",
|
|
1876
|
+
category: "mixer",
|
|
1877
|
+
engine: "mixer",
|
|
1878
|
+
tip: { size: 28, hardness: 0.75, spacing: 0.1, opacity: 1, flow: 0.7, pressureSize: false, pressureOpacity: false },
|
|
1879
|
+
mixer: { wet: 0.45, load: 1, mix: 0.4, loadEachStroke: true, cleanEachStroke: true, mixing: "pigment" }
|
|
1880
|
+
});
|
|
1881
|
+
var PAD_KNIFE = normalizePaintBrush({
|
|
1882
|
+
id: "pad-knife",
|
|
1883
|
+
name: "Palette knife",
|
|
1884
|
+
category: "smudge",
|
|
1885
|
+
engine: "mixer",
|
|
1886
|
+
loadsColor: false,
|
|
1887
|
+
tip: { size: 34, hardness: 0.9, spacing: 0.08, roundness: 0.35, angle: 90, opacity: 1, flow: 0.85, pressureSize: false, pressureOpacity: false },
|
|
1888
|
+
mixer: { wet: 1, load: 0, mix: 1, loadEachStroke: false, cleanEachStroke: true, mixing: "pigment" }
|
|
1889
|
+
});
|
|
1890
|
+
var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.call(this);_class8.prototype.__init30.call(this);_class8.prototype.__init31.call(this);_class8.prototype.__init32.call(this); }
|
|
1891
|
+
__init29() {this.width = MIXING_PAD_WIDTH}
|
|
1892
|
+
__init30() {this.height = MIXING_PAD_HEIGHT}
|
|
1893
|
+
__init31() {this.surface = _chunkCRMAXIUVcjs.emptySurface.call(void 0, 4)}
|
|
1894
|
+
/** Increments on every change (for redrawing). */
|
|
1895
|
+
__init32() {this.version = 0}
|
|
1896
|
+
/** Paints (`paint`, with `color`) or smears (`knife`) through `points` in pad pixels. */
|
|
1897
|
+
stroke(points, options) {
|
|
1898
|
+
if (!points.length) throw new Error("points is empty; give at least one {x, y} in pad pixels.");
|
|
1899
|
+
for (const [i, p] of points.entries()) {
|
|
1900
|
+
if (!Number.isFinite(p.x) || !Number.isFinite(p.y) || p.x < 0 || p.y < 0 || p.x > this.width || p.y > this.height) {
|
|
1901
|
+
throw new Error(`points[${i}] (${p.x}, ${p.y}) is off the ${this.width}\xD7${this.height} mixing pad.`);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
let brush = options.tool === "knife" ? PAD_KNIFE : PAD_PAINT;
|
|
1905
|
+
if (options.size !== void 0) brush = normalizePaintBrush({ tip: { ...brush.tip, size: options.size } }, brush);
|
|
1906
|
+
const [r, g, b] = hexToRgb8(options.color);
|
|
1907
|
+
let result = null;
|
|
1908
|
+
const session = new PaintStrokeSession(
|
|
1909
|
+
{ update: () => {
|
|
1910
|
+
}, commit: (_s, s) => result = s },
|
|
1911
|
+
{
|
|
1912
|
+
layerId: "mixing-pad",
|
|
1913
|
+
surface: this.surface,
|
|
1914
|
+
offset: { x: 0, y: 0 },
|
|
1915
|
+
lockTransparency: false,
|
|
1916
|
+
brush,
|
|
1917
|
+
color: [r, g, b, 255],
|
|
1918
|
+
background: [255, 255, 255, 255],
|
|
1919
|
+
grain: null,
|
|
1920
|
+
paperDepth: 0,
|
|
1921
|
+
seed: 1,
|
|
1922
|
+
colorHex: options.color,
|
|
1923
|
+
affineCopies: [],
|
|
1924
|
+
copies: [IDENTITY_COPY],
|
|
1925
|
+
mixerLoad: mixerLoadForStroke(brush, _chunkPV42JITTcjs.EMPTY_MIXER_LOAD, [r / 255, g / 255, b / 255])
|
|
1926
|
+
}
|
|
1927
|
+
);
|
|
1928
|
+
session.push(points);
|
|
1929
|
+
const res = session.end();
|
|
1930
|
+
if (result) {
|
|
1931
|
+
this.surface = result;
|
|
1932
|
+
this.version++;
|
|
1933
|
+
}
|
|
1934
|
+
return { bounds: res.bounds };
|
|
1935
|
+
}
|
|
1936
|
+
/** The average colour within `radius` pixels of (x, y), or `null` where the pad is bare. */
|
|
1937
|
+
pick(x, y, radius = 3) {
|
|
1938
|
+
if (!(x >= 0 && y >= 0 && x < this.width && y < this.height)) throw new Error(`(${x}, ${y}) is off the ${this.width}\xD7${this.height} mixing pad.`);
|
|
1939
|
+
const r = Math.max(0, Math.round(radius));
|
|
1940
|
+
const x0 = Math.max(0, Math.floor(x) - r);
|
|
1941
|
+
const y0 = Math.max(0, Math.floor(y) - r);
|
|
1942
|
+
const x1 = Math.min(this.width, Math.floor(x) + r + 1);
|
|
1943
|
+
const y1 = Math.min(this.height, Math.floor(y) + r + 1);
|
|
1944
|
+
const px = _chunkCRMAXIUVcjs.readRegion.call(void 0, this.surface, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
|
|
1945
|
+
let R = 0;
|
|
1946
|
+
let G = 0;
|
|
1947
|
+
let B = 0;
|
|
1948
|
+
let A = 0;
|
|
1949
|
+
for (let i = 0; i < px.length; i += 4) {
|
|
1950
|
+
const a = px[i + 3];
|
|
1951
|
+
R += px[i] * a;
|
|
1952
|
+
G += px[i + 1] * a;
|
|
1953
|
+
B += px[i + 2] * a;
|
|
1954
|
+
A += a;
|
|
1955
|
+
}
|
|
1956
|
+
if (A === 0) return null;
|
|
1957
|
+
const hex = (v) => Math.round(v / A).toString(16).padStart(2, "0");
|
|
1958
|
+
return `#${hex(R)}${hex(G)}${hex(B)}`;
|
|
1959
|
+
}
|
|
1960
|
+
clear() {
|
|
1961
|
+
this.surface = _chunkCRMAXIUVcjs.emptySurface.call(void 0, 4);
|
|
1962
|
+
this.version++;
|
|
1963
|
+
}
|
|
1964
|
+
/** The pad over white, `width × height` RGBA. */
|
|
1965
|
+
pixels() {
|
|
1966
|
+
const px = _chunkCRMAXIUVcjs.readRegion.call(void 0, this.surface, { x: 0, y: 0, width: this.width, height: this.height });
|
|
1967
|
+
for (let i = 0; i < px.length; i += 4) {
|
|
1968
|
+
const a = px[i + 3] / 255;
|
|
1969
|
+
px[i] = Math.round(px[i] * a + 255 * (1 - a));
|
|
1970
|
+
px[i + 1] = Math.round(px[i + 1] * a + 255 * (1 - a));
|
|
1971
|
+
px[i + 2] = Math.round(px[i + 2] * a + 255 * (1 - a));
|
|
1972
|
+
px[i + 3] = 255;
|
|
1973
|
+
}
|
|
1974
|
+
return px;
|
|
1975
|
+
}
|
|
1976
|
+
}, _class8);
|
|
1977
|
+
|
|
1565
1978
|
// src/paint-editor/controller.ts
|
|
1566
1979
|
var PaintEditorError = class extends Error {
|
|
1567
1980
|
constructor(message) {
|
|
@@ -1569,9 +1982,9 @@ var PaintEditorError = class extends Error {
|
|
|
1569
1982
|
this.name = "PaintEditorError";
|
|
1570
1983
|
}
|
|
1571
1984
|
};
|
|
1572
|
-
var
|
|
1985
|
+
var HEX3 = /^#[0-9a-f]{6}$/i;
|
|
1573
1986
|
function parseColor(value, what = "color") {
|
|
1574
|
-
if (typeof value !== "string" || !
|
|
1987
|
+
if (typeof value !== "string" || !HEX3.test(value.trim())) throw new PaintEditorError(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
|
|
1575
1988
|
return value.trim().toLowerCase();
|
|
1576
1989
|
}
|
|
1577
1990
|
function rgbaOf(hex) {
|
|
@@ -1588,33 +2001,40 @@ function unionRect(a, b) {
|
|
|
1588
2001
|
const y = Math.min(a.y, b.y);
|
|
1589
2002
|
return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
|
|
1590
2003
|
}
|
|
1591
|
-
var isPixelLayer = (l) => _optionalChain([l, 'optionalAccess',
|
|
1592
|
-
var PaintEditorController = (
|
|
2004
|
+
var isPixelLayer = (l) => _optionalChain([l, 'optionalAccess', _30 => _30.type]) === "pixel";
|
|
2005
|
+
var PaintEditorController = (_class9 = class {
|
|
1593
2006
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
2007
|
+
__init33() {this.extensions = {}}
|
|
2008
|
+
__init34() {this.undoStack = []}
|
|
2009
|
+
__init35() {this.redoStack = []}
|
|
1597
2010
|
|
|
1598
2011
|
|
|
1599
2012
|
|
|
1600
2013
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
2014
|
+
__init36() {this.secondaryColor = "#ffffff"}
|
|
2015
|
+
__init37() {this.mixerLoad = _chunkPV42JITTcjs.EMPTY_MIXER_LOAD}
|
|
2016
|
+
__init38() {this.view = { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } }}
|
|
1604
2017
|
|
|
1605
2018
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2019
|
+
__init39() {this.listeners = /* @__PURE__ */ new Set()}
|
|
2020
|
+
__init40() {this.version = 0}
|
|
2021
|
+
__init41() {this.cached = null}
|
|
2022
|
+
__init42() {this.session = null}
|
|
2023
|
+
__init43() {this.live = null}
|
|
2024
|
+
__init44() {this.liveLayers = null}
|
|
2025
|
+
__init45() {this.dirty = "all"}
|
|
2026
|
+
__init46() {this.seedCounter = 1}
|
|
2027
|
+
__init47() {this.strokeCounter = 1}
|
|
2028
|
+
|
|
2029
|
+
__init48() {this.swatches = DEFAULT_SWATCHES.map((s) => ({ ...s }))}
|
|
2030
|
+
__init49() {this.pad = new MixingPad()}
|
|
2031
|
+
__init50() {this.tracingVersion = 1}
|
|
2032
|
+
__init51() {this.tracingCache = null}
|
|
2033
|
+
__init52() {this.imagePixels = /* @__PURE__ */ new Map()}
|
|
2034
|
+
__init53() {this.nextReferenceId = 1}
|
|
2035
|
+
__init54() {this.viewCompositor = createPaintCompositor()}
|
|
2036
|
+
__init55() {this.exportCompositor = createPaintCompositor()}
|
|
2037
|
+
constructor(options = {}) {;_class9.prototype.__init33.call(this);_class9.prototype.__init34.call(this);_class9.prototype.__init35.call(this);_class9.prototype.__init36.call(this);_class9.prototype.__init37.call(this);_class9.prototype.__init38.call(this);_class9.prototype.__init39.call(this);_class9.prototype.__init40.call(this);_class9.prototype.__init41.call(this);_class9.prototype.__init42.call(this);_class9.prototype.__init43.call(this);_class9.prototype.__init44.call(this);_class9.prototype.__init45.call(this);_class9.prototype.__init46.call(this);_class9.prototype.__init47.call(this);_class9.prototype.__init48.call(this);_class9.prototype.__init49.call(this);_class9.prototype.__init50.call(this);_class9.prototype.__init51.call(this);_class9.prototype.__init52.call(this);_class9.prototype.__init53.call(this);_class9.prototype.__init54.call(this);_class9.prototype.__init55.call(this);
|
|
1618
2038
|
this.historyLimit = Math.max(1, _nullishCoalesce(options.historyLimit, () => ( 200)));
|
|
1619
2039
|
this.doc = createPaintDocument({ width: options.width, height: options.height, dpi: options.dpi, paper: this.resolvePaper(options.paper) });
|
|
1620
2040
|
this.userBrushes = (_nullishCoalesce(options.brushes, () => ( []))).map((b) => normalizePaintBrush({ ...b, builtin: false }));
|
|
@@ -1623,6 +2043,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1623
2043
|
this.color = options.color ? parseColor(options.color) : "#1f2937";
|
|
1624
2044
|
this.hand = options.hand === "left" ? "left" : "right";
|
|
1625
2045
|
this.encodeImage = options.encodeImage;
|
|
2046
|
+
this.guides = defaultGuides(this.doc.canvas);
|
|
1626
2047
|
}
|
|
1627
2048
|
// --- State and events ----------------------------------------------------------
|
|
1628
2049
|
subscribe(listener) {
|
|
@@ -1652,8 +2073,11 @@ var PaintEditorController = (_class7 = class {
|
|
|
1652
2073
|
hand: this.hand,
|
|
1653
2074
|
canUndo: this.undoStack.length > 0 && !this.session,
|
|
1654
2075
|
canRedo: this.redoStack.length > 0 && !this.session,
|
|
1655
|
-
undoLabel: _nullishCoalesce(_optionalChain([last, 'optionalAccess',
|
|
1656
|
-
redoLabel: _nullishCoalesce(_optionalChain([next, 'optionalAccess',
|
|
2076
|
+
undoLabel: _nullishCoalesce(_optionalChain([last, 'optionalAccess', _31 => _31.label]), () => ( null)),
|
|
2077
|
+
redoLabel: _nullishCoalesce(_optionalChain([next, 'optionalAccess', _32 => _32.label]), () => ( null)),
|
|
2078
|
+
guides: this.guides,
|
|
2079
|
+
swatches: this.swatches,
|
|
2080
|
+
mixingPadVersion: this.pad.version,
|
|
1657
2081
|
stroking: Boolean(this.session),
|
|
1658
2082
|
version: this.version
|
|
1659
2083
|
};
|
|
@@ -1723,6 +2147,8 @@ var PaintEditorController = (_class7 = class {
|
|
|
1723
2147
|
paper: options.paper === void 0 ? this.doc.paper : this.resolvePaper(options.paper)
|
|
1724
2148
|
});
|
|
1725
2149
|
this.extensions = {};
|
|
2150
|
+
this.guides = { ...this.guides, symmetry: { ...this.guides.symmetry, center: { x: next.canvas.width / 2, y: next.canvas.height / 2 } }, tracing: null };
|
|
2151
|
+
this.tracingVersion++;
|
|
1726
2152
|
this.commit("New canvas", next);
|
|
1727
2153
|
return next;
|
|
1728
2154
|
}
|
|
@@ -1756,7 +2182,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1756
2182
|
}
|
|
1757
2183
|
// --- Layers --------------------------------------------------------------------
|
|
1758
2184
|
getLayer(id) {
|
|
1759
|
-
return _optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, id), 'optionalAccess',
|
|
2185
|
+
return _optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, id), 'optionalAccess', _33 => _33.layer]);
|
|
1760
2186
|
}
|
|
1761
2187
|
/** The active pixel layer (or `undefined`). */
|
|
1762
2188
|
getActiveLayer() {
|
|
@@ -1775,7 +2201,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1775
2201
|
walk(this.doc.layers, null, 0);
|
|
1776
2202
|
return out;
|
|
1777
2203
|
}
|
|
1778
|
-
describeLayer(l, parentId = _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess',
|
|
2204
|
+
describeLayer(l, parentId = _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _34 => _34.parent, 'optionalAccess', _35 => _35.id]), () => ( null)), depth = _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _36 => _36.path, 'access', _37 => _37.length]), () => ( 0))) {
|
|
1779
2205
|
let bounds = null;
|
|
1780
2206
|
if (l.type === "pixel") {
|
|
1781
2207
|
const b = _chunkCRMAXIUVcjs.contentBounds.call(void 0, l.surface);
|
|
@@ -1822,7 +2248,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1822
2248
|
this.requireIdle("adding a layer");
|
|
1823
2249
|
const count = _chunkWSTEXEKDcjs.flattenLayers.call(void 0, this.doc.layers).length;
|
|
1824
2250
|
const type = _nullishCoalesce(options.type, () => ( "pixel"));
|
|
1825
|
-
const name = _optionalChain([options, 'access',
|
|
2251
|
+
const name = _optionalChain([options, 'access', _38 => _38.name, 'optionalAccess', _39 => _39.trim, 'call', _40 => _40()]) || (type === "group" ? `Group ${count + 1}` : `Layer ${count + 1}`);
|
|
1826
2252
|
const layer = type === "group" ? { type: "group", id: newPaintLayerId(), name, visible: true, opacity: 1, blend: "pass-through", children: [] } : createPaintLayer(name);
|
|
1827
2253
|
let parentId = null;
|
|
1828
2254
|
let index;
|
|
@@ -1834,7 +2260,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1834
2260
|
} else if (above) {
|
|
1835
2261
|
const loc = _chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, above);
|
|
1836
2262
|
if (!loc) throw new PaintEditorError(`Unknown layer "${above}". Call list_layers for the ids.`);
|
|
1837
|
-
parentId = _nullishCoalesce(_optionalChain([loc, 'access',
|
|
2263
|
+
parentId = _nullishCoalesce(_optionalChain([loc, 'access', _41 => _41.parent, 'optionalAccess', _42 => _42.id]), () => ( null));
|
|
1838
2264
|
index = loc.index + 1;
|
|
1839
2265
|
}
|
|
1840
2266
|
const layers = _chunkWSTEXEKDcjs.insertLayer.call(void 0, this.doc.layers, layer, parentId, index);
|
|
@@ -1869,7 +2295,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1869
2295
|
if (_chunkWSTEXEKDcjs.isLayerLocked.call(void 0, l, "all")) throw new PaintEditorError(`Layer "${l.name}" is fully locked; unlock it before removing it.`);
|
|
1870
2296
|
const layers = _chunkWSTEXEKDcjs.mapLayer.call(void 0, this.doc.layers, id, () => null);
|
|
1871
2297
|
let active = this.doc.activeLayerId;
|
|
1872
|
-
if (active && !_chunkWSTEXEKDcjs.findLayer.call(void 0, layers, active)) active = _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.flattenLayers.call(void 0, layers), 'access',
|
|
2298
|
+
if (active && !_chunkWSTEXEKDcjs.findLayer.call(void 0, layers, active)) active = _nullishCoalesce(_optionalChain([_chunkWSTEXEKDcjs.flattenLayers.call(void 0, layers), 'access', _43 => _43.filter, 'call', _44 => _44((x) => x.type === "pixel"), 'access', _45 => _45.pop, 'call', _46 => _46(), 'optionalAccess', _47 => _47.id]), () => ( null));
|
|
1873
2299
|
this.commit("Remove layer", { ...this.doc, layers, activeLayerId: active });
|
|
1874
2300
|
return { removed: id, activeLayerId: active };
|
|
1875
2301
|
}
|
|
@@ -1879,7 +2305,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1879
2305
|
if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
|
|
1880
2306
|
const copy = (l) => l.type === "group" ? { ...l, id: newPaintLayerId(), children: l.children.map(copy) } : { ...l, id: newPaintLayerId() };
|
|
1881
2307
|
const dup = { ...copy(loc.layer), name: `${loc.layer.name} copy` };
|
|
1882
|
-
const layers = _chunkWSTEXEKDcjs.insertLayer.call(void 0, this.doc.layers, dup, _nullishCoalesce(_optionalChain([loc, 'access',
|
|
2308
|
+
const layers = _chunkWSTEXEKDcjs.insertLayer.call(void 0, this.doc.layers, dup, _nullishCoalesce(_optionalChain([loc, 'access', _48 => _48.parent, 'optionalAccess', _49 => _49.id]), () => ( null)), loc.index + 1);
|
|
1883
2309
|
this.commit("Duplicate layer", { ...this.doc, layers, activeLayerId: dup.id });
|
|
1884
2310
|
return this.describeLayer(dup);
|
|
1885
2311
|
}
|
|
@@ -1888,7 +2314,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1888
2314
|
this.requireIdle("moving a layer");
|
|
1889
2315
|
const loc = _chunkWSTEXEKDcjs.findLayer.call(void 0, this.doc.layers, id);
|
|
1890
2316
|
if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
|
|
1891
|
-
const target = parentId === void 0 ? _nullishCoalesce(_optionalChain([loc, 'access',
|
|
2317
|
+
const target = parentId === void 0 ? _nullishCoalesce(_optionalChain([loc, 'access', _50 => _50.parent, 'optionalAccess', _51 => _51.id]), () => ( null)) : parentId;
|
|
1892
2318
|
if (target) {
|
|
1893
2319
|
const g = this.requireLayer(target);
|
|
1894
2320
|
if (g.type !== "group") throw new PaintEditorError(`Layer "${g.name}" is not a group; pass a group id as parentId.`);
|
|
@@ -1976,7 +2402,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
1976
2402
|
const tiles = new Map(base.tiles);
|
|
1977
2403
|
for (const { tx, ty } of _chunkCRMAXIUVcjs.tilesInRect.call(void 0, { x: rect.x - l.offset.x, y: rect.y - l.offset.y, width: rect.width, height: rect.height })) {
|
|
1978
2404
|
const key = _chunkCRMAXIUVcjs.tileKey.call(void 0, tx, ty);
|
|
1979
|
-
const src = _nullishCoalesce(_optionalChain([base, 'access',
|
|
2405
|
+
const src = _nullishCoalesce(_optionalChain([base, 'access', _52 => _52.tiles, 'access', _53 => _53.get, 'call', _54 => _54(key), 'optionalAccess', _55 => _55.data]), () => ( new Uint8ClampedArray(_chunkCRMAXIUVcjs.TILE * _chunkCRMAXIUVcjs.TILE * 4)));
|
|
1980
2406
|
const tile = _chunkCRMAXIUVcjs.finishTile.call(void 0, _chunkPV42JITTcjs.applyStrokeToTile.call(void 0, coverage, src, 4, tx, ty, apply), 4, 0);
|
|
1981
2407
|
if (tile) tiles.set(key, tile);
|
|
1982
2408
|
else tiles.delete(key);
|
|
@@ -2145,7 +2571,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2145
2571
|
}
|
|
2146
2572
|
/** Extra copies every new stroke paints (the painting guides add mirror and kaleidoscope copies). */
|
|
2147
2573
|
strokeCopies() {
|
|
2148
|
-
return
|
|
2574
|
+
return symmetryCopies(this.guides.symmetry);
|
|
2149
2575
|
}
|
|
2150
2576
|
sessionFor(doc, init, host) {
|
|
2151
2577
|
const layer = (() => {
|
|
@@ -2160,7 +2586,11 @@ var PaintEditorController = (_class7 = class {
|
|
|
2160
2586
|
return l;
|
|
2161
2587
|
})();
|
|
2162
2588
|
const brush = this.strokeBrush(init);
|
|
2163
|
-
|
|
2589
|
+
let color = init.color === void 0 ? this.color : parseColor(init.color);
|
|
2590
|
+
const tracing = this.guides.tracing;
|
|
2591
|
+
if (init.color === void 0 && init.start && _optionalChain([tracing, 'optionalAccess', _56 => _56.visible]) && tracing.sampleColors) color = _nullishCoalesce(this.sampleTracingColor(init.start.x, init.start.y), () => ( color));
|
|
2592
|
+
const perspective = this.guides.perspective;
|
|
2593
|
+
const snapper = init.snapToPerspective && perspective.enabled && perspective.snap ? new PerspectiveSnapper(perspective) : null;
|
|
2164
2594
|
const rgba = rgbaOf(color);
|
|
2165
2595
|
const seed = _nullishCoalesce(init.seed, () => ( mix32(this.seedCounter++ * 2654435761 >>> 0) & 2147483647));
|
|
2166
2596
|
const copies = (_nullishCoalesce(init.copies, () => ( this.strokeCopies()))).filter((m) => m.some((v, i) => v !== IDENTITY_AFFINE[i]));
|
|
@@ -2178,6 +2608,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2178
2608
|
colorHex: color,
|
|
2179
2609
|
affineCopies: copies,
|
|
2180
2610
|
copies: [IDENTITY_AFFINE, ...copies].map(affineCopy),
|
|
2611
|
+
inputFilter: snapper ? (p) => snapper.snap(p) : void 0,
|
|
2181
2612
|
mixerLoad: brush.engine === "mixer" ? mixerLoadForStroke(brush, this.mixerLoad, [rgba[0] / 255, rgba[1] / 255, rgba[2] / 255]) : _chunkPV42JITTcjs.EMPTY_MIXER_LOAD
|
|
2182
2613
|
});
|
|
2183
2614
|
return session;
|
|
@@ -2211,7 +2642,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2211
2642
|
this.live = null;
|
|
2212
2643
|
this.liveLayers = null;
|
|
2213
2644
|
const brush = s.init.brush;
|
|
2214
|
-
if (!cancelled && _optionalChain([s, 'access',
|
|
2645
|
+
if (!cancelled && _optionalChain([s, 'access', _57 => _57.result, 'optionalAccess', _58 => _58.mixerLoad]) && brush.loadsColor) this.mixerLoad = s.result.mixerLoad;
|
|
2215
2646
|
if (cancelled || !surface) {
|
|
2216
2647
|
this.emit();
|
|
2217
2648
|
return;
|
|
@@ -2222,7 +2653,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2222
2653
|
this.commit(`${brush.name} stroke`, { ...this.doc, layers: _chunkWSTEXEKDcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface })), strokes: [...this.doc.strokes, stroke] });
|
|
2223
2654
|
}
|
|
2224
2655
|
};
|
|
2225
|
-
const session = this.sessionFor(this.doc, init, host);
|
|
2656
|
+
const session = this.sessionFor(this.doc, { snapToPerspective: true, ...init }, host);
|
|
2226
2657
|
this.session = session;
|
|
2227
2658
|
this.emit([]);
|
|
2228
2659
|
return session;
|
|
@@ -2254,7 +2685,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2254
2685
|
paintStroke(init) {
|
|
2255
2686
|
this.requireIdle("painting");
|
|
2256
2687
|
const points = this.checkPoints(init.points);
|
|
2257
|
-
const { doc, result, brush } = this.runStroke(this.doc, init, points);
|
|
2688
|
+
const { doc, result, brush } = this.runStroke(this.doc, { ...init, start: _nullishCoalesce(init.start, () => ( points[0])) }, points);
|
|
2258
2689
|
if (result.mixerLoad && brush.loadsColor) this.mixerLoad = result.mixerLoad;
|
|
2259
2690
|
if (doc !== this.doc) this.commit(`${brush.name} stroke`, doc);
|
|
2260
2691
|
else this.emit([]);
|
|
@@ -2280,7 +2711,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2280
2711
|
*/
|
|
2281
2712
|
replayStrokes(options) {
|
|
2282
2713
|
this.requireIdle("replaying");
|
|
2283
|
-
if (!_optionalChain([options, 'access',
|
|
2714
|
+
if (!_optionalChain([options, 'access', _59 => _59.strokeIds, 'optionalAccess', _60 => _60.length])) throw new PaintEditorError("strokeIds is empty; pass ids from list_strokes.");
|
|
2284
2715
|
const strokes = options.strokeIds.map((id) => {
|
|
2285
2716
|
const s = this.doc.strokes.find((x) => x.id === id);
|
|
2286
2717
|
if (!s) throw new PaintEditorError(`Unknown stroke "${id}". Call list_strokes for the ids.`);
|
|
@@ -2332,14 +2763,27 @@ var PaintEditorController = (_class7 = class {
|
|
|
2332
2763
|
currentLayers() {
|
|
2333
2764
|
const live = this.live;
|
|
2334
2765
|
if (!live) return this.doc.layers;
|
|
2335
|
-
if (_optionalChain([this, 'access',
|
|
2766
|
+
if (_optionalChain([this, 'access', _61 => _61.liveLayers, 'optionalAccess', _62 => _62.key]) === live.surface) return this.liveLayers.layers;
|
|
2336
2767
|
const layers = _chunkWSTEXEKDcjs.mapLayer.call(void 0, this.doc.layers, live.layerId, (l) => ({ ...l, surface: live.surface }));
|
|
2337
2768
|
this.liveLayers = { key: live.surface, layers };
|
|
2338
2769
|
return layers;
|
|
2339
2770
|
}
|
|
2340
2771
|
/** View-only layers between the paper and the paint (the painting guides add the tracing image). */
|
|
2341
2772
|
underlayLayers() {
|
|
2342
|
-
|
|
2773
|
+
const t = this.guides.tracing;
|
|
2774
|
+
if (!t || !t.visible) return [];
|
|
2775
|
+
return [this.tracingNode(t)];
|
|
2776
|
+
}
|
|
2777
|
+
/** The tracing image resampled onto the canvas (cached until it or the canvas changes). */
|
|
2778
|
+
tracingNode(t) {
|
|
2779
|
+
const { width, height } = this.doc.canvas;
|
|
2780
|
+
const key = this.tracingVersion * 100003 + width * 7 + height;
|
|
2781
|
+
if (_optionalChain([this, 'access', _63 => _63.tracingCache, 'optionalAccess', _64 => _64.key]) === key && this.tracingCache.node.opacity === t.opacity) return this.tracingCache.node;
|
|
2782
|
+
const src = _nullishCoalesce(this.imagePixels.get(t.src), () => ( this.decodeImage(t.src)));
|
|
2783
|
+
const image = resampleInto(src, t.rect, width, height);
|
|
2784
|
+
const node = { type: "image", id: TRACING_NODE_ID, name: "Tracing", visible: true, opacity: t.opacity, blend: "normal", image, version: key + Math.round(t.opacity * 1e3) / 1e4 };
|
|
2785
|
+
this.tracingCache = { key, node };
|
|
2786
|
+
return node;
|
|
2343
2787
|
}
|
|
2344
2788
|
renderLayers(options, view = false) {
|
|
2345
2789
|
let layers = this.currentLayers();
|
|
@@ -2359,7 +2803,7 @@ var PaintEditorController = (_class7 = class {
|
|
|
2359
2803
|
*/
|
|
2360
2804
|
compositeTiles(rect, onTile, options = {}) {
|
|
2361
2805
|
const dirty = options.dirty === "all" ? void 0 : options.dirty;
|
|
2362
|
-
this.viewCompositor.compositeTiles(this.renderLayers({}, true), rect, this.sources(), onTile, { dirty, checkpointLayerId: _nullishCoalesce(_optionalChain([this, 'access',
|
|
2806
|
+
this.viewCompositor.compositeTiles(this.renderLayers({}, true), rect, this.sources(), onTile, { dirty, checkpointLayerId: _nullishCoalesce(_optionalChain([this, 'access', _65 => _65.live, 'optionalAccess', _66 => _66.layerId]), () => ( this.doc.activeLayerId)) });
|
|
2363
2807
|
}
|
|
2364
2808
|
/** The canvas as canvas-sized RGBA (paper included by default). */
|
|
2365
2809
|
compositePixels(options = {}) {
|
|
@@ -2380,16 +2824,20 @@ var PaintEditorController = (_class7 = class {
|
|
|
2380
2824
|
// --- Save, load and export -------------------------------------------------------
|
|
2381
2825
|
/** The document as JSON with inline PNG assets. */
|
|
2382
2826
|
toJSON(options = {}) {
|
|
2383
|
-
return serializePaintDocument(this.doc, { strokes: options.strokes, extensions: this.extensions });
|
|
2827
|
+
return serializePaintDocument(this.doc, { strokes: options.strokes, extensions: this.extensions, guides: this.guides, swatches: this.swatches });
|
|
2384
2828
|
}
|
|
2385
2829
|
/** The document as JSON with each PNG stored in `store`. */
|
|
2386
2830
|
toJSONExternal(store, options = {}) {
|
|
2387
|
-
return serializePaintDocumentExternal(this.doc, store, { strokes: options.strokes, extensions: this.extensions });
|
|
2831
|
+
return serializePaintDocumentExternal(this.doc, store, { strokes: options.strokes, extensions: this.extensions, guides: this.guides, swatches: this.swatches });
|
|
2388
2832
|
}
|
|
2389
2833
|
adopt(loaded) {
|
|
2390
2834
|
this.extensions = loaded.extensions;
|
|
2835
|
+
this.readGuides(loaded.guides, loaded.state, loaded.problems);
|
|
2836
|
+
if (Array.isArray(loaded.swatches)) {
|
|
2837
|
+
this.swatches = loaded.swatches.filter((x) => Boolean(x) && typeof x.color === "string" && /^#[0-9a-f]{6}$/i.test(x.color)).map((x) => typeof x.name === "string" ? { color: x.color.toLowerCase(), name: x.name } : { color: x.color.toLowerCase() });
|
|
2838
|
+
}
|
|
2391
2839
|
for (const s of loaded.state.strokes) {
|
|
2392
|
-
const n = Number(_nullishCoalesce(_optionalChain([/^s(\d+)$/, 'access',
|
|
2840
|
+
const n = Number(_nullishCoalesce(_optionalChain([/^s(\d+)$/, 'access', _67 => _67.exec, 'call', _68 => _68(s.id), 'optionalAccess', _69 => _69[1]]), () => ( 0)));
|
|
2393
2841
|
if (n >= this.strokeCounter) this.strokeCounter = n + 1;
|
|
2394
2842
|
}
|
|
2395
2843
|
this.undoStack = [];
|
|
@@ -2476,20 +2924,630 @@ var PaintEditorController = (_class7 = class {
|
|
|
2476
2924
|
const img = this.renderPreview(options);
|
|
2477
2925
|
return { data: _chunkWSTEXEKDcjs.bytesToBase64.call(void 0, _chunkWSTEXEKDcjs.encodePng.call(void 0, img.width, img.height, img.data)), width: img.width, height: img.height };
|
|
2478
2926
|
}
|
|
2927
|
+
// --- Guides --------------------------------------------------------------------
|
|
2928
|
+
setGuides(next) {
|
|
2929
|
+
this.guides = next;
|
|
2930
|
+
this.emit("all");
|
|
2931
|
+
return next;
|
|
2932
|
+
}
|
|
2933
|
+
/** Mirror and kaleidoscope painting: every new stroke paints its copies too (one stroke, one undo step). */
|
|
2934
|
+
setSymmetry(update) {
|
|
2935
|
+
const symmetry = normalizeSymmetry(update, this.guides.symmetry);
|
|
2936
|
+
this.setGuides({ ...this.guides, symmetry });
|
|
2937
|
+
return symmetry;
|
|
2938
|
+
}
|
|
2939
|
+
/** Perspective guides with one to three vanishing points that pointer strokes snap to. */
|
|
2940
|
+
setPerspective(update) {
|
|
2941
|
+
let perspective;
|
|
2942
|
+
try {
|
|
2943
|
+
perspective = normalizePerspective(update, this.guides.perspective);
|
|
2944
|
+
} catch (e) {
|
|
2945
|
+
throw new PaintEditorError(e.message);
|
|
2946
|
+
}
|
|
2947
|
+
this.setGuides({ ...this.guides, perspective });
|
|
2948
|
+
return perspective;
|
|
2949
|
+
}
|
|
2950
|
+
/** An image from a PNG (base64 or data URL) or from RGBA pixels, stored as a PNG data URL. */
|
|
2951
|
+
importImage(input, what) {
|
|
2952
|
+
let pixels;
|
|
2953
|
+
if (input.pixels) {
|
|
2954
|
+
const { width, height, data } = input.pixels;
|
|
2955
|
+
if (!(width > 0 && height > 0 && Number.isInteger(width) && Number.isInteger(height)) || data.length !== width * height * 4) {
|
|
2956
|
+
throw new PaintEditorError(`${what}: pixels must be width \xD7 height \xD7 4 bytes of RGBA.`);
|
|
2957
|
+
}
|
|
2958
|
+
pixels = { width, height, data: new Uint8ClampedArray(data) };
|
|
2959
|
+
} else if (typeof input.png === "string" && input.png.trim()) {
|
|
2960
|
+
pixels = this.decodeImage(input.png.trim().startsWith("data:") ? input.png.trim() : `data:image/png;base64,${input.png.trim()}`, what);
|
|
2961
|
+
} else throw new PaintEditorError(`${what}: pass png (a PNG as base64 or a data URL) or pixels.`);
|
|
2962
|
+
if (pixels.width > MAX_IMAGE_SIDE || pixels.height > MAX_IMAGE_SIDE) throw new PaintEditorError(`${what} is ${pixels.width}\xD7${pixels.height}; the limit is ${MAX_IMAGE_SIDE} px per side.`);
|
|
2963
|
+
const src = _chunkWSTEXEKDcjs.pngDataUrl.call(void 0, pixels.width, pixels.height, pixels.data);
|
|
2964
|
+
this.imagePixels.set(src, pixels);
|
|
2965
|
+
return { src, pixels };
|
|
2966
|
+
}
|
|
2967
|
+
decodeImage(src, what = "Image") {
|
|
2968
|
+
const cached = this.imagePixels.get(src);
|
|
2969
|
+
if (cached) return cached;
|
|
2970
|
+
const parsed = _chunkWSTEXEKDcjs.parseDataUrl.call(void 0, src);
|
|
2971
|
+
if (!parsed) throw new PaintEditorError(`${what} is not a data URL or base64 PNG.`);
|
|
2972
|
+
try {
|
|
2973
|
+
const png = _chunkWSTEXEKDcjs.decodePng.call(void 0, parsed.bytes);
|
|
2974
|
+
const px = { width: png.width, height: png.height, data: png.data };
|
|
2975
|
+
this.imagePixels.set(src, px);
|
|
2976
|
+
return px;
|
|
2977
|
+
} catch (e) {
|
|
2978
|
+
throw new PaintEditorError(`${what} could not be decoded as a PNG (${e.message}); decode other formats in the browser and pass pixels.`);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Puts an image under the paint to trace over: view only, never in exports. Placed to fit the
|
|
2983
|
+
* canvas unless `rect` is given; `sampleColors` makes strokes take their colour from it.
|
|
2984
|
+
*/
|
|
2985
|
+
setTracingImage(options) {
|
|
2986
|
+
const { src, pixels } = this.importImage(options, "The tracing image");
|
|
2987
|
+
const { width, height } = this.doc.canvas;
|
|
2988
|
+
const k = Math.min(width / pixels.width, height / pixels.height);
|
|
2989
|
+
const fit = { x: (width - pixels.width * k) / 2, y: (height - pixels.height * k) / 2, width: pixels.width * k, height: pixels.height * k };
|
|
2990
|
+
const tracing = { src, width: pixels.width, height: pixels.height, rect: fit, opacity: 0.35, visible: true, sampleColors: false };
|
|
2991
|
+
return this.updateTracing({ ...options, rect: _nullishCoalesce(options.rect, () => ( fit)) }, tracing);
|
|
2992
|
+
}
|
|
2993
|
+
/** Changes the tracing image's opacity, visibility, placement or colour sampling. */
|
|
2994
|
+
updateTracing(update, base = this.guides.tracing) {
|
|
2995
|
+
if (!base) throw new PaintEditorError("There is no tracing image; set one with set_tracing_image first.");
|
|
2996
|
+
if (update.opacity !== void 0 && !(update.opacity >= 0 && update.opacity <= 1)) throw new PaintEditorError(`opacity ${update.opacity} is out of range; use 0..1.`);
|
|
2997
|
+
if (update.rect && !(update.rect.width > 0 && update.rect.height > 0)) throw new PaintEditorError("rect needs a width and height above 0.");
|
|
2998
|
+
const tracing = {
|
|
2999
|
+
...base,
|
|
3000
|
+
...update.opacity !== void 0 ? { opacity: update.opacity } : {},
|
|
3001
|
+
...update.visible !== void 0 ? { visible: update.visible } : {},
|
|
3002
|
+
...update.sampleColors !== void 0 ? { sampleColors: update.sampleColors } : {},
|
|
3003
|
+
...update.rect ? { rect: { ...update.rect } } : {}
|
|
3004
|
+
};
|
|
3005
|
+
if (tracing.src !== base.src || update.rect) this.tracingVersion++;
|
|
3006
|
+
if (base !== this.guides.tracing) this.tracingVersion++;
|
|
3007
|
+
this.setGuides({ ...this.guides, tracing });
|
|
3008
|
+
return tracing;
|
|
3009
|
+
}
|
|
3010
|
+
clearTracingImage() {
|
|
3011
|
+
this.tracingVersion++;
|
|
3012
|
+
this.setGuides({ ...this.guides, tracing: null });
|
|
3013
|
+
}
|
|
3014
|
+
/** The tracing image's colour at a document pixel, or `null` outside it or where it is clear. */
|
|
3015
|
+
sampleTracingColor(x, y) {
|
|
3016
|
+
const t = this.guides.tracing;
|
|
3017
|
+
if (!t) return null;
|
|
3018
|
+
const src = this.decodeImage(t.src);
|
|
3019
|
+
const u = Math.floor((x - t.rect.x) / t.rect.width * src.width);
|
|
3020
|
+
const v = Math.floor((y - t.rect.y) / t.rect.height * src.height);
|
|
3021
|
+
if (u < 0 || v < 0 || u >= src.width || v >= src.height) return null;
|
|
3022
|
+
const i = (v * src.width + u) * 4;
|
|
3023
|
+
if (src.data[i + 3] === 0) return null;
|
|
3024
|
+
return rgbToHex(src.data[i], src.data[i + 1], src.data[i + 2]);
|
|
3025
|
+
}
|
|
3026
|
+
/** Adds a floating reference image to look at and pick colours from. */
|
|
3027
|
+
addReferenceImage(options) {
|
|
3028
|
+
const { src, pixels } = this.importImage(options, "The reference image");
|
|
3029
|
+
const ref = {
|
|
3030
|
+
id: `ref${this.nextReferenceId++}`,
|
|
3031
|
+
name: _optionalChain([options, 'access', _70 => _70.name, 'optionalAccess', _71 => _71.trim, 'call', _72 => _72()]) || `Reference ${this.guides.references.length + 1}`,
|
|
3032
|
+
src,
|
|
3033
|
+
width: pixels.width,
|
|
3034
|
+
height: pixels.height,
|
|
3035
|
+
x: _nullishCoalesce(options.x, () => ( 16 + this.guides.references.length * 24)),
|
|
3036
|
+
y: _nullishCoalesce(options.y, () => ( 16 + this.guides.references.length * 24)),
|
|
3037
|
+
displayWidth: _nullishCoalesce(options.displayWidth, () => ( Math.min(240, pixels.width))),
|
|
3038
|
+
visible: true
|
|
3039
|
+
};
|
|
3040
|
+
this.setGuides({ ...this.guides, references: [...this.guides.references, ref] });
|
|
3041
|
+
return ref;
|
|
3042
|
+
}
|
|
3043
|
+
requireReference(id) {
|
|
3044
|
+
const r = this.guides.references.find((x) => x.id === id);
|
|
3045
|
+
if (!r) throw new PaintEditorError(`Unknown reference image "${id}". Call list_reference_images for the ids.`);
|
|
3046
|
+
return r;
|
|
3047
|
+
}
|
|
3048
|
+
/** Moves, resizes, renames, shows or hides a reference image. */
|
|
3049
|
+
updateReferenceImage(id, update) {
|
|
3050
|
+
const r = this.requireReference(id);
|
|
3051
|
+
if (update.displayWidth !== void 0 && !(update.displayWidth >= 16 && update.displayWidth <= 4096)) throw new PaintEditorError(`displayWidth ${update.displayWidth} is out of range; use 16..4096.`);
|
|
3052
|
+
const next = { ...r, ...Object.fromEntries(Object.entries(update).filter(([, v]) => v !== void 0)) };
|
|
3053
|
+
this.setGuides({ ...this.guides, references: this.guides.references.map((x) => x.id === id ? next : x) });
|
|
3054
|
+
return next;
|
|
3055
|
+
}
|
|
3056
|
+
removeReferenceImage(id) {
|
|
3057
|
+
this.requireReference(id);
|
|
3058
|
+
this.setGuides({ ...this.guides, references: this.guides.references.filter((x) => x.id !== id) });
|
|
3059
|
+
}
|
|
3060
|
+
/** A reference image's pixels (decoded once). */
|
|
3061
|
+
referencePixels(id) {
|
|
3062
|
+
return this.decodeImage(this.requireReference(id).src);
|
|
3063
|
+
}
|
|
3064
|
+
/** The colour of a reference image at its pixel (x, y). */
|
|
3065
|
+
pickReferenceColor(id, x, y) {
|
|
3066
|
+
const r = this.requireReference(id);
|
|
3067
|
+
const px = this.decodeImage(r.src);
|
|
3068
|
+
const u = Math.floor(x);
|
|
3069
|
+
const v = Math.floor(y);
|
|
3070
|
+
if (!(u >= 0 && v >= 0 && u < px.width && v < px.height)) throw new PaintEditorError(`(${x}, ${y}) is outside reference "${r.name}" (${px.width}\xD7${px.height}); use its own pixel coordinates.`);
|
|
3071
|
+
const i = (v * px.width + u) * 4;
|
|
3072
|
+
return rgbToHex(px.data[i], px.data[i + 1], px.data[i + 2]);
|
|
3073
|
+
}
|
|
3074
|
+
readGuides(raw, state, problems) {
|
|
3075
|
+
const base = defaultGuides(state.canvas);
|
|
3076
|
+
this.guides = base;
|
|
3077
|
+
this.tracingVersion++;
|
|
3078
|
+
if (!raw || typeof raw !== "object") return;
|
|
3079
|
+
const r = raw;
|
|
3080
|
+
const guides = { ...base };
|
|
3081
|
+
guides.symmetry = normalizeSymmetry(_nullishCoalesce(r.symmetry, () => ( {})), base.symmetry);
|
|
3082
|
+
try {
|
|
3083
|
+
guides.perspective = normalizePerspective(_nullishCoalesce(r.perspective, () => ( {})), base.perspective);
|
|
3084
|
+
} catch (e) {
|
|
3085
|
+
problems.push(`Perspective guide: ${e.message}`);
|
|
3086
|
+
}
|
|
3087
|
+
const t = r.tracing;
|
|
3088
|
+
if (t && typeof t.src === "string") {
|
|
3089
|
+
try {
|
|
3090
|
+
const px = this.decodeImage(t.src, "The tracing image");
|
|
3091
|
+
guides.tracing = {
|
|
3092
|
+
src: t.src,
|
|
3093
|
+
width: px.width,
|
|
3094
|
+
height: px.height,
|
|
3095
|
+
rect: t.rect && t.rect.width > 0 && t.rect.height > 0 ? { ...t.rect } : { x: 0, y: 0, width: state.canvas.width, height: state.canvas.height },
|
|
3096
|
+
opacity: typeof t.opacity === "number" ? Math.min(1, Math.max(0, t.opacity)) : 0.35,
|
|
3097
|
+
visible: t.visible !== false,
|
|
3098
|
+
sampleColors: t.sampleColors === true
|
|
3099
|
+
};
|
|
3100
|
+
} catch (e) {
|
|
3101
|
+
problems.push(e.message);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
if (Array.isArray(r.references)) {
|
|
3105
|
+
for (const ref of r.references) {
|
|
3106
|
+
if (!ref || typeof ref.src !== "string" || typeof ref.id !== "string") continue;
|
|
3107
|
+
try {
|
|
3108
|
+
const px = this.decodeImage(ref.src, `Reference "${ref.name}"`);
|
|
3109
|
+
guides.references.push({ id: ref.id, name: String(_nullishCoalesce(ref.name, () => ( ref.id))), src: ref.src, width: px.width, height: px.height, x: Number(ref.x) || 0, y: Number(ref.y) || 0, displayWidth: Number(ref.displayWidth) || 240, visible: ref.visible !== false });
|
|
3110
|
+
const n = Number(_nullishCoalesce(_optionalChain([/^ref(\d+)$/, 'access', _73 => _73.exec, 'call', _74 => _74(ref.id), 'optionalAccess', _75 => _75[1]]), () => ( 0)));
|
|
3111
|
+
if (n >= this.nextReferenceId) this.nextReferenceId = n + 1;
|
|
3112
|
+
} catch (e) {
|
|
3113
|
+
problems.push(e.message);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
this.guides = guides;
|
|
3118
|
+
}
|
|
3119
|
+
// --- Swatches and harmonies ------------------------------------------------------
|
|
3120
|
+
/** Colour harmonies around a colour (default the paint colour) on the painter's RYB wheel or the RGB wheel. */
|
|
3121
|
+
harmony(kind, options = {}) {
|
|
3122
|
+
try {
|
|
3123
|
+
return colorHarmony(options.color === void 0 ? this.color : parseColor(options.color), kind, options);
|
|
3124
|
+
} catch (e) {
|
|
3125
|
+
throw new PaintEditorError(e.message);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
addSwatch(color, name) {
|
|
3129
|
+
const c = parseColor(color);
|
|
3130
|
+
this.swatches = [...this.swatches, _optionalChain([name, 'optionalAccess', _76 => _76.trim, 'call', _77 => _77()]) ? { color: c, name: name.trim() } : { color: c }];
|
|
3131
|
+
this.emit([]);
|
|
3132
|
+
return this.swatches;
|
|
3133
|
+
}
|
|
3134
|
+
removeSwatch(index) {
|
|
3135
|
+
if (!Number.isInteger(index) || index < 0 || index >= this.swatches.length) throw new PaintEditorError(`index ${index} is out of range; there are ${this.swatches.length} swatches (0-based).`);
|
|
3136
|
+
this.swatches = this.swatches.filter((_, i) => i !== index);
|
|
3137
|
+
this.emit([]);
|
|
3138
|
+
return this.swatches;
|
|
3139
|
+
}
|
|
3140
|
+
setSwatches(swatches) {
|
|
3141
|
+
this.swatches = swatches.map((x) => x.name ? { color: parseColor(x.color), name: x.name } : { color: parseColor(x.color) });
|
|
3142
|
+
this.emit([]);
|
|
3143
|
+
return this.swatches;
|
|
3144
|
+
}
|
|
3145
|
+
// --- Mixing pad ------------------------------------------------------------------
|
|
3146
|
+
get mixingPad() {
|
|
3147
|
+
return this.pad;
|
|
3148
|
+
}
|
|
3149
|
+
/** Paints (`paint`) or smears with the palette knife (`knife`) on the mixing pad, in pad pixels. */
|
|
3150
|
+
mixingPadStroke(points, options = {}) {
|
|
3151
|
+
const color = options.color === void 0 ? this.color : parseColor(options.color);
|
|
3152
|
+
if (options.size !== void 0 && !(options.size >= 1 && options.size <= 200)) throw new PaintEditorError(`size ${options.size} is out of range; use 1..200 px.`);
|
|
3153
|
+
try {
|
|
3154
|
+
const r = this.pad.stroke(points, { tool: _nullishCoalesce(options.tool, () => ( "paint")), color, size: options.size });
|
|
3155
|
+
this.emit([]);
|
|
3156
|
+
return r;
|
|
3157
|
+
} catch (e) {
|
|
3158
|
+
throw new PaintEditorError(e.message);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
/** Picks the mixed colour on the pad (averaged over `radius` px); with `setColor` it becomes the paint colour. */
|
|
3162
|
+
mixingPadPick(x, y, options = {}) {
|
|
3163
|
+
let c;
|
|
3164
|
+
try {
|
|
3165
|
+
c = this.pad.pick(x, y, options.radius);
|
|
3166
|
+
} catch (e) {
|
|
3167
|
+
throw new PaintEditorError(e.message);
|
|
3168
|
+
}
|
|
3169
|
+
if (c && options.setColor) this.setColor(c);
|
|
3170
|
+
return c;
|
|
3171
|
+
}
|
|
3172
|
+
clearMixingPad() {
|
|
3173
|
+
this.pad.clear();
|
|
3174
|
+
this.emit([]);
|
|
3175
|
+
}
|
|
2479
3176
|
/** A layer's tile surface (live while stroking), for thumbnails. */
|
|
2480
3177
|
layerSurface(id) {
|
|
2481
|
-
const l = _optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.currentLayers(), id), 'optionalAccess',
|
|
3178
|
+
const l = _optionalChain([_chunkWSTEXEKDcjs.findLayer.call(void 0, this.currentLayers(), id), 'optionalAccess', _78 => _78.layer]);
|
|
2482
3179
|
return isPixelLayer(l) ? l.surface : null;
|
|
2483
3180
|
}
|
|
2484
|
-
},
|
|
3181
|
+
}, _class9);
|
|
3182
|
+
var MAX_IMAGE_SIDE = 8192;
|
|
3183
|
+
function resampleInto(src, rect, width, height) {
|
|
3184
|
+
const out = new Uint8ClampedArray(width * height * 4);
|
|
3185
|
+
const x0 = Math.max(0, Math.floor(rect.x));
|
|
3186
|
+
const y0 = Math.max(0, Math.floor(rect.y));
|
|
3187
|
+
const x1 = Math.min(width, Math.ceil(rect.x + rect.width));
|
|
3188
|
+
const y1 = Math.min(height, Math.ceil(rect.y + rect.height));
|
|
3189
|
+
const sx = src.width / rect.width;
|
|
3190
|
+
const sy = src.height / rect.height;
|
|
3191
|
+
const d = src.data;
|
|
3192
|
+
for (let y = y0; y < y1; y++) {
|
|
3193
|
+
const v = Math.min(src.height - 1, Math.max(0, (y + 0.5 - rect.y) * sy - 0.5));
|
|
3194
|
+
const j0 = Math.floor(v);
|
|
3195
|
+
const j1 = Math.min(src.height - 1, j0 + 1);
|
|
3196
|
+
const fy = v - j0;
|
|
3197
|
+
for (let x = x0; x < x1; x++) {
|
|
3198
|
+
const u = (x + 0.5 - rect.x) * sx - 0.5;
|
|
3199
|
+
if (u < -0.5 || u > src.width - 0.5 || (y + 0.5 - rect.y) * sy < 0 || (y + 0.5 - rect.y) * sy > src.height) continue;
|
|
3200
|
+
const uc = Math.min(src.width - 1, Math.max(0, u));
|
|
3201
|
+
const i0 = Math.floor(uc);
|
|
3202
|
+
const i1 = Math.min(src.width - 1, i0 + 1);
|
|
3203
|
+
const fx = uc - i0;
|
|
3204
|
+
const o = (y * width + x) * 4;
|
|
3205
|
+
for (let c = 0; c < 4; c++) {
|
|
3206
|
+
const a = d[(j0 * src.width + i0) * 4 + c] + (d[(j0 * src.width + i1) * 4 + c] - d[(j0 * src.width + i0) * 4 + c]) * fx;
|
|
3207
|
+
const b = d[(j1 * src.width + i0) * 4 + c] + (d[(j1 * src.width + i1) * 4 + c] - d[(j1 * src.width + i0) * 4 + c]) * fx;
|
|
3208
|
+
out[o + c] = a + (b - a) * fy;
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
return { width, height, data: out };
|
|
3213
|
+
}
|
|
2485
3214
|
|
|
2486
|
-
// src/paint-editor/tools.ts
|
|
3215
|
+
// src/paint-editor/guide-tools.ts
|
|
2487
3216
|
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
2488
3217
|
var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
2489
3218
|
var SET = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
2490
3219
|
var DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
2491
3220
|
var EMPTY = { type: "object", properties: {}, additionalProperties: false };
|
|
2492
|
-
var COLOR = { type: "string", pattern: "^#[0-9a-fA-F]{6}$", description:
|
|
3221
|
+
var COLOR = { type: "string", pattern: "^#[0-9a-fA-F]{6}$", description: "Colour #rrggbb." };
|
|
3222
|
+
var POINT = {
|
|
3223
|
+
type: "object",
|
|
3224
|
+
properties: { x: { type: "number", description: "x in document pixels." }, y: { type: "number", description: "y in document pixels." } },
|
|
3225
|
+
required: ["x", "y"],
|
|
3226
|
+
additionalProperties: false
|
|
3227
|
+
};
|
|
3228
|
+
var RECT = {
|
|
3229
|
+
type: "object",
|
|
3230
|
+
description: "Placement in document pixels.",
|
|
3231
|
+
properties: {
|
|
3232
|
+
x: { type: "number", description: "Left edge." },
|
|
3233
|
+
y: { type: "number", description: "Top edge." },
|
|
3234
|
+
width: { type: "number", exclusiveMinimum: 0, description: "Width." },
|
|
3235
|
+
height: { type: "number", exclusiveMinimum: 0, description: "Height." }
|
|
3236
|
+
},
|
|
3237
|
+
required: ["x", "y", "width", "height"],
|
|
3238
|
+
additionalProperties: false
|
|
3239
|
+
};
|
|
3240
|
+
var PNG = { type: "string", minLength: 1, description: "The image as a PNG, base64 or a data:image/png URL." };
|
|
3241
|
+
var tracingInfo = (t) => t ? { width: t.width, height: t.height, rect: t.rect, opacity: t.opacity, visible: t.visible, sampleColors: t.sampleColors } : null;
|
|
3242
|
+
var refInfo = ({ src: _s, ...r }) => r;
|
|
3243
|
+
function createPaintGuideTools(c) {
|
|
3244
|
+
const guides = () => c.getState().guides;
|
|
3245
|
+
return [
|
|
3246
|
+
{
|
|
3247
|
+
name: "get_guides",
|
|
3248
|
+
title: "Get the painting guides",
|
|
3249
|
+
description: "Reads the painting guides: symmetry (mode, centre, rotation, segments, mirror, and how many copies each stroke paints), the perspective guide (vanishing points, snap), the tracing image (size, placement, opacity, visibility, colour sampling) and the reference images.",
|
|
3250
|
+
inputSchema: EMPTY,
|
|
3251
|
+
annotations: { title: "Get the painting guides", ...READ },
|
|
3252
|
+
handler: () => {
|
|
3253
|
+
const g = guides();
|
|
3254
|
+
return { symmetry: { ...g.symmetry, copies: symmetryCopies(g.symmetry).length }, perspective: g.perspective, tracing: tracingInfo(g.tracing), references: g.references.map(refInfo) };
|
|
3255
|
+
}
|
|
3256
|
+
},
|
|
3257
|
+
{
|
|
3258
|
+
name: "set_symmetry",
|
|
3259
|
+
title: "Set mirror or kaleidoscope painting",
|
|
3260
|
+
description: "Turns symmetry on or off. Every stroke after it also paints its copies, as one stroke and one undo step: vertical, horizontal or diagonal mirror (one copy), dual (four-way), or kaleidoscope with n segments (rotated copies, and with mirror the reflections between them). Editor state, saved with the document. Returns the guide and the number of copies.",
|
|
3261
|
+
inputSchema: {
|
|
3262
|
+
type: "object",
|
|
3263
|
+
properties: {
|
|
3264
|
+
mode: { type: "string", enum: [...SYMMETRY_MODES], description: "off, vertical (mirror left / right), horizontal (top / bottom), dual, diagonal or kaleidoscope." },
|
|
3265
|
+
center: { ...POINT, description: "Centre of the axes in document pixels (default the canvas centre)." },
|
|
3266
|
+
rotation: { type: "number", minimum: -360, maximum: 360, description: "Turn the axes, degrees clockwise." },
|
|
3267
|
+
segments: { type: "integer", minimum: 2, maximum: 32, description: "Kaleidoscope segments (default 6)." },
|
|
3268
|
+
mirror: { type: "boolean", description: "Kaleidoscope: also reflect alternate segments (default true)." }
|
|
3269
|
+
},
|
|
3270
|
+
additionalProperties: false
|
|
3271
|
+
},
|
|
3272
|
+
annotations: { title: "Set mirror or kaleidoscope painting", ...SET },
|
|
3273
|
+
handler: (a) => {
|
|
3274
|
+
const s = c.setSymmetry(a);
|
|
3275
|
+
return { ...s, copies: symmetryCopies(s).length };
|
|
3276
|
+
}
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
name: "set_perspective_guide",
|
|
3280
|
+
title: "Set the perspective guide",
|
|
3281
|
+
description: "Sets one to three vanishing points (document pixels, may be off the canvas). With snap on, pen strokes snap to the line through their start towards a vanishing point (one-point also keeps horizontals and verticals, two-point verticals); for paint_stroke pass snapToPerspective. Editor state, saved with the document. Returns the guide.",
|
|
3282
|
+
inputSchema: {
|
|
3283
|
+
type: "object",
|
|
3284
|
+
properties: {
|
|
3285
|
+
enabled: { type: "boolean", description: "Show and use the guide." },
|
|
3286
|
+
points: { type: "array", minItems: 1, maxItems: 3, items: { ...POINT, description: "A vanishing point." }, description: "The vanishing points." },
|
|
3287
|
+
snap: { type: "boolean", description: "Snap strokes to the guide lines (default true)." }
|
|
3288
|
+
},
|
|
3289
|
+
additionalProperties: false
|
|
3290
|
+
},
|
|
3291
|
+
annotations: { title: "Set the perspective guide", ...SET },
|
|
3292
|
+
handler: (a) => c.setPerspective(a)
|
|
3293
|
+
},
|
|
3294
|
+
{
|
|
3295
|
+
name: "set_tracing_image",
|
|
3296
|
+
title: "Set a tracing image",
|
|
3297
|
+
description: "Puts an image under the paint to trace over (view only: never exported), fitted to the canvas unless rect is given, at opacity (default 0.35). With sampleColors, strokes without a colour take the image colour where they start, so painting over it copies its colours. Replaces any tracing image. Returns its size and placement.",
|
|
3298
|
+
inputSchema: {
|
|
3299
|
+
type: "object",
|
|
3300
|
+
properties: {
|
|
3301
|
+
png: PNG,
|
|
3302
|
+
rect: RECT,
|
|
3303
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 0.35)." },
|
|
3304
|
+
sampleColors: { type: "boolean", description: "Strokes take their colour from the image (default false)." }
|
|
3305
|
+
},
|
|
3306
|
+
required: ["png"],
|
|
3307
|
+
additionalProperties: false
|
|
3308
|
+
},
|
|
3309
|
+
annotations: { title: "Set a tracing image", ...WRITE },
|
|
3310
|
+
handler: (a) => tracingInfo(c.setTracingImage(a))
|
|
3311
|
+
},
|
|
3312
|
+
{
|
|
3313
|
+
name: "update_tracing_image",
|
|
3314
|
+
title: "Change the tracing image",
|
|
3315
|
+
description: "Changes the tracing image opacity, visibility, placement or colour sampling. Returns it.",
|
|
3316
|
+
inputSchema: {
|
|
3317
|
+
type: "object",
|
|
3318
|
+
properties: {
|
|
3319
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1." },
|
|
3320
|
+
visible: { type: "boolean", description: "Show or hide it." },
|
|
3321
|
+
sampleColors: { type: "boolean", description: "Strokes take their colour from it." },
|
|
3322
|
+
rect: RECT
|
|
3323
|
+
},
|
|
3324
|
+
additionalProperties: false
|
|
3325
|
+
},
|
|
3326
|
+
annotations: { title: "Change the tracing image", ...SET },
|
|
3327
|
+
handler: (a) => tracingInfo(c.updateTracing(a))
|
|
3328
|
+
},
|
|
3329
|
+
{
|
|
3330
|
+
name: "clear_tracing_image",
|
|
3331
|
+
title: "Remove the tracing image",
|
|
3332
|
+
description: "Removes the tracing image. Returns null.",
|
|
3333
|
+
inputSchema: EMPTY,
|
|
3334
|
+
annotations: { title: "Remove the tracing image", ...DESTRUCTIVE },
|
|
3335
|
+
handler: () => {
|
|
3336
|
+
c.clearTracingImage();
|
|
3337
|
+
return { tracing: null };
|
|
3338
|
+
}
|
|
3339
|
+
},
|
|
3340
|
+
{
|
|
3341
|
+
name: "add_reference_image",
|
|
3342
|
+
title: "Add a reference image",
|
|
3343
|
+
description: "Adds a floating reference image (shown beside the canvas, never painted) to look at and pick colours from with pick_reference_color. Returns its id, size and placement.",
|
|
3344
|
+
inputSchema: {
|
|
3345
|
+
type: "object",
|
|
3346
|
+
properties: {
|
|
3347
|
+
png: PNG,
|
|
3348
|
+
name: { type: "string", minLength: 1, description: "Display name." },
|
|
3349
|
+
x: { type: "number", description: "Panel left in viewport pixels." },
|
|
3350
|
+
y: { type: "number", description: "Panel top in viewport pixels." },
|
|
3351
|
+
displayWidth: { type: "number", minimum: 16, maximum: 4096, description: "Panel width in screen pixels (default up to 240)." }
|
|
3352
|
+
},
|
|
3353
|
+
required: ["png"],
|
|
3354
|
+
additionalProperties: false
|
|
3355
|
+
},
|
|
3356
|
+
annotations: { title: "Add a reference image", ...WRITE },
|
|
3357
|
+
handler: (a) => refInfo(c.addReferenceImage(a))
|
|
3358
|
+
},
|
|
3359
|
+
{
|
|
3360
|
+
name: "list_reference_images",
|
|
3361
|
+
title: "List reference images",
|
|
3362
|
+
description: "Lists the reference images: id, name, size in pixels, panel placement and visibility.",
|
|
3363
|
+
inputSchema: EMPTY,
|
|
3364
|
+
annotations: { title: "List reference images", ...READ },
|
|
3365
|
+
handler: () => ({ references: guides().references.map(refInfo) })
|
|
3366
|
+
},
|
|
3367
|
+
{
|
|
3368
|
+
name: "update_reference_image",
|
|
3369
|
+
title: "Change a reference image",
|
|
3370
|
+
description: "Moves, resizes, renames, shows or hides a reference image panel. Returns it.",
|
|
3371
|
+
inputSchema: {
|
|
3372
|
+
type: "object",
|
|
3373
|
+
properties: {
|
|
3374
|
+
id: { type: "string", description: "Reference id from list_reference_images." },
|
|
3375
|
+
name: { type: "string", minLength: 1, description: "Display name." },
|
|
3376
|
+
x: { type: "number", description: "Panel left in viewport pixels." },
|
|
3377
|
+
y: { type: "number", description: "Panel top in viewport pixels." },
|
|
3378
|
+
displayWidth: { type: "number", minimum: 16, maximum: 4096, description: "Panel width in screen pixels." },
|
|
3379
|
+
visible: { type: "boolean", description: "Show or hide it." }
|
|
3380
|
+
},
|
|
3381
|
+
required: ["id"],
|
|
3382
|
+
additionalProperties: false
|
|
3383
|
+
},
|
|
3384
|
+
annotations: { title: "Change a reference image", ...SET },
|
|
3385
|
+
handler: ({ id, ...u }) => refInfo(c.updateReferenceImage(id, u))
|
|
3386
|
+
},
|
|
3387
|
+
{
|
|
3388
|
+
name: "remove_reference_image",
|
|
3389
|
+
title: "Remove a reference image",
|
|
3390
|
+
description: "Removes a reference image. Returns the removed id.",
|
|
3391
|
+
inputSchema: { type: "object", properties: { id: { type: "string", description: "Reference id from list_reference_images." } }, required: ["id"], additionalProperties: false },
|
|
3392
|
+
annotations: { title: "Remove a reference image", ...DESTRUCTIVE },
|
|
3393
|
+
handler: ({ id }) => {
|
|
3394
|
+
c.removeReferenceImage(id);
|
|
3395
|
+
return { removed: id };
|
|
3396
|
+
}
|
|
3397
|
+
},
|
|
3398
|
+
{
|
|
3399
|
+
name: "pick_reference_color",
|
|
3400
|
+
title: "Pick a colour from a reference",
|
|
3401
|
+
description: "Reads the colour of a reference image at its own pixel (x, y). With setColor it becomes the paint colour. Returns #rrggbb.",
|
|
3402
|
+
inputSchema: {
|
|
3403
|
+
type: "object",
|
|
3404
|
+
properties: {
|
|
3405
|
+
id: { type: "string", description: "Reference id from list_reference_images." },
|
|
3406
|
+
x: { type: "number", minimum: 0, description: "x in the image\u2019s pixels." },
|
|
3407
|
+
y: { type: "number", minimum: 0, description: "y in the image\u2019s pixels." },
|
|
3408
|
+
setColor: { type: "boolean", description: "Also make it the paint colour (default false)." }
|
|
3409
|
+
},
|
|
3410
|
+
required: ["id", "x", "y"],
|
|
3411
|
+
additionalProperties: false
|
|
3412
|
+
},
|
|
3413
|
+
annotations: { title: "Pick a colour from a reference", ...SET },
|
|
3414
|
+
handler: ({ id, x, y, setColor }) => {
|
|
3415
|
+
const color = c.pickReferenceColor(id, x, y);
|
|
3416
|
+
if (setColor) c.setColor(color);
|
|
3417
|
+
return { color };
|
|
3418
|
+
}
|
|
3419
|
+
},
|
|
3420
|
+
{
|
|
3421
|
+
name: "color_harmony",
|
|
3422
|
+
title: "Colour harmonies",
|
|
3423
|
+
description: "Returns the colours of a harmony around a colour (default the paint colour): complementary, split-complementary, analogous, triadic, tetradic, square or monochromatic, on the painter\u2019s red-yellow-blue wheel (default: red\u2019s complement is green) or the RGB wheel. Changes nothing. Returns the colours with their wheel angles, the base first.",
|
|
3424
|
+
inputSchema: {
|
|
3425
|
+
type: "object",
|
|
3426
|
+
properties: {
|
|
3427
|
+
kind: { type: "string", enum: [...HARMONY_KINDS], description: "The harmony." },
|
|
3428
|
+
color: { ...COLOR, description: "Base colour #rrggbb (default the paint colour)." },
|
|
3429
|
+
wheel: { type: "string", enum: ["ryb", "rgb"], description: "ryb (painter\u2019s wheel, default) or rgb." },
|
|
3430
|
+
spread: { type: "number", minimum: 5, maximum: 90, description: "Analogous and split-complementary spread in degrees (default 30)." }
|
|
3431
|
+
},
|
|
3432
|
+
required: ["kind"],
|
|
3433
|
+
additionalProperties: false
|
|
3434
|
+
},
|
|
3435
|
+
annotations: { title: "Colour harmonies", ...READ },
|
|
3436
|
+
handler: ({ kind, color, wheel, spread }) => ({ kind, wheel: _nullishCoalesce(wheel, () => ( "ryb")), colors: c.harmony(kind, { color, wheel, spread }) })
|
|
3437
|
+
},
|
|
3438
|
+
{
|
|
3439
|
+
name: "list_swatches",
|
|
3440
|
+
title: "List swatches",
|
|
3441
|
+
description: "Lists the colour swatches (index, colour, name).",
|
|
3442
|
+
inputSchema: EMPTY,
|
|
3443
|
+
annotations: { title: "List swatches", ...READ },
|
|
3444
|
+
handler: () => ({ swatches: c.getState().swatches.map((s, index) => ({ index, ...s })) })
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
name: "add_swatch",
|
|
3448
|
+
title: "Add a swatch",
|
|
3449
|
+
description: "Adds a colour to the swatches (saved with the document). Returns the swatches.",
|
|
3450
|
+
inputSchema: {
|
|
3451
|
+
type: "object",
|
|
3452
|
+
properties: { color: COLOR, name: { type: "string", minLength: 1, description: "Optional name." } },
|
|
3453
|
+
required: ["color"],
|
|
3454
|
+
additionalProperties: false
|
|
3455
|
+
},
|
|
3456
|
+
annotations: { title: "Add a swatch", ...WRITE },
|
|
3457
|
+
handler: ({ color, name }) => ({ swatches: c.addSwatch(color, name).map((s, index) => ({ index, ...s })) })
|
|
3458
|
+
},
|
|
3459
|
+
{
|
|
3460
|
+
name: "remove_swatch",
|
|
3461
|
+
title: "Remove a swatch",
|
|
3462
|
+
description: "Removes a swatch by index (from list_swatches). Returns the swatches.",
|
|
3463
|
+
inputSchema: { type: "object", properties: { index: { type: "integer", minimum: 0, description: "0-based index." } }, required: ["index"], additionalProperties: false },
|
|
3464
|
+
annotations: { title: "Remove a swatch", ...DESTRUCTIVE },
|
|
3465
|
+
handler: ({ index }) => ({ swatches: c.removeSwatch(index).map((s, i) => ({ index: i, ...s })) })
|
|
3466
|
+
},
|
|
3467
|
+
{
|
|
3468
|
+
name: "mixing_pad_stroke",
|
|
3469
|
+
title: "Paint on the mixing pad",
|
|
3470
|
+
description: "Paints on the mixing pad (a 360 \xD7 220 scratch canvas, not the painting): tool paint lays down a colour with a pigment Mixer Brush; tool knife smears what is there like a palette knife, mixing it as paint. Points are pad pixels. Not an undo step. Returns the changed bounds; then pick_mixing_pad_color.",
|
|
3471
|
+
inputSchema: {
|
|
3472
|
+
type: "object",
|
|
3473
|
+
properties: {
|
|
3474
|
+
points: {
|
|
3475
|
+
type: "array",
|
|
3476
|
+
minItems: 1,
|
|
3477
|
+
maxItems: 2e3,
|
|
3478
|
+
description: "The path in pad pixels (0..360, 0..220).",
|
|
3479
|
+
items: {
|
|
3480
|
+
type: "object",
|
|
3481
|
+
properties: { x: { type: "number", description: "Pad x." }, y: { type: "number", description: "Pad y." }, pressure: { type: "number", minimum: 0, maximum: 1, description: "Pressure (default 1)." } },
|
|
3482
|
+
required: ["x", "y"],
|
|
3483
|
+
additionalProperties: false
|
|
3484
|
+
}
|
|
3485
|
+
},
|
|
3486
|
+
tool: { type: "string", enum: ["paint", "knife"], description: "paint (default) or knife." },
|
|
3487
|
+
color: { ...COLOR, description: "Paint colour (default the paint colour; ignored by the knife)." },
|
|
3488
|
+
size: { type: "number", minimum: 1, maximum: 200, description: "Tool size in pad pixels (default 28 paint, 34 knife)." }
|
|
3489
|
+
},
|
|
3490
|
+
required: ["points"],
|
|
3491
|
+
additionalProperties: false
|
|
3492
|
+
},
|
|
3493
|
+
annotations: { title: "Paint on the mixing pad", ...WRITE },
|
|
3494
|
+
handler: ({ points, tool, color, size }) => c.mixingPadStroke(points, { tool, color, size })
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
name: "pick_mixing_pad_color",
|
|
3498
|
+
title: "Pick from the mixing pad",
|
|
3499
|
+
description: "Reads the mixed colour on the pad at (x, y), averaged over radius pixels (default 3); with setColor it becomes the paint colour. Returns #rrggbb, or null where the pad is bare.",
|
|
3500
|
+
inputSchema: {
|
|
3501
|
+
type: "object",
|
|
3502
|
+
properties: {
|
|
3503
|
+
x: { type: "number", minimum: 0, maximum: 360, description: "Pad x." },
|
|
3504
|
+
y: { type: "number", minimum: 0, maximum: 220, description: "Pad y." },
|
|
3505
|
+
radius: { type: "integer", minimum: 0, maximum: 20, description: "Averaging radius in pixels (default 3)." },
|
|
3506
|
+
setColor: { type: "boolean", description: "Also make it the paint colour (default false)." }
|
|
3507
|
+
},
|
|
3508
|
+
required: ["x", "y"],
|
|
3509
|
+
additionalProperties: false
|
|
3510
|
+
},
|
|
3511
|
+
annotations: { title: "Pick from the mixing pad", ...SET },
|
|
3512
|
+
handler: ({ x, y, radius, setColor }) => ({ color: c.mixingPadPick(x, y, { radius, setColor }) })
|
|
3513
|
+
},
|
|
3514
|
+
{
|
|
3515
|
+
name: "clear_mixing_pad",
|
|
3516
|
+
title: "Clear the mixing pad",
|
|
3517
|
+
description: "Wipes the mixing pad clean.",
|
|
3518
|
+
inputSchema: EMPTY,
|
|
3519
|
+
annotations: { title: "Clear the mixing pad", ...DESTRUCTIVE },
|
|
3520
|
+
handler: () => {
|
|
3521
|
+
c.clearMixingPad();
|
|
3522
|
+
return { cleared: true };
|
|
3523
|
+
}
|
|
3524
|
+
},
|
|
3525
|
+
{
|
|
3526
|
+
name: "render_mixing_pad",
|
|
3527
|
+
title: "Render the mixing pad",
|
|
3528
|
+
description: "Renders the mixing pad as a PNG (360 \xD7 220, over white). Changes nothing.",
|
|
3529
|
+
inputSchema: EMPTY,
|
|
3530
|
+
annotations: { title: "Render the mixing pad", ...READ },
|
|
3531
|
+
handler: () => {
|
|
3532
|
+
const pad = c.mixingPad;
|
|
3533
|
+
return {
|
|
3534
|
+
content: [
|
|
3535
|
+
{ type: "image", data: _chunkWSTEXEKDcjs.bytesToBase64.call(void 0, _chunkWSTEXEKDcjs.encodePng.call(void 0, pad.width, pad.height, pad.pixels())), mimeType: "image/png" },
|
|
3536
|
+
{ type: "text", text: `Mixing pad ${pad.width}\xD7${pad.height}` }
|
|
3537
|
+
]
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
];
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
// src/paint-editor/tools.ts
|
|
3545
|
+
var READ2 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
3546
|
+
var WRITE2 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
3547
|
+
var SET2 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
3548
|
+
var DESTRUCTIVE2 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
3549
|
+
var EMPTY2 = { type: "object", properties: {}, additionalProperties: false };
|
|
3550
|
+
var COLOR2 = { type: "string", pattern: "^#[0-9a-fA-F]{6}$", description: 'Colour #rrggbb, e.g. "#2a4b8d".' };
|
|
2493
3551
|
var LAYER_ID = { type: "string", description: "Layer id from list_layers (default the active layer)." };
|
|
2494
3552
|
var TIP = {
|
|
2495
3553
|
type: "object",
|
|
@@ -2589,8 +3647,8 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2589
3647
|
name: "get_canvas_info",
|
|
2590
3648
|
title: "Get the canvas",
|
|
2591
3649
|
description: "Reads the canvas: size in pixels and dpi, the paper (preset, colour, grain depth), the active layer, the active brush id, the paint colour, the view (zoom, rotation, mirror), the hand, how many strokes are recorded, the undo / redo labels, and the blend modes. Call it first.",
|
|
2592
|
-
inputSchema:
|
|
2593
|
-
annotations: { title: "Get the canvas", ...
|
|
3650
|
+
inputSchema: EMPTY2,
|
|
3651
|
+
annotations: { title: "Get the canvas", ...READ2 },
|
|
2594
3652
|
handler: () => {
|
|
2595
3653
|
const s = c.getState();
|
|
2596
3654
|
const d = s.document;
|
|
@@ -2627,7 +3685,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2627
3685
|
},
|
|
2628
3686
|
additionalProperties: false
|
|
2629
3687
|
},
|
|
2630
|
-
annotations: { title: "Start a new canvas", ...
|
|
3688
|
+
annotations: { title: "Start a new canvas", ...DESTRUCTIVE2 },
|
|
2631
3689
|
handler: (a) => {
|
|
2632
3690
|
const d = c.newCanvas({ width: a.width, height: a.height, dpi: a.dpi, paper: a.paper });
|
|
2633
3691
|
return { width: d.canvas.width, height: d.canvas.height, dpi: d.canvas.dpi, paper: d.paper.preset, layerId: d.activeLayerId };
|
|
@@ -2646,15 +3704,15 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2646
3704
|
},
|
|
2647
3705
|
additionalProperties: false
|
|
2648
3706
|
},
|
|
2649
|
-
annotations: { title: "Resize the canvas", ...
|
|
3707
|
+
annotations: { title: "Resize the canvas", ...SET2 },
|
|
2650
3708
|
handler: (a) => c.setCanvas({ width: a.width, height: a.height, dpi: a.dpi })
|
|
2651
3709
|
});
|
|
2652
3710
|
add({
|
|
2653
3711
|
name: "list_papers",
|
|
2654
3712
|
title: "List papers",
|
|
2655
3713
|
description: "Lists the built-in paper library (generated grain, no textures): id, name, family, grain pattern, colour, grain depth and the wet-media properties (absorption, permeability, roughness). Use an id with set_paper or new_canvas.",
|
|
2656
|
-
inputSchema:
|
|
2657
|
-
annotations: { title: "List papers", ...
|
|
3714
|
+
inputSchema: EMPTY2,
|
|
3715
|
+
annotations: { title: "List papers", ...READ2 },
|
|
2658
3716
|
handler: () => ({
|
|
2659
3717
|
papers: PAPER_LIBRARY.map((p) => ({ id: p.preset, name: p.name, family: p.family, pattern: p.pattern, color: p.color, depth: p.depth, absorption: p.absorption, permeability: p.permeability, roughness: p.roughness })),
|
|
2660
3718
|
families: [...PAPER_FAMILIES],
|
|
@@ -2665,8 +3723,8 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2665
3723
|
name: "get_paper",
|
|
2666
3724
|
title: "Get the paper",
|
|
2667
3725
|
description: "Reads every setting of the current paper (preset, pattern, seed, colour, scale, tiling, depth, shading, dry and wet tuning, absorption, permeability, roughness).",
|
|
2668
|
-
inputSchema:
|
|
2669
|
-
annotations: { title: "Get the paper", ...
|
|
3726
|
+
inputSchema: EMPTY2,
|
|
3727
|
+
annotations: { title: "Get the paper", ...READ2 },
|
|
2670
3728
|
handler: () => JSON.parse(JSON.stringify(c.document.paper))
|
|
2671
3729
|
});
|
|
2672
3730
|
const tuning2 = {
|
|
@@ -2685,7 +3743,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2685
3743
|
type: "object",
|
|
2686
3744
|
properties: {
|
|
2687
3745
|
preset: { type: "string", description: "Paper id from list_papers (its settings, then the other fields here on top)." },
|
|
2688
|
-
color: { ...
|
|
3746
|
+
color: { ...COLOR2, description: "Paper colour #rrggbb." },
|
|
2689
3747
|
depth: { type: "number", minimum: 0, maximum: 1, description: "Grain depth 0..1 (0: brushes ignore the grain)." },
|
|
2690
3748
|
scale: { type: "number", minimum: 0.25, maximum: 8, description: "Grain scale: 1 natural, 2 twice as coarse." },
|
|
2691
3749
|
seed: { type: "integer", minimum: 0, description: "Grain seed: another sheet of the same paper." },
|
|
@@ -2701,15 +3759,15 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2701
3759
|
},
|
|
2702
3760
|
additionalProperties: false
|
|
2703
3761
|
},
|
|
2704
|
-
annotations: { title: "Set the paper", ...
|
|
3762
|
+
annotations: { title: "Set the paper", ...SET2 },
|
|
2705
3763
|
handler: (a) => c.setPaper(a)
|
|
2706
3764
|
});
|
|
2707
3765
|
add({
|
|
2708
3766
|
name: "list_layers",
|
|
2709
3767
|
title: "List layers",
|
|
2710
3768
|
description: "Lists the layers top first: id, name, type (pixel or group), visibility, opacity, blend, clipping, locks, whether it is active, the painted bounds in document pixels (null when empty), parent and depth.",
|
|
2711
|
-
inputSchema:
|
|
2712
|
-
annotations: { title: "List layers", ...
|
|
3769
|
+
inputSchema: EMPTY2,
|
|
3770
|
+
annotations: { title: "List layers", ...READ2 },
|
|
2713
3771
|
handler: () => ({ layers: c.listLayers(), activeLayerId: c.document.activeLayerId })
|
|
2714
3772
|
});
|
|
2715
3773
|
add({
|
|
@@ -2726,7 +3784,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2726
3784
|
},
|
|
2727
3785
|
additionalProperties: false
|
|
2728
3786
|
},
|
|
2729
|
-
annotations: { title: "Add a layer", ...
|
|
3787
|
+
annotations: { title: "Add a layer", ...WRITE2 },
|
|
2730
3788
|
handler: (a) => c.addLayer({ name: a.name, type: a.type, aboveId: a.aboveId, parentId: a.parentId })
|
|
2731
3789
|
});
|
|
2732
3790
|
add({
|
|
@@ -2757,7 +3815,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2757
3815
|
required: ["layerId"],
|
|
2758
3816
|
additionalProperties: false
|
|
2759
3817
|
},
|
|
2760
|
-
annotations: { title: "Change a layer", ...
|
|
3818
|
+
annotations: { title: "Change a layer", ...SET2 },
|
|
2761
3819
|
handler: ({ layerId, ...u }) => c.updateLayer(layerId, u)
|
|
2762
3820
|
});
|
|
2763
3821
|
add({
|
|
@@ -2765,7 +3823,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2765
3823
|
title: "Choose the active layer",
|
|
2766
3824
|
description: "Makes a layer active: strokes, fills and clears without a layerId go to it. Editor state, not an undo step. Returns the layer.",
|
|
2767
3825
|
inputSchema: { type: "object", properties: { layerId: { type: "string", description: "Layer id from list_layers." } }, required: ["layerId"], additionalProperties: false },
|
|
2768
|
-
annotations: { title: "Choose the active layer", ...
|
|
3826
|
+
annotations: { title: "Choose the active layer", ...SET2 },
|
|
2769
3827
|
handler: ({ layerId }) => {
|
|
2770
3828
|
c.setActiveLayer(layerId);
|
|
2771
3829
|
return c.describeLayer(c.getLayer(layerId));
|
|
@@ -2776,7 +3834,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2776
3834
|
title: "Remove a layer",
|
|
2777
3835
|
description: "Removes a layer (a group with its children). One undo step. Returns the removed id and the new active layer.",
|
|
2778
3836
|
inputSchema: { type: "object", properties: { layerId: { type: "string", description: "Layer id from list_layers." } }, required: ["layerId"], additionalProperties: false },
|
|
2779
|
-
annotations: { title: "Remove a layer", ...
|
|
3837
|
+
annotations: { title: "Remove a layer", ...DESTRUCTIVE2 },
|
|
2780
3838
|
handler: ({ layerId }) => c.removeLayer(layerId)
|
|
2781
3839
|
});
|
|
2782
3840
|
add({
|
|
@@ -2784,7 +3842,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2784
3842
|
title: "Duplicate a layer",
|
|
2785
3843
|
description: "Copies a layer (a group with its children) directly above it; the copy becomes active. One undo step. Returns the copy.",
|
|
2786
3844
|
inputSchema: { type: "object", properties: { layerId: { type: "string", description: "Layer id from list_layers." } }, required: ["layerId"], additionalProperties: false },
|
|
2787
|
-
annotations: { title: "Duplicate a layer", ...
|
|
3845
|
+
annotations: { title: "Duplicate a layer", ...WRITE2 },
|
|
2788
3846
|
handler: ({ layerId }) => c.duplicateLayer(layerId)
|
|
2789
3847
|
});
|
|
2790
3848
|
add({
|
|
@@ -2801,7 +3859,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2801
3859
|
required: ["layerId", "index"],
|
|
2802
3860
|
additionalProperties: false
|
|
2803
3861
|
},
|
|
2804
|
-
annotations: { title: "Reorder a layer", ...
|
|
3862
|
+
annotations: { title: "Reorder a layer", ...WRITE2 },
|
|
2805
3863
|
handler: ({ layerId, index, parentId }) => c.moveLayer(layerId, index, parentId)
|
|
2806
3864
|
});
|
|
2807
3865
|
add({
|
|
@@ -2809,7 +3867,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2809
3867
|
title: "Merge a layer down",
|
|
2810
3868
|
description: "Merges a pixel layer into the pixel layer directly below it, which keeps its name, opacity and blend. One undo step. Returns the merged layer.",
|
|
2811
3869
|
inputSchema: { type: "object", properties: { layerId: { type: "string", description: "The upper layer, from list_layers." } }, required: ["layerId"], additionalProperties: false },
|
|
2812
|
-
annotations: { title: "Merge a layer down", ...
|
|
3870
|
+
annotations: { title: "Merge a layer down", ...DESTRUCTIVE2 },
|
|
2813
3871
|
handler: ({ layerId }) => c.mergeDown(layerId)
|
|
2814
3872
|
});
|
|
2815
3873
|
add({
|
|
@@ -2820,7 +3878,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2820
3878
|
type: "object",
|
|
2821
3879
|
properties: {
|
|
2822
3880
|
layerId: LAYER_ID,
|
|
2823
|
-
color: { ...
|
|
3881
|
+
color: { ...COLOR2, description: "Fill colour #rrggbb (default the paint colour)." },
|
|
2824
3882
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 1)." },
|
|
2825
3883
|
grain: { type: "number", minimum: 0, maximum: 1, description: "Paper grain response 0..1 (default 0: flat)." },
|
|
2826
3884
|
pressure: { type: "number", minimum: 0, maximum: 1, description: "With grain: how far into the valleys, 0..1 (default 0.5)." },
|
|
@@ -2828,7 +3886,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2828
3886
|
},
|
|
2829
3887
|
additionalProperties: false
|
|
2830
3888
|
},
|
|
2831
|
-
annotations: { title: "Fill a layer", ...
|
|
3889
|
+
annotations: { title: "Fill a layer", ...WRITE2 },
|
|
2832
3890
|
handler: (a) => c.fillLayer(a)
|
|
2833
3891
|
});
|
|
2834
3892
|
add({
|
|
@@ -2836,18 +3894,18 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2836
3894
|
title: "Clear a layer",
|
|
2837
3895
|
description: "Removes every pixel of a pixel layer. One undo step. Returns the layer id and the bounds that were cleared (null if it was empty).",
|
|
2838
3896
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID }, additionalProperties: false },
|
|
2839
|
-
annotations: { title: "Clear a layer", ...
|
|
3897
|
+
annotations: { title: "Clear a layer", ...DESTRUCTIVE2 },
|
|
2840
3898
|
handler: ({ layerId }) => c.clearLayer(layerId)
|
|
2841
3899
|
});
|
|
2842
3900
|
add({
|
|
2843
3901
|
name: "list_brushes",
|
|
2844
3902
|
title: "List brushes",
|
|
2845
3903
|
description: "Lists the brush library: built-in dry media (pencils, pastels, charcoal, airbrush, marker, ink, erasers, blender, smudge, Mixer Brush) and saved user brushes, with id, name, category, engine, size, grain response and rotation mode, plus the active brush id.",
|
|
2846
|
-
inputSchema:
|
|
2847
|
-
annotations: { title: "List brushes", ...
|
|
3904
|
+
inputSchema: EMPTY2,
|
|
3905
|
+
annotations: { title: "List brushes", ...READ2 },
|
|
2848
3906
|
handler: () => {
|
|
2849
3907
|
const s = c.getState();
|
|
2850
|
-
return { activeBrushId: s.brush.id, activeEdited: !s.brush.builtin && Boolean(_optionalChain([c, 'access',
|
|
3908
|
+
return { activeBrushId: s.brush.id, activeEdited: !s.brush.builtin && Boolean(_optionalChain([c, 'access', _79 => _79.getBrush, 'call', _80 => _80(s.brush.id), 'optionalAccess', _81 => _81.builtin])), brushes: s.brushes.map(brushSummary) };
|
|
2851
3909
|
}
|
|
2852
3910
|
});
|
|
2853
3911
|
add({
|
|
@@ -2855,7 +3913,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2855
3913
|
title: "Get a brush",
|
|
2856
3914
|
description: "Reads every setting of a brush (tip, grain, pressure smoothing, rotation, dynamics, mixer). Without id, the active brush as edited.",
|
|
2857
3915
|
inputSchema: { type: "object", properties: { id: { type: "string", description: "Brush id from list_brushes (default the active brush)." } }, additionalProperties: false },
|
|
2858
|
-
annotations: { title: "Get a brush", ...
|
|
3916
|
+
annotations: { title: "Get a brush", ...READ2 },
|
|
2859
3917
|
handler: ({ id }) => {
|
|
2860
3918
|
if (id === void 0) return brushToJSON(c.getState().brush);
|
|
2861
3919
|
const b = c.getBrush(id);
|
|
@@ -2868,7 +3926,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2868
3926
|
title: "Set the brush",
|
|
2869
3927
|
description: "Chooses the active brush (id) and / or changes its settings; strokes without a brush use it. Editor state, not an undo step. Changes stay on the active brush until save_brush stores them. Returns the active brush.",
|
|
2870
3928
|
inputSchema: { type: "object", properties: { id: { type: "string", description: "Brush id from list_brushes to start from." }, ...BRUSH_FIELDS }, additionalProperties: false },
|
|
2871
|
-
annotations: { title: "Set the brush", ...
|
|
3929
|
+
annotations: { title: "Set the brush", ...SET2 },
|
|
2872
3930
|
handler: (a) => brushToJSON(c.setBrush(a))
|
|
2873
3931
|
});
|
|
2874
3932
|
add({
|
|
@@ -2884,7 +3942,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2884
3942
|
required: ["id"],
|
|
2885
3943
|
additionalProperties: false
|
|
2886
3944
|
},
|
|
2887
|
-
annotations: { title: "Save a brush", ...
|
|
3945
|
+
annotations: { title: "Save a brush", ...SET2 },
|
|
2888
3946
|
handler: ({ id, name }) => brushToJSON(c.saveBrush({ id, name }))
|
|
2889
3947
|
});
|
|
2890
3948
|
add({
|
|
@@ -2892,7 +3950,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2892
3950
|
title: "Remove a saved brush",
|
|
2893
3951
|
description: "Removes a user brush from the library (built-in brushes cannot be removed). Library state, not an undo step. Returns the removed id.",
|
|
2894
3952
|
inputSchema: { type: "object", properties: { id: { type: "string", description: "User brush id from list_brushes." } }, required: ["id"], additionalProperties: false },
|
|
2895
|
-
annotations: { title: "Remove a saved brush", ...
|
|
3953
|
+
annotations: { title: "Remove a saved brush", ...DESTRUCTIVE2 },
|
|
2896
3954
|
handler: ({ id }) => {
|
|
2897
3955
|
c.removeBrush(id);
|
|
2898
3956
|
return { removed: id };
|
|
@@ -2907,7 +3965,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2907
3965
|
properties: { ids: { type: "array", items: { type: "string", description: "Brush id." }, description: "Brush ids from list_brushes (default every user brush)." } },
|
|
2908
3966
|
additionalProperties: false
|
|
2909
3967
|
},
|
|
2910
|
-
annotations: { title: "Export brushes", ...
|
|
3968
|
+
annotations: { title: "Export brushes", ...READ2 },
|
|
2911
3969
|
handler: ({ ids }) => c.exportBrushes(ids)
|
|
2912
3970
|
});
|
|
2913
3971
|
add({
|
|
@@ -2923,7 +3981,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2923
3981
|
required: ["library"],
|
|
2924
3982
|
additionalProperties: false
|
|
2925
3983
|
},
|
|
2926
|
-
annotations: { title: "Import brushes", ...
|
|
3984
|
+
annotations: { title: "Import brushes", ...WRITE2 },
|
|
2927
3985
|
handler: ({ library, replace }) => c.importBrushes(library, { replace })
|
|
2928
3986
|
});
|
|
2929
3987
|
add({
|
|
@@ -2936,11 +3994,11 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2936
3994
|
id: { type: "string", description: "Brush id from list_brushes (default the active brush)." },
|
|
2937
3995
|
width: { type: "integer", minimum: 16, maximum: 512, description: "Width in pixels (default 160)." },
|
|
2938
3996
|
height: { type: "integer", minimum: 16, maximum: 512, description: "Height in pixels (default 48)." },
|
|
2939
|
-
color: { ...
|
|
3997
|
+
color: { ...COLOR2, description: "Stroke colour (default the paint colour)." }
|
|
2940
3998
|
},
|
|
2941
3999
|
additionalProperties: false
|
|
2942
4000
|
},
|
|
2943
|
-
annotations: { title: "Render a brush thumbnail", ...
|
|
4001
|
+
annotations: { title: "Render a brush thumbnail", ...READ2 },
|
|
2944
4002
|
handler: ({ id, width, height, color }) => {
|
|
2945
4003
|
const t = c.brushThumbnail(_nullishCoalesce(id, () => ( c.getState().brush.id)), { width, height, color });
|
|
2946
4004
|
return {
|
|
@@ -2957,10 +4015,10 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2957
4015
|
description: "Sets the paint colour (and optionally the second colour) that strokes and fills use by default. Editor state, not an undo step. Returns both colours.",
|
|
2958
4016
|
inputSchema: {
|
|
2959
4017
|
type: "object",
|
|
2960
|
-
properties: { color: { ...
|
|
4018
|
+
properties: { color: { ...COLOR2, description: "Paint colour #rrggbb." }, secondaryColor: { ...COLOR2, description: "Second colour #rrggbb." } },
|
|
2961
4019
|
additionalProperties: false
|
|
2962
4020
|
},
|
|
2963
|
-
annotations: { title: "Set the paint colour", ...
|
|
4021
|
+
annotations: { title: "Set the paint colour", ...SET2 },
|
|
2964
4022
|
handler: ({ color, secondaryColor }) => {
|
|
2965
4023
|
if (color !== void 0) c.setColor(color);
|
|
2966
4024
|
if (secondaryColor !== void 0) c.setSecondaryColor(secondaryColor);
|
|
@@ -2982,23 +4040,23 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
2982
4040
|
required: ["x", "y"],
|
|
2983
4041
|
additionalProperties: false
|
|
2984
4042
|
},
|
|
2985
|
-
annotations: { title: "Pick a colour", ...
|
|
4043
|
+
annotations: { title: "Pick a colour", ...READ2 },
|
|
2986
4044
|
handler: ({ x, y, layerId }) => c.pickColor(x, y, { layerId })
|
|
2987
4045
|
});
|
|
2988
4046
|
add({
|
|
2989
4047
|
name: "get_mixer_brush",
|
|
2990
4048
|
title: "Get the Mixer Brush load",
|
|
2991
4049
|
description: "Reads what the Mixer Brush carries between strokes: reservoir #rrggbb or null, amount 0..1 left, pickup #rrggbb or null. Call before painting with mixer-brush when its loadEachStroke is false.",
|
|
2992
|
-
inputSchema:
|
|
2993
|
-
annotations: { title: "Get the Mixer Brush load", ...
|
|
4050
|
+
inputSchema: EMPTY2,
|
|
4051
|
+
annotations: { title: "Get the Mixer Brush load", ...READ2 },
|
|
2994
4052
|
handler: () => loadResult(c.getState().mixerLoad)
|
|
2995
4053
|
});
|
|
2996
4054
|
add({
|
|
2997
4055
|
name: "load_mixer_brush",
|
|
2998
4056
|
title: "Load the Mixer Brush",
|
|
2999
4057
|
description: "Fills the Mixer Brush reservoir with a colour (amount 1), keeping what it picked up. Editor state, not an undo step. Returns the load.",
|
|
3000
|
-
inputSchema: { type: "object", properties: { color: { ...
|
|
3001
|
-
annotations: { title: "Load the Mixer Brush", ...
|
|
4058
|
+
inputSchema: { type: "object", properties: { color: { ...COLOR2, description: "Colour #rrggbb (default the paint colour)." } }, additionalProperties: false },
|
|
4059
|
+
annotations: { title: "Load the Mixer Brush", ...SET2 },
|
|
3002
4060
|
handler: ({ color }) => loadResult(c.loadMixerBrush(color))
|
|
3003
4061
|
});
|
|
3004
4062
|
add({
|
|
@@ -3006,13 +4064,13 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3006
4064
|
title: "Clean the Mixer Brush",
|
|
3007
4065
|
description: "Empties the Mixer Brush pickup well (and with emptyReservoir its paint too). Editor state, not an undo step. Returns the load.",
|
|
3008
4066
|
inputSchema: { type: "object", properties: { emptyReservoir: { type: "boolean", description: "Also drop the loaded paint (default false)." } }, additionalProperties: false },
|
|
3009
|
-
annotations: { title: "Clean the Mixer Brush", ...
|
|
4067
|
+
annotations: { title: "Clean the Mixer Brush", ...SET2 },
|
|
3010
4068
|
handler: ({ emptyReservoir }) => loadResult(c.cleanMixerBrush(emptyReservoir === true))
|
|
3011
4069
|
});
|
|
3012
4070
|
add({
|
|
3013
4071
|
name: "paint_stroke",
|
|
3014
4072
|
title: "Paint a brush stroke",
|
|
3015
|
-
description: "Paints one stroke through points (document pixels, with optional pressure, time, tilt and barrel rotation) on a pixel layer with a library brush or the active one, optionally changed for this stroke. Grain-responsive brushes follow the paper; the mixer engine (mixer-brush, blender, smudge) picks up and moves paint already on the layer. The
|
|
4073
|
+
description: "Paints one stroke through points (document pixels, with optional pressure, time, tilt and barrel rotation) on a pixel layer with a library brush or the active one, optionally changed for this stroke. Grain-responsive brushes follow the paper; the mixer engine (mixer-brush, blender, smudge) picks up and moves paint already on the layer. The symmetry guide adds its mirror copies; with a tracing image that samples colours, a stroke without color takes the image colour at its first point. One undo step. Returns the layer id, changed bounds, dab count, the recorded strokeId (for replay_strokes) and, for the mixer, its load after the stroke.",
|
|
3016
4074
|
inputSchema: {
|
|
3017
4075
|
type: "object",
|
|
3018
4076
|
properties: {
|
|
@@ -3020,15 +4078,16 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3020
4078
|
layerId: LAYER_ID,
|
|
3021
4079
|
brushId: { type: "string", description: "Brush id from list_brushes (default the active brush)." },
|
|
3022
4080
|
brush: { type: "object", description: "Brush changes for this stroke only.", properties: BRUSH_FIELDS, additionalProperties: false },
|
|
3023
|
-
color: { ...
|
|
3024
|
-
seed: { type: "integer", minimum: 0, description: "Jitter seed (default a new one); the same seed and points give the same marks." }
|
|
4081
|
+
color: { ...COLOR2, description: "Paint colour #rrggbb (default the paint colour)." },
|
|
4082
|
+
seed: { type: "integer", minimum: 0, description: "Jitter seed (default a new one); the same seed and points give the same marks." },
|
|
4083
|
+
snapToPerspective: { type: "boolean", description: "Snap the points to the perspective guide, as a pen stroke would (default false)." }
|
|
3025
4084
|
},
|
|
3026
4085
|
required: ["points"],
|
|
3027
4086
|
additionalProperties: false
|
|
3028
4087
|
},
|
|
3029
|
-
annotations: { title: "Paint a brush stroke", ...
|
|
4088
|
+
annotations: { title: "Paint a brush stroke", ...WRITE2 },
|
|
3030
4089
|
handler: (a) => {
|
|
3031
|
-
const r = c.paintStroke({ points: a.points, layerId: a.layerId, brushId: a.brushId, brush: a.brush, color: a.color, seed: a.seed });
|
|
4090
|
+
const r = c.paintStroke({ points: a.points, layerId: a.layerId, brushId: a.brushId, brush: a.brush, color: a.color, seed: a.seed, snapToPerspective: a.snapToPerspective });
|
|
3032
4091
|
return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
|
|
3033
4092
|
}
|
|
3034
4093
|
});
|
|
@@ -3044,7 +4103,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3044
4103
|
},
|
|
3045
4104
|
additionalProperties: false
|
|
3046
4105
|
},
|
|
3047
|
-
annotations: { title: "List recorded strokes", ...
|
|
4106
|
+
annotations: { title: "List recorded strokes", ...READ2 },
|
|
3048
4107
|
handler: ({ ids, last }) => {
|
|
3049
4108
|
const all = c.getStrokes();
|
|
3050
4109
|
const picked = ids ? all.filter((s) => ids.includes(s.id)) : all.slice(-(_nullishCoalesce(last, () => ( 100))));
|
|
@@ -3065,7 +4124,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3065
4124
|
color: s.color,
|
|
3066
4125
|
points: ids ? s.points : s.points.length,
|
|
3067
4126
|
seed: s.seed,
|
|
3068
|
-
copies: _nullishCoalesce(_optionalChain([s, 'access',
|
|
4127
|
+
copies: _nullishCoalesce(_optionalChain([s, 'access', _82 => _82.copies, 'optionalAccess', _83 => _83.length]), () => ( 0)),
|
|
3069
4128
|
bounds: { x: Math.floor(Math.min(...xs) - pad), y: Math.floor(Math.min(...ys) - pad), width: Math.ceil(Math.max(...xs) - Math.min(...xs) + 2 * pad), height: Math.ceil(Math.max(...ys) - Math.min(...ys) + 2 * pad) }
|
|
3070
4129
|
};
|
|
3071
4130
|
})
|
|
@@ -3093,7 +4152,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3093
4152
|
additionalProperties: false
|
|
3094
4153
|
},
|
|
3095
4154
|
pressureScale: { type: "number", minimum: 0, maximum: 4, description: "Multiply every pressure (default 1)." },
|
|
3096
|
-
color: { ...
|
|
4155
|
+
color: { ...COLOR2, description: "Recolour (default each stroke\u2019s colour)." },
|
|
3097
4156
|
brushId: { type: "string", description: "Replay with this brush from list_brushes (default each stroke\u2019s brush)." },
|
|
3098
4157
|
layerId: { type: "string", description: "Paint on this layer (default each stroke\u2019s layer)." },
|
|
3099
4158
|
repeat: { type: "integer", minimum: 1, maximum: 100, description: "How many copies (default 1)." }
|
|
@@ -3101,7 +4160,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3101
4160
|
required: ["strokeIds"],
|
|
3102
4161
|
additionalProperties: false
|
|
3103
4162
|
},
|
|
3104
|
-
annotations: { title: "Replay strokes, varied", ...
|
|
4163
|
+
annotations: { title: "Replay strokes, varied", ...WRITE2 },
|
|
3105
4164
|
handler: (a) => c.replayStrokes(a)
|
|
3106
4165
|
});
|
|
3107
4166
|
add({
|
|
@@ -3118,7 +4177,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3118
4177
|
},
|
|
3119
4178
|
additionalProperties: false
|
|
3120
4179
|
},
|
|
3121
|
-
annotations: { title: "Rotate, mirror or zoom the view", ...
|
|
4180
|
+
annotations: { title: "Rotate, mirror or zoom the view", ...SET2 },
|
|
3122
4181
|
handler: ({ reset, ...v }) => {
|
|
3123
4182
|
if (reset) c.resetView();
|
|
3124
4183
|
return c.setView(v);
|
|
@@ -3129,15 +4188,15 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3129
4188
|
title: "Set the drawing hand",
|
|
3130
4189
|
description: "Says which hand holds the pen; palm rejection ignores touches on that side of the pen. Editor state, not an undo step. Returns the hand.",
|
|
3131
4190
|
inputSchema: { type: "object", properties: { hand: { type: "string", enum: ["right", "left"], description: "right or left." } }, required: ["hand"], additionalProperties: false },
|
|
3132
|
-
annotations: { title: "Set the drawing hand", ...
|
|
4191
|
+
annotations: { title: "Set the drawing hand", ...SET2 },
|
|
3133
4192
|
handler: ({ hand }) => ({ hand: c.setHand(hand) })
|
|
3134
4193
|
});
|
|
3135
4194
|
add({
|
|
3136
4195
|
name: "undo_paint",
|
|
3137
4196
|
title: "Undo",
|
|
3138
4197
|
description: "Undoes the last step (a stroke, a fill, a layer or paper change). Returns the label undone (null when there was nothing) and what can be undone and redone next.",
|
|
3139
|
-
inputSchema:
|
|
3140
|
-
annotations: { title: "Undo", ...
|
|
4198
|
+
inputSchema: EMPTY2,
|
|
4199
|
+
annotations: { title: "Undo", ...WRITE2 },
|
|
3141
4200
|
handler: () => {
|
|
3142
4201
|
const undone = c.undo();
|
|
3143
4202
|
const s = c.getState();
|
|
@@ -3148,8 +4207,8 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3148
4207
|
name: "redo_paint",
|
|
3149
4208
|
title: "Redo",
|
|
3150
4209
|
description: "Redoes the last undone step. Returns the label redone (null when there was nothing) and what can be undone and redone next.",
|
|
3151
|
-
inputSchema:
|
|
3152
|
-
annotations: { title: "Redo", ...
|
|
4210
|
+
inputSchema: EMPTY2,
|
|
4211
|
+
annotations: { title: "Redo", ...WRITE2 },
|
|
3153
4212
|
handler: () => {
|
|
3154
4213
|
const redone = c.redo();
|
|
3155
4214
|
const s = c.getState();
|
|
@@ -3169,7 +4228,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3169
4228
|
},
|
|
3170
4229
|
additionalProperties: false
|
|
3171
4230
|
},
|
|
3172
|
-
annotations: { title: "Render a preview", ...
|
|
4231
|
+
annotations: { title: "Render a preview", ...READ2 },
|
|
3173
4232
|
handler: ({ maxSide, layerIds, paper }) => {
|
|
3174
4233
|
if (layerIds) {
|
|
3175
4234
|
for (const id of layerIds) if (!c.getLayer(id)) throw new Error(`Unknown layer "${id}". Call list_layers for the ids.`);
|
|
@@ -3192,7 +4251,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3192
4251
|
},
|
|
3193
4252
|
additionalProperties: false
|
|
3194
4253
|
},
|
|
3195
|
-
annotations: { title: "Export the painting", ...
|
|
4254
|
+
annotations: { title: "Export the painting", ...READ2 },
|
|
3196
4255
|
handler: async ({ format, quality, scale, paper }) => {
|
|
3197
4256
|
const bytes = await c.exportImage({ format, quality, scale, paper });
|
|
3198
4257
|
return { mimeType: _nullishCoalesce(format, () => ( "image/png")), bytes: bytes.length, data: _chunkWSTEXEKDcjs.bytesToBase64.call(void 0, bytes) };
|
|
@@ -3202,8 +4261,8 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3202
4261
|
name: "export_psd",
|
|
3203
4262
|
title: "Export as PSD",
|
|
3204
4263
|
description: 'Writes the painting as an 8-bit RGB PSD (PSB above 30000 px) through the shared PSD writer: every layer and group with its opacity, blend, mask and locks, the paper as a locked "Paper" pixel layer at the bottom, and the composite as the merged image. Changes nothing. Returns the file as base64, its size, whether it is a PSB, and warnings (pigment blending has no PSD equivalent).',
|
|
3205
|
-
inputSchema:
|
|
3206
|
-
annotations: { title: "Export as PSD", ...
|
|
4264
|
+
inputSchema: EMPTY2,
|
|
4265
|
+
annotations: { title: "Export as PSD", ...READ2 },
|
|
3207
4266
|
handler: () => {
|
|
3208
4267
|
const r = c.exportPsd();
|
|
3209
4268
|
return { data: _chunkWSTEXEKDcjs.bytesToBase64.call(void 0, r.bytes), bytes: r.bytes.length, psb: r.psb, warnings: r.warnings };
|
|
@@ -3214,7 +4273,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3214
4273
|
title: "Save the document",
|
|
3215
4274
|
description: "Returns the document as JSON (format pixerate-paint, version 1): canvas, paper, layers with PNG data URLs, and the recorded strokes unless strokes is false. Changes nothing.",
|
|
3216
4275
|
inputSchema: { type: "object", properties: { strokes: { type: "boolean", description: "Include the recorded strokes (default true)." } }, additionalProperties: false },
|
|
3217
|
-
annotations: { title: "Save the document", ...
|
|
4276
|
+
annotations: { title: "Save the document", ...READ2 },
|
|
3218
4277
|
handler: ({ strokes }) => c.toJSON({ strokes })
|
|
3219
4278
|
});
|
|
3220
4279
|
add({
|
|
@@ -3222,13 +4281,14 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3222
4281
|
title: "Open a document",
|
|
3223
4282
|
description: "Opens a saved paint document (as save_document returns), replacing the painting and clearing the undo history. Returns the canvas, the layers and anything that could not be read.",
|
|
3224
4283
|
inputSchema: { type: "object", properties: { document: { type: "object", description: "The saved document JSON." } }, required: ["document"], additionalProperties: false },
|
|
3225
|
-
annotations: { title: "Open a document", ...
|
|
4284
|
+
annotations: { title: "Open a document", ...DESTRUCTIVE2 },
|
|
3226
4285
|
handler: ({ document }) => {
|
|
3227
4286
|
const problems = c.loadJSON(document);
|
|
3228
4287
|
const d = c.document;
|
|
3229
4288
|
return { width: d.canvas.width, height: d.canvas.height, layers: c.listLayers(), strokes: d.strokes.length, problems };
|
|
3230
4289
|
}
|
|
3231
4290
|
});
|
|
4291
|
+
tools.push(...createPaintGuideTools(c));
|
|
3232
4292
|
tools.push(..._chunkPV42JITTcjs.createPigmentTools.call(void 0, ));
|
|
3233
4293
|
return { id: _nullishCoalesce(options.id, () => ( "paint-editor")), namespace: options.namespace, tools };
|
|
3234
4294
|
}
|
|
@@ -3326,5 +4386,32 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
3326
4386
|
|
|
3327
4387
|
|
|
3328
4388
|
|
|
3329
|
-
|
|
4389
|
+
|
|
4390
|
+
|
|
4391
|
+
|
|
4392
|
+
|
|
4393
|
+
|
|
4394
|
+
|
|
4395
|
+
|
|
4396
|
+
|
|
4397
|
+
|
|
4398
|
+
|
|
4399
|
+
|
|
4400
|
+
|
|
4401
|
+
|
|
4402
|
+
|
|
4403
|
+
|
|
4404
|
+
|
|
4405
|
+
|
|
4406
|
+
|
|
4407
|
+
|
|
4408
|
+
|
|
4409
|
+
|
|
4410
|
+
|
|
4411
|
+
|
|
4412
|
+
|
|
4413
|
+
|
|
4414
|
+
|
|
4415
|
+
|
|
4416
|
+
exports.BRUSH_ID = BRUSH_ID; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.DEFAULT_DPI = DEFAULT_DPI; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.DEFAULT_PAPER = DEFAULT_PAPER; exports.DEFAULT_SWATCHES = DEFAULT_SWATCHES; exports.DEFAULT_VIEW = DEFAULT_VIEW; exports.DryStroke = DryStroke; exports.GRAIN_PATTERNS = GRAIN_PATTERNS; exports.GRAIN_TILE = GRAIN_TILE; exports.HARMONY_KINDS = HARMONY_KINDS; exports.IDENTITY_AFFINE = IDENTITY_AFFINE; exports.IDENTITY_COPY = IDENTITY_COPY; exports.MAX_BRUSH_SIZE = _chunkPV42JITTcjs.MAX_BRUSH_SIZE; exports.MAX_CANVAS_SIDE = MAX_CANVAS_SIDE; exports.MAX_COORDINATE = MAX_COORDINATE; exports.MAX_IMAGE_SIDE = MAX_IMAGE_SIDE; exports.MAX_ZOOM = MAX_ZOOM; exports.MIN_CANVAS_SIDE = MIN_CANVAS_SIDE; exports.MIN_ZOOM = MIN_ZOOM; exports.MIXING_PAD_HEIGHT = MIXING_PAD_HEIGHT; exports.MIXING_PAD_WIDTH = MIXING_PAD_WIDTH; exports.MixingPad = MixingPad; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_FORMAT = PAINT_FORMAT; exports.PAINT_SCHEMA_VERSION = PAINT_SCHEMA_VERSION; exports.PAPER_FAMILIES = PAPER_FAMILIES; exports.PAPER_LIBRARY = PAPER_LIBRARY; exports.PAPER_NODE_ID = PAPER_NODE_ID; exports.PaintDocumentError = PaintDocumentError; exports.PaintEditorController = PaintEditorController; exports.PaintEditorError = PaintEditorError; exports.PaintStrokeSession = PaintStrokeSession; exports.PaperGrain = PaperGrain; exports.PenDabWalker = PenDabWalker; exports.PenSmoother = PenSmoother; exports.PerspectiveSnapper = PerspectiveSnapper; exports.SNAP_DECIDE_DISTANCE = SNAP_DECIDE_DISTANCE; exports.SYMMETRY_MODES = SYMMETRY_MODES; exports.TRACING_NODE_ID = TRACING_NODE_ID; exports.TileableNoise = TileableNoise; exports.affineCopy = affineCopy; exports.applyAffine = applyAffine; exports.brushToJSON = brushToJSON; exports.clonePaper = clonePaper; exports.colorHarmony = colorHarmony; exports.composeAffine = composeAffine; exports.createPaintCompositor = createPaintCompositor; exports.createPaintDocument = createPaintDocument; exports.createPaintEditorToolProvider = createPaintEditorToolProvider; exports.createPaintLayer = createPaintLayer; exports.defaultGuides = defaultGuides; exports.defaultImageEncoder = defaultImageEncoder; exports.deserializePaintDocument = deserializePaintDocument; exports.deserializePaintDocumentAsync = deserializePaintDocumentAsync; exports.documentExtensions = documentExtensions; exports.documentToScreen = documentToScreen; exports.downsampleRgba = downsampleRgba; exports.fitZoom = fitZoom; exports.grainCatch = grainCatch; exports.grainHeights = grainHeights; exports.hash2 = hash2; exports.hexToRgb8 = hexToRgb8; exports.hslToRgb = hslToRgb; exports.hsvToRgb = hsvToRgb; exports.hueToWheel = hueToWheel; exports.invertAffine = invertAffine; exports.isPalmTouch = isPalmTouch; exports.mapPenPoint = mapPenPoint; exports.mix32 = mix32; exports.mixerLoadForStroke = mixerLoadForStroke; exports.mixerTip = mixerTip; exports.newPaintLayerId = newPaintLayerId; exports.normalizeBrushStroke = normalizeBrushStroke; exports.normalizeCanvas = normalizeCanvas; exports.normalizeDynamics = normalizeDynamics; exports.normalizePaintBrush = normalizePaintBrush; exports.normalizePaper = normalizePaper; exports.normalizePerspective = normalizePerspective; exports.normalizeRotation = normalizeRotation; exports.normalizeSymmetry = normalizeSymmetry; exports.normalizeView = normalizeView; exports.paperGrain = paperGrain; exports.paperKey = paperKey; exports.paperNode = paperNode; exports.paperPreset = paperPreset; exports.parseBrushLibrary = parseBrushLibrary; exports.parseHexColor = parseHexColor; exports.penOrientation = penOrientation; exports.perspectiveDirections = perspectiveDirections; exports.perspectiveGuideLines = perspectiveGuideLines; exports.pointerToPenPoint = pointerToPenPoint; exports.rand01 = rand01; exports.randSigned = randSigned; exports.renderBrushThumbnail = renderBrushThumbnail; exports.renderPaper = renderPaper; exports.rgbToHexColor = rgbToHexColor; exports.rgbToHsl = rgbToHsl; exports.rgbToHsv = rgbToHsv; exports.samplePressure = samplePressure; exports.screenToDocument = screenToDocument; exports.seedFromString = seedFromString; exports.serializePaintDocument = serializePaintDocument; exports.serializePaintDocumentExternal = serializePaintDocumentExternal; exports.strokeBounds = strokeBounds; exports.symmetryAxisAngles = symmetryAxisAngles; exports.symmetryAxisSegments = symmetryAxisSegments; exports.symmetryCopies = symmetryCopies; exports.thumbnailStroke = thumbnailStroke; exports.tiltToOrientation = tiltToOrientation; exports.toStrokePoint = toStrokePoint; exports.variationAffine = variationAffine; exports.varyStroke = varyStroke; exports.viewCssTransform = viewCssTransform; exports.viewMatrix = viewMatrix; exports.viewPenToDocument = viewPenToDocument; exports.wheelToHue = wheelToHue;
|
|
3330
4417
|
//# sourceMappingURL=index.cjs.map
|