@pixerate/editor 0.109.0 → 0.110.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.
Files changed (61) hide show
  1. package/dist/blending-DXl06NSx.d.ts +486 -0
  2. package/dist/blending-aOE6Y3UE.d.cts +486 -0
  3. package/dist/chunk-7VOHOPLW.cjs +1136 -0
  4. package/dist/chunk-7VOHOPLW.cjs.map +1 -0
  5. package/dist/{chunk-RE77QYUF.js → chunk-DMRSJBJF.js} +3 -3
  6. package/dist/chunk-FTBZPI77.cjs +1 -0
  7. package/dist/chunk-FTBZPI77.cjs.map +1 -0
  8. package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
  9. package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
  10. package/dist/{chunk-CFMWALBM.js → chunk-LQEEDHOP.js} +2 -2
  11. package/dist/{chunk-D37BX2ZA.cjs → chunk-MGL4U5WA.cjs} +199 -199
  12. package/dist/{chunk-D37BX2ZA.cjs.map → chunk-MGL4U5WA.cjs.map} +1 -1
  13. package/dist/{chunk-AZTZRLDI.cjs → chunk-POGBUYWE.cjs} +92 -92
  14. package/dist/{chunk-AZTZRLDI.cjs.map → chunk-POGBUYWE.cjs.map} +1 -1
  15. package/dist/chunk-VKK5VS2T.js +1 -0
  16. package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
  17. package/dist/chunk-XIOMMGPD.cjs.map +1 -0
  18. package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
  19. package/dist/chunk-Z4BC6M74.js.map +1 -0
  20. package/dist/{export-BLPLaE6Z.d.ts → export-CO2ryu3I.d.ts} +3 -3
  21. package/dist/{export-DSUc4fQD.d.cts → export-DRiLzP_j.d.cts} +3 -3
  22. package/dist/image-editor/index.cjs +722 -696
  23. package/dist/image-editor/index.cjs.map +1 -1
  24. package/dist/image-editor/index.d.cts +9 -9
  25. package/dist/image-editor/index.d.ts +9 -9
  26. package/dist/image-editor/index.js +31 -5
  27. package/dist/image-editor/index.js.map +1 -1
  28. package/dist/paint/index.cjs +28 -4
  29. package/dist/paint/index.cjs.map +1 -1
  30. package/dist/paint/index.d.cts +3 -89
  31. package/dist/paint/index.d.ts +3 -89
  32. package/dist/paint/index.js +27 -3
  33. package/dist/paint-editor/index.cjs +1052 -286
  34. package/dist/paint-editor/index.cjs.map +1 -1
  35. package/dist/paint-editor/index.d.cts +301 -22
  36. package/dist/paint-editor/index.d.ts +301 -22
  37. package/dist/paint-editor/index.js +848 -82
  38. package/dist/paint-editor/index.js.map +1 -1
  39. package/dist/raster/index.cjs +5 -3
  40. package/dist/raster/index.cjs.map +1 -1
  41. package/dist/raster/index.d.cts +2 -2
  42. package/dist/raster/index.d.ts +2 -2
  43. package/dist/raster/index.js +4 -2
  44. package/dist/{render-C-fO_mes.d.cts → render-BEriFuqK.d.cts} +1 -1
  45. package/dist/{render-OLIMk_v0.d.ts → render-tjzKaik-.d.ts} +1 -1
  46. package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
  47. package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
  48. package/package.json +1 -1
  49. package/skills/pixerate-paint-editor/SKILL.md +49 -13
  50. package/dist/chunk-3PHA3UKI.js.map +0 -1
  51. package/dist/chunk-6BU5WU4R.cjs +0 -1
  52. package/dist/chunk-6BU5WU4R.cjs.map +0 -1
  53. package/dist/chunk-HUFQD4CB.cjs +0 -734
  54. package/dist/chunk-HUFQD4CB.cjs.map +0 -1
  55. package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
  56. package/dist/chunk-XYBJKUAU.js +0 -1
  57. package/dist/mixer-5ziHU8OS.d.cts +0 -282
  58. package/dist/mixer-DNqiYXYQ.d.ts +0 -282
  59. /package/dist/{chunk-RE77QYUF.js.map → chunk-DMRSJBJF.js.map} +0 -0
  60. /package/dist/{chunk-CFMWALBM.js.map → chunk-LQEEDHOP.js.map} +0 -0
  61. /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
@@ -1,10 +1,10 @@
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; var _class10; var _class11;
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; var _class10; var _class11; var _class12;
2
2
 
3
3
 
4
4
 
5
5
 
6
6
 
7
- var _chunkD37BX2ZAcjs = require('../chunk-D37BX2ZA.cjs');
7
+ var _chunkMGL4U5WAcjs = require('../chunk-MGL4U5WA.cjs');
8
8
 
9
9
 
10
10
 
@@ -17,7 +17,9 @@ var _chunkD37BX2ZAcjs = require('../chunk-D37BX2ZA.cjs');
17
17
 
18
18
 
19
19
 
20
- var _chunkAZTZRLDIcjs = require('../chunk-AZTZRLDI.cjs');
20
+
21
+
22
+ var _chunkPOGBUYWEcjs = require('../chunk-POGBUYWE.cjs');
21
23
  require('../chunk-MUUXNRU4.cjs');
22
24
 
23
25
 
@@ -35,11 +37,18 @@ require('../chunk-MUUXNRU4.cjs');
35
37
 
36
38
 
37
39
 
38
- var _chunkHUFQD4CBcjs = require('../chunk-HUFQD4CB.cjs');
39
40
 
40
41
 
41
42
 
42
43
 
44
+
45
+
46
+
47
+
48
+
49
+
50
+ var _chunk7VOHOPLWcjs = require('../chunk-7VOHOPLW.cjs');
51
+
43
52
 
44
53
 
45
54
 
@@ -53,7 +62,13 @@ var _chunkHUFQD4CBcjs = require('../chunk-HUFQD4CB.cjs');
53
62
 
54
63
 
55
64
 
56
- var _chunkSZ6KMZNMcjs = require('../chunk-SZ6KMZNM.cjs');
65
+
66
+
67
+
68
+
69
+
70
+
71
+ var _chunkXIOMMGPDcjs = require('../chunk-XIOMMGPD.cjs');
57
72
 
58
73
 
59
74
 
@@ -412,7 +427,7 @@ function newPaintLayerId() {
412
427
  return `pl${Date.now().toString(36)}${(nextId++).toString(36)}`;
413
428
  }
414
429
  function createPaintLayer(name, id = newPaintLayerId()) {
415
- return { type: "pixel", id, name, visible: true, opacity: 1, blend: "normal", surface: _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4), offset: { x: 0, y: 0 } };
430
+ return { type: "pixel", id, name, visible: true, opacity: 1, blend: "normal", surface: _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4), offset: { x: 0, y: 0 } };
416
431
  }
417
432
  var PaintDocumentError = class extends Error {
418
433
  constructor(message) {
@@ -438,8 +453,11 @@ function createPaintDocument(options = {}) {
438
453
  }
439
454
 
440
455
  // src/paint-editor/brushes.ts
441
- var PAINT_BRUSH_ENGINES = ["dab", "eraser", "mixer"];
442
- var PAINT_BRUSH_CATEGORIES = ["pencil", "pastel", "charcoal", "airbrush", "marker", "ink", "eraser", "blender", "smudge", "mixer", "custom"];
456
+ var PAINT_BRUSH_ENGINES = ["dab", "eraser", "mixer", "blender", "smudge"];
457
+ var PAINT_BRUSH_CATEGORIES = ["pencil", "pastel", "charcoal", "airbrush", "marker", "ink", "paint", "eraser", "blender", "smudge", "mixer", "custom"];
458
+ var MULTI_COLOR_MODES = ["off", "tip", "dab"];
459
+ var DEFAULT_MULTI_COLOR = { mode: "off", colors: [], order: "sequence", length: 12, softness: 1 };
460
+ var DEFAULT_BEHAVIOR = { pickup: 0, mix: 0.5, dirty: false, mixing: "pigment", multicolor: DEFAULT_MULTI_COLOR };
443
461
  var BRUSH_ROTATIONS = ["fixed", "stroke", "barrel"];
444
462
  var DEFAULT_DYNAMICS = {
445
463
  minSize: 0,
@@ -472,21 +490,62 @@ function normalizeDynamics(raw = {}, base = DEFAULT_DYNAMICS) {
472
490
  };
473
491
  }
474
492
  var BRUSH_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
493
+ var HEX2 = /^#[0-9a-f]{6}$/i;
494
+ function normalizeMultiColor(raw = {}, base = DEFAULT_MULTI_COLOR) {
495
+ const colors = Array.isArray(raw.colors) ? raw.colors.filter((c) => typeof c === "string" && HEX2.test(c.trim())).map((c) => c.trim().toLowerCase()).slice(0, 8) : base.colors;
496
+ return {
497
+ mode: MULTI_COLOR_MODES.includes(raw.mode) ? raw.mode : base.mode,
498
+ colors: [...colors],
499
+ order: raw.order === "random" || raw.order === "sequence" ? raw.order : base.order,
500
+ length: typeof raw.length === "number" && Number.isFinite(raw.length) ? Math.min(500, Math.max(1, Math.round(raw.length))) : base.length,
501
+ softness: unit2(raw.softness, base.softness)
502
+ };
503
+ }
504
+ function normalizeBehavior(raw = {}, base = DEFAULT_BEHAVIOR) {
505
+ return {
506
+ pickup: unit2(raw.pickup, base.pickup),
507
+ mix: unit2(raw.mix, base.mix),
508
+ dirty: typeof raw.dirty === "boolean" ? raw.dirty : base.dirty,
509
+ mixing: raw.mixing === "rgb" || raw.mixing === "pigment" ? raw.mixing : base.mixing,
510
+ multicolor: normalizeMultiColor(_nullishCoalesce(raw.multicolor, () => ( {})), base.multicolor)
511
+ };
512
+ }
513
+ function isWetCapable(brush) {
514
+ return brush.engine === "dab" || brush.engine === "mixer";
515
+ }
516
+ function usesPaintExchange(brush) {
517
+ return brush.engine === "dab" && (brush.behavior.pickup > 0 || brush.behavior.multicolor.mode !== "off");
518
+ }
475
519
  function normalizePaintBrush(raw = {}, base) {
476
520
  const b = _nullishCoalesce(base, () => ( BASE_BRUSH));
477
521
  const id = typeof raw.id === "string" && BRUSH_ID.test(raw.id) ? raw.id : b.id;
522
+ const engine = PAINT_BRUSH_ENGINES.includes(raw.engine) ? raw.engine : b.engine;
523
+ const rb = _nullishCoalesce(raw.behavior, () => ( {}));
524
+ const mixerIn = { ...b.mixer, ..._nullishCoalesce(raw.mixer, () => ( {})) };
525
+ if (engine === "mixer") {
526
+ if (rb.pickup !== void 0) mixerIn.wet = rb.pickup;
527
+ if (rb.mix !== void 0) mixerIn.mix = rb.mix;
528
+ if (rb.dirty !== void 0) mixerIn.cleanEachStroke = !rb.dirty;
529
+ if (rb.mixing !== void 0) mixerIn.mixing = rb.mixing;
530
+ }
531
+ const mixer = _chunk7VOHOPLWcjs.normalizeMixer.call(void 0, mixerIn);
532
+ let behavior = normalizeBehavior(rb, b.behavior);
533
+ if (engine === "mixer") behavior = { ...behavior, pickup: mixer.wet, mix: mixer.mix, dirty: !mixer.cleanEachStroke, mixing: mixer.mixing };
478
534
  return {
479
535
  id,
480
536
  name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, 80) : b.name,
481
537
  category: PAINT_BRUSH_CATEGORIES.includes(raw.category) ? raw.category : b.category,
482
- engine: PAINT_BRUSH_ENGINES.includes(raw.engine) ? raw.engine : b.engine,
483
- tip: _chunkHUFQD4CBcjs.normalizeBrush.call(void 0, { ...b.tip, ..._nullishCoalesce(raw.tip, () => ( {})) }),
538
+ engine,
539
+ tip: _chunk7VOHOPLWcjs.normalizeBrush.call(void 0, { ...b.tip, ..._nullishCoalesce(raw.tip, () => ( {})) }),
484
540
  grain: unit2(raw.grain, b.grain),
485
541
  pressureSmoothing: range(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
486
542
  rotation: BRUSH_ROTATIONS.includes(raw.rotation) ? raw.rotation : b.rotation,
487
543
  dynamics: normalizeDynamics(_nullishCoalesce(raw.dynamics, () => ( {})), b.dynamics),
488
- mixer: _chunkHUFQD4CBcjs.normalizeMixer.call(void 0, { ...b.mixer, ..._nullishCoalesce(raw.mixer, () => ( {})) }),
544
+ mixer,
489
545
  loadsColor: typeof raw.loadsColor === "boolean" ? raw.loadsColor : b.loadsColor,
546
+ behavior,
547
+ blender: _chunk7VOHOPLWcjs.normalizeBlender.call(void 0, _nullishCoalesce(raw.blender, () => ( {})), b.blender),
548
+ smudge: _chunk7VOHOPLWcjs.normalizeSmudge.call(void 0, _nullishCoalesce(raw.smudge, () => ( {})), b.smudge),
490
549
  ...raw.builtin ? { builtin: true } : {}
491
550
  };
492
551
  }
@@ -495,13 +554,16 @@ var BASE_BRUSH = {
495
554
  name: "Round",
496
555
  category: "custom",
497
556
  engine: "dab",
498
- tip: { ..._chunkHUFQD4CBcjs.DEFAULT_BRUSH },
557
+ tip: { ..._chunk7VOHOPLWcjs.DEFAULT_BRUSH },
499
558
  grain: 0,
500
559
  pressureSmoothing: 0,
501
560
  rotation: "fixed",
502
561
  dynamics: { ...DEFAULT_DYNAMICS },
503
- mixer: { ..._chunkHUFQD4CBcjs.DEFAULT_MIXER },
504
- loadsColor: true
562
+ mixer: { ..._chunk7VOHOPLWcjs.DEFAULT_MIXER },
563
+ loadsColor: true,
564
+ behavior: { ...DEFAULT_BEHAVIOR, multicolor: { ...DEFAULT_MULTI_COLOR, colors: [] } },
565
+ blender: { ..._chunk7VOHOPLWcjs.DEFAULT_BLENDER },
566
+ smudge: { ..._chunk7VOHOPLWcjs.DEFAULT_SMUDGE }
505
567
  };
506
568
  var builtin = (init) => ({ ...normalizePaintBrush(init), builtin: true });
507
569
  var BUILTIN_BRUSHES = [
@@ -615,19 +677,54 @@ var BUILTIN_BRUSHES = [
615
677
  id: "blender",
616
678
  name: "Blender",
617
679
  category: "blender",
618
- engine: "mixer",
680
+ engine: "blender",
619
681
  loadsColor: false,
620
- tip: { size: 34, hardness: 0.4, spacing: 0.08, opacity: 1, flow: 0.35, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
621
- mixer: { wet: 0.7, load: 0, mix: 1, loadEachStroke: false, cleanEachStroke: true, mixing: "pigment" }
682
+ tip: { size: 34, hardness: 0.4, spacing: 0.1, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
683
+ blender: { strength: 0.5, distance: 0.35, mixing: "pigment" }
622
684
  }),
623
685
  builtin({
624
686
  id: "smudge",
625
687
  name: "Smudge",
626
688
  category: "smudge",
627
- engine: "mixer",
689
+ engine: "smudge",
628
690
  loadsColor: false,
629
- tip: { size: 26, hardness: 0.6, spacing: 0.06, opacity: 1, flow: 0.8, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
630
- mixer: { wet: 1, load: 0, mix: 1, loadEachStroke: false, cleanEachStroke: true, mixing: "rgb" }
691
+ tip: { size: 26, hardness: 0.6, spacing: 0.06, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
692
+ smudge: { strength: 0.75, length: 0.85, mixing: "rgb" }
693
+ }),
694
+ builtin({
695
+ id: "wet-smudge",
696
+ name: "Pigment smudge",
697
+ category: "smudge",
698
+ engine: "smudge",
699
+ loadsColor: false,
700
+ tip: { size: 30, hardness: 0.5, spacing: 0.06, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: true, pressureOpacity: true },
701
+ smudge: { strength: 0.7, length: 0.9, mixing: "pigment" },
702
+ dynamics: { minSize: 0.6 }
703
+ }),
704
+ builtin({
705
+ id: "dirty-round",
706
+ name: "Dirty round",
707
+ category: "paint",
708
+ tip: { size: 22, hardness: 0.65, spacing: 0.06, opacity: 1, flow: 0.7, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
709
+ behavior: { pickup: 0.55, mix: 0.6, dirty: true, mixing: "pigment" },
710
+ dynamics: { minSize: 0.5 }
711
+ }),
712
+ builtin({
713
+ id: "multicolor-flat",
714
+ name: "Multi-colour flat",
715
+ category: "paint",
716
+ rotation: "stroke",
717
+ tip: { size: 30, hardness: 0.75, spacing: 0.05, angle: 90, roundness: 0.3, opacity: 1, flow: 0.85, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
718
+ behavior: { pickup: 0.15, mix: 0.4, mixing: "pigment", multicolor: { mode: "tip", softness: 0.7 } },
719
+ dynamics: { minSize: 0.6 }
720
+ }),
721
+ builtin({
722
+ id: "palette-dabber",
723
+ name: "Palette dabber",
724
+ category: "paint",
725
+ tip: { size: 18, hardness: 0.7, spacing: 0.35, opacity: 1, flow: 0.9, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
726
+ behavior: { multicolor: { mode: "dab", order: "sequence", length: 6, softness: 0.4 } },
727
+ dynamics: { minSize: 0.5, sizeJitter: 0.15, scatter: 0.3 }
631
728
  }),
632
729
  builtin({
633
730
  id: "mixer-brush",
@@ -807,7 +904,9 @@ var PenDabWalker = (_class4 = class {
807
904
  opacity: Math.min(1, Math.max(0, opacity)),
808
905
  angle,
809
906
  roundness: Math.min(1, Math.max(0.01, tip.roundness * (1 - dyn.tiltRoundness * s.lean * 0.8))),
810
- pressure: s.pressure
907
+ pressure: s.pressure,
908
+ direction: this.dir,
909
+ index: i
811
910
  };
812
911
  }
813
912
  push(p, out) {
@@ -877,7 +976,7 @@ var DryStroke = (_class5 = class {
877
976
  }
878
977
 
879
978
 
880
- __init12() {this.coverage = new (0, _chunkHUFQD4CBcjs.CoverageMap)()}
979
+ __init12() {this.coverage = new (0, _chunk7VOHOPLWcjs.CoverageMap)()}
881
980
  __init13() {this.dabCount = 0}
882
981
 
883
982
 
@@ -911,11 +1010,11 @@ var DryStroke = (_class5 = class {
911
1010
  const hardness = this.brush.tip.hardness;
912
1011
  const grain = this.options.grain;
913
1012
  for (const d of dabs) {
914
- const rect = _chunkHUFQD4CBcjs.dabRect.call(void 0, d, hardness);
1013
+ const rect = _chunk7VOHOPLWcjs.dabRect.call(void 0, d, hardness);
915
1014
  const n = rect.width * rect.height;
916
1015
  if (this.buf.length < n) this.buf = new Float32Array(n);
917
1016
  const vals = this.buf.subarray(0, n);
918
- _chunkHUFQD4CBcjs.rasterizeDab.call(void 0, d, hardness, rect, vals);
1017
+ _chunk7VOHOPLWcjs.rasterizeDab.call(void 0, d, hardness, rect, vals);
919
1018
  if (this.depth > 0 && grain) {
920
1019
  for (let yy = 0, k = 0; yy < rect.height; yy++) {
921
1020
  const y = rect.y + yy + 0.5;
@@ -938,9 +1037,251 @@ function toStrokePoint(p) {
938
1037
  return { x: p.x, y: p.y, pressure: pressureOf(p), ...typeof p.t === "number" ? { t: p.t } : {} };
939
1038
  }
940
1039
 
1040
+ // src/paint-editor/dab-stroke.ts
1041
+ var GRADIENT_STEPS = 32;
1042
+ function multiColorPalette(mc, color, secondary) {
1043
+ if (mc.colors.length >= 2) {
1044
+ return mc.colors.map((h) => {
1045
+ const n = parseInt(h.slice(1), 16);
1046
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
1047
+ });
1048
+ }
1049
+ return [[...color], [...secondary]];
1050
+ }
1051
+ function soften(f, softness) {
1052
+ if (softness >= 1) return f;
1053
+ const w = Math.max(1e-3, softness);
1054
+ return Math.min(1, Math.max(0, (f - 0.5) / w + 0.5));
1055
+ }
1056
+ function mixRgb(a, b, t, mixing) {
1057
+ if (t <= 0) return [...a];
1058
+ if (t >= 1) return [...b];
1059
+ if (mixing === "pigment") {
1060
+ const o = [0, 0, 0];
1061
+ const q = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255);
1062
+ _chunkXIOMMGPDcjs.mixPigment8.call(void 0, q(a[0]), q(a[1]), q(a[2]), q(b[0]), q(b[1]), q(b[2]), t, o);
1063
+ return [o[0], o[1], o[2]];
1064
+ }
1065
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
1066
+ }
1067
+ function multiColorGradient(palette, softness, mixing) {
1068
+ const n = palette.length;
1069
+ const out = [];
1070
+ for (let j = 0; j < GRADIENT_STEPS; j++) {
1071
+ const f = j / (GRADIENT_STEPS - 1) * (n - 1);
1072
+ const i = Math.min(n - 2, Math.floor(f));
1073
+ out.push(mixRgb(palette[i], palette[i + 1], soften(f - i, softness), mixing));
1074
+ }
1075
+ return out;
1076
+ }
1077
+ function multiColorForDab(mc, palette, index, seed, mixing) {
1078
+ const n = palette.length;
1079
+ if (mc.order === "random") {
1080
+ const run = Math.floor(index / mc.length);
1081
+ return [...palette[Math.min(n - 1, Math.floor(rand01(seed, run, 11) * n))]];
1082
+ }
1083
+ const f = index / mc.length % n;
1084
+ const i = Math.floor(f);
1085
+ return mixRgb(palette[i], palette[(i + 1) % n], soften(f - i, mc.softness), mixing);
1086
+ }
1087
+ var PaintDabStroke = (_class6 = class {
1088
+ constructor(brush, options) {;_class6.prototype.__init17.call(this);_class6.prototype.__init18.call(this);_class6.prototype.__init19.call(this);_class6.prototype.__init20.call(this);_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);
1089
+ this.brush = brush;
1090
+ this.options = options;
1091
+ this.smoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
1092
+ this.copies = _optionalChain([options, 'access', _4 => _4.copies, 'optionalAccess', _5 => _5.length]) ? options.copies : [IDENTITY_COPY];
1093
+ const exchanging = brush.engine === "mixer" || brush.engine === "dab";
1094
+ const rates = brush.engine === "mixer" ? _chunk7VOHOPLWcjs.mixerRates.call(void 0, brush.mixer) : _chunk7VOHOPLWcjs.exchangeRates.call(void 0, brush.behavior.pickup, brush.behavior.mix, 1, brush.behavior.mixing);
1095
+ this.mixing = brush.engine === "mixer" ? brush.mixer.mixing : brush.behavior.mixing;
1096
+ this.states = this.copies.map((c, i) => ({
1097
+ walker: new PenDabWalker(brush, (_nullishCoalesce(options.seed, () => ( 0))) + i * 7919 | 0, c.linear),
1098
+ exchange: exchanging ? new (0, _chunk7VOHOPLWcjs.PaintExchange)(rates, options.load) : null,
1099
+ smudge: brush.engine === "smudge" ? new (0, _chunk7VOHOPLWcjs.SmudgeTip)(brush.smudge, brush.tip.size * (1 + brush.dynamics.tiltSize * 1.5)) : null
1100
+ }));
1101
+ this.medium = new (0, _chunk7VOHOPLWcjs.TileMedium)(options.target, brush.engine === "dab" ? { blend: brush.tip.blend } : {});
1102
+ this.depth = options.grain && !options.grain.flat ? Math.min(1, Math.max(0, options.paperDepth * brush.grain)) : 0;
1103
+ this.coverage = brush.engine === "dab" ? new (0, _chunk7VOHOPLWcjs.CoverageMap)() : null;
1104
+ const mc = brush.behavior.multicolor;
1105
+ this.palette = exchanging && mc.mode !== "off" ? multiColorPalette(mc, options.color, options.secondary) : null;
1106
+ this.gradient = this.palette && mc.mode === "tip" ? multiColorGradient(this.palette, mc.softness, this.mixing) : null;
1107
+ this.scratch = brush.engine === "blender" ? _chunk7VOHOPLWcjs.createBlenderScratch.call(void 0, ) : null;
1108
+ }
1109
+
1110
+
1111
+ __init17() {this.dabCount = 0}
1112
+
1113
+
1114
+
1115
+
1116
+
1117
+
1118
+
1119
+
1120
+ __init18() {this.fieldLut = []}
1121
+
1122
+
1123
+ __init19() {this.tip = new Float32Array(0)}
1124
+ __init20() {this.cov = new Float32Array(0)}
1125
+ __init21() {this.field = new Float32Array(0)}
1126
+ __init22() {this.paint = [0, 0, 0]}
1127
+ __init23() {this.lastRaw = null}
1128
+ __init24() {this.lastFed = null}
1129
+ /** What copy 0's brush carries after the dabs so far (mixer and wet-capable dab brushes). */
1130
+ get load() {
1131
+ return _nullishCoalesce(_optionalChain([this, 'access', _6 => _6.states, 'access', _7 => _7[0], 'access', _8 => _8.exchange, 'optionalAccess', _9 => _9.load]), () => ( null));
1132
+ }
1133
+ push(points) {
1134
+ const rects = [];
1135
+ for (const raw of points) {
1136
+ this.lastRaw = raw;
1137
+ const p = this.smoother.push(raw);
1138
+ this.lastFed = p;
1139
+ this.copies.forEach((c, i) => {
1140
+ const dabs = [];
1141
+ this.states[i].walker.push(c.map(p), dabs);
1142
+ this.paintDabs(i, dabs, rects);
1143
+ });
1144
+ }
1145
+ return rects;
1146
+ }
1147
+ /** Ends the stroke: the smoothed path catches up with the last raw sample; a tap paints one dab. */
1148
+ finish() {
1149
+ const rects = [];
1150
+ const raw = this.lastRaw;
1151
+ const fed = this.lastFed;
1152
+ this.copies.forEach((c, i) => {
1153
+ const dabs = [];
1154
+ if (raw && fed && (raw.x !== fed.x || raw.y !== fed.y)) this.states[i].walker.push(c.map({ ...raw, pressure: fed.pressure }), dabs);
1155
+ this.states[i].walker.finish(dabs);
1156
+ this.paintDabs(i, dabs, rects);
1157
+ });
1158
+ return rects;
1159
+ }
1160
+ paintDabs(copy, dabs, rects) {
1161
+ for (const d of dabs) {
1162
+ const rect = _chunk7VOHOPLWcjs.dabRect.call(void 0, d, this.brush.tip.hardness);
1163
+ if (rect.width <= 0 || rect.height <= 0) continue;
1164
+ this.dab(copy, d, rect);
1165
+ rects.push(rect);
1166
+ this.dabCount++;
1167
+ }
1168
+ }
1169
+ /** The tip's pure coverage (`tip`) and, with grain, what the paper catches of it (`cov`). */
1170
+ rasterize(d, rect) {
1171
+ const n = rect.width * rect.height;
1172
+ if (this.tip.length < n) {
1173
+ this.tip = new Float32Array(n);
1174
+ this.cov = new Float32Array(n);
1175
+ }
1176
+ const tip = this.tip.subarray(0, n);
1177
+ tip.fill(0);
1178
+ _chunk7VOHOPLWcjs.rasterizeDab.call(void 0, { ...d, alpha: 1 }, this.brush.tip.hardness, rect, tip);
1179
+ if (!(this.depth > 0 && this.options.grain)) return { tip, cov: tip };
1180
+ const cov = this.cov.subarray(0, n);
1181
+ const grain = this.options.grain;
1182
+ for (let yy = 0, k = 0; yy < rect.height; yy++) {
1183
+ const y = rect.y + yy + 0.5;
1184
+ for (let xx = 0; xx < rect.width; xx++, k++) cov[k] = tip[k] > 0 ? tip[k] * grainCatch(grain.at(rect.x + xx + 0.5, y), this.depth, d.pressure) : 0;
1185
+ }
1186
+ return { tip, cov };
1187
+ }
1188
+ dab(copy, d, rect) {
1189
+ const brush = this.brush;
1190
+ const { tip, cov } = this.rasterize(d, rect);
1191
+ const st = this.states[copy];
1192
+ if (brush.engine === "blender") {
1193
+ _chunk7VOHOPLWcjs.blenderDab.call(void 0, this.medium, rect, cov, d.opacity * d.alpha * brush.tip.opacity, d.radius, brush.blender, this.scratch);
1194
+ return;
1195
+ }
1196
+ if (brush.engine === "smudge") {
1197
+ st.smudge.dab(this.medium, { x: d.x, y: d.y, radius: d.radius, opacity: d.opacity * d.alpha * brush.tip.opacity }, rect, cov);
1198
+ return;
1199
+ }
1200
+ const ex = st.exchange;
1201
+ ex.pickUp(this.medium.sample(rect, tip));
1202
+ const mc = brush.behavior.multicolor;
1203
+ if (this.palette && mc.mode === "dab") {
1204
+ const c = multiColorForDab(mc, this.palette, d.index, _nullishCoalesce(this.options.seed, () => ( 0)), this.mixing);
1205
+ ex.setReservoirColor(c[0], c[1], c[2]);
1206
+ }
1207
+ let field = null;
1208
+ let total;
1209
+ if (this.gradient) {
1210
+ total = this.tipField(ex, d, rect);
1211
+ field = this.field;
1212
+ } else total = ex.paint(this.paint);
1213
+ if (total > 0) {
1214
+ if (this.coverage) this.depositWithCeiling(d, rect, cov, field, ex);
1215
+ else {
1216
+ const dep = Math.min(1, total) * d.alpha * brush.tip.opacity * d.opacity;
1217
+ if (dep > 0) this.medium.deposit(rect, cov, dep, this.paint, ex.paintPigment, field);
1218
+ }
1219
+ }
1220
+ ex.drain();
1221
+ }
1222
+ /** Fills `this.field` with the tip gradient (mixed with the carried paint) per pixel; returns the paint weight. */
1223
+ tipField(ex, d, rect) {
1224
+ const g = this.gradient;
1225
+ const lut = this.fieldLut;
1226
+ let total = 0;
1227
+ for (let j = 0; j < g.length; j++) {
1228
+ ex.setReservoirColor(g[j][0], g[j][1], g[j][2]);
1229
+ const out = _nullishCoalesce(lut[j], () => ( [0, 0, 0]));
1230
+ total = ex.paint(out);
1231
+ lut[j] = out;
1232
+ }
1233
+ const n = rect.width * rect.height;
1234
+ if (this.field.length < n * 3) this.field = new Float32Array(n * 3);
1235
+ const f = this.field;
1236
+ const nx = Math.sin(d.direction);
1237
+ const ny = Math.cos(d.direction);
1238
+ const r = Math.max(0.5, d.radius);
1239
+ const last = g.length - 1;
1240
+ for (let yy = 0, k = 0; yy < rect.height; yy++) {
1241
+ const py = rect.y + yy + 0.5 - d.y;
1242
+ for (let xx = 0; xx < rect.width; xx++, k++) {
1243
+ const u = ((rect.x + xx + 0.5 - d.x) * nx + py * ny) / r;
1244
+ const j = Math.min(last, Math.max(0, Math.round((u + 1) / 2 * last)));
1245
+ f[k * 3] = lut[j][0];
1246
+ f[k * 3 + 1] = lut[j][1];
1247
+ f[k * 3 + 2] = lut[j][2];
1248
+ }
1249
+ }
1250
+ return total;
1251
+ }
1252
+ /** A dab brush's deposit: only what this dab adds to the stroke's coverage, under its opacity ceiling. */
1253
+ depositWithCeiling(d, rect, cov, field, ex) {
1254
+ const map = this.coverage;
1255
+ const n = rect.width * rect.height;
1256
+ const before = new Float32Array(n);
1257
+ const vals = new Float32Array(n);
1258
+ for (let yy = 0, k = 0; yy < rect.height; yy++) {
1259
+ for (let xx = 0; xx < rect.width; xx++, k++) {
1260
+ if (cov[k] <= 0) continue;
1261
+ before[k] = map.get(rect.x + xx, rect.y + yy);
1262
+ vals[k] = cov[k] * d.alpha;
1263
+ }
1264
+ }
1265
+ map.accumulate(rect, vals, d.opacity);
1266
+ const o = this.brush.tip.opacity;
1267
+ for (let yy = 0, k = 0; yy < rect.height; yy++) {
1268
+ for (let xx = 0; xx < rect.width; xx++, k++) {
1269
+ if (cov[k] <= 0) {
1270
+ vals[k] = 0;
1271
+ continue;
1272
+ }
1273
+ const c0 = before[k] * o;
1274
+ const c1 = map.get(rect.x + xx, rect.y + yy) * o;
1275
+ vals[k] = c1 > c0 && c0 < 1 ? (c1 - c0) / (1 - c0) : 0;
1276
+ }
1277
+ }
1278
+ this.medium.deposit(rect, vals, 1, this.paint, ex.paintPigment, field);
1279
+ }
1280
+ }, _class6);
1281
+
941
1282
  // src/paint-editor/stroke.ts
942
1283
  function defaultTile(surface) {
943
- const data = new Uint8ClampedArray(_chunkSZ6KMZNMcjs.TILE * _chunkSZ6KMZNMcjs.TILE * surface.channels);
1284
+ const data = new Uint8ClampedArray(_chunkXIOMMGPDcjs.TILE * _chunkXIOMMGPDcjs.TILE * surface.channels);
944
1285
  if (surface.defaultValue) data.fill(surface.defaultValue);
945
1286
  return data;
946
1287
  }
@@ -957,8 +1298,8 @@ function union(a, b) {
957
1298
  const y = Math.min(a.y, b.y);
958
1299
  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 };
959
1300
  }
960
- var PaintStrokeSession = (_class6 = class {
961
- constructor(host, init) {;_class6.prototype.__init17.call(this);_class6.prototype.__init18.call(this);_class6.prototype.__init19.call(this);_class6.prototype.__init20.call(this);_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);
1301
+ var PaintStrokeSession = (_class7 = class {
1302
+ constructor(host, init) {;_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);
962
1303
  this.host = host;
963
1304
  this.init = init;
964
1305
  this.layerId = init.layerId;
@@ -976,10 +1317,18 @@ var PaintStrokeSession = (_class6 = class {
976
1317
  selection: null,
977
1318
  offset: init.offset
978
1319
  };
979
- if (brush.engine === "mixer") {
980
- this.mixerSmoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
981
- const load = init.mixerLoad;
982
- for (let i = 0; i < this.copies.length; i++) this.mixers.push(new (0, _chunkHUFQD4CBcjs.MixerStroke)(mixerTip(brush), brush.mixer, load, this.mixerTarget()));
1320
+ if (brush.engine === "mixer" || brush.engine === "blender" || brush.engine === "smudge" || usesPaintExchange(brush)) {
1321
+ const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
1322
+ this.wet = new PaintDabStroke(brush, {
1323
+ target: this.tileTarget(),
1324
+ color: rgb(init.color),
1325
+ secondary: rgb(_nullishCoalesce(init.secondary, () => ( init.background))),
1326
+ load: init.mixerLoad,
1327
+ grain: init.grain,
1328
+ paperDepth: init.paperDepth,
1329
+ seed: init.seed,
1330
+ copies: this.copies
1331
+ });
983
1332
  } else {
984
1333
  this.dry = new DryStroke(brush, { grain: init.grain, paperDepth: init.paperDepth, seed: init.seed, copies: this.copies });
985
1334
  }
@@ -988,30 +1337,29 @@ var PaintStrokeSession = (_class6 = class {
988
1337
 
989
1338
 
990
1339
  /** The raw input so far (what a replay feeds back in). */
991
- __init17() {this.points = []}
1340
+ __init25() {this.points = []}
992
1341
 
993
1342
 
994
- __init18() {this.touched = /* @__PURE__ */ new Set()}
995
- __init19() {this.fresh = /* @__PURE__ */ new Set()}
996
- __init20() {this.dry = null}
997
- __init21() {this.mixers = []}
998
- __init22() {this.mixerSmoother = null}
1343
+ __init26() {this.touched = /* @__PURE__ */ new Set()}
1344
+ __init27() {this.fresh = /* @__PURE__ */ new Set()}
1345
+ __init28() {this.dry = null}
1346
+ __init29() {this.wet = null}
999
1347
 
1000
1348
 
1001
- __init23() {this.bounds = null}
1002
- __init24() {this.lastRaw = null}
1003
- __init25() {this.closed = false}
1004
- __init26() {this.result = null}
1005
- mixerTarget() {
1349
+ __init30() {this.bounds = null}
1350
+ __init31() {this.lastRaw = null}
1351
+ __init32() {this.closed = false}
1352
+ __init33() {this.result = null}
1353
+ tileTarget() {
1006
1354
  return {
1007
1355
  offset: this.init.offset,
1008
1356
  lockTransparency: this.init.lockTransparency,
1009
1357
  selection: null,
1010
- readTile: (tx, ty) => _nullishCoalesce(_optionalChain([this, 'access', _4 => _4.live, 'access', _5 => _5.get, 'call', _6 => _6(_chunkSZ6KMZNMcjs.tileKey.call(void 0, tx, ty)), 'optionalAccess', _7 => _7.data]), () => ( null)),
1358
+ readTile: (tx, ty) => _nullishCoalesce(_optionalChain([this, 'access', _10 => _10.live, 'access', _11 => _11.get, 'call', _12 => _12(_chunkXIOMMGPDcjs.tileKey.call(void 0, tx, ty)), 'optionalAccess', _13 => _13.data]), () => ( null)),
1011
1359
  writeTile: (tx, ty) => {
1012
- const key = _chunkSZ6KMZNMcjs.tileKey.call(void 0, tx, ty);
1360
+ const key = _chunkXIOMMGPDcjs.tileKey.call(void 0, tx, ty);
1013
1361
  if (!this.touched.has(key)) {
1014
- 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)));
1362
+ const src = _nullishCoalesce(_optionalChain([this, 'access', _14 => _14.base, 'access', _15 => _15.tiles, 'access', _16 => _16.get, 'call', _17 => _17(key), 'optionalAccess', _18 => _18.data]), () => ( defaultTile(this.base)));
1015
1363
  this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
1016
1364
  this.touched.add(key);
1017
1365
  }
@@ -1030,15 +1378,7 @@ var PaintStrokeSession = (_class6 = class {
1030
1378
  if (filter) points = points.map(filter);
1031
1379
  for (const p of points) this.points.push(p);
1032
1380
  this.lastRaw = points[points.length - 1];
1033
- if (this.dry) this.update(this.dry.push(points));
1034
- else {
1035
- const rects = [];
1036
- for (const raw of points) {
1037
- const p = toStrokePoint(this.mixerSmoother.push(raw));
1038
- this.copies.forEach((c, i) => rects.push(...this.mixers[i].push([toStrokePoint(c.map(p))])));
1039
- }
1040
- this.update(rects);
1041
- }
1381
+ this.update(this.dry ? this.dry.push(points) : this.wet.push(points));
1042
1382
  }
1043
1383
  update(rects) {
1044
1384
  if (!rects.length) return;
@@ -1047,57 +1387,47 @@ var PaintStrokeSession = (_class6 = class {
1047
1387
  if (!this.dry) {
1048
1388
  for (const key of this.fresh) this.live.set(key, { data: this.live.get(key).data, uniform: null });
1049
1389
  this.fresh.clear();
1050
- this.host.update(this.layerId, new (0, _chunkSZ6KMZNMcjs.TiledSurface)(ch, new Map(this.live), this.base.defaultValue), rects);
1390
+ this.host.update(this.layerId, new (0, _chunkXIOMMGPDcjs.TiledSurface)(ch, new Map(this.live), this.base.defaultValue), rects);
1051
1391
  return;
1052
1392
  }
1053
1393
  const off = this.init.offset;
1054
1394
  const regions = /* @__PURE__ */ new Map();
1055
1395
  for (const r of rects) {
1056
- for (const t of _chunkSZ6KMZNMcjs.tilesInRect.call(void 0, { x: r.x - off.x, y: r.y - off.y, width: r.width, height: r.height })) {
1057
- const key = _chunkSZ6KMZNMcjs.tileKey.call(void 0, t.tx, t.ty);
1396
+ for (const t of _chunkXIOMMGPDcjs.tilesInRect.call(void 0, { x: r.x - off.x, y: r.y - off.y, width: r.width, height: r.height })) {
1397
+ const key = _chunkXIOMMGPDcjs.tileKey.call(void 0, t.tx, t.ty);
1058
1398
  const cur = regions.get(key);
1059
1399
  regions.set(key, { tx: t.tx, ty: t.ty, r: cur ? union(cur.r, r) : r });
1060
1400
  }
1061
1401
  }
1062
1402
  for (const [key, { tx, ty, r }] of regions) {
1063
- 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)));
1064
- 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;
1403
+ const src = _nullishCoalesce(_optionalChain([this, 'access', _19 => _19.base, 'access', _20 => _20.tiles, 'access', _21 => _21.get, 'call', _22 => _22(key), 'optionalAccess', _23 => _23.data]), () => ( defaultTile(this.base)));
1404
+ const prev = this.touched.has(key) ? _optionalChain([this, 'access', _24 => _24.live, 'access', _25 => _25.get, 'call', _26 => _26(key), 'optionalAccess', _27 => _27.data]) : void 0;
1065
1405
  this.touched.add(key);
1066
- const data = _chunkHUFQD4CBcjs.applyStrokeToTile.call(void 0, this.dry.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
1406
+ const data = _chunk7VOHOPLWcjs.applyStrokeToTile.call(void 0, this.dry.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
1067
1407
  this.live.set(key, { data, uniform: null });
1068
1408
  }
1069
- this.host.update(this.layerId, new (0, _chunkSZ6KMZNMcjs.TiledSurface)(ch, new Map(this.live), this.base.defaultValue), rects);
1409
+ this.host.update(this.layerId, new (0, _chunkXIOMMGPDcjs.TiledSurface)(ch, new Map(this.live), this.base.defaultValue), rects);
1070
1410
  }
1071
1411
  /** Ends the stroke (one undo step) and returns what changed. */
1072
1412
  end() {
1073
1413
  if (this.closed) throw new Error("This stroke has already ended.");
1074
- if (this.dry) this.update(this.dry.finish());
1075
- else {
1076
- const rects = [];
1077
- const last = this.lastRaw ? toStrokePoint(this.lastRaw) : void 0;
1078
- this.copies.forEach((c, i) => rects.push(...this.mixers[i].finish(last ? toStrokePoint(c.map(last)) : void 0)));
1079
- this.update(rects);
1080
- }
1414
+ this.update(this.dry ? this.dry.finish() : this.wet.finish());
1081
1415
  this.closed = true;
1082
1416
  const ch = this.base.channels;
1083
1417
  const tiles = new Map(this.base.tiles);
1084
1418
  let changed = false;
1085
1419
  for (const key of this.touched) {
1086
- const tile = _chunkSZ6KMZNMcjs.finishTile.call(void 0, this.live.get(key).data, ch, this.base.defaultValue);
1420
+ const tile = _chunkXIOMMGPDcjs.finishTile.call(void 0, this.live.get(key).data, ch, this.base.defaultValue);
1087
1421
  const prev = this.base.tiles.get(key);
1088
1422
  if (tile) tiles.set(key, tile);
1089
1423
  else tiles.delete(key);
1090
1424
  if (!sameTile(prev, tile)) changed = true;
1091
1425
  }
1092
- const surface = changed ? new (0, _chunkSZ6KMZNMcjs.TiledSurface)(ch, tiles, this.base.defaultValue) : null;
1093
- const dabs = this.dry ? this.dry.dabCount : this.mixers.reduce((n, m) => n + m.dabCount, 0);
1426
+ const surface = changed ? new (0, _chunkXIOMMGPDcjs.TiledSurface)(ch, tiles, this.base.defaultValue) : null;
1427
+ const dabs = this.dry ? this.dry.dabCount : this.wet.dabCount;
1094
1428
  this.result = { layerId: this.layerId, bounds: changed ? this.bounds : null, dabs, strokeId: null };
1095
- if (this.mixers.length) {
1096
- const brush = this.init.brush;
1097
- let load = this.mixers[0].load;
1098
- if (brush.mixer.cleanEachStroke) load = _chunkHUFQD4CBcjs.cleanMixer.call(void 0, load);
1099
- this.result.mixerLoad = load;
1100
- }
1429
+ const carried = _optionalChain([this, 'access', _28 => _28.wet, 'optionalAccess', _29 => _29.load]);
1430
+ if (carried) this.result.mixerLoad = this.init.brush.behavior.dirty ? carried : _chunk7VOHOPLWcjs.cleanMixer.call(void 0, carried);
1101
1431
  this.host.commit(this, surface, false);
1102
1432
  return this.result;
1103
1433
  }
@@ -1107,12 +1437,17 @@ var PaintStrokeSession = (_class6 = class {
1107
1437
  this.closed = true;
1108
1438
  this.host.commit(this, null, true);
1109
1439
  }
1110
- }, _class6);
1440
+ }, _class7);
1111
1441
  function mixerLoadForStroke(brush, current, color) {
1112
1442
  if (!brush.loadsColor) return { reservoir: null, amount: 0, pickup: brush.mixer.cleanEachStroke ? null : current.pickup };
1113
- if (brush.mixer.loadEachStroke || !current.reservoir) return _chunkHUFQD4CBcjs.loadMixer.call(void 0, current, color);
1443
+ if (brush.mixer.loadEachStroke || !current.reservoir) return _chunk7VOHOPLWcjs.loadMixer.call(void 0, current, color);
1114
1444
  return current;
1115
1445
  }
1446
+ function brushLoadForStroke(brush, current, color) {
1447
+ if (brush.engine === "mixer") return mixerLoadForStroke(brush, current, color);
1448
+ if (usesPaintExchange(brush)) return { reservoir: [color[0], color[1], color[2]], amount: 1, pickup: brush.behavior.dirty ? current.pickup : null };
1449
+ return _chunk7VOHOPLWcjs.EMPTY_MIXER_LOAD;
1450
+ }
1116
1451
 
1117
1452
  // src/paint-editor/strokes.ts
1118
1453
  var IDENTITY_AFFINE = [1, 0, 0, 1, 0, 0];
@@ -1158,6 +1493,17 @@ function readPoint(raw) {
1158
1493
  function readAffine(raw) {
1159
1494
  return Array.isArray(raw) && raw.length === 6 && raw.every(finite) ? raw : null;
1160
1495
  }
1496
+ function readRgb(raw) {
1497
+ return Array.isArray(raw) && raw.length === 3 && raw.every(finite) ? raw.map((v) => Math.min(1, Math.max(0, v))) : null;
1498
+ }
1499
+ function readLoad(raw) {
1500
+ if (!raw || typeof raw !== "object") return null;
1501
+ const r = raw;
1502
+ const reservoir = readRgb(r.reservoir);
1503
+ const pickup = readRgb(r.pickup);
1504
+ if (!reservoir && !pickup) return null;
1505
+ return { reservoir, amount: reservoir && finite(r.amount) ? Math.min(1, Math.max(0, r.amount)) : 0, pickup };
1506
+ }
1161
1507
  function normalizeBrushStroke(raw) {
1162
1508
  if (!raw || typeof raw !== "object") return null;
1163
1509
  const r = raw;
@@ -1174,6 +1520,8 @@ function normalizeBrushStroke(raw) {
1174
1520
  };
1175
1521
  const copies = Array.isArray(r.copies) ? r.copies.map(readAffine).filter((m) => m !== null) : [];
1176
1522
  if (copies.length) out.copies = copies;
1523
+ const load = readLoad(r.load);
1524
+ if (load) out.load = load;
1177
1525
  return out;
1178
1526
  }
1179
1527
  function strokeBounds(points) {
@@ -1210,7 +1558,7 @@ function varyStroke(stroke, m, v) {
1210
1558
  });
1211
1559
  const brush = s !== 1 ? normalizePaintBrush({ ...stroke.brush, tip: { ...stroke.brush.tip, size: stroke.brush.tip.size * s } }) : stroke.brush;
1212
1560
  const out = { ...stroke, brush, points };
1213
- if (_optionalChain([stroke, 'access', _22 => _22.copies, 'optionalAccess', _23 => _23.length])) {
1561
+ if (_optionalChain([stroke, 'access', _30 => _30.copies, 'optionalAccess', _31 => _31.length])) {
1214
1562
  const inv = invertAffine(m);
1215
1563
  out.copies = stroke.copies.map((c) => composeAffine(m, composeAffine(c, inv)));
1216
1564
  }
@@ -1277,7 +1625,7 @@ var PROFILE = {
1277
1625
  }
1278
1626
  };
1279
1627
  function createPaintCompositor() {
1280
- return new (0, _chunkAZTZRLDIcjs.RasterCompositor)(PROFILE);
1628
+ return new (0, _chunkPOGBUYWEcjs.RasterCompositor)(PROFILE);
1281
1629
  }
1282
1630
  function downsampleRgba(data, width, height, tw, th) {
1283
1631
  tw = Math.max(1, Math.round(tw));
@@ -1325,7 +1673,7 @@ function downsampleRgba(data, width, height, tw, th) {
1325
1673
  // src/paint-editor/serialize.ts
1326
1674
  var PAINT_FORMAT = "pixerate-paint";
1327
1675
  var PAINT_SCHEMA_VERSION = 1;
1328
- var KNOWN = /* @__PURE__ */ new Set(["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId", "strokes", "guides", "swatches"]);
1676
+ var KNOWN = /* @__PURE__ */ new Set(["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId", "strokes", "guides", "swatches", "mixingPad"]);
1329
1677
  function documentExtensions(raw) {
1330
1678
  const out = {};
1331
1679
  for (const [k, v] of Object.entries(raw)) if (!KNOWN.has(k)) out[k] = v;
@@ -1345,14 +1693,15 @@ function header(state, options) {
1345
1693
  if (options.strokes !== false && state.strokes.length) out.strokes = JSON.parse(JSON.stringify(state.strokes));
1346
1694
  if (options.guides !== void 0) out.guides = JSON.parse(JSON.stringify(options.guides));
1347
1695
  if (options.swatches !== void 0) out.swatches = JSON.parse(JSON.stringify(options.swatches));
1696
+ if (options.mixingPad !== void 0) out.mixingPad = JSON.parse(JSON.stringify(options.mixingPad));
1348
1697
  return out;
1349
1698
  }
1350
1699
  function serializePaintDocument(state, options = {}) {
1351
- const { layers, assets } = _chunkD37BX2ZAcjs.serializeLayers.call(void 0, state.layers);
1700
+ const { layers, assets } = _chunkMGL4U5WAcjs.serializeLayers.call(void 0, state.layers);
1352
1701
  return { ...header(state, options), layers, assets };
1353
1702
  }
1354
1703
  async function serializePaintDocumentExternal(state, store, options = {}) {
1355
- const { layers, assets } = await _chunkD37BX2ZAcjs.serializeLayersExternal.call(void 0, state.layers, store);
1704
+ const { layers, assets } = await _chunkMGL4U5WAcjs.serializeLayersExternal.call(void 0, state.layers, store);
1356
1705
  return { ...header(state, options), layers, assets };
1357
1706
  }
1358
1707
  function paintLayersOnly(layers, problems) {
@@ -1360,7 +1709,7 @@ function paintLayersOnly(layers, problems) {
1360
1709
  for (const l of layers) {
1361
1710
  if (l.type === "pixel") {
1362
1711
  const { filters, source, ...rest } = l;
1363
- if (_optionalChain([filters, 'optionalAccess', _24 => _24.length])) problems.push(`Layer "${l.name}": its filters are an image editor feature and were dropped.`);
1712
+ if (_optionalChain([filters, 'optionalAccess', _32 => _32.length])) problems.push(`Layer "${l.name}": its filters are an image editor feature and were dropped.`);
1364
1713
  if (source) problems.push(`Layer "${l.name}" refers to an image editor source image; it loads empty.`);
1365
1714
  out.push(rest);
1366
1715
  } else if (l.type === "group") out.push({ ...l, children: paintLayersOnly(l.children, problems) });
@@ -1389,16 +1738,16 @@ function deserializePaintDocument(raw) {
1389
1738
  const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
1390
1739
  const paper = normalizePaper(_nullishCoalesce(r.paper, () => ( {})));
1391
1740
  const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
1392
- const layers = paintLayersOnly(_chunkD37BX2ZAcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1741
+ const layers = paintLayersOnly(_chunkMGL4U5WAcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1393
1742
  const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
1394
- const activeLayerId = wanted && _chunkAZTZRLDIcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkAZTZRLDIcjs.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));
1395
- return { state: { canvas, paper, layers, activeLayerId, strokes: readStrokes(r.strokes, problems) }, extensions: documentExtensions(r), guides: r.guides, swatches: r.swatches, problems };
1743
+ const activeLayerId = wanted && _chunkPOGBUYWEcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkPOGBUYWEcjs.flattenLayers.call(void 0, layers), 'access', _33 => _33.filter, 'call', _34 => _34((l) => l.type === "pixel"), 'access', _35 => _35.pop, 'call', _36 => _36(), 'optionalAccess', _37 => _37.id]), () => ( null));
1744
+ return { state: { canvas, paper, layers, activeLayerId, strokes: readStrokes(r.strokes, problems) }, extensions: documentExtensions(r), guides: r.guides, swatches: r.swatches, mixingPad: r.mixingPad, problems };
1396
1745
  }
1397
1746
  async function deserializePaintDocumentAsync(raw, store) {
1398
1747
  if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
1399
1748
  const r = raw;
1400
1749
  const problems = [];
1401
- const assets = await _chunkD37BX2ZAcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1750
+ const assets = await _chunkMGL4U5WAcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1402
1751
  const loaded = deserializePaintDocument({ ...r, assets });
1403
1752
  return { ...loaded, problems: [...problems, ...loaded.problems] };
1404
1753
  }
@@ -1530,7 +1879,7 @@ async function decodeImageBlob(blob) {
1530
1879
  if (!ctx) throw new Error("No 2D context to decode with.");
1531
1880
  ctx.drawImage(bitmap, 0, 0);
1532
1881
  const data = new Uint8ClampedArray(ctx.getImageData(0, 0, bitmap.width, bitmap.height).data);
1533
- _optionalChain([bitmap, 'access', _30 => _30.close, 'optionalCall', _31 => _31()]);
1882
+ _optionalChain([bitmap, 'access', _38 => _38.close, 'optionalCall', _39 => _39()]);
1534
1883
  return { width: bitmap.width, height: bitmap.height, data };
1535
1884
  }
1536
1885
  function downloadBytes(bytes, fileName, mimeType) {
@@ -1577,7 +1926,7 @@ function paintTestStroke(surface, brush, paper, color, width, height) {
1577
1926
  colorHex: color,
1578
1927
  affineCopies: [],
1579
1928
  copies: [IDENTITY_COPY],
1580
- mixerLoad: brush.engine === "mixer" ? mixerLoadForStroke(brush, _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD, [r / 255, g / 255, b / 255]) : _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD
1929
+ mixerLoad: brushLoadForStroke(brush, _chunk7VOHOPLWcjs.EMPTY_MIXER_LOAD, [r / 255, g / 255, b / 255])
1581
1930
  }
1582
1931
  );
1583
1932
  session.push(thumbnailStroke(width, height));
@@ -1590,13 +1939,13 @@ function renderBrushThumbnail(brush, options) {
1590
1939
  const maxSize = height * 0.42;
1591
1940
  const b = brush.tip.size > maxSize ? normalizePaintBrush({ tip: { ...brush.tip, size: maxSize } }, brush) : brush;
1592
1941
  const rgba = [...hexToRgb8(options.color), 255];
1593
- let surface = _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4);
1942
+ let surface = _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4);
1594
1943
  if (b.engine === "eraser") {
1595
- surface = _chunkSZ6KMZNMcjs.fillRect.call(void 0, surface, { x: 0, y: 0, width, height }, rgba);
1596
- } else if (b.engine === "mixer" && !b.loadsColor) {
1944
+ surface = _chunkXIOMMGPDcjs.fillRect.call(void 0, surface, { x: 0, y: 0, width, height }, rgba);
1945
+ } else if (b.engine === "blender" || b.engine === "smudge" || b.engine === "mixer" && !b.loadsColor || b.behavior.pickup > 0) {
1597
1946
  const half = Math.ceil(height / 2);
1598
- surface = _chunkSZ6KMZNMcjs.fillRect.call(void 0, surface, { x: 0, y: 0, width, height: half }, rgba);
1599
- surface = _chunkSZ6KMZNMcjs.fillRect.call(void 0, surface, { x: 0, y: half, width, height: height - half }, [232, 196, 64, 255]);
1947
+ surface = _chunkXIOMMGPDcjs.fillRect.call(void 0, surface, { x: 0, y: 0, width, height: half }, rgba);
1948
+ surface = _chunkXIOMMGPDcjs.fillRect.call(void 0, surface, { x: 0, y: half, width, height: height - half }, [232, 196, 64, 255]);
1600
1949
  }
1601
1950
  surface = paintTestStroke(surface, b, options.paper, options.color, width, height);
1602
1951
  const layer = { type: "pixel", id: "thumbnail", name: "thumbnail", visible: true, opacity: 1, blend: "normal", surface, offset: { x: 0, y: 0 } };
@@ -1728,13 +2077,13 @@ function perspectiveDirections(g, p) {
1728
2077
  return out;
1729
2078
  }
1730
2079
  var SNAP_DECIDE_DISTANCE = 6;
1731
- var PerspectiveSnapper = (_class7 = class {
1732
- constructor(guide) {;_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);
2080
+ var PerspectiveSnapper = (_class8 = class {
2081
+ constructor(guide) {;_class8.prototype.__init34.call(this);_class8.prototype.__init35.call(this);
1733
2082
  this.guide = guide;
1734
2083
  }
1735
2084
 
1736
- __init27() {this.start = null}
1737
- __init28() {this.dir = null}
2085
+ __init34() {this.start = null}
2086
+ __init35() {this.dir = null}
1738
2087
  /** The line the stroke follows, once chosen. */
1739
2088
  get line() {
1740
2089
  return this.start && this.dir ? { from: this.start, direction: this.dir } : null;
@@ -1767,7 +2116,7 @@ var PerspectiveSnapper = (_class7 = class {
1767
2116
  const t = (p.x - s.x) * d.x + (p.y - s.y) * d.y;
1768
2117
  return { ...p, x: s.x + d.x * t, y: s.y + d.y * t };
1769
2118
  }
1770
- }, _class7);
2119
+ }, _class8);
1771
2120
  function perspectiveGuideLines(g, size, count = 12) {
1772
2121
  const out = [];
1773
2122
  const far = Math.hypot(size.width, size.height) * 4;
@@ -1786,9 +2135,9 @@ function perspectiveGuideLines(g, size, count = 12) {
1786
2135
  }
1787
2136
 
1788
2137
  // src/paint-editor/color.ts
1789
- var HEX2 = /^#?([0-9a-f]{6})$/i;
2138
+ var HEX3 = /^#?([0-9a-f]{6})$/i;
1790
2139
  function parseHexColor(value, what = "color") {
1791
- const m = typeof value === "string" ? HEX2.exec(value.trim()) : null;
2140
+ const m = typeof value === "string" ? HEX3.exec(value.trim()) : null;
1792
2141
  if (!m) throw new Error(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
1793
2142
  const n = parseInt(m[1], 16);
1794
2143
  return [n >> 16 & 255, n >> 8 & 255, n & 255];
@@ -1916,17 +2265,25 @@ var PAD_KNIFE = normalizePaintBrush({
1916
2265
  id: "pad-knife",
1917
2266
  name: "Palette knife",
1918
2267
  category: "smudge",
1919
- engine: "mixer",
2268
+ engine: "smudge",
1920
2269
  loadsColor: false,
1921
- tip: { size: 34, hardness: 0.9, spacing: 0.08, roundness: 0.35, angle: 90, opacity: 1, flow: 0.85, pressureSize: false, pressureOpacity: false },
1922
- mixer: { wet: 1, load: 0, mix: 1, loadEachStroke: false, cleanEachStroke: true, mixing: "pigment" }
2270
+ tip: { size: 34, hardness: 0.9, spacing: 0.08, roundness: 0.35, angle: 90, opacity: 1, flow: 1, pressureSize: false, pressureOpacity: false },
2271
+ // A knife drags streaks of paint and folds them into each other, mixing them as pigment.
2272
+ smudge: { strength: 0.6, length: 0.8, mixing: "pigment" }
2273
+ });
2274
+ var PAD_BLOB = normalizePaintBrush({
2275
+ id: "pad-blob",
2276
+ name: "Pad blob",
2277
+ category: "custom",
2278
+ engine: "dab",
2279
+ tip: { size: 20, hardness: 1, spacing: 0.1, opacity: 1, flow: 1, pressureSize: false, pressureOpacity: false }
1923
2280
  });
1924
- 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); }
1925
- __init29() {this.width = MIXING_PAD_WIDTH}
1926
- __init30() {this.height = MIXING_PAD_HEIGHT}
1927
- __init31() {this.surface = _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4)}
2281
+ var MixingPad = (_class9 = class {constructor() { _class9.prototype.__init36.call(this);_class9.prototype.__init37.call(this);_class9.prototype.__init38.call(this);_class9.prototype.__init39.call(this); }
2282
+ __init36() {this.width = MIXING_PAD_WIDTH}
2283
+ __init37() {this.height = MIXING_PAD_HEIGHT}
2284
+ __init38() {this.surface = _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4)}
1928
2285
  /** Increments on every change (for redrawing). */
1929
- __init32() {this.version = 0}
2286
+ __init39() {this.version = 0}
1930
2287
  /** Paints (`paint`, with `color`) or smears (`knife`) through `points` in pad pixels. */
1931
2288
  stroke(points, options) {
1932
2289
  if (!points.length) throw new Error("points is empty; give at least one {x, y} in pad pixels.");
@@ -1937,7 +2294,11 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1937
2294
  }
1938
2295
  let brush = options.tool === "knife" ? PAD_KNIFE : PAD_PAINT;
1939
2296
  if (options.size !== void 0) brush = normalizePaintBrush({ tip: { ...brush.tip, size: options.size } }, brush);
1940
- const [r, g, b] = hexToRgb8(options.color);
2297
+ return this.run(brush, points, options.color);
2298
+ }
2299
+ /** Paints `points` with `brush` in `color` on the pad. */
2300
+ run(brush, points, color) {
2301
+ const [r, g, b] = hexToRgb8(color);
1941
2302
  let result = null;
1942
2303
  const session = new PaintStrokeSession(
1943
2304
  { update: () => {
@@ -1953,10 +2314,10 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1953
2314
  grain: null,
1954
2315
  paperDepth: 0,
1955
2316
  seed: 1,
1956
- colorHex: options.color,
2317
+ colorHex: color,
1957
2318
  affineCopies: [],
1958
2319
  copies: [IDENTITY_COPY],
1959
- mixerLoad: mixerLoadForStroke(brush, _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD, [r / 255, g / 255, b / 255])
2320
+ mixerLoad: brushLoadForStroke(brush, _chunk7VOHOPLWcjs.EMPTY_MIXER_LOAD, [r / 255, g / 255, b / 255])
1960
2321
  }
1961
2322
  );
1962
2323
  session.push(points);
@@ -1975,7 +2336,7 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1975
2336
  const y0 = Math.max(0, Math.floor(y) - r);
1976
2337
  const x1 = Math.min(this.width, Math.floor(x) + r + 1);
1977
2338
  const y1 = Math.min(this.height, Math.floor(y) + r + 1);
1978
- const px = _chunkSZ6KMZNMcjs.readRegion.call(void 0, this.surface, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
2339
+ const px = _chunkXIOMMGPDcjs.readRegion.call(void 0, this.surface, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
1979
2340
  let R = 0;
1980
2341
  let G = 0;
1981
2342
  let B = 0;
@@ -1992,12 +2353,88 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1992
2353
  return `#${hex(R)}${hex(G)}${hex(B)}`;
1993
2354
  }
1994
2355
  clear() {
1995
- this.surface = _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4);
2356
+ this.surface = _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4);
2357
+ this.version++;
2358
+ }
2359
+ /**
2360
+ * Mixes paints the way a painter does on a palette: blobs whose areas are in proportion to
2361
+ * their parts, side by side around (x, y), spiral knife passes that fold them together, and then the centre worked until it is fully mixed: a soft-edged core
2362
+ * of the Kubelka-Munk mix of the parts (`mixPigments`), with partly mixed streaks around it.
2363
+ * Returns that colour (picked back from the centre) and the pile's bounds.
2364
+ *
2365
+ * The core is laid, not smeared, because smearing alone drifts: the knife samples in RGB and a
2366
+ * mixed colour's scattering is re-estimated from its luminance, so repeated small mixes favour
2367
+ * the lighter paint (see GOTCHAS).
2368
+ */
2369
+ mix(paints, options = {}) {
2370
+ if (!paints.length || paints.length > 8) throw new Error(`Give 1 to 8 paints to mix; got ${paints.length}.`);
2371
+ const total = paints.reduce((n, p) => n + p.parts, 0);
2372
+ paints.forEach((p, i) => {
2373
+ if (!(Number.isFinite(p.parts) && p.parts >= 0)) throw new Error(`paints[${i}].parts is ${p.parts}; use a number \u2265 0.`);
2374
+ });
2375
+ if (!(total > 0)) throw new Error("Every paint has 0 parts; give at least one paint parts above 0.");
2376
+ const R = _nullishCoalesce(options.radius, () => ( 40));
2377
+ if (!(R >= 12 && R <= 100)) throw new Error(`radius ${R} is out of range; use 12..100 pad px.`);
2378
+ const passes = _nullishCoalesce(options.passes, () => ( 3));
2379
+ if (!(Number.isInteger(passes) && passes >= 1 && passes <= 20)) throw new Error(`passes ${passes} is out of range; use a whole number 1..20.`);
2380
+ const cx = _nullishCoalesce(options.x, () => ( this.width / 2));
2381
+ const cy = _nullishCoalesce(options.y, () => ( this.height / 2));
2382
+ if (!(cx - R >= 0 && cy - R >= 0 && cx + R <= this.width && cy + R <= this.height)) {
2383
+ throw new Error(`A pile of radius ${R} at (${cx}, ${cy}) does not fit on the ${this.width}\xD7${this.height} pad; move it at least ${R} px from every edge or use a smaller radius.`);
2384
+ }
2385
+ const bounds = { x: Math.floor(cx - R), y: Math.floor(cy - R), width: Math.ceil(2 * R), height: Math.ceil(2 * R) };
2386
+ if (options.clear !== false) this.surface = _chunkXIOMMGPDcjs.writeRegion.call(void 0, this.surface, bounds, new Uint8ClampedArray(bounds.width * bounds.height * 4));
2387
+ const used = paints.filter((p) => p.parts > 0);
2388
+ const area = Math.PI * (0.7 * R) ** 2;
2389
+ used.forEach((p, i) => {
2390
+ const r = Math.sqrt(area * (p.parts / total) / Math.PI);
2391
+ const a = i / used.length * Math.PI * 2;
2392
+ const off = used.length === 1 ? 0 : 0.45 * R;
2393
+ const x = Math.min(cx + R - r, Math.max(cx - R + r, cx + Math.cos(a) * off));
2394
+ const y = Math.min(cy + R - r, Math.max(cy - R + r, cy + Math.sin(a) * off));
2395
+ const blob = normalizePaintBrush({ tip: { ...PAD_BLOB.tip, size: Math.max(2, 2 * r) } }, PAD_BLOB);
2396
+ this.run(blob, [{ x, y }], p.color.toLowerCase());
2397
+ });
2398
+ const knife = normalizePaintBrush({ tip: { ...PAD_KNIFE.tip, size: Math.max(12, R * 0.5) } }, PAD_KNIFE);
2399
+ for (let k = 0; k < passes; k++) {
2400
+ const stir = [];
2401
+ const turns = 1.6;
2402
+ for (let t = 0; t <= 48; t++) {
2403
+ const a = t / 48 * Math.PI * 2 * turns + k * 1.3;
2404
+ const rr = R * (0.85 - 0.6 * (t / 48));
2405
+ stir.push({ x: cx + Math.cos(a) * rr, y: cy + Math.sin(a) * rr });
2406
+ }
2407
+ this.run(knife, stir, "#000000");
2408
+ }
2409
+ const mixed = _chunkXIOMMGPDcjs.mixPigments.call(void 0,
2410
+ paints.map((p) => hexToRgb8(p.color).map((v) => v / 255)),
2411
+ paints.map((p) => p.parts)
2412
+ );
2413
+ const core = normalizePaintBrush({ tip: { ...PAD_BLOB.tip, size: R * 1.1, hardness: 0.55 } }, PAD_BLOB);
2414
+ const hex = "#" + mixed.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("");
2415
+ this.run(core, [{ x: cx, y: cy }], hex);
2416
+ return { color: this.pick(cx, cy, 2), bounds };
2417
+ }
2418
+ /** The pad's pixels as a PNG data URL (with alpha), for saving. */
2419
+ toPng() {
2420
+ return _chunkPOGBUYWEcjs.pngDataUrl.call(void 0, this.width, this.height, _chunkXIOMMGPDcjs.readRegion.call(void 0, this.surface, { x: 0, y: 0, width: this.width, height: this.height }));
2421
+ }
2422
+ /** Replaces the pad with a PNG (data URL or base64) of the pad's size, as `toPng` writes. */
2423
+ fromPng(png) {
2424
+ const bytes = png.startsWith("data:") ? _optionalChain([_chunkPOGBUYWEcjs.parseDataUrl.call(void 0, png), 'optionalAccess', _40 => _40.bytes]) : _chunkPOGBUYWEcjs.base64ToBytes.call(void 0, png);
2425
+ if (!bytes || !_chunkPOGBUYWEcjs.isPng.call(void 0, bytes)) throw new Error("The pad image is not a PNG data URL or base64 PNG.");
2426
+ const img = _chunkPOGBUYWEcjs.decodePng.call(void 0, bytes);
2427
+ if (img.width !== this.width || img.height !== this.height) throw new Error(`The pad image is ${img.width}\xD7${img.height}; a mixing pad is ${this.width}\xD7${this.height}.`);
2428
+ this.surface = _chunkXIOMMGPDcjs.surfaceFromPixels.call(void 0, { width: img.width, height: img.height, data: img.data }, 4);
1996
2429
  this.version++;
1997
2430
  }
2431
+ /** Whether nothing is on the pad. */
2432
+ get empty() {
2433
+ return this.surface.tiles.size === 0;
2434
+ }
1998
2435
  /** The pad over white, `width × height` RGBA. */
1999
2436
  pixels() {
2000
- const px = _chunkSZ6KMZNMcjs.readRegion.call(void 0, this.surface, { x: 0, y: 0, width: this.width, height: this.height });
2437
+ const px = _chunkXIOMMGPDcjs.readRegion.call(void 0, this.surface, { x: 0, y: 0, width: this.width, height: this.height });
2001
2438
  for (let i = 0; i < px.length; i += 4) {
2002
2439
  const a = px[i + 3] / 255;
2003
2440
  px[i] = Math.round(px[i] * a + 255 * (1 - a));
@@ -2007,7 +2444,7 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
2007
2444
  }
2008
2445
  return px;
2009
2446
  }
2010
- }, _class8);
2447
+ }, _class9);
2011
2448
 
2012
2449
  // src/paint-editor/controller.ts
2013
2450
  var PaintEditorError = class extends Error {
@@ -2016,9 +2453,9 @@ var PaintEditorError = class extends Error {
2016
2453
  this.name = "PaintEditorError";
2017
2454
  }
2018
2455
  };
2019
- var HEX3 = /^#[0-9a-f]{6}$/i;
2456
+ var HEX4 = /^#[0-9a-f]{6}$/i;
2020
2457
  function parseColor(value, what = "color") {
2021
- if (typeof value !== "string" || !HEX3.test(value.trim())) throw new PaintEditorError(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
2458
+ if (typeof value !== "string" || !HEX4.test(value.trim())) throw new PaintEditorError(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
2022
2459
  return value.trim().toLowerCase();
2023
2460
  }
2024
2461
  function rgbaOf(hex) {
@@ -2035,40 +2472,44 @@ function unionRect(a, b) {
2035
2472
  const y = Math.min(a.y, b.y);
2036
2473
  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 };
2037
2474
  }
2038
- var isPixelLayer = (l) => _optionalChain([l, 'optionalAccess', _32 => _32.type]) === "pixel";
2039
- var PaintEditorController = (_class9 = class {
2475
+ var isPixelLayer = (l) => _optionalChain([l, 'optionalAccess', _41 => _41.type]) === "pixel";
2476
+ var PaintEditorController = (_class10 = class {
2040
2477
 
2041
- __init33() {this.extensions = {}}
2042
- __init34() {this.undoStack = []}
2043
- __init35() {this.redoStack = []}
2478
+ __init40() {this.extensions = {}}
2479
+ __init41() {this.undoStack = []}
2480
+ __init42() {this.redoStack = []}
2044
2481
 
2045
2482
 
2046
2483
 
2484
+ /** The library list in the state (the same array until the user brushes change, so thumbnails are not redrawn). */
2485
+ __init43() {this.library = null}
2047
2486
 
2048
- __init36() {this.secondaryColor = "#ffffff"}
2049
- __init37() {this.mixerLoad = _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD}
2050
- __init38() {this.view = { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } }}
2487
+ __init44() {this.secondaryColor = "#ffffff"}
2488
+ __init45() {this.mixerLoad = _chunk7VOHOPLWcjs.EMPTY_MIXER_LOAD}
2489
+ __init46() {this.view = { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } }}
2051
2490
 
2052
2491
 
2053
- __init39() {this.listeners = /* @__PURE__ */ new Set()}
2054
- __init40() {this.version = 0}
2055
- __init41() {this.cached = null}
2056
- __init42() {this.session = null}
2057
- __init43() {this.live = null}
2058
- __init44() {this.liveLayers = null}
2059
- __init45() {this.dirty = "all"}
2060
- __init46() {this.seedCounter = 1}
2061
- __init47() {this.strokeCounter = 1}
2492
+ __init47() {this.listeners = /* @__PURE__ */ new Set()}
2493
+ __init48() {this.version = 0}
2494
+ __init49() {this.cached = null}
2495
+ __init50() {this.session = null}
2496
+ __init51() {this.live = null}
2497
+ __init52() {this.liveLayers = null}
2498
+ __init53() {this.dirty = "all"}
2499
+ __init54() {this.seedCounter = 1}
2500
+ __init55() {this.strokeCounter = 1}
2062
2501
 
2063
- __init48() {this.swatches = DEFAULT_SWATCHES.map((s) => ({ ...s }))}
2064
- __init49() {this.pad = new MixingPad()}
2065
- __init50() {this.tracingVersion = 1}
2066
- __init51() {this.tracingCache = null}
2067
- __init52() {this.imagePixels = /* @__PURE__ */ new Map()}
2068
- __init53() {this.nextReferenceId = 1}
2069
- __init54() {this.viewCompositor = createPaintCompositor()}
2070
- __init55() {this.exportCompositor = createPaintCompositor()}
2071
- 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);
2502
+ __init56() {this.swatches = DEFAULT_SWATCHES.map((s) => ({ ...s }))}
2503
+ __init57() {this.pad = new MixingPad()}
2504
+ __init58() {this.savedPads = []}
2505
+ __init59() {this.nextPadId = 1}
2506
+ __init60() {this.tracingVersion = 1}
2507
+ __init61() {this.tracingCache = null}
2508
+ __init62() {this.imagePixels = /* @__PURE__ */ new Map()}
2509
+ __init63() {this.nextReferenceId = 1}
2510
+ __init64() {this.viewCompositor = createPaintCompositor()}
2511
+ __init65() {this.exportCompositor = createPaintCompositor()}
2512
+ constructor(options = {}) {;_class10.prototype.__init40.call(this);_class10.prototype.__init41.call(this);_class10.prototype.__init42.call(this);_class10.prototype.__init43.call(this);_class10.prototype.__init44.call(this);_class10.prototype.__init45.call(this);_class10.prototype.__init46.call(this);_class10.prototype.__init47.call(this);_class10.prototype.__init48.call(this);_class10.prototype.__init49.call(this);_class10.prototype.__init50.call(this);_class10.prototype.__init51.call(this);_class10.prototype.__init52.call(this);_class10.prototype.__init53.call(this);_class10.prototype.__init54.call(this);_class10.prototype.__init55.call(this);_class10.prototype.__init56.call(this);_class10.prototype.__init57.call(this);_class10.prototype.__init58.call(this);_class10.prototype.__init59.call(this);_class10.prototype.__init60.call(this);_class10.prototype.__init61.call(this);_class10.prototype.__init62.call(this);_class10.prototype.__init63.call(this);_class10.prototype.__init64.call(this);_class10.prototype.__init65.call(this);
2072
2513
  this.historyLimit = Math.max(1, _nullishCoalesce(options.historyLimit, () => ( 200)));
2073
2514
  this.doc = createPaintDocument({ width: options.width, height: options.height, dpi: options.dpi, paper: this.resolvePaper(options.paper) });
2074
2515
  this.userBrushes = (_nullishCoalesce(options.brushes, () => ( []))).map((b) => normalizePaintBrush({ ...b, builtin: false }));
@@ -2099,7 +2540,7 @@ var PaintEditorController = (_class9 = class {
2099
2540
  this.cached = {
2100
2541
  document: this.doc,
2101
2542
  brush: this.brush,
2102
- brushes: [...BUILTIN_BRUSHES, ...this.userBrushes],
2543
+ brushes: this.library ??= [...BUILTIN_BRUSHES, ...this.userBrushes],
2103
2544
  color: this.color,
2104
2545
  secondaryColor: this.secondaryColor,
2105
2546
  mixerLoad: this.mixerLoad,
@@ -2107,11 +2548,12 @@ var PaintEditorController = (_class9 = class {
2107
2548
  hand: this.hand,
2108
2549
  canUndo: this.undoStack.length > 0 && !this.session,
2109
2550
  canRedo: this.redoStack.length > 0 && !this.session,
2110
- undoLabel: _nullishCoalesce(_optionalChain([last, 'optionalAccess', _33 => _33.label]), () => ( null)),
2111
- redoLabel: _nullishCoalesce(_optionalChain([next, 'optionalAccess', _34 => _34.label]), () => ( null)),
2551
+ undoLabel: _nullishCoalesce(_optionalChain([last, 'optionalAccess', _42 => _42.label]), () => ( null)),
2552
+ redoLabel: _nullishCoalesce(_optionalChain([next, 'optionalAccess', _43 => _43.label]), () => ( null)),
2112
2553
  guides: this.guides,
2113
2554
  swatches: this.swatches,
2114
2555
  mixingPadVersion: this.pad.version,
2556
+ savedMixingPads: this.savedPads.map(({ id, name }) => ({ id, name })),
2115
2557
  stroking: Boolean(this.session),
2116
2558
  version: this.version
2117
2559
  };
@@ -2216,7 +2658,7 @@ var PaintEditorController = (_class9 = class {
2216
2658
  }
2217
2659
  // --- Layers --------------------------------------------------------------------
2218
2660
  getLayer(id) {
2219
- return _optionalChain([_chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, id), 'optionalAccess', _35 => _35.layer]);
2661
+ return _optionalChain([_chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id), 'optionalAccess', _44 => _44.layer]);
2220
2662
  }
2221
2663
  /** The active pixel layer (or `undefined`). */
2222
2664
  getActiveLayer() {
@@ -2235,12 +2677,12 @@ var PaintEditorController = (_class9 = class {
2235
2677
  walk(this.doc.layers, null, 0);
2236
2678
  return out;
2237
2679
  }
2238
- describeLayer(l, parentId = _nullishCoalesce(_optionalChain([_chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _36 => _36.parent, 'optionalAccess', _37 => _37.id]), () => ( null)), depth = _nullishCoalesce(_optionalChain([_chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _38 => _38.path, 'access', _39 => _39.length]), () => ( 0))) {
2680
+ describeLayer(l, parentId = _nullishCoalesce(_optionalChain([_chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _45 => _45.parent, 'optionalAccess', _46 => _46.id]), () => ( null)), depth = _nullishCoalesce(_optionalChain([_chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _47 => _47.path, 'access', _48 => _48.length]), () => ( 0))) {
2239
2681
  let bounds = null;
2240
2682
  if (l.type === "pixel") {
2241
- const b = _chunkSZ6KMZNMcjs.contentBounds.call(void 0, l.surface);
2683
+ const b = _chunkXIOMMGPDcjs.contentBounds.call(void 0, l.surface);
2242
2684
  bounds = b ? { ...b, x: b.x + l.offset.x, y: b.y + l.offset.y } : null;
2243
- } else for (const c of _chunkAZTZRLDIcjs.flattenLayers.call(void 0, l.children)) if (c.type === "pixel") bounds = unionRect(bounds, this.describeLayer(c, l.id, depth + 1).bounds);
2685
+ } else for (const c of _chunkPOGBUYWEcjs.flattenLayers.call(void 0, l.children)) if (c.type === "pixel") bounds = unionRect(bounds, this.describeLayer(c, l.id, depth + 1).bounds);
2244
2686
  return {
2245
2687
  id: l.id,
2246
2688
  name: l.name,
@@ -2267,7 +2709,7 @@ var PaintEditorController = (_class9 = class {
2267
2709
  if (!id) throw new PaintEditorError(`There is no active layer to ${what}; add one with add_layer.`);
2268
2710
  const l = this.requireLayer(id);
2269
2711
  if (!isPixelLayer(l)) throw new PaintEditorError(`Layer "${l.name}" is a group; ${what} a pixel layer inside it.`);
2270
- if (_chunkAZTZRLDIcjs.isLayerLocked.call(void 0, l, "pixels")) throw new PaintEditorError(`Layer "${l.name}" has its pixels locked; unlock it (update_layer locks) before you ${what} it.`);
2712
+ if (_chunkPOGBUYWEcjs.isLayerLocked.call(void 0, l, "pixels")) throw new PaintEditorError(`Layer "${l.name}" has its pixels locked; unlock it (update_layer locks) before you ${what} it.`);
2271
2713
  if (!l.visible) throw new PaintEditorError(`Layer "${l.name}" is hidden; show it (update_layer visible true) before you ${what} it.`);
2272
2714
  return l;
2273
2715
  }
@@ -2280,9 +2722,9 @@ var PaintEditorController = (_class9 = class {
2280
2722
  /** Adds an empty pixel layer or group above `aboveId` (default: the active layer) and makes it active. */
2281
2723
  addLayer(options = {}) {
2282
2724
  this.requireIdle("adding a layer");
2283
- const count = _chunkAZTZRLDIcjs.flattenLayers.call(void 0, this.doc.layers).length;
2725
+ const count = _chunkPOGBUYWEcjs.flattenLayers.call(void 0, this.doc.layers).length;
2284
2726
  const type = _nullishCoalesce(options.type, () => ( "pixel"));
2285
- const name = _optionalChain([options, 'access', _40 => _40.name, 'optionalAccess', _41 => _41.trim, 'call', _42 => _42()]) || (type === "group" ? `Group ${count + 1}` : `Layer ${count + 1}`);
2727
+ const name = _optionalChain([options, 'access', _49 => _49.name, 'optionalAccess', _50 => _50.trim, 'call', _51 => _51()]) || (type === "group" ? `Group ${count + 1}` : `Layer ${count + 1}`);
2286
2728
  const layer = type === "group" ? { type: "group", id: newPaintLayerId(), name, visible: true, opacity: 1, blend: "pass-through", children: [] } : createPaintLayer(name);
2287
2729
  let parentId = null;
2288
2730
  let index;
@@ -2292,21 +2734,21 @@ var PaintEditorController = (_class9 = class {
2292
2734
  if (g.type !== "group") throw new PaintEditorError(`Layer "${g.name}" is not a group; pass a group id as parentId.`);
2293
2735
  parentId = g.id;
2294
2736
  } else if (above) {
2295
- const loc = _chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, above);
2737
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, above);
2296
2738
  if (!loc) throw new PaintEditorError(`Unknown layer "${above}". Call list_layers for the ids.`);
2297
- parentId = _nullishCoalesce(_optionalChain([loc, 'access', _43 => _43.parent, 'optionalAccess', _44 => _44.id]), () => ( null));
2739
+ parentId = _nullishCoalesce(_optionalChain([loc, 'access', _52 => _52.parent, 'optionalAccess', _53 => _53.id]), () => ( null));
2298
2740
  index = loc.index + 1;
2299
2741
  }
2300
- const layers = _chunkAZTZRLDIcjs.insertLayer.call(void 0, this.doc.layers, layer, parentId, index);
2742
+ const layers = _chunkPOGBUYWEcjs.insertLayer.call(void 0, this.doc.layers, layer, parentId, index);
2301
2743
  this.commit(type === "group" ? "Add group" : "Add layer", { ...this.doc, layers, activeLayerId: layer.id });
2302
2744
  return this.describeLayer(layer);
2303
2745
  }
2304
2746
  updateLayer(id, update) {
2305
2747
  this.requireIdle("changing a layer");
2306
2748
  const l = this.requireLayer(id);
2307
- if (_chunkAZTZRLDIcjs.isLayerLocked.call(void 0, l, "all") && Object.keys(update).some((k) => k !== "locks")) throw new PaintEditorError(`Layer "${l.name}" is fully locked; unlock it first (locks {all: false}).`);
2749
+ if (_chunkPOGBUYWEcjs.isLayerLocked.call(void 0, l, "all") && Object.keys(update).some((k) => k !== "locks")) throw new PaintEditorError(`Layer "${l.name}" is fully locked; unlock it first (locks {all: false}).`);
2308
2750
  if (update.opacity !== void 0 && !(update.opacity >= 0 && update.opacity <= 1)) throw new PaintEditorError(`opacity ${update.opacity} is out of range; use 0..1.`);
2309
- if (update.blend !== void 0 && !_chunkSZ6KMZNMcjs.isBlendMode.call(void 0, update.blend) && !(l.type === "group" && update.blend === "pass-through")) {
2751
+ if (update.blend !== void 0 && !_chunkXIOMMGPDcjs.isBlendMode.call(void 0, update.blend) && !(l.type === "group" && update.blend === "pass-through")) {
2310
2752
  throw new PaintEditorError(`Unknown blend mode "${update.blend}". Call get_canvas_info for the blend modes.`);
2311
2753
  }
2312
2754
  if (update.name !== void 0 && !update.name.trim()) throw new PaintEditorError("name is empty; give the layer a name.");
@@ -2320,66 +2762,66 @@ var PaintEditorController = (_class9 = class {
2320
2762
  const locks = { ..._nullishCoalesce(l.locks, () => ( {})), ...update.locks };
2321
2763
  next.locks = Object.values(locks).some(Boolean) ? locks : void 0;
2322
2764
  }
2323
- this.commit("Layer properties", { ...this.doc, layers: _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, id, () => next) });
2765
+ this.commit("Layer properties", { ...this.doc, layers: _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, id, () => next) });
2324
2766
  return this.describeLayer(next);
2325
2767
  }
2326
2768
  removeLayer(id) {
2327
2769
  this.requireIdle("removing a layer");
2328
2770
  const l = this.requireLayer(id);
2329
- if (_chunkAZTZRLDIcjs.isLayerLocked.call(void 0, l, "all")) throw new PaintEditorError(`Layer "${l.name}" is fully locked; unlock it before removing it.`);
2330
- const layers = _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, id, () => null);
2771
+ if (_chunkPOGBUYWEcjs.isLayerLocked.call(void 0, l, "all")) throw new PaintEditorError(`Layer "${l.name}" is fully locked; unlock it before removing it.`);
2772
+ const layers = _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, id, () => null);
2331
2773
  let active = this.doc.activeLayerId;
2332
- if (active && !_chunkAZTZRLDIcjs.findLayer.call(void 0, layers, active)) active = _nullishCoalesce(_optionalChain([_chunkAZTZRLDIcjs.flattenLayers.call(void 0, layers), 'access', _45 => _45.filter, 'call', _46 => _46((x) => x.type === "pixel"), 'access', _47 => _47.pop, 'call', _48 => _48(), 'optionalAccess', _49 => _49.id]), () => ( null));
2774
+ if (active && !_chunkPOGBUYWEcjs.findLayer.call(void 0, layers, active)) active = _nullishCoalesce(_optionalChain([_chunkPOGBUYWEcjs.flattenLayers.call(void 0, layers), 'access', _54 => _54.filter, 'call', _55 => _55((x) => x.type === "pixel"), 'access', _56 => _56.pop, 'call', _57 => _57(), 'optionalAccess', _58 => _58.id]), () => ( null));
2333
2775
  this.commit("Remove layer", { ...this.doc, layers, activeLayerId: active });
2334
2776
  return { removed: id, activeLayerId: active };
2335
2777
  }
2336
2778
  duplicateLayer(id) {
2337
2779
  this.requireIdle("duplicating a layer");
2338
- const loc = _chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, id);
2780
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id);
2339
2781
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2340
2782
  const copy = (l) => l.type === "group" ? { ...l, id: newPaintLayerId(), children: l.children.map(copy) } : { ...l, id: newPaintLayerId() };
2341
2783
  const dup = { ...copy(loc.layer), name: `${loc.layer.name} copy` };
2342
- const layers = _chunkAZTZRLDIcjs.insertLayer.call(void 0, this.doc.layers, dup, _nullishCoalesce(_optionalChain([loc, 'access', _50 => _50.parent, 'optionalAccess', _51 => _51.id]), () => ( null)), loc.index + 1);
2784
+ const layers = _chunkPOGBUYWEcjs.insertLayer.call(void 0, this.doc.layers, dup, _nullishCoalesce(_optionalChain([loc, 'access', _59 => _59.parent, 'optionalAccess', _60 => _60.id]), () => ( null)), loc.index + 1);
2343
2785
  this.commit("Duplicate layer", { ...this.doc, layers, activeLayerId: dup.id });
2344
2786
  return this.describeLayer(dup);
2345
2787
  }
2346
2788
  /** Moves a layer to `index` (0 = bottom) within `parentId` (default its own parent; `null` = top level). */
2347
2789
  moveLayer(id, index, parentId) {
2348
2790
  this.requireIdle("moving a layer");
2349
- const loc = _chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, id);
2791
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id);
2350
2792
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2351
- const target = parentId === void 0 ? _nullishCoalesce(_optionalChain([loc, 'access', _52 => _52.parent, 'optionalAccess', _53 => _53.id]), () => ( null)) : parentId;
2793
+ const target = parentId === void 0 ? _nullishCoalesce(_optionalChain([loc, 'access', _61 => _61.parent, 'optionalAccess', _62 => _62.id]), () => ( null)) : parentId;
2352
2794
  if (target) {
2353
2795
  const g = this.requireLayer(target);
2354
2796
  if (g.type !== "group") throw new PaintEditorError(`Layer "${g.name}" is not a group; pass a group id as parentId.`);
2355
- if (target === id || loc.layer.type === "group" && _chunkAZTZRLDIcjs.findLayer.call(void 0, loc.layer.children, target)) throw new PaintEditorError("A group cannot move into itself.");
2797
+ if (target === id || loc.layer.type === "group" && _chunkPOGBUYWEcjs.findLayer.call(void 0, loc.layer.children, target)) throw new PaintEditorError("A group cannot move into itself.");
2356
2798
  }
2357
- const without = _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, id, () => null);
2358
- const siblings = target ? _chunkAZTZRLDIcjs.findLayer.call(void 0, without, target).layer.children : without;
2799
+ const without = _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, id, () => null);
2800
+ const siblings = target ? _chunkPOGBUYWEcjs.findLayer.call(void 0, without, target).layer.children : without;
2359
2801
  if (!Number.isInteger(index) || index < 0 || index > siblings.length) throw new PaintEditorError(`index ${index} is out of range; use 0..${siblings.length} (0 is the bottom).`);
2360
- const layers = _chunkAZTZRLDIcjs.insertLayer.call(void 0, without, loc.layer, target, index);
2802
+ const layers = _chunkPOGBUYWEcjs.insertLayer.call(void 0, without, loc.layer, target, index);
2361
2803
  this.commit("Move layer", { ...this.doc, layers });
2362
2804
  return this.describeLayer(loc.layer);
2363
2805
  }
2364
2806
  /** Merges a pixel layer into the pixel layer below it (keeping the lower one's settings). */
2365
2807
  mergeDown(id) {
2366
2808
  this.requireIdle("merging");
2367
- const loc = _chunkAZTZRLDIcjs.findLayer.call(void 0, this.doc.layers, id);
2809
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id);
2368
2810
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2369
2811
  const siblings = loc.parent ? loc.parent.children : this.doc.layers;
2370
2812
  const below = siblings[loc.index - 1];
2371
2813
  const upper = loc.layer;
2372
2814
  if (!isPixelLayer(upper) || !isPixelLayer(below)) throw new PaintEditorError("Merge down needs a pixel layer directly above another pixel layer in the same group.");
2373
- if (_chunkAZTZRLDIcjs.isLayerLocked.call(void 0, below, "pixels")) throw new PaintEditorError(`Layer "${below.name}" has its pixels locked; unlock it to merge into it.`);
2815
+ if (_chunkPOGBUYWEcjs.isLayerLocked.call(void 0, below, "pixels")) throw new PaintEditorError(`Layer "${below.name}" has its pixels locked; unlock it to merge into it.`);
2374
2816
  const rect = unionRect(this.describeLayer(below).bounds, this.describeLayer(upper).bounds);
2375
- let surface = _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4);
2817
+ let surface = _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4);
2376
2818
  if (rect) {
2377
2819
  const base = { ...below, opacity: 1, blend: "normal", visible: true, clipped: void 0, mask: void 0 };
2378
2820
  const px = createPaintCompositor().composite([base, upper.visible ? upper : { ...upper, opacity: 0 }], rect, this.sources());
2379
- surface = _chunkSZ6KMZNMcjs.surfaceFromPixels.call(void 0, { width: rect.width, height: rect.height, data: px }, 4, rect.x, rect.y);
2821
+ surface = _chunkXIOMMGPDcjs.surfaceFromPixels.call(void 0, { width: rect.width, height: rect.height, data: px }, 4, rect.x, rect.y);
2380
2822
  }
2381
2823
  const merged = { ...below, surface, offset: { x: 0, y: 0 } };
2382
- const layers = _chunkAZTZRLDIcjs.mapLayer.call(void 0, _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, upper.id, () => null), below.id, () => merged);
2824
+ const layers = _chunkPOGBUYWEcjs.mapLayer.call(void 0, _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, upper.id, () => null), below.id, () => merged);
2383
2825
  this.commit("Merge down", { ...this.doc, layers, activeLayerId: below.id });
2384
2826
  return this.describeLayer(merged);
2385
2827
  }
@@ -2388,7 +2830,7 @@ var PaintEditorController = (_class9 = class {
2388
2830
  this.requireIdle("clearing");
2389
2831
  const l = this.paintTarget(layerId, "clear");
2390
2832
  const bounds = this.describeLayer(l).bounds;
2391
- this.commit("Clear layer", { ...this.doc, layers: _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface: _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4) })) });
2833
+ this.commit("Clear layer", { ...this.doc, layers: _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface: _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4) })) });
2392
2834
  return { layerId: l.id, bounds };
2393
2835
  }
2394
2836
  /**
@@ -2405,14 +2847,14 @@ var PaintEditorController = (_class9 = class {
2405
2847
  if (!(grainAmount >= 0 && grainAmount <= 1)) throw new PaintEditorError(`grain ${grainAmount} is out of range; use 0..1.`);
2406
2848
  const pressure = _nullishCoalesce(options.pressure, () => ( 0.5));
2407
2849
  const blend = _nullishCoalesce(options.blend, () => ( "normal"));
2408
- if (!_chunkSZ6KMZNMcjs.isBlendMode.call(void 0, blend)) throw new PaintEditorError(`Unknown blend mode "${blend}".`);
2850
+ if (!_chunkXIOMMGPDcjs.isBlendMode.call(void 0, blend)) throw new PaintEditorError(`Unknown blend mode "${blend}".`);
2409
2851
  const { width, height } = this.doc.canvas;
2410
2852
  const rect = { x: 0, y: 0, width, height };
2411
- const coverage = new (0, _chunkHUFQD4CBcjs.CoverageMap)();
2853
+ const coverage = new (0, _chunk7VOHOPLWcjs.CoverageMap)();
2412
2854
  const depth = grainAmount * this.doc.paper.depth;
2413
2855
  const grain = depth > 0 ? paperGrain(this.doc.paper) : null;
2414
- for (const { tx, ty } of _chunkSZ6KMZNMcjs.tilesInRect.call(void 0, rect)) {
2415
- const r = { x: tx * _chunkSZ6KMZNMcjs.TILE, y: ty * _chunkSZ6KMZNMcjs.TILE, width: Math.min(_chunkSZ6KMZNMcjs.TILE, width - tx * _chunkSZ6KMZNMcjs.TILE), height: Math.min(_chunkSZ6KMZNMcjs.TILE, height - ty * _chunkSZ6KMZNMcjs.TILE) };
2856
+ for (const { tx, ty } of _chunkXIOMMGPDcjs.tilesInRect.call(void 0, rect)) {
2857
+ const r = { x: tx * _chunkXIOMMGPDcjs.TILE, y: ty * _chunkXIOMMGPDcjs.TILE, width: Math.min(_chunkXIOMMGPDcjs.TILE, width - tx * _chunkXIOMMGPDcjs.TILE), height: Math.min(_chunkXIOMMGPDcjs.TILE, height - ty * _chunkXIOMMGPDcjs.TILE) };
2416
2858
  const vals = new Float32Array(r.width * r.height);
2417
2859
  for (let y = 0, k = 0; y < r.height; y++) for (let x = 0; x < r.width; x++, k++) vals[k] = grain ? grainCatch(grain.at(r.x + x + 0.5, r.y + y + 0.5), depth, pressure) : 1;
2418
2860
  coverage.accumulate(r, vals, 1);
@@ -2423,25 +2865,25 @@ var PaintEditorController = (_class9 = class {
2423
2865
  background: rgbaOf(this.doc.paper.color),
2424
2866
  opacity,
2425
2867
  blend,
2426
- lockTransparency: _chunkAZTZRLDIcjs.isLayerLocked.call(void 0, l, "transparency"),
2868
+ lockTransparency: _chunkPOGBUYWEcjs.isLayerLocked.call(void 0, l, "transparency"),
2427
2869
  selection: null,
2428
2870
  offset: l.offset
2429
2871
  };
2430
2872
  const surface = this.applyCoverage(l, coverage, rect, apply);
2431
- this.commit("Fill layer", { ...this.doc, layers: _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface })) });
2873
+ this.commit("Fill layer", { ...this.doc, layers: _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface })) });
2432
2874
  return { layerId: l.id, bounds: rect };
2433
2875
  }
2434
2876
  applyCoverage(l, coverage, rect, apply) {
2435
2877
  const base = l.surface;
2436
2878
  const tiles = new Map(base.tiles);
2437
- for (const { tx, ty } of _chunkSZ6KMZNMcjs.tilesInRect.call(void 0, { x: rect.x - l.offset.x, y: rect.y - l.offset.y, width: rect.width, height: rect.height })) {
2438
- const key = _chunkSZ6KMZNMcjs.tileKey.call(void 0, tx, ty);
2439
- const src = _nullishCoalesce(_optionalChain([base, 'access', _54 => _54.tiles, 'access', _55 => _55.get, 'call', _56 => _56(key), 'optionalAccess', _57 => _57.data]), () => ( new Uint8ClampedArray(_chunkSZ6KMZNMcjs.TILE * _chunkSZ6KMZNMcjs.TILE * 4)));
2440
- const tile = _chunkSZ6KMZNMcjs.finishTile.call(void 0, _chunkHUFQD4CBcjs.applyStrokeToTile.call(void 0, coverage, src, 4, tx, ty, apply), 4, 0);
2879
+ for (const { tx, ty } of _chunkXIOMMGPDcjs.tilesInRect.call(void 0, { x: rect.x - l.offset.x, y: rect.y - l.offset.y, width: rect.width, height: rect.height })) {
2880
+ const key = _chunkXIOMMGPDcjs.tileKey.call(void 0, tx, ty);
2881
+ const src = _nullishCoalesce(_optionalChain([base, 'access', _63 => _63.tiles, 'access', _64 => _64.get, 'call', _65 => _65(key), 'optionalAccess', _66 => _66.data]), () => ( new Uint8ClampedArray(_chunkXIOMMGPDcjs.TILE * _chunkXIOMMGPDcjs.TILE * 4)));
2882
+ const tile = _chunkXIOMMGPDcjs.finishTile.call(void 0, _chunk7VOHOPLWcjs.applyStrokeToTile.call(void 0, coverage, src, 4, tx, ty, apply), 4, 0);
2441
2883
  if (tile) tiles.set(key, tile);
2442
2884
  else tiles.delete(key);
2443
2885
  }
2444
- return new (0, _chunkSZ6KMZNMcjs.TiledSurface)(4, tiles, 0);
2886
+ return new (0, _chunkXIOMMGPDcjs.TiledSurface)(4, tiles, 0);
2445
2887
  }
2446
2888
  // --- Brushes -------------------------------------------------------------------
2447
2889
  findBrush(id) {
@@ -2481,6 +2923,7 @@ var PaintEditorController = (_class9 = class {
2481
2923
  const i = this.userBrushes.findIndex((b) => b.id === options.id);
2482
2924
  if (i >= 0) this.userBrushes[i] = saved;
2483
2925
  else this.userBrushes.push(saved);
2926
+ this.library = null;
2484
2927
  this.emit([]);
2485
2928
  return saved;
2486
2929
  }
@@ -2489,6 +2932,7 @@ var PaintEditorController = (_class9 = class {
2489
2932
  const i = this.userBrushes.findIndex((b) => b.id === id);
2490
2933
  if (i < 0) throw new PaintEditorError(`Unknown brush "${id}". Call list_brushes for the ids.`);
2491
2934
  this.userBrushes.splice(i, 1);
2935
+ this.library = null;
2492
2936
  this.emit([]);
2493
2937
  }
2494
2938
  /** The user's saved brushes. */
@@ -2529,7 +2973,10 @@ var PaintEditorController = (_class9 = class {
2529
2973
  else this.userBrushes.push(b);
2530
2974
  added.push(b.id);
2531
2975
  }
2532
- if (added.length) this.emit([]);
2976
+ if (added.length) {
2977
+ this.library = null;
2978
+ this.emit([]);
2979
+ }
2533
2980
  return { added, skipped, problems };
2534
2981
  }
2535
2982
  /** A thumbnail of a brush: a test stroke on the current paper. */
@@ -2561,7 +3008,7 @@ var PaintEditorController = (_class9 = class {
2561
3008
  if (options.layerId !== void 0) {
2562
3009
  const l = this.requireLayer(options.layerId);
2563
3010
  if (!isPixelLayer(l)) throw new PaintEditorError(`Layer "${l.name}" is a group; pick from a pixel layer or the composite.`);
2564
- const [r, g, b, a] = _chunkSZ6KMZNMcjs.readPixel.call(void 0, l.surface, px - l.offset.x, py - l.offset.y);
3011
+ const [r, g, b, a] = _chunkXIOMMGPDcjs.readPixel.call(void 0, l.surface, px - l.offset.x, py - l.offset.y);
2565
3012
  return { color: rgbToHex(r, g, b), alpha: Math.round(a / 255 * 1e3) / 1e3 };
2566
3013
  }
2567
3014
  const d = this.exportCompositor.composite(this.renderLayers({}), { x: px, y: py, width: 1, height: 1 }, this.sources());
@@ -2571,13 +3018,13 @@ var PaintEditorController = (_class9 = class {
2571
3018
  /** Fills the Mixer Brush reservoir with a colour (default the editor colour). */
2572
3019
  loadMixerBrush(color) {
2573
3020
  const [r, g, b] = hexToRgb8(color === void 0 ? this.color : parseColor(color));
2574
- this.mixerLoad = _chunkHUFQD4CBcjs.loadMixer.call(void 0, this.mixerLoad, [r / 255, g / 255, b / 255]);
3021
+ this.mixerLoad = _chunk7VOHOPLWcjs.loadMixer.call(void 0, this.mixerLoad, [r / 255, g / 255, b / 255]);
2575
3022
  this.emit([]);
2576
3023
  return this.mixerLoad;
2577
3024
  }
2578
3025
  /** Empties the pickup well (and with `emptyReservoir` the loaded paint too). */
2579
3026
  cleanMixerBrush(emptyReservoir = false) {
2580
- this.mixerLoad = emptyReservoir ? _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD : _chunkHUFQD4CBcjs.cleanMixer.call(void 0, this.mixerLoad);
3027
+ this.mixerLoad = emptyReservoir ? _chunk7VOHOPLWcjs.EMPTY_MIXER_LOAD : _chunk7VOHOPLWcjs.cleanMixer.call(void 0, this.mixerLoad);
2581
3028
  this.emit([]);
2582
3029
  return this.mixerLoad;
2583
3030
  }
@@ -2603,6 +3050,14 @@ var PaintEditorController = (_class9 = class {
2603
3050
  if (init.brush) brush = normalizePaintBrush({ ...init.brush, id: brush.id }, brush);
2604
3051
  return brush;
2605
3052
  }
3053
+ /**
3054
+ * After a stroke: a Mixer Brush that loads colour keeps its whole load; any other dirty brush
3055
+ * keeps only the paint it carries (the reservoir stays the Mixer Brush's).
3056
+ */
3057
+ keepLoad(brush, load) {
3058
+ if (brush.engine === "mixer" && brush.loadsColor) this.mixerLoad = load;
3059
+ else if (brush.behavior.dirty) this.mixerLoad = { ...this.mixerLoad, pickup: load.pickup };
3060
+ }
2606
3061
  /** Extra copies every new stroke paints (the painting guides add mirror and kaleidoscope copies). */
2607
3062
  strokeCopies() {
2608
3063
  return symmetryCopies(this.guides.symmetry);
@@ -2611,18 +3066,18 @@ var PaintEditorController = (_class9 = class {
2611
3066
  const layer = (() => {
2612
3067
  const id = _nullishCoalesce(init.layerId, () => ( doc.activeLayerId));
2613
3068
  if (!id) throw new PaintEditorError("There is no active layer to paint on; add one with add_layer.");
2614
- const loc = _chunkAZTZRLDIcjs.findLayer.call(void 0, doc.layers, id);
3069
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, doc.layers, id);
2615
3070
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2616
3071
  const l = loc.layer;
2617
3072
  if (!isPixelLayer(l)) throw new PaintEditorError(`Layer "${l.name}" is a group; paint on a pixel layer inside it.`);
2618
- if (_chunkAZTZRLDIcjs.isLayerLocked.call(void 0, l, "pixels")) throw new PaintEditorError(`Layer "${l.name}" has its pixels locked; unlock it (update_layer locks) to paint on it.`);
3073
+ if (_chunkPOGBUYWEcjs.isLayerLocked.call(void 0, l, "pixels")) throw new PaintEditorError(`Layer "${l.name}" has its pixels locked; unlock it (update_layer locks) to paint on it.`);
2619
3074
  if (!l.visible) throw new PaintEditorError(`Layer "${l.name}" is hidden; show it (update_layer visible true) to paint on it.`);
2620
3075
  return l;
2621
3076
  })();
2622
3077
  const brush = this.strokeBrush(init);
2623
3078
  let color = init.color === void 0 ? this.color : parseColor(init.color);
2624
3079
  const tracing = this.guides.tracing;
2625
- if (init.color === void 0 && init.start && _optionalChain([tracing, 'optionalAccess', _58 => _58.visible]) && tracing.sampleColors) color = _nullishCoalesce(this.sampleTracingColor(init.start.x, init.start.y), () => ( color));
3080
+ if (init.color === void 0 && init.start && _optionalChain([tracing, 'optionalAccess', _67 => _67.visible]) && tracing.sampleColors) color = _nullishCoalesce(this.sampleTracingColor(init.start.x, init.start.y), () => ( color));
2626
3081
  const perspective = this.guides.perspective;
2627
3082
  const snapper = init.snapToPerspective && perspective.enabled && perspective.snap ? new PerspectiveSnapper(perspective) : null;
2628
3083
  const rgba = rgbaOf(color);
@@ -2632,7 +3087,7 @@ var PaintEditorController = (_class9 = class {
2632
3087
  layerId: layer.id,
2633
3088
  surface: layer.surface,
2634
3089
  offset: layer.offset,
2635
- lockTransparency: _chunkAZTZRLDIcjs.isLayerLocked.call(void 0, layer, "transparency"),
3090
+ lockTransparency: _chunkPOGBUYWEcjs.isLayerLocked.call(void 0, layer, "transparency"),
2636
3091
  brush,
2637
3092
  color: rgba,
2638
3093
  background: rgbaOf(doc.paper.color),
@@ -2643,7 +3098,8 @@ var PaintEditorController = (_class9 = class {
2643
3098
  affineCopies: copies,
2644
3099
  copies: [IDENTITY_AFFINE, ...copies].map(affineCopy),
2645
3100
  inputFilter: snapper ? (p) => snapper.snap(p) : void 0,
2646
- mixerLoad: brush.engine === "mixer" ? mixerLoadForStroke(brush, this.mixerLoad, [rgba[0] / 255, rgba[1] / 255, rgba[2] / 255]) : _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD
3101
+ mixerLoad: _nullishCoalesce(init.load, () => ( brushLoadForStroke(brush, this.mixerLoad, [rgba[0] / 255, rgba[1] / 255, rgba[2] / 255]))),
3102
+ secondary: rgbaOf(this.secondaryColor)
2647
3103
  });
2648
3104
  return session;
2649
3105
  }
@@ -2658,6 +3114,7 @@ var PaintEditorController = (_class9 = class {
2658
3114
  seed: init.seed
2659
3115
  };
2660
3116
  if (init.affineCopies.length) stroke.copies = init.affineCopies.map((m) => [...m]);
3117
+ if (init.mixerLoad.reservoir || init.mixerLoad.pickup) stroke.load = JSON.parse(JSON.stringify(init.mixerLoad));
2661
3118
  return stroke;
2662
3119
  }
2663
3120
  /**
@@ -2676,7 +3133,7 @@ var PaintEditorController = (_class9 = class {
2676
3133
  this.live = null;
2677
3134
  this.liveLayers = null;
2678
3135
  const brush = s.init.brush;
2679
- if (!cancelled && _optionalChain([s, 'access', _59 => _59.result, 'optionalAccess', _60 => _60.mixerLoad]) && brush.loadsColor) this.mixerLoad = s.result.mixerLoad;
3136
+ if (!cancelled && _optionalChain([s, 'access', _68 => _68.result, 'optionalAccess', _69 => _69.mixerLoad])) this.keepLoad(brush, s.result.mixerLoad);
2680
3137
  if (cancelled || !surface) {
2681
3138
  this.emit();
2682
3139
  return;
@@ -2684,7 +3141,7 @@ var PaintEditorController = (_class9 = class {
2684
3141
  const stroke = this.record(s);
2685
3142
  s.result.strokeId = stroke.id;
2686
3143
  const l = this.getLayer(s.layerId);
2687
- this.commit(`${brush.name} stroke`, { ...this.doc, layers: _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface })), strokes: [...this.doc.strokes, stroke] });
3144
+ this.commit(`${brush.name} stroke`, { ...this.doc, layers: _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, l.id, () => ({ ...l, surface })), strokes: [...this.doc.strokes, stroke] });
2688
3145
  }
2689
3146
  };
2690
3147
  const session = this.sessionFor(this.doc, { snapToPerspective: true, ...init }, host);
@@ -2706,8 +3163,8 @@ var PaintEditorController = (_class9 = class {
2706
3163
  if (cancelled || !surface) return;
2707
3164
  const stroke = this.record(s);
2708
3165
  s.result.strokeId = stroke.id;
2709
- const l = _chunkAZTZRLDIcjs.findLayer.call(void 0, out.layers, s.layerId).layer;
2710
- out = { ...out, layers: _chunkAZTZRLDIcjs.mapLayer.call(void 0, out.layers, l.id, () => ({ ...l, surface })), strokes: [...out.strokes, stroke] };
3166
+ const l = _chunkPOGBUYWEcjs.findLayer.call(void 0, out.layers, s.layerId).layer;
3167
+ out = { ...out, layers: _chunkPOGBUYWEcjs.mapLayer.call(void 0, out.layers, l.id, () => ({ ...l, surface })), strokes: [...out.strokes, stroke] };
2711
3168
  }
2712
3169
  };
2713
3170
  const session = this.sessionFor(doc, init, host);
@@ -2720,7 +3177,7 @@ var PaintEditorController = (_class9 = class {
2720
3177
  this.requireIdle("painting");
2721
3178
  const points = this.checkPoints(init.points);
2722
3179
  const { doc, result, brush } = this.runStroke(this.doc, { ...init, start: _nullishCoalesce(init.start, () => ( points[0])) }, points);
2723
- if (result.mixerLoad && brush.loadsColor) this.mixerLoad = result.mixerLoad;
3180
+ if (result.mixerLoad) this.keepLoad(brush, result.mixerLoad);
2724
3181
  if (doc !== this.doc) this.commit(`${brush.name} stroke`, doc);
2725
3182
  else this.emit([]);
2726
3183
  return result;
@@ -2746,7 +3203,7 @@ var PaintEditorController = (_class9 = class {
2746
3203
  */
2747
3204
  replayStrokes(options) {
2748
3205
  this.requireIdle("replaying");
2749
- if (!_optionalChain([options, 'access', _61 => _61.strokeIds, 'optionalAccess', _62 => _62.length])) throw new PaintEditorError("strokeIds is empty; pass ids from list_strokes.");
3206
+ if (!_optionalChain([options, 'access', _70 => _70.strokeIds, 'optionalAccess', _71 => _71.length])) throw new PaintEditorError("strokeIds is empty; pass ids from list_strokes.");
2750
3207
  const strokes = options.strokeIds.map((id) => {
2751
3208
  const s = this.doc.strokes.find((x) => x.id === id);
2752
3209
  if (!s) throw new PaintEditorError(`Unknown stroke "${id}". Call list_strokes for the ids.`);
@@ -2779,8 +3236,13 @@ var PaintEditorController = (_class9 = class {
2779
3236
  brush = normalizePaintBrush({ tip: { ...b.tip, size: b.tip.size * (v.brush.tip.size / s.brush.tip.size) } }, b);
2780
3237
  }
2781
3238
  const layerId = _nullishCoalesce(options.layerId, () => ( s.layerId));
2782
- if (!_chunkAZTZRLDIcjs.findLayer.call(void 0, doc.layers, layerId)) throw new PaintEditorError(`Stroke "${s.id}" was painted on layer "${layerId}", which no longer exists; pass layerId to replay it elsewhere.`);
2783
- const run = this.runStroke(doc, { layerId, color: _nullishCoalesce(options.color, () => ( s.color)), brush, seed: s.seed, copies: _nullishCoalesce(v.copies, () => ( [])) }, v.points);
3239
+ if (!_chunkPOGBUYWEcjs.findLayer.call(void 0, doc.layers, layerId)) throw new PaintEditorError(`Stroke "${s.id}" was painted on layer "${layerId}", which no longer exists; pass layerId to replay it elsewhere.`);
3240
+ let load = s.load;
3241
+ if (load && options.color !== void 0 && load.reservoir) {
3242
+ const [r, g, b] = hexToRgb8(options.color);
3243
+ load = { ...load, reservoir: [r / 255, g / 255, b / 255] };
3244
+ }
3245
+ const run = this.runStroke(doc, { layerId, color: _nullishCoalesce(options.color, () => ( s.color)), brush, seed: s.seed, copies: _nullishCoalesce(v.copies, () => ( [])), ...load && options.brushId === void 0 ? { load } : {} }, v.points);
2784
3246
  doc = run.doc;
2785
3247
  bounds = unionRect(bounds, run.result.bounds);
2786
3248
  if (run.result.strokeId) ids.push(run.result.strokeId);
@@ -2798,8 +3260,8 @@ var PaintEditorController = (_class9 = class {
2798
3260
  currentLayers() {
2799
3261
  const live = this.live;
2800
3262
  if (!live) return this.doc.layers;
2801
- if (_optionalChain([this, 'access', _63 => _63.liveLayers, 'optionalAccess', _64 => _64.key]) === live.surface) return this.liveLayers.layers;
2802
- const layers = _chunkAZTZRLDIcjs.mapLayer.call(void 0, this.doc.layers, live.layerId, (l) => ({ ...l, surface: live.surface }));
3263
+ if (_optionalChain([this, 'access', _72 => _72.liveLayers, 'optionalAccess', _73 => _73.key]) === live.surface) return this.liveLayers.layers;
3264
+ const layers = _chunkPOGBUYWEcjs.mapLayer.call(void 0, this.doc.layers, live.layerId, (l) => ({ ...l, surface: live.surface }));
2803
3265
  this.liveLayers = { key: live.surface, layers };
2804
3266
  return layers;
2805
3267
  }
@@ -2813,7 +3275,7 @@ var PaintEditorController = (_class9 = class {
2813
3275
  tracingNode(t) {
2814
3276
  const { width, height } = this.doc.canvas;
2815
3277
  const key = this.tracingVersion * 100003 + width * 7 + height;
2816
- if (_optionalChain([this, 'access', _65 => _65.tracingCache, 'optionalAccess', _66 => _66.key]) === key && this.tracingCache.node.opacity === t.opacity) return this.tracingCache.node;
3278
+ if (_optionalChain([this, 'access', _74 => _74.tracingCache, 'optionalAccess', _75 => _75.key]) === key && this.tracingCache.node.opacity === t.opacity) return this.tracingCache.node;
2817
3279
  const src = _nullishCoalesce(this.imagePixels.get(t.src), () => ( this.decodeImage(t.src)));
2818
3280
  const image = resampleInto(src, t.rect, width, height);
2819
3281
  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 };
@@ -2838,7 +3300,7 @@ var PaintEditorController = (_class9 = class {
2838
3300
  */
2839
3301
  compositeTiles(rect, onTile, options = {}) {
2840
3302
  const dirty = options.dirty === "all" ? void 0 : options.dirty;
2841
- this.viewCompositor.compositeTiles(this.renderLayers({}, true), rect, this.sources(), onTile, { dirty, checkpointLayerId: _nullishCoalesce(_optionalChain([this, 'access', _67 => _67.live, 'optionalAccess', _68 => _68.layerId]), () => ( this.doc.activeLayerId)) });
3303
+ this.viewCompositor.compositeTiles(this.renderLayers({}, true), rect, this.sources(), onTile, { dirty, checkpointLayerId: _nullishCoalesce(_optionalChain([this, 'access', _76 => _76.live, 'optionalAccess', _77 => _77.layerId]), () => ( this.doc.activeLayerId)) });
2842
3304
  }
2843
3305
  /** The canvas as canvas-sized RGBA (paper included by default). */
2844
3306
  compositePixels(options = {}) {
@@ -2859,20 +3321,21 @@ var PaintEditorController = (_class9 = class {
2859
3321
  // --- Save, load and export -------------------------------------------------------
2860
3322
  /** The document as JSON with inline PNG assets. */
2861
3323
  toJSON(options = {}) {
2862
- return serializePaintDocument(this.doc, { strokes: options.strokes, extensions: this.extensions, guides: this.guides, swatches: this.swatches });
3324
+ return serializePaintDocument(this.doc, { strokes: options.strokes, extensions: this.extensions, guides: this.guides, swatches: this.swatches, mixingPad: this.mixingPadJSON() });
2863
3325
  }
2864
3326
  /** The document as JSON with each PNG stored in `store`. */
2865
3327
  toJSONExternal(store, options = {}) {
2866
- return serializePaintDocumentExternal(this.doc, store, { strokes: options.strokes, extensions: this.extensions, guides: this.guides, swatches: this.swatches });
3328
+ return serializePaintDocumentExternal(this.doc, store, { strokes: options.strokes, extensions: this.extensions, guides: this.guides, swatches: this.swatches, mixingPad: this.mixingPadJSON() });
2867
3329
  }
2868
3330
  adopt(loaded) {
2869
3331
  this.extensions = loaded.extensions;
3332
+ this.readMixingPad(loaded.mixingPad, loaded.problems);
2870
3333
  this.readGuides(loaded.guides, loaded.state, loaded.problems);
2871
3334
  if (Array.isArray(loaded.swatches)) {
2872
3335
  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() });
2873
3336
  }
2874
3337
  for (const s of loaded.state.strokes) {
2875
- const n = Number(_nullishCoalesce(_optionalChain([/^s(\d+)$/, 'access', _69 => _69.exec, 'call', _70 => _70(s.id), 'optionalAccess', _71 => _71[1]]), () => ( 0)));
3338
+ const n = Number(_nullishCoalesce(_optionalChain([/^s(\d+)$/, 'access', _78 => _78.exec, 'call', _79 => _79(s.id), 'optionalAccess', _80 => _80[1]]), () => ( 0)));
2876
3339
  if (n >= this.strokeCounter) this.strokeCounter = n + 1;
2877
3340
  }
2878
3341
  this.undoStack = [];
@@ -2903,7 +3366,7 @@ var PaintEditorController = (_class9 = class {
2903
3366
  /** The canvas as PNG bytes (paper included by default; `scale` below 1 shrinks it). */
2904
3367
  exportPng(options = {}) {
2905
3368
  const img = this.scaled(options);
2906
- return _chunkAZTZRLDIcjs.encodePng.call(void 0, img.width, img.height, img.data);
3369
+ return _chunkPOGBUYWEcjs.encodePng.call(void 0, img.width, img.height, img.data);
2907
3370
  }
2908
3371
  scaled(options) {
2909
3372
  const full = this.compositePixels(options);
@@ -2945,22 +3408,22 @@ var PaintEditorController = (_class9 = class {
2945
3408
  opacity: 1,
2946
3409
  blend: "normal",
2947
3410
  locks: { all: true },
2948
- surface: _chunkSZ6KMZNMcjs.surfaceFromPixels.call(void 0, paperPixels, 4),
3411
+ surface: _chunkXIOMMGPDcjs.surfaceFromPixels.call(void 0, paperPixels, 4),
2949
3412
  offset: { x: 0, y: 0 }
2950
3413
  };
2951
3414
  const merged = this.compositePixels({ paper: true }).data;
2952
3415
  const changed = /* @__PURE__ */ new Map();
2953
3416
  const toStandard = (layers2) => layers2.map((l) => {
2954
- const blend = l.blend === "pass-through" ? l.blend : _chunkSZ6KMZNMcjs.standardBlendMode.call(void 0, l.blend);
3417
+ const blend = l.blend === "pass-through" ? l.blend : _chunkXIOMMGPDcjs.standardBlendMode.call(void 0, l.blend);
2955
3418
  if (blend !== l.blend) changed.set(l.blend, [..._nullishCoalesce(changed.get(l.blend), () => ( [])), l.name]);
2956
3419
  const next = blend === l.blend ? l : { ...l, blend };
2957
3420
  return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
2958
3421
  });
2959
3422
  const layers = toStandard(this.doc.layers);
2960
- const result = _chunkD37BX2ZAcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
3423
+ const result = _chunkMGL4U5WAcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
2961
3424
  for (const [mode, names] of changed) {
2962
- const label = _nullishCoalesce(_optionalChain([_chunkSZ6KMZNMcjs.BLEND_MODES, 'access', _72 => _72.find, 'call', _73 => _73((m) => m.id === mode), 'optionalAccess', _74 => _74.label]), () => ( mode));
2963
- const std = _nullishCoalesce(_optionalChain([_chunkSZ6KMZNMcjs.BLEND_MODES, 'access', _75 => _75.find, 'call', _76 => _76((m) => m.id === _chunkSZ6KMZNMcjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _77 => _77.label]), () => ( _chunkSZ6KMZNMcjs.standardBlendMode.call(void 0, mode)));
3425
+ const label = _nullishCoalesce(_optionalChain([_chunkXIOMMGPDcjs.BLEND_MODES, 'access', _81 => _81.find, 'call', _82 => _82((m) => m.id === mode), 'optionalAccess', _83 => _83.label]), () => ( mode));
3426
+ const std = _nullishCoalesce(_optionalChain([_chunkXIOMMGPDcjs.BLEND_MODES, 'access', _84 => _84.find, 'call', _85 => _85((m) => m.id === _chunkXIOMMGPDcjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _86 => _86.label]), () => ( _chunkXIOMMGPDcjs.standardBlendMode.call(void 0, mode)));
2964
3427
  const list = names.map((n) => `"${n}"`).join(", ");
2965
3428
  const one = names.length === 1;
2966
3429
  result.warnings.push(`${label} blending has no PSD equivalent; ${list} ${one ? "was" : "were"} written as ${std}, the nearest Photoshop mode, so ${one ? "it looks" : "they look"} different in Photoshop (the merged image is exact).`);
@@ -2970,7 +3433,7 @@ var PaintEditorController = (_class9 = class {
2970
3433
  /** `render_preview` as a base64 PNG. */
2971
3434
  previewPngBase64(options = {}) {
2972
3435
  const img = this.renderPreview(options);
2973
- return { data: _chunkAZTZRLDIcjs.bytesToBase64.call(void 0, _chunkAZTZRLDIcjs.encodePng.call(void 0, img.width, img.height, img.data)), width: img.width, height: img.height };
3436
+ return { data: _chunkPOGBUYWEcjs.bytesToBase64.call(void 0, _chunkPOGBUYWEcjs.encodePng.call(void 0, img.width, img.height, img.data)), width: img.width, height: img.height };
2974
3437
  }
2975
3438
  // --- Guides --------------------------------------------------------------------
2976
3439
  setGuides(next) {
@@ -3008,17 +3471,17 @@ var PaintEditorController = (_class9 = class {
3008
3471
  pixels = this.decodeImage(input.png.trim().startsWith("data:") ? input.png.trim() : `data:image/png;base64,${input.png.trim()}`, what);
3009
3472
  } else throw new PaintEditorError(`${what}: pass png (a PNG as base64 or a data URL) or pixels.`);
3010
3473
  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.`);
3011
- const src = _chunkAZTZRLDIcjs.pngDataUrl.call(void 0, pixels.width, pixels.height, pixels.data);
3474
+ const src = _chunkPOGBUYWEcjs.pngDataUrl.call(void 0, pixels.width, pixels.height, pixels.data);
3012
3475
  this.imagePixels.set(src, pixels);
3013
3476
  return { src, pixels };
3014
3477
  }
3015
3478
  decodeImage(src, what = "Image") {
3016
3479
  const cached = this.imagePixels.get(src);
3017
3480
  if (cached) return cached;
3018
- const parsed = _chunkAZTZRLDIcjs.parseDataUrl.call(void 0, src);
3481
+ const parsed = _chunkPOGBUYWEcjs.parseDataUrl.call(void 0, src);
3019
3482
  if (!parsed) throw new PaintEditorError(`${what} is not a data URL or base64 PNG.`);
3020
3483
  try {
3021
- const png = _chunkAZTZRLDIcjs.decodePng.call(void 0, parsed.bytes);
3484
+ const png = _chunkPOGBUYWEcjs.decodePng.call(void 0, parsed.bytes);
3022
3485
  const px = { width: png.width, height: png.height, data: png.data };
3023
3486
  this.imagePixels.set(src, px);
3024
3487
  return px;
@@ -3076,7 +3539,7 @@ var PaintEditorController = (_class9 = class {
3076
3539
  const { src, pixels } = this.importImage(options, "The reference image");
3077
3540
  const ref = {
3078
3541
  id: `ref${this.nextReferenceId++}`,
3079
- name: _optionalChain([options, 'access', _78 => _78.name, 'optionalAccess', _79 => _79.trim, 'call', _80 => _80()]) || `Reference ${this.guides.references.length + 1}`,
3542
+ name: _optionalChain([options, 'access', _87 => _87.name, 'optionalAccess', _88 => _88.trim, 'call', _89 => _89()]) || `Reference ${this.guides.references.length + 1}`,
3080
3543
  src,
3081
3544
  width: pixels.width,
3082
3545
  height: pixels.height,
@@ -3155,7 +3618,7 @@ var PaintEditorController = (_class9 = class {
3155
3618
  try {
3156
3619
  const px = this.decodeImage(ref.src, `Reference "${ref.name}"`);
3157
3620
  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 });
3158
- const n = Number(_nullishCoalesce(_optionalChain([/^ref(\d+)$/, 'access', _81 => _81.exec, 'call', _82 => _82(ref.id), 'optionalAccess', _83 => _83[1]]), () => ( 0)));
3621
+ const n = Number(_nullishCoalesce(_optionalChain([/^ref(\d+)$/, 'access', _90 => _90.exec, 'call', _91 => _91(ref.id), 'optionalAccess', _92 => _92[1]]), () => ( 0)));
3159
3622
  if (n >= this.nextReferenceId) this.nextReferenceId = n + 1;
3160
3623
  } catch (e) {
3161
3624
  problems.push(e.message);
@@ -3175,7 +3638,7 @@ var PaintEditorController = (_class9 = class {
3175
3638
  }
3176
3639
  addSwatch(color, name) {
3177
3640
  const c = parseColor(color);
3178
- this.swatches = [...this.swatches, _optionalChain([name, 'optionalAccess', _84 => _84.trim, 'call', _85 => _85()]) ? { color: c, name: name.trim() } : { color: c }];
3641
+ this.swatches = [...this.swatches, _optionalChain([name, 'optionalAccess', _93 => _93.trim, 'call', _94 => _94()]) ? { color: c, name: name.trim() } : { color: c }];
3179
3642
  this.emit([]);
3180
3643
  return this.swatches;
3181
3644
  }
@@ -3221,12 +3684,105 @@ var PaintEditorController = (_class9 = class {
3221
3684
  this.pad.clear();
3222
3685
  this.emit([]);
3223
3686
  }
3687
+ /**
3688
+ * Mixes paints on the pad like a painter: blobs in proportion to their `parts`, knife passes,
3689
+ * and a fully mixed core of the Kubelka-Munk mix (see `MixingPad.mix`). With `setColor` the
3690
+ * mix becomes the paint colour. Returns the mixed colour and the pile's bounds.
3691
+ */
3692
+ mixOnPad(paints, options = {}) {
3693
+ const parsed = (_nullishCoalesce(paints, () => ( []))).map((p, i) => ({ color: parseColor(_optionalChain([p, 'optionalAccess', _95 => _95.color]), `paints[${i}].color`), parts: _optionalChain([p, 'optionalAccess', _96 => _96.parts]) }));
3694
+ let r;
3695
+ try {
3696
+ r = this.pad.mix(parsed, options);
3697
+ } catch (e) {
3698
+ throw new PaintEditorError(e.message);
3699
+ }
3700
+ const color = _nullishCoalesce(r.color, () => ( parsed[0].color));
3701
+ this.emit([]);
3702
+ if (options.setColor) this.setColor(color);
3703
+ return { color, bounds: r.bounds };
3704
+ }
3705
+ /** The mixing pad as a PNG data URL (with alpha). */
3706
+ mixingPadPng() {
3707
+ return this.pad.toPng();
3708
+ }
3709
+ /** Replaces the mixing pad with a PNG (data URL or base64, 360 × 220). */
3710
+ setMixingPadPng(png) {
3711
+ try {
3712
+ this.pad.fromPng(png);
3713
+ } catch (e) {
3714
+ throw new PaintEditorError(e.message);
3715
+ }
3716
+ this.emit([]);
3717
+ }
3718
+ /** The mixing pads kept by name. */
3719
+ listMixingPads() {
3720
+ return this.savedPads.map(({ id, name }) => ({ id, name }));
3721
+ }
3722
+ /** Keeps the mixing pad as it is now under `name` (saved with the document). Returns its id. */
3723
+ saveMixingPad(name) {
3724
+ if (this.pad.empty) throw new PaintEditorError("The mixing pad is empty; put paint on it (mixing_pad_stroke or mix_on_pad) before saving it.");
3725
+ const label = (typeof name === "string" && name.trim() ? name.trim() : `Pad ${this.nextPadId}`).slice(0, 80);
3726
+ const pad = { id: `pad${this.nextPadId++}`, name: label, png: this.pad.toPng() };
3727
+ this.savedPads.push(pad);
3728
+ this.emit([]);
3729
+ return { id: pad.id, name: pad.name };
3730
+ }
3731
+ requirePad(id) {
3732
+ const p = this.savedPads.find((x) => x.id === id);
3733
+ if (!p) throw new PaintEditorError(`Unknown mixing pad "${id}". Call list_mixing_pads for the ids.`);
3734
+ return p;
3735
+ }
3736
+ /** Puts a kept pad back on the mixing pad (replacing what is there). */
3737
+ loadMixingPad(id) {
3738
+ const p = this.requirePad(id);
3739
+ this.setMixingPadPng(p.png);
3740
+ return { id: p.id, name: p.name };
3741
+ }
3742
+ /** Forgets a kept pad. */
3743
+ removeMixingPad(id) {
3744
+ const p = this.requirePad(id);
3745
+ this.savedPads = this.savedPads.filter((x) => x !== p);
3746
+ this.emit([]);
3747
+ return { id: p.id, name: p.name };
3748
+ }
3749
+ mixingPadJSON() {
3750
+ if (this.pad.empty && !this.savedPads.length) return void 0;
3751
+ return { ...this.pad.empty ? {} : { current: this.pad.toPng() }, saved: this.savedPads.map((p) => ({ ...p })) };
3752
+ }
3753
+ readMixingPad(raw, problems) {
3754
+ this.savedPads = [];
3755
+ this.nextPadId = 1;
3756
+ this.pad.clear();
3757
+ if (raw === void 0 || raw === null) return;
3758
+ const r = raw;
3759
+ if (typeof r.current === "string") {
3760
+ try {
3761
+ this.pad.fromPng(r.current);
3762
+ } catch (e) {
3763
+ problems.push(`The mixing pad could not be read (${e.message}); it starts empty.`);
3764
+ }
3765
+ }
3766
+ if (Array.isArray(r.saved)) {
3767
+ r.saved.forEach((p, i) => {
3768
+ const x = p;
3769
+ if (!x || typeof x.png !== "string" || typeof x.name !== "string") {
3770
+ problems.push(`mixingPad.saved[${i}] is not a saved pad; skipped.`);
3771
+ return;
3772
+ }
3773
+ const id = typeof x.id === "string" && /^pad\d+$/.test(x.id) ? x.id : `pad${this.nextPadId}`;
3774
+ const n = Number(id.slice(3));
3775
+ if (n >= this.nextPadId) this.nextPadId = n + 1;
3776
+ this.savedPads.push({ id, name: x.name, png: x.png });
3777
+ });
3778
+ }
3779
+ }
3224
3780
  /** A layer's tile surface (live while stroking), for thumbnails. */
3225
3781
  layerSurface(id) {
3226
- const l = _optionalChain([_chunkAZTZRLDIcjs.findLayer.call(void 0, this.currentLayers(), id), 'optionalAccess', _86 => _86.layer]);
3782
+ const l = _optionalChain([_chunkPOGBUYWEcjs.findLayer.call(void 0, this.currentLayers(), id), 'optionalAccess', _97 => _97.layer]);
3227
3783
  return isPixelLayer(l) ? l.surface : null;
3228
3784
  }
3229
- }, _class9);
3785
+ }, _class10);
3230
3786
  var MAX_IMAGE_SIDE = 8192;
3231
3787
  function resampleInto(src, rect, width, height) {
3232
3788
  const out = new Uint8ClampedArray(width * height * 4);
@@ -3277,8 +3833,8 @@ function strokeSchedule(points) {
3277
3833
  return total > 0 ? acc.map((d) => d / total) : points.map(() => 1);
3278
3834
  }
3279
3835
  function strokeDuration(points, max = 1800) {
3280
- const t0 = _optionalChain([points, 'access', _87 => _87[0], 'optionalAccess', _88 => _88.t]);
3281
- const t1 = _optionalChain([points, 'access', _89 => _89[points.length - 1], 'optionalAccess', _90 => _90.t]);
3836
+ const t0 = _optionalChain([points, 'access', _98 => _98[0], 'optionalAccess', _99 => _99.t]);
3837
+ const t1 = _optionalChain([points, 'access', _100 => _100[points.length - 1], 'optionalAccess', _101 => _101.t]);
3282
3838
  let ms;
3283
3839
  if (points.every((p) => Number.isFinite(p.t)) && t1 > t0) ms = t1 - t0;
3284
3840
  else {
@@ -3332,7 +3888,7 @@ var PaintDirector = class {
3332
3888
  }
3333
3889
  where(id) {
3334
3890
  const a = this.presence.get(id);
3335
- return { x: _nullishCoalesce(_optionalChain([a, 'optionalAccess', _91 => _91.x]), () => ( 0)), y: _nullishCoalesce(_optionalChain([a, 'optionalAccess', _92 => _92.y]), () => ( 0)) };
3891
+ return { x: _nullishCoalesce(_optionalChain([a, 'optionalAccess', _102 => _102.x]), () => ( 0)), y: _nullishCoalesce(_optionalChain([a, 'optionalAccess', _103 => _103.y]), () => ( 0)) };
3336
3892
  }
3337
3893
  /** Glides to `to`; an actor that just appeared starts there instead of the corner. */
3338
3894
  async glide(id, to, ms = 380) {
@@ -3379,8 +3935,8 @@ var PaintDirector = class {
3379
3935
  }
3380
3936
  case "replay": {
3381
3937
  await this.say(id, command.note, 500);
3382
- const source = c.getStrokes().filter((s) => _optionalChain([command, 'access', _93 => _93.replay, 'access', _94 => _94.strokeIds, 'optionalAccess', _95 => _95.includes, 'call', _96 => _96(s.id)]));
3383
- const first = _optionalChain([source, 'access', _97 => _97[0], 'optionalAccess', _98 => _98.points, 'access', _99 => _99[0]]);
3938
+ const source = c.getStrokes().filter((s) => _optionalChain([command, 'access', _104 => _104.replay, 'access', _105 => _105.strokeIds, 'optionalAccess', _106 => _106.includes, 'call', _107 => _107(s.id)]));
3939
+ const first = _optionalChain([source, 'access', _108 => _108[0], 'optionalAccess', _109 => _109.points, 'access', _110 => _110[0]]);
3384
3940
  if (first) await this.glide(id, first);
3385
3941
  const result = c.replayStrokes(command.replay);
3386
3942
  if (result.bounds) await this.glide(id, centre(result.bounds), 520);
@@ -3678,7 +4234,7 @@ function createPaintGuideTools(c) {
3678
4234
  {
3679
4235
  name: "mixing_pad_stroke",
3680
4236
  title: "Paint on the mixing pad",
3681
- 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.",
4237
+ 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. For a measured mix in one call use mix_on_pad.",
3682
4238
  inputSchema: {
3683
4239
  type: "object",
3684
4240
  properties: {
@@ -3733,6 +4289,73 @@ function createPaintGuideTools(c) {
3733
4289
  return { cleared: true };
3734
4290
  }
3735
4291
  },
4292
+ {
4293
+ name: "mix_on_pad",
4294
+ title: "Mix paints on the pad",
4295
+ description: "Mixes paints on the mixing pad as a painter would: blobs whose areas follow their parts, worked with the palette knife, and a fully mixed core of the Kubelka-Munk (pigment) mix, so blue and yellow make green and white tints. One call per mix. Not an undo step (the pad is scratch space). Returns the mixed colour #rrggbb and the pile bounds in pad pixels; with setColor it becomes the paint colour. pick_mixing_pad_color reads the streaks around it.",
4296
+ inputSchema: {
4297
+ type: "object",
4298
+ properties: {
4299
+ paints: {
4300
+ type: "array",
4301
+ minItems: 1,
4302
+ maxItems: 8,
4303
+ description: "The paints, 1 to 8.",
4304
+ items: {
4305
+ type: "object",
4306
+ properties: {
4307
+ color: { ...COLOR, description: "Paint colour #rrggbb." },
4308
+ parts: { type: "number", minimum: 0, description: "Parts of this paint, any scale (1 and 3 for a 1:3 mix); at least one above 0." }
4309
+ },
4310
+ required: ["color", "parts"],
4311
+ additionalProperties: false
4312
+ }
4313
+ },
4314
+ x: { type: "number", minimum: 0, maximum: 360, description: "Centre of the pile, pad x (default 180)." },
4315
+ y: { type: "number", minimum: 0, maximum: 220, description: "Centre of the pile, pad y (default 110)." },
4316
+ radius: { type: "number", minimum: 12, maximum: 100, description: "Radius of the pile in pad pixels (default 40); it must fit on the pad." },
4317
+ passes: { type: "integer", minimum: 1, maximum: 20, description: "Palette-knife passes (default 3; more gives finer streaks around the core)." },
4318
+ clear: { type: "boolean", description: "Wipe the pile's area first (default true); false mixes into the paint already there." },
4319
+ setColor: { type: "boolean", description: "Also make the mix the paint colour (default false)." }
4320
+ },
4321
+ required: ["paints"],
4322
+ additionalProperties: false
4323
+ },
4324
+ annotations: { title: "Mix paints on the pad", ...WRITE },
4325
+ handler: ({ paints, x, y, radius, passes, clear, setColor }) => c.mixOnPad(paints, { x, y, radius, passes, clear, setColor })
4326
+ },
4327
+ {
4328
+ name: "save_mixing_pad",
4329
+ title: "Keep the mixing pad",
4330
+ description: "Keeps the mixing pad as it is now under a name, saved with the document, to come back to with load_mixing_pad. Refused while the pad is empty. Returns the kept pad id and name.",
4331
+ inputSchema: { type: "object", properties: { name: { type: "string", minLength: 1, maxLength: 80, description: 'Name (default "Pad n").' } }, additionalProperties: false },
4332
+ annotations: { title: "Keep the mixing pad", ...WRITE },
4333
+ handler: ({ name }) => c.saveMixingPad(name)
4334
+ },
4335
+ {
4336
+ name: "list_mixing_pads",
4337
+ title: "List kept mixing pads",
4338
+ description: "Lists the mixing pads kept with save_mixing_pad (id, name). Changes nothing.",
4339
+ inputSchema: EMPTY,
4340
+ annotations: { title: "List kept mixing pads", ...READ },
4341
+ handler: () => ({ pads: c.listMixingPads() })
4342
+ },
4343
+ {
4344
+ name: "load_mixing_pad",
4345
+ title: "Load a kept mixing pad",
4346
+ description: "Puts a kept pad (id from list_mixing_pads) back on the mixing pad, replacing what is on it. Returns the pad id and name.",
4347
+ inputSchema: { type: "object", properties: { id: { type: "string", description: "Kept pad id from list_mixing_pads." } }, required: ["id"], additionalProperties: false },
4348
+ annotations: { title: "Load a kept mixing pad", ...DESTRUCTIVE },
4349
+ handler: ({ id }) => c.loadMixingPad(id)
4350
+ },
4351
+ {
4352
+ name: "remove_mixing_pad",
4353
+ title: "Remove a kept mixing pad",
4354
+ description: "Forgets a kept pad (id from list_mixing_pads); the pad on screen is untouched. Returns the removed id and name.",
4355
+ inputSchema: { type: "object", properties: { id: { type: "string", description: "Kept pad id from list_mixing_pads." } }, required: ["id"], additionalProperties: false },
4356
+ annotations: { title: "Remove a kept mixing pad", ...DESTRUCTIVE },
4357
+ handler: ({ id }) => c.removeMixingPad(id)
4358
+ },
3736
4359
  {
3737
4360
  name: "render_mixing_pad",
3738
4361
  title: "Render the mixing pad",
@@ -3743,7 +4366,7 @@ function createPaintGuideTools(c) {
3743
4366
  const pad = c.mixingPad;
3744
4367
  return {
3745
4368
  content: [
3746
- { type: "image", data: _chunkAZTZRLDIcjs.bytesToBase64.call(void 0, _chunkAZTZRLDIcjs.encodePng.call(void 0, pad.width, pad.height, pad.pixels())), mimeType: "image/png" },
4369
+ { type: "image", data: _chunkPOGBUYWEcjs.bytesToBase64.call(void 0, _chunkPOGBUYWEcjs.encodePng.call(void 0, pad.width, pad.height, pad.pixels())), mimeType: "image/png" },
3747
4370
  { type: "text", text: `Mixing pad ${pad.width}\xD7${pad.height}` }
3748
4371
  ]
3749
4372
  };
@@ -3764,7 +4387,7 @@ var TIP = {
3764
4387
  type: "object",
3765
4388
  description: "The tip (the shared brush engine). Missing fields keep their current values.",
3766
4389
  properties: {
3767
- size: { type: "number", minimum: 1, maximum: _chunkHUFQD4CBcjs.MAX_BRUSH_SIZE, description: "Diameter in document pixels." },
4390
+ size: { type: "number", minimum: 1, maximum: _chunk7VOHOPLWcjs.MAX_BRUSH_SIZE, description: "Diameter in document pixels." },
3768
4391
  hardness: { type: "number", minimum: 0, maximum: 1, description: "Edge hardness, 0 (soft) .. 1 (hard)." },
3769
4392
  spacing: { type: "number", minimum: 0.01, maximum: 10, description: "Dab spacing as a fraction of the diameter (0.05 is smooth, 1 is dotted)." },
3770
4393
  angle: { type: "number", minimum: -360, maximum: 360, description: "Tip angle in degrees, counter-clockwise; with rotation stroke or barrel it is added to the stroke direction or pen rotation." },
@@ -3776,7 +4399,7 @@ var TIP = {
3776
4399
  pressureOpacity: { type: "boolean", description: "Pressure scales the opacity." },
3777
4400
  blend: {
3778
4401
  type: "string",
3779
- enum: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id),
4402
+ enum: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id),
3780
4403
  description: "How paint combines with the layer. pigment mixes it in like real paint (blue into yellow gives green); multiply darkens like marker ink; watercolor glazes like a transparent wash; tint, highlight and shadow recolour, light and shade (see list_blend_modes)."
3781
4404
  }
3782
4405
  },
@@ -3802,28 +4425,79 @@ var DYNAMICS = {
3802
4425
  };
3803
4426
  var MIXER = {
3804
4427
  type: "object",
3805
- description: "Mixer engine settings (Mixer Brush, blender, smudge). Missing fields keep their current values.",
4428
+ description: "Mixer engine settings (the Mixer Brush). Missing fields keep their current values.",
3806
4429
  properties: {
3807
- wet: { type: "number", minimum: 0, maximum: 1, description: "How much canvas colour each dab picks up, 0 (dry) .. 1." },
4430
+ wet: { type: "number", minimum: 0, maximum: 1, description: "Pick-up ratio: how much canvas colour each dab picks up, 0 (dry) .. 1." },
3808
4431
  load: { type: "number", minimum: 0, maximum: 1, description: "How much paint the reservoir holds; below 1 it drains per dab, so long strokes run dry." },
3809
- mix: { type: "number", minimum: 0, maximum: 1, description: "How far each dab's paint moves from the reservoir towards the picked-up colour." },
4432
+ mix: { type: "number", minimum: 0, maximum: 1, description: "Mix ratio: how far each dab's paint moves from the reservoir towards the picked-up colour." },
3810
4433
  loadEachStroke: { type: "boolean", description: "Refill the reservoir with the colour before each stroke." },
3811
4434
  cleanEachStroke: { type: "boolean", description: "Empty the pickup well after each stroke." },
3812
- mixing: { type: "string", enum: [..._chunkHUFQD4CBcjs.MIXING_MODELS], description: "rgb: RGB averages. pigment: mixes like paint (Kubelka-Munk)." }
4435
+ mixing: { type: "string", enum: [..._chunk7VOHOPLWcjs.MIXING_MODELS], description: "rgb: RGB averages. pigment: mixes like paint (Kubelka-Munk)." }
4436
+ },
4437
+ additionalProperties: false
4438
+ };
4439
+ var MIXING = { type: "string", enum: [..._chunk7VOHOPLWcjs.MIXING_MODELS], description: "pigment: mixes like paint (Kubelka-Munk, blue and yellow make green); rgb: straight RGB averages." };
4440
+ var BEHAVIOR = {
4441
+ type: "object",
4442
+ description: "How the brush handles paint on the canvas (dab and mixer engines; on a mixer brush pickup, mix, dirty and mixing are its mixer wet, mix, !cleanEachStroke and mixing). Missing fields keep their current values.",
4443
+ properties: {
4444
+ pickup: { type: "number", minimum: 0, maximum: 1, description: "Pick-up ratio 0..1: how much of the paint under each dab the brush lifts and carries along (0: none, a dry brush)." },
4445
+ mix: { type: "number", minimum: 0, maximum: 1, description: "Mix ratio 0..1: how much of the carried paint goes into what the brush lays down, against the brush's own colour (scaled by pickup)." },
4446
+ dirty: { type: "boolean", description: "Dirty brush: keep the carried paint after the stroke, so the next stroke starts with the last colour it touched (needs pickup above 0). clean_mixer_brush wipes it." },
4447
+ mixing: MIXING,
4448
+ multicolor: {
4449
+ type: "object",
4450
+ description: "Multi-coloured brush. Missing fields keep their current values.",
4451
+ properties: {
4452
+ mode: { type: "string", enum: [...MULTI_COLOR_MODES], description: "off; tip: the colours lie side by side across the tip (stripes along the stroke, like a brush loaded with several paints); dab: the colour changes along the stroke." },
4453
+ colors: { type: "array", maxItems: 8, items: { ...COLOR2 }, description: "2..8 colours #rrggbb; fewer than 2 uses the paint colour and the secondary colour." },
4454
+ order: { type: "string", enum: ["sequence", "random"], description: "dab mode: sequence runs through the colours in order, blending between them; random picks one per run of dabs (seeded)." },
4455
+ length: { type: "integer", minimum: 1, maximum: 500, description: "dab mode: dabs per colour (default 12)." },
4456
+ softness: { type: "number", minimum: 0, maximum: 1, description: "How softly neighbouring colours blend: 0 hard stripes or steps, 1 a smooth gradient." }
4457
+ },
4458
+ additionalProperties: false
4459
+ }
4460
+ },
4461
+ additionalProperties: false
4462
+ };
4463
+ var BLENDER = {
4464
+ type: "object",
4465
+ description: "Blender engine settings. Missing fields keep their current values.",
4466
+ properties: {
4467
+ strength: { type: "number", minimum: 0, maximum: 1, description: "How far each pixel under the tip moves towards the mix of the paint near it, per dab." },
4468
+ distance: { type: "number", minimum: 0, maximum: 1, description: "Blend distance: how far away colours are mixed in, as a fraction of the tip diameter (0.35 default; higher blends wider and softer)." },
4469
+ mixing: MIXING
4470
+ },
4471
+ additionalProperties: false
4472
+ };
4473
+ var SMUDGE = {
4474
+ type: "object",
4475
+ description: "Smudge engine settings. Missing fields keep their current values.",
4476
+ properties: {
4477
+ strength: { type: "number", minimum: 0, maximum: 1, description: "How strongly the dragged paint covers what is under the tip, per dab." },
4478
+ length: { type: "number", minimum: 0, maximum: 1, description: "How far paint is dragged: the share of the carried paint kept from dab to dab (0.85 default; 1 drags it the whole stroke)." },
4479
+ mixing: { ...MIXING, description: "rgb (default): smeared colours average; pigment: they mix like paint." }
3813
4480
  },
3814
4481
  additionalProperties: false
3815
4482
  };
3816
4483
  var BRUSH_FIELDS = {
3817
4484
  name: { type: "string", minLength: 1, maxLength: 80, description: "Display name." },
3818
4485
  category: { type: "string", enum: [...PAINT_BRUSH_CATEGORIES], description: "Library category." },
3819
- engine: { type: "string", enum: [...PAINT_BRUSH_ENGINES], description: "dab: paints dabs; eraser: removes paint; mixer: picks up and moves paint (Mixer Brush, blender, smudge)." },
4486
+ engine: {
4487
+ type: "string",
4488
+ enum: [...PAINT_BRUSH_ENGINES],
4489
+ description: "dab: paints dabs (with behavior.pickup or multicolor it paints dab by dab, lifting and mixing paint); eraser: removes paint; mixer: the Mixer Brush (a reservoir plus picked-up paint); blender: mixes the colours under and near the tip; smudge: drags paint along the stroke."
4490
+ },
3820
4491
  tip: TIP,
3821
4492
  grain: { type: "number", minimum: 0, maximum: 1, description: "Paper grain response: 0 ignores the grain, 1 catches only its peaks at light pressure (pencil, pastel, charcoal). Multiplied by the paper depth." },
3822
4493
  pressureSmoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Pressure smoothing, separate from the tip position smoothing." },
3823
4494
  rotation: { type: "string", enum: [...BRUSH_ROTATIONS], description: "What turns the tip: fixed (tip.angle), stroke (follows the direction of travel) or barrel (the pen barrel rotation, twist)." },
3824
4495
  dynamics: DYNAMICS,
3825
4496
  mixer: MIXER,
3826
- loadsColor: { type: "boolean", description: "Mixer engine: load the colour each stroke (false for blender and smudge, which only move paint)." }
4497
+ loadsColor: { type: "boolean", description: "Mixer engine: load the colour each stroke (false for a mixer that only moves paint)." },
4498
+ behavior: BEHAVIOR,
4499
+ blender: BLENDER,
4500
+ smudge: SMUDGE
3827
4501
  };
3828
4502
  var POINTS = {
3829
4503
  type: "array",
@@ -3852,7 +4526,20 @@ function loadResult(load) {
3852
4526
  return { reservoir: hex(load.reservoir), amount: Math.round(load.amount * 1e3) / 1e3, pickup: hex(load.pickup) };
3853
4527
  }
3854
4528
  function brushSummary(b) {
3855
- return { id: b.id, name: b.name, category: b.category, engine: b.engine, size: b.tip.size, grain: b.grain, rotation: b.rotation, builtin: Boolean(b.builtin) };
4529
+ return {
4530
+ id: b.id,
4531
+ name: b.name,
4532
+ category: b.category,
4533
+ engine: b.engine,
4534
+ size: b.tip.size,
4535
+ grain: b.grain,
4536
+ rotation: b.rotation,
4537
+ wetCapable: isWetCapable(b),
4538
+ pickup: b.behavior.pickup,
4539
+ dirty: b.behavior.dirty,
4540
+ multicolor: b.behavior.multicolor.mode,
4541
+ builtin: Boolean(b.builtin)
4542
+ };
3856
4543
  }
3857
4544
  function createPaintEditorToolProvider(controller, options = {}) {
3858
4545
  const c = controller;
@@ -3888,7 +4575,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
3888
4575
  strokes: d.strokes.length,
3889
4576
  undo: s.undoLabel,
3890
4577
  redo: s.redoLabel,
3891
- blendModes: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id)
4578
+ blendModes: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id)
3892
4579
  };
3893
4580
  }
3894
4581
  });
@@ -4021,7 +4708,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4021
4708
  opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0..1." },
4022
4709
  blend: {
4023
4710
  type: "string",
4024
- enum: [..._chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id), "pass-through"],
4711
+ enum: [..._chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id), "pass-through"],
4025
4712
  description: "Blend mode (pass-through for groups only). Painterly modes: watercolor (the layer glazes like a transparent wash), tint (recolours keeping lightness), highlight (adds soft coloured light), shadow (shades with ambient fill); see list_blend_modes."
4026
4713
  },
4027
4714
  clipped: { type: "boolean", description: "Clip to the layer below (paint only shows where it has paint)." },
@@ -4050,12 +4737,12 @@ function createPaintEditorToolProvider(controller, options = {}) {
4050
4737
  inputSchema: EMPTY2,
4051
4738
  annotations: { title: "List blend modes", ...READ2 },
4052
4739
  handler: () => ({
4053
- modes: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => ({
4740
+ modes: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => ({
4054
4741
  id: m.id,
4055
4742
  label: m.label,
4056
4743
  group: m.painterly ? "painterly" : m.id === "pigment" ? "pigment" : "standard",
4057
4744
  ...m.description ? { description: m.description } : {},
4058
- psd: _chunkSZ6KMZNMcjs.standardBlendMode.call(void 0, m.id)
4745
+ psd: _chunkXIOMMGPDcjs.standardBlendMode.call(void 0, m.id)
4059
4746
  }))
4060
4747
  })
4061
4748
  });
@@ -4123,7 +4810,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4123
4810
  opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 1)." },
4124
4811
  grain: { type: "number", minimum: 0, maximum: 1, description: "Paper grain response 0..1 (default 0: flat)." },
4125
4812
  pressure: { type: "number", minimum: 0, maximum: 1, description: "With grain: how far into the valleys, 0..1 (default 0.5)." },
4126
- blend: { type: "string", enum: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal; pigment mixes like paint, watercolor glazes; see list_blend_modes)." }
4813
+ blend: { type: "string", enum: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal; pigment mixes like paint, watercolor glazes; see list_blend_modes)." }
4127
4814
  },
4128
4815
  additionalProperties: false
4129
4816
  },
@@ -4141,18 +4828,18 @@ function createPaintEditorToolProvider(controller, options = {}) {
4141
4828
  add({
4142
4829
  name: "list_brushes",
4143
4830
  title: "List brushes",
4144
- 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.",
4831
+ description: "Lists the brush library: built-in dry media (pencils, pastels, charcoal, airbrush, marker, ink, erasers), paint brushes (dirty round, multi-colour flat, palette dabber), blending tools (blender, smudge, pigment smudge, Mixer Brush) and saved user brushes, with id, name, category, engine, size, grain response, rotation mode, whether it can pick up paint (wetCapable), its pick-up ratio, dirty and multi-colour mode, plus the active brush id.",
4145
4832
  inputSchema: EMPTY2,
4146
4833
  annotations: { title: "List brushes", ...READ2 },
4147
4834
  handler: () => {
4148
4835
  const s = c.getState();
4149
- return { activeBrushId: s.brush.id, activeEdited: !s.brush.builtin && Boolean(_optionalChain([c, 'access', _100 => _100.getBrush, 'call', _101 => _101(s.brush.id), 'optionalAccess', _102 => _102.builtin])), brushes: s.brushes.map(brushSummary) };
4836
+ return { activeBrushId: s.brush.id, activeEdited: !s.brush.builtin && Boolean(_optionalChain([c, 'access', _111 => _111.getBrush, 'call', _112 => _112(s.brush.id), 'optionalAccess', _113 => _113.builtin])), brushes: s.brushes.map(brushSummary) };
4150
4837
  }
4151
4838
  });
4152
4839
  add({
4153
4840
  name: "get_brush",
4154
4841
  title: "Get a brush",
4155
- description: "Reads every setting of a brush (tip, grain, pressure smoothing, rotation, dynamics, mixer). Without id, the active brush as edited.",
4842
+ description: "Reads every setting of a brush (tip, grain, pressure smoothing, rotation, dynamics, mixer, behavior: pick-up / mix / dirty / multi-colour, blender, smudge). Without id, the active brush as edited.",
4156
4843
  inputSchema: { type: "object", properties: { id: { type: "string", description: "Brush id from list_brushes (default the active brush)." } }, additionalProperties: false },
4157
4844
  annotations: { title: "Get a brush", ...READ2 },
4158
4845
  handler: ({ id }) => {
@@ -4244,7 +4931,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4244
4931
  const t = c.brushThumbnail(_nullishCoalesce(id, () => ( c.getState().brush.id)), { width, height, color });
4245
4932
  return {
4246
4933
  content: [
4247
- { type: "image", data: _chunkAZTZRLDIcjs.bytesToBase64.call(void 0, _chunkAZTZRLDIcjs.encodePng.call(void 0, t.width, t.height, t.data)), mimeType: "image/png" },
4934
+ { type: "image", data: _chunkPOGBUYWEcjs.bytesToBase64.call(void 0, _chunkPOGBUYWEcjs.encodePng.call(void 0, t.width, t.height, t.data)), mimeType: "image/png" },
4248
4935
  { type: "text", text: `Brush ${_nullishCoalesce(id, () => ( c.getState().brush.id))} ${t.width}\xD7${t.height}` }
4249
4936
  ]
4250
4937
  };
@@ -4287,7 +4974,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4287
4974
  add({
4288
4975
  name: "get_mixer_brush",
4289
4976
  title: "Get the Mixer Brush load",
4290
- 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.",
4977
+ description: "Reads what the brush carries between strokes: reservoir #rrggbb or null (the Mixer Brush's loaded paint), amount 0..1 left, pickup #rrggbb or null (paint picked up from the canvas, kept by dirty brushes and a Mixer Brush that does not clean each stroke). Call before painting with mixer-brush when its loadEachStroke is false, or with a dirty brush.",
4291
4978
  inputSchema: EMPTY2,
4292
4979
  annotations: { title: "Get the Mixer Brush load", ...READ2 },
4293
4980
  handler: () => loadResult(c.getState().mixerLoad)
@@ -4303,7 +4990,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4303
4990
  add({
4304
4991
  name: "clean_mixer_brush",
4305
4992
  title: "Clean the Mixer Brush",
4306
- description: "Empties the Mixer Brush pickup well (and with emptyReservoir its paint too). Editor state, not an undo step. Returns the load.",
4993
+ description: "Cleans the brush: empties the picked-up paint a dirty brush or the Mixer Brush carries (and with emptyReservoir the Mixer Brush paint too). Editor state, not an undo step. Returns the load.",
4307
4994
  inputSchema: { type: "object", properties: { emptyReservoir: { type: "boolean", description: "Also drop the loaded paint (default false)." } }, additionalProperties: false },
4308
4995
  annotations: { title: "Clean the Mixer Brush", ...SET2 },
4309
4996
  handler: ({ emptyReservoir }) => loadResult(c.cleanMixerBrush(emptyReservoir === true))
@@ -4311,7 +4998,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4311
4998
  add({
4312
4999
  name: "paint_stroke",
4313
5000
  title: "Paint a brush stroke",
4314
- description: "Paints one stroke through points (document pixels, with optional pressure, time, tilt and barrel rotation) on a pixel layer with a library brush or the active one, optionally changed for this stroke. Grain-responsive brushes follow the paper; the mixer engine (mixer-brush, blender, smudge) picks up and moves paint already on the layer. The symmetry guide adds its mirror copies; with a tracing image that samples colours, a stroke without color takes the image colour at its first point. With an agent cursor (a director), the stroke is painted live along the points at the pace of their times. One undo step. Returns the layer id, changed bounds, dab count, the recorded strokeId (for replay_strokes) and, for the mixer, its load after the stroke.",
5001
+ description: "Paints one stroke through points (document pixels, with optional pressure, time, tilt and barrel rotation) on a pixel layer with a library brush or the active one, optionally changed for this stroke. Grain-responsive brushes follow the paper; the mixer engine (mixer-brush) picks up and mixes paint already on the layer, the blender mixes nearby colours, the smudge drags paint, and dab brushes with behavior.pickup lift and carry paint. The symmetry guide adds its mirror copies; with a tracing image that samples colours, a stroke without color takes the image colour at its first point. With an agent cursor (a director), the stroke is painted live along the points at the pace of their times. One undo step. Returns the layer id, changed bounds, dab count, the recorded strokeId (for replay_strokes) and, for brushes that pick up paint, mixerLoad: what the brush carries after the stroke.",
4315
5002
  inputSchema: {
4316
5003
  type: "object",
4317
5004
  properties: {
@@ -4366,7 +5053,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4366
5053
  color: s.color,
4367
5054
  points: ids ? s.points : s.points.length,
4368
5055
  seed: s.seed,
4369
- copies: _nullishCoalesce(_optionalChain([s, 'access', _103 => _103.copies, 'optionalAccess', _104 => _104.length]), () => ( 0)),
5056
+ copies: _nullishCoalesce(_optionalChain([s, 'access', _114 => _114.copies, 'optionalAccess', _115 => _115.length]), () => ( 0)),
4370
5057
  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) }
4371
5058
  };
4372
5059
  })
@@ -4499,7 +5186,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4499
5186
  annotations: { title: "Export the painting", ...READ2 },
4500
5187
  handler: async ({ format, quality, scale, paper }) => {
4501
5188
  const bytes = await c.exportImage({ format, quality, scale, paper });
4502
- return { mimeType: _nullishCoalesce(format, () => ( "image/png")), bytes: bytes.length, data: _chunkAZTZRLDIcjs.bytesToBase64.call(void 0, bytes) };
5189
+ return { mimeType: _nullishCoalesce(format, () => ( "image/png")), bytes: bytes.length, data: _chunkPOGBUYWEcjs.bytesToBase64.call(void 0, bytes) };
4503
5190
  }
4504
5191
  });
4505
5192
  add({
@@ -4510,7 +5197,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4510
5197
  annotations: { title: "Export as PSD", ...READ2 },
4511
5198
  handler: () => {
4512
5199
  const r = c.exportPsd();
4513
- return { data: _chunkAZTZRLDIcjs.bytesToBase64.call(void 0, r.bytes), bytes: r.bytes.length, psb: r.psb, warnings: r.warnings };
5200
+ return { data: _chunkPOGBUYWEcjs.bytesToBase64.call(void 0, r.bytes), bytes: r.bytes.length, psb: r.psb, warnings: r.warnings };
4514
5201
  }
4515
5202
  });
4516
5203
  add({
@@ -4534,7 +5221,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4534
5221
  }
4535
5222
  });
4536
5223
  tools.push(...createPaintGuideTools(c));
4537
- tools.push(..._chunkHUFQD4CBcjs.createPigmentTools.call(void 0, ));
5224
+ tools.push(..._chunk7VOHOPLWcjs.createPigmentTools.call(void 0, ));
4538
5225
  return { id: _nullishCoalesce(options.id, () => ( "paint-editor")), namespace: options.namespace, tools };
4539
5226
  }
4540
5227
 
@@ -4545,21 +5232,21 @@ var PAINT_TOOLS = [
4545
5232
  { id: "pan", label: "Pan", key: "h" },
4546
5233
  { id: "rotate", label: "Rotate view", key: "r" }
4547
5234
  ];
4548
- var PaintGesture = (_class10 = class {
4549
- constructor(controller, box) {;_class10.prototype.__init56.call(this);_class10.prototype.__init57.call(this);_class10.prototype.__init58.call(this);_class10.prototype.__init59.call(this);_class10.prototype.__init60.call(this);_class10.prototype.__init61.call(this);
5235
+ var PaintGesture = (_class11 = class {
5236
+ constructor(controller, box) {;_class11.prototype.__init66.call(this);_class11.prototype.__init67.call(this);_class11.prototype.__init68.call(this);_class11.prototype.__init69.call(this);_class11.prototype.__init70.call(this);_class11.prototype.__init71.call(this);
4550
5237
  this.controller = controller;
4551
5238
  this.box = box;
4552
5239
  }
4553
5240
 
4554
5241
 
4555
- __init56() {this.tool = "brush"}
5242
+ __init66() {this.tool = "brush"}
4556
5243
  /** The last refusal (a hidden or locked layer), for the editor to show. */
4557
- __init57() {this.error = null}
5244
+ __init67() {this.error = null}
4558
5245
  /** The predicted tail of the current stroke in document pixels (draw it as a thin preview). */
4559
- __init58() {this.predicted = []}
4560
- __init59() {this.mode = null}
4561
- __init60() {this.penAt = null}
4562
- __init61() {this.penDown = false}
5246
+ __init68() {this.predicted = []}
5247
+ __init69() {this.mode = null}
5248
+ __init70() {this.penAt = null}
5249
+ __init71() {this.penDown = false}
4563
5250
  doc(e) {
4564
5251
  const b = this.box();
4565
5252
  const s = this.controller.getState();
@@ -4640,12 +5327,12 @@ var PaintGesture = (_class10 = class {
4640
5327
  if (!m || m.pointerId !== e.pointerId) return;
4641
5328
  this.mode = null;
4642
5329
  this.predicted = [];
4643
- if (m.kind === "stroke") _optionalChain([this, 'access', _105 => _105.controller, 'access', _106 => _106.activeStroke, 'optionalAccess', _107 => _107.end, 'call', _108 => _108()]);
5330
+ if (m.kind === "stroke") _optionalChain([this, 'access', _116 => _116.controller, 'access', _117 => _117.activeStroke, 'optionalAccess', _118 => _118.end, 'call', _119 => _119()]);
4644
5331
  }
4645
5332
  cancel() {
4646
5333
  this.penDown = false;
4647
5334
  this.predicted = [];
4648
- if (_optionalChain([this, 'access', _109 => _109.mode, 'optionalAccess', _110 => _110.kind]) === "stroke") _optionalChain([this, 'access', _111 => _111.controller, 'access', _112 => _112.activeStroke, 'optionalAccess', _113 => _113.cancel, 'call', _114 => _114()]);
5335
+ if (_optionalChain([this, 'access', _120 => _120.mode, 'optionalAccess', _121 => _121.kind]) === "stroke") _optionalChain([this, 'access', _122 => _122.controller, 'access', _123 => _123.activeStroke, 'optionalAccess', _124 => _124.cancel, 'call', _125 => _125()]);
4649
5336
  this.mode = null;
4650
5337
  }
4651
5338
  get active() {
@@ -4665,7 +5352,7 @@ var PaintGesture = (_class10 = class {
4665
5352
  const py = e.clientY - b.top - b.height / 2;
4666
5353
  this.controller.setView({ zoom, pan: { x: px - (px - s.view.pan.x) * k, y: py - (py - s.view.pan.y) * k } });
4667
5354
  }
4668
- }, _class10);
5355
+ }, _class11);
4669
5356
  function handlePaintShortcut(controller, gesture, e) {
4670
5357
  const mod = e.ctrlKey || e.metaKey;
4671
5358
  const key = e.key.toLowerCase();
@@ -4705,14 +5392,14 @@ function handlePaintShortcut(controller, gesture, e) {
4705
5392
  }
4706
5393
  return false;
4707
5394
  }
4708
- var PaintCanvasPresenter = (_class11 = class {
4709
- constructor(controller) {;_class11.prototype.__init62.call(this);_class11.prototype.__init63.call(this);_class11.prototype.__init64.call(this);
5395
+ var PaintCanvasPresenter = (_class12 = class {
5396
+ constructor(controller) {;_class12.prototype.__init72.call(this);_class12.prototype.__init73.call(this);_class12.prototype.__init74.call(this);
4710
5397
  this.controller = controller;
4711
5398
  }
4712
5399
 
4713
- __init62() {this.uploaded = /* @__PURE__ */ new Map()}
4714
- __init63() {this.scratch = null}
4715
- __init64() {this.size = ""}
5400
+ __init72() {this.uploaded = /* @__PURE__ */ new Map()}
5401
+ __init73() {this.scratch = null}
5402
+ __init74() {this.size = ""}
4716
5403
  /** Draws what changed into `ctx` (a canvas the size of the document); returns the tiles drawn. */
4717
5404
  render(ctx) {
4718
5405
  const { width, height } = this.controller.document.canvas;
@@ -4729,16 +5416,16 @@ var PaintCanvasPresenter = (_class11 = class {
4729
5416
  const key = `${tx},${ty}`;
4730
5417
  if (this.uploaded.get(key) === data) return;
4731
5418
  this.uploaded.set(key, data);
4732
- if (!this.scratch) this.scratch = ctx.createImageData(_chunkSZ6KMZNMcjs.TILE, _chunkSZ6KMZNMcjs.TILE);
5419
+ if (!this.scratch) this.scratch = ctx.createImageData(_chunkXIOMMGPDcjs.TILE, _chunkXIOMMGPDcjs.TILE);
4733
5420
  this.scratch.data.set(data);
4734
- const ox = tx * _chunkSZ6KMZNMcjs.TILE;
4735
- const oy = ty * _chunkSZ6KMZNMcjs.TILE;
4736
- ctx.putImageData(this.scratch, ox, oy, 0, 0, Math.min(_chunkSZ6KMZNMcjs.TILE, width - ox), Math.min(_chunkSZ6KMZNMcjs.TILE, height - oy));
5421
+ const ox = tx * _chunkXIOMMGPDcjs.TILE;
5422
+ const oy = ty * _chunkXIOMMGPDcjs.TILE;
5423
+ ctx.putImageData(this.scratch, ox, oy, 0, 0, Math.min(_chunkXIOMMGPDcjs.TILE, width - ox), Math.min(_chunkXIOMMGPDcjs.TILE, height - oy));
4737
5424
  drawn++;
4738
5425
  }, { dirty });
4739
5426
  return drawn;
4740
5427
  }
4741
- }, _class11);
5428
+ }, _class12);
4742
5429
  function guideOverlay(controller) {
4743
5430
  const s = controller.getState();
4744
5431
  const size = s.document.canvas;
@@ -4773,7 +5460,7 @@ var PAPER_SLIDERS = [
4773
5460
  ];
4774
5461
  function readPath(obj, path) {
4775
5462
  let v = obj;
4776
- for (const k of path.split(".")) v = _optionalChain([v, 'optionalAccess', _115 => _115[k]]);
5463
+ for (const k of path.split(".")) v = _optionalChain([v, 'optionalAccess', _126 => _126[k]]);
4777
5464
  return typeof v === "number" ? v : 0;
4778
5465
  }
4779
5466
  function patchPath(obj, path, value) {
@@ -4815,9 +5502,88 @@ function wheelPick(dx, dy, wheel, color) {
4815
5502
  return rgbToHexColor(nr, ng, nb);
4816
5503
  }
4817
5504
  var BLEND_MENU_GROUPS = [
4818
- { label: "Standard", modes: _chunkSZ6KMZNMcjs.BLEND_MODES.filter((m) => !m.painterly && m.id !== "pigment") },
4819
- { label: "Paint", modes: _chunkSZ6KMZNMcjs.BLEND_MODES.filter((m) => m.painterly || m.id === "pigment") }
5505
+ { label: "Standard", modes: _chunkXIOMMGPDcjs.BLEND_MODES.filter((m) => !m.painterly && m.id !== "pigment") },
5506
+ { label: "Paint", modes: _chunkXIOMMGPDcjs.BLEND_MODES.filter((m) => m.painterly || m.id === "pigment") }
4820
5507
  ];
5508
+ var PICKUP_SLIDERS = [
5509
+ { path: "behavior.pickup", label: "Pick-up", min: 0, max: 1, step: 0.01 },
5510
+ { path: "behavior.mix", label: "Mix", min: 0, max: 1, step: 0.01 }
5511
+ ];
5512
+ function brushEngineControls(brush) {
5513
+ switch (brush.engine) {
5514
+ case "blender":
5515
+ return {
5516
+ title: "Blender",
5517
+ sliders: [
5518
+ { path: "blender.strength", label: "Strength", min: 0, max: 1, step: 0.01 },
5519
+ { path: "blender.distance", label: "Blend distance", min: 0, max: 1, step: 0.01 }
5520
+ ],
5521
+ mixingPath: "blender.mixing",
5522
+ dirty: false,
5523
+ multicolor: false,
5524
+ load: false
5525
+ };
5526
+ case "smudge":
5527
+ return {
5528
+ title: "Smudge",
5529
+ sliders: [
5530
+ { path: "smudge.strength", label: "Strength", min: 0, max: 1, step: 0.01 },
5531
+ { path: "smudge.length", label: "Length", min: 0, max: 1, step: 0.01 }
5532
+ ],
5533
+ mixingPath: "smudge.mixing",
5534
+ dirty: false,
5535
+ multicolor: false,
5536
+ load: false
5537
+ };
5538
+ case "mixer":
5539
+ return { title: "Mixer Brush", sliders: [...PICKUP_SLIDERS, { path: "mixer.load", label: "Load", min: 0, max: 1, step: 0.01 }], mixingPath: "behavior.mixing", dirty: true, multicolor: true, load: true };
5540
+ case "dab":
5541
+ return { title: "Paint behaviour", sliders: PICKUP_SLIDERS, mixingPath: "behavior.mixing", dirty: true, multicolor: true, load: true };
5542
+ default:
5543
+ return null;
5544
+ }
5545
+ }
5546
+ function multiColorSliders(mode) {
5547
+ if (mode === "off") return [];
5548
+ const soft = { path: "behavior.multicolor.softness", label: "Colour softness", min: 0, max: 1, step: 0.01 };
5549
+ return mode === "dab" ? [soft, { path: "behavior.multicolor.length", label: "Dabs per colour", min: 1, max: 100, step: 1 }] : [soft];
5550
+ }
5551
+ function patchBrushValue(brush, path, value) {
5552
+ const set = (obj, keys) => {
5553
+ const [head2, ...rest2] = keys;
5554
+ if (!rest2.length) return { ...obj, [head2]: value };
5555
+ return { ...obj, [head2]: set(_nullishCoalesce(obj[head2], () => ( {})), rest2) };
5556
+ };
5557
+ const [head, ...rest] = path.split(".");
5558
+ if (!rest.length) return { [head]: value };
5559
+ return { [head]: set({ ..._nullishCoalesce(brush[head], () => ( {})) }, rest) };
5560
+ }
5561
+ function brushLoadColor(load) {
5562
+ const c = _nullishCoalesce(load.pickup, () => ( load.reservoir));
5563
+ return c ? rgbToHexColor(Math.round(c[0] * 255), Math.round(c[1] * 255), Math.round(c[2] * 255)) : null;
5564
+ }
5565
+ var PAD_SAMPLE_SIZES = [1, 3, 5, 9, 15];
5566
+ function padSampleRadius(size) {
5567
+ return Math.max(0, Math.floor((size - 1) / 2));
5568
+ }
5569
+
5570
+
5571
+
5572
+
5573
+
5574
+
5575
+
5576
+
5577
+
5578
+
5579
+
5580
+
5581
+
5582
+
5583
+
5584
+
5585
+
5586
+
4821
5587
 
4822
5588
 
4823
5589
 
@@ -4965,5 +5731,5 @@ var BLEND_MENU_GROUPS = [
4965
5731
 
4966
5732
 
4967
5733
 
4968
- exports.BLEND_MENU_GROUPS = BLEND_MENU_GROUPS; exports.BLEND_MODES = _chunkSZ6KMZNMcjs.BLEND_MODES; exports.BRUSH_ID = BRUSH_ID; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.BRUSH_SLIDERS = BRUSH_SLIDERS; 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 = _chunkHUFQD4CBcjs.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.PAINTERLY_BLEND_MODES = _chunkSZ6KMZNMcjs.PAINTERLY_BLEND_MODES; 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.PAINT_TOOLS = PAINT_TOOLS; exports.PAPER_FAMILIES = PAPER_FAMILIES; exports.PAPER_LIBRARY = PAPER_LIBRARY; exports.PAPER_NODE_ID = PAPER_NODE_ID; exports.PAPER_SLIDERS = PAPER_SLIDERS; exports.PaintCanvasPresenter = PaintCanvasPresenter; exports.PaintDirector = PaintDirector; exports.PaintDocumentError = PaintDocumentError; exports.PaintEditorController = PaintEditorController; exports.PaintEditorError = PaintEditorError; exports.PaintGesture = PaintGesture; exports.PaintPresence = PaintPresence; 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.decodeImageBlob = decodeImageBlob; exports.defaultGuides = defaultGuides; exports.defaultImageEncoder = defaultImageEncoder; exports.deserializePaintDocument = deserializePaintDocument; exports.deserializePaintDocumentAsync = deserializePaintDocumentAsync; exports.documentExtensions = documentExtensions; exports.documentToScreen = documentToScreen; exports.downloadBytes = downloadBytes; exports.downsampleRgba = downsampleRgba; exports.fitZoom = fitZoom; exports.grainCatch = grainCatch; exports.grainHeights = grainHeights; exports.guideOverlay = guideOverlay; exports.handlePaintShortcut = handlePaintShortcut; exports.hash2 = hash2; exports.hexToRgb8 = hexToRgb8; exports.hslToRgb = hslToRgb; exports.hsvToRgb = hsvToRgb; exports.hueToWheel = hueToWheel; exports.invertAffine = invertAffine; exports.isBlendMode = _chunkSZ6KMZNMcjs.isBlendMode; exports.isPainterlyBlendMode = _chunkSZ6KMZNMcjs.isPainterlyBlendMode; 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.patchPath = patchPath; exports.penOrientation = penOrientation; exports.perspectiveDirections = perspectiveDirections; exports.perspectiveGuideLines = perspectiveGuideLines; exports.pointerToPenPoint = pointerToPenPoint; exports.rand01 = rand01; exports.randSigned = randSigned; exports.readPath = readPath; exports.renderBrushThumbnail = renderBrushThumbnail; exports.renderColorWheel = renderColorWheel; 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.standardBlendMode = _chunkSZ6KMZNMcjs.standardBlendMode; exports.strokeBounds = strokeBounds; exports.strokeDuration = strokeDuration; exports.strokeSchedule = strokeSchedule; exports.sweepPath = sweepPath; 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.wheelAngle = wheelAngle; exports.wheelPick = wheelPick; exports.wheelPoint = wheelPoint; exports.wheelToHue = wheelToHue;
5734
+ exports.BLEND_MENU_GROUPS = BLEND_MENU_GROUPS; exports.BLEND_MODES = _chunkXIOMMGPDcjs.BLEND_MODES; exports.BRUSH_ID = BRUSH_ID; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.BRUSH_SLIDERS = BRUSH_SLIDERS; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.DEFAULT_DPI = DEFAULT_DPI; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; 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 = _chunk7VOHOPLWcjs.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.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.MixingPad = MixingPad; exports.PAD_SAMPLE_SIZES = PAD_SAMPLE_SIZES; exports.PAINTERLY_BLEND_MODES = _chunkXIOMMGPDcjs.PAINTERLY_BLEND_MODES; 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.PAINT_TOOLS = PAINT_TOOLS; exports.PAPER_FAMILIES = PAPER_FAMILIES; exports.PAPER_LIBRARY = PAPER_LIBRARY; exports.PAPER_NODE_ID = PAPER_NODE_ID; exports.PAPER_SLIDERS = PAPER_SLIDERS; exports.PaintCanvasPresenter = PaintCanvasPresenter; exports.PaintDabStroke = PaintDabStroke; exports.PaintDirector = PaintDirector; exports.PaintDocumentError = PaintDocumentError; exports.PaintEditorController = PaintEditorController; exports.PaintEditorError = PaintEditorError; exports.PaintGesture = PaintGesture; exports.PaintPresence = PaintPresence; 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.brushEngineControls = brushEngineControls; exports.brushLoadColor = brushLoadColor; exports.brushLoadForStroke = brushLoadForStroke; 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.decodeImageBlob = decodeImageBlob; exports.defaultGuides = defaultGuides; exports.defaultImageEncoder = defaultImageEncoder; exports.deserializePaintDocument = deserializePaintDocument; exports.deserializePaintDocumentAsync = deserializePaintDocumentAsync; exports.documentExtensions = documentExtensions; exports.documentToScreen = documentToScreen; exports.downloadBytes = downloadBytes; exports.downsampleRgba = downsampleRgba; exports.fitZoom = fitZoom; exports.grainCatch = grainCatch; exports.grainHeights = grainHeights; exports.guideOverlay = guideOverlay; exports.handlePaintShortcut = handlePaintShortcut; exports.hash2 = hash2; exports.hexToRgb8 = hexToRgb8; exports.hslToRgb = hslToRgb; exports.hsvToRgb = hsvToRgb; exports.hueToWheel = hueToWheel; exports.invertAffine = invertAffine; exports.isBlendMode = _chunkXIOMMGPDcjs.isBlendMode; exports.isPainterlyBlendMode = _chunkXIOMMGPDcjs.isPainterlyBlendMode; exports.isPalmTouch = isPalmTouch; exports.isWetCapable = isWetCapable; exports.mapPenPoint = mapPenPoint; exports.mix32 = mix32; exports.mixerLoadForStroke = mixerLoadForStroke; exports.mixerTip = mixerTip; exports.multiColorForDab = multiColorForDab; exports.multiColorGradient = multiColorGradient; exports.multiColorPalette = multiColorPalette; exports.multiColorSliders = multiColorSliders; exports.newPaintLayerId = newPaintLayerId; exports.normalizeBehavior = normalizeBehavior; exports.normalizeBrushStroke = normalizeBrushStroke; exports.normalizeCanvas = normalizeCanvas; exports.normalizeDynamics = normalizeDynamics; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizePaintBrush = normalizePaintBrush; exports.normalizePaper = normalizePaper; exports.normalizePerspective = normalizePerspective; exports.normalizeRotation = normalizeRotation; exports.normalizeSymmetry = normalizeSymmetry; exports.normalizeView = normalizeView; exports.padSampleRadius = padSampleRadius; exports.paperGrain = paperGrain; exports.paperKey = paperKey; exports.paperNode = paperNode; exports.paperPreset = paperPreset; exports.parseBrushLibrary = parseBrushLibrary; exports.parseHexColor = parseHexColor; exports.patchBrushValue = patchBrushValue; exports.patchPath = patchPath; exports.penOrientation = penOrientation; exports.perspectiveDirections = perspectiveDirections; exports.perspectiveGuideLines = perspectiveGuideLines; exports.pointerToPenPoint = pointerToPenPoint; exports.rand01 = rand01; exports.randSigned = randSigned; exports.readPath = readPath; exports.renderBrushThumbnail = renderBrushThumbnail; exports.renderColorWheel = renderColorWheel; 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.standardBlendMode = _chunkXIOMMGPDcjs.standardBlendMode; exports.strokeBounds = strokeBounds; exports.strokeDuration = strokeDuration; exports.strokeSchedule = strokeSchedule; exports.sweepPath = sweepPath; exports.symmetryAxisAngles = symmetryAxisAngles; exports.symmetryAxisSegments = symmetryAxisSegments; exports.symmetryCopies = symmetryCopies; exports.thumbnailStroke = thumbnailStroke; exports.tiltToOrientation = tiltToOrientation; exports.toStrokePoint = toStrokePoint; exports.usesPaintExchange = usesPaintExchange; exports.variationAffine = variationAffine; exports.varyStroke = varyStroke; exports.viewCssTransform = viewCssTransform; exports.viewMatrix = viewMatrix; exports.viewPenToDocument = viewPenToDocument; exports.wheelAngle = wheelAngle; exports.wheelPick = wheelPick; exports.wheelPoint = wheelPoint; exports.wheelToHue = wheelToHue;
4969
5735
  //# sourceMappingURL=index.cjs.map