@pixerate/editor 0.108.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 (105) hide show
  1. package/dist/animation/index.cjs +5 -5
  2. package/dist/animation/index.cjs.map +1 -1
  3. package/dist/animation/index.js +9 -9
  4. package/dist/animation-editor/index.cjs +59 -59
  5. package/dist/animation-editor/index.cjs.map +1 -1
  6. package/dist/animation-editor/index.d.cts +2 -1
  7. package/dist/animation-editor/index.d.ts +2 -1
  8. package/dist/animation-editor/index.js +29 -29
  9. package/dist/blending-DXl06NSx.d.ts +486 -0
  10. package/dist/blending-aOE6Y3UE.d.cts +486 -0
  11. package/dist/canvas/index.cjs +27 -27
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.js +11 -11
  14. package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
  15. package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
  16. package/dist/chunk-7VOHOPLW.cjs +1136 -0
  17. package/dist/chunk-7VOHOPLW.cjs.map +1 -0
  18. package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
  19. package/dist/chunk-BLWCAQCW.js.map +1 -0
  20. package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
  21. package/dist/chunk-DMRSJBJF.js.map +1 -0
  22. package/dist/chunk-FTBZPI77.cjs +1 -0
  23. package/dist/chunk-FTBZPI77.cjs.map +1 -0
  24. package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
  25. package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
  26. package/dist/chunk-JLYX4V57.cjs +2001 -0
  27. package/dist/chunk-JLYX4V57.cjs.map +1 -0
  28. package/dist/chunk-KB2YBCVS.js +2001 -0
  29. package/dist/chunk-KB2YBCVS.js.map +1 -0
  30. package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
  31. package/dist/chunk-LQEEDHOP.js.map +1 -0
  32. package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
  33. package/dist/chunk-MGL4U5WA.cjs.map +1 -0
  34. package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
  35. package/dist/chunk-POGBUYWE.cjs.map +1 -0
  36. package/dist/chunk-VKK5VS2T.js +1 -0
  37. package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
  38. package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
  39. package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
  40. package/dist/chunk-XIOMMGPD.cjs.map +1 -0
  41. package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
  42. package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
  43. package/dist/chunk-Z4BC6M74.js.map +1 -0
  44. package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
  45. package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
  46. package/dist/image-editor/index.cjs +1665 -1113
  47. package/dist/image-editor/index.cjs.map +1 -1
  48. package/dist/image-editor/index.d.cts +78 -14
  49. package/dist/image-editor/index.d.ts +78 -14
  50. package/dist/image-editor/index.js +843 -291
  51. package/dist/image-editor/index.js.map +1 -1
  52. package/dist/paint/index.cjs +28 -4
  53. package/dist/paint/index.cjs.map +1 -1
  54. package/dist/paint/index.d.cts +3 -89
  55. package/dist/paint/index.d.ts +3 -89
  56. package/dist/paint/index.js +27 -3
  57. package/dist/paint-editor/index.cjs +1056 -287
  58. package/dist/paint-editor/index.cjs.map +1 -1
  59. package/dist/paint-editor/index.d.cts +304 -22
  60. package/dist/paint-editor/index.d.ts +304 -22
  61. package/dist/paint-editor/index.js +852 -83
  62. package/dist/paint-editor/index.js.map +1 -1
  63. package/dist/raster/index.cjs +7 -3
  64. package/dist/raster/index.cjs.map +1 -1
  65. package/dist/raster/index.d.cts +3 -129
  66. package/dist/raster/index.d.ts +3 -129
  67. package/dist/raster/index.js +6 -2
  68. package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
  69. package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
  70. package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
  71. package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
  72. package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
  73. package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
  74. package/dist/types-DtL_qLFz.d.ts +630 -0
  75. package/dist/types-DyQq-WPY.d.cts +630 -0
  76. package/dist/vector/index.cjs +563 -563
  77. package/dist/vector/index.cjs.map +1 -1
  78. package/dist/vector/index.d.cts +4 -2
  79. package/dist/vector/index.d.ts +4 -2
  80. package/dist/vector/index.js +69 -69
  81. package/dist/video-editor/index.cjs +3 -3
  82. package/dist/video-editor/index.js +2 -2
  83. package/package.json +1 -1
  84. package/skills/pixerate-paint-editor/SKILL.md +49 -13
  85. package/dist/chunk-3PHA3UKI.js.map +0 -1
  86. package/dist/chunk-6BU5WU4R.cjs +0 -1
  87. package/dist/chunk-6BU5WU4R.cjs.map +0 -1
  88. package/dist/chunk-DWGZUSES.cjs +0 -694
  89. package/dist/chunk-DWGZUSES.cjs.map +0 -1
  90. package/dist/chunk-FRD3FUXI.cjs.map +0 -1
  91. package/dist/chunk-G4BIYE6R.js.map +0 -1
  92. package/dist/chunk-HUFQD4CB.cjs +0 -734
  93. package/dist/chunk-HUFQD4CB.cjs.map +0 -1
  94. package/dist/chunk-HUQ7EHVI.js.map +0 -1
  95. package/dist/chunk-KKREERP2.js +0 -694
  96. package/dist/chunk-KKREERP2.js.map +0 -1
  97. package/dist/chunk-SOROZ2BF.js.map +0 -1
  98. package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
  99. package/dist/chunk-T3QZ6S47.cjs.map +0 -1
  100. package/dist/chunk-UTEVPQTS.cjs.map +0 -1
  101. package/dist/chunk-XYBJKUAU.js +0 -1
  102. package/dist/mixer-5ziHU8OS.d.cts +0 -282
  103. package/dist/mixer-DNqiYXYQ.d.ts +0 -282
  104. /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
  105. /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.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 _chunkFRD3FUXIcjs = require('../chunk-FRD3FUXI.cjs');
7
+ var _chunkMGL4U5WAcjs = require('../chunk-MGL4U5WA.cjs');
8
8
 
9
9
 
10
10
 
@@ -17,7 +17,9 @@ var _chunkFRD3FUXIcjs = require('../chunk-FRD3FUXI.cjs');
17
17
 
18
18
 
19
19
 
20
- var _chunkT3QZ6S47cjs = require('../chunk-T3QZ6S47.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,14 +62,23 @@ 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
 
60
75
 
61
76
 
62
77
  var _chunkEGFUVXKKcjs = require('../chunk-EGFUVXKK.cjs');
63
- require('../chunk-DWGZUSES.cjs');
78
+ require('../chunk-W3GXSUSQ.cjs');
79
+ require('../chunk-XGD3FSNC.cjs');
80
+ require('../chunk-MR3Z4OQI.cjs');
81
+ require('../chunk-A6PHCAGX.cjs');
64
82
 
65
83
  // src/paint-editor/noise.ts
66
84
  function mix32(h) {
@@ -409,7 +427,7 @@ function newPaintLayerId() {
409
427
  return `pl${Date.now().toString(36)}${(nextId++).toString(36)}`;
410
428
  }
411
429
  function createPaintLayer(name, id = newPaintLayerId()) {
412
- 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 } };
413
431
  }
414
432
  var PaintDocumentError = class extends Error {
415
433
  constructor(message) {
@@ -435,8 +453,11 @@ function createPaintDocument(options = {}) {
435
453
  }
436
454
 
437
455
  // src/paint-editor/brushes.ts
438
- var PAINT_BRUSH_ENGINES = ["dab", "eraser", "mixer"];
439
- 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 };
440
461
  var BRUSH_ROTATIONS = ["fixed", "stroke", "barrel"];
441
462
  var DEFAULT_DYNAMICS = {
442
463
  minSize: 0,
@@ -469,21 +490,62 @@ function normalizeDynamics(raw = {}, base = DEFAULT_DYNAMICS) {
469
490
  };
470
491
  }
471
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
+ }
472
519
  function normalizePaintBrush(raw = {}, base) {
473
520
  const b = _nullishCoalesce(base, () => ( BASE_BRUSH));
474
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 };
475
534
  return {
476
535
  id,
477
536
  name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, 80) : b.name,
478
537
  category: PAINT_BRUSH_CATEGORIES.includes(raw.category) ? raw.category : b.category,
479
- engine: PAINT_BRUSH_ENGINES.includes(raw.engine) ? raw.engine : b.engine,
480
- 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, () => ( {})) }),
481
540
  grain: unit2(raw.grain, b.grain),
482
541
  pressureSmoothing: range(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
483
542
  rotation: BRUSH_ROTATIONS.includes(raw.rotation) ? raw.rotation : b.rotation,
484
543
  dynamics: normalizeDynamics(_nullishCoalesce(raw.dynamics, () => ( {})), b.dynamics),
485
- mixer: _chunkHUFQD4CBcjs.normalizeMixer.call(void 0, { ...b.mixer, ..._nullishCoalesce(raw.mixer, () => ( {})) }),
544
+ mixer,
486
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),
487
549
  ...raw.builtin ? { builtin: true } : {}
488
550
  };
489
551
  }
@@ -492,13 +554,16 @@ var BASE_BRUSH = {
492
554
  name: "Round",
493
555
  category: "custom",
494
556
  engine: "dab",
495
- tip: { ..._chunkHUFQD4CBcjs.DEFAULT_BRUSH },
557
+ tip: { ..._chunk7VOHOPLWcjs.DEFAULT_BRUSH },
496
558
  grain: 0,
497
559
  pressureSmoothing: 0,
498
560
  rotation: "fixed",
499
561
  dynamics: { ...DEFAULT_DYNAMICS },
500
- mixer: { ..._chunkHUFQD4CBcjs.DEFAULT_MIXER },
501
- 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 }
502
567
  };
503
568
  var builtin = (init) => ({ ...normalizePaintBrush(init), builtin: true });
504
569
  var BUILTIN_BRUSHES = [
@@ -612,19 +677,54 @@ var BUILTIN_BRUSHES = [
612
677
  id: "blender",
613
678
  name: "Blender",
614
679
  category: "blender",
615
- engine: "mixer",
680
+ engine: "blender",
616
681
  loadsColor: false,
617
- tip: { size: 34, hardness: 0.4, spacing: 0.08, opacity: 1, flow: 0.35, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
618
- 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" }
619
684
  }),
620
685
  builtin({
621
686
  id: "smudge",
622
687
  name: "Smudge",
623
688
  category: "smudge",
624
- engine: "mixer",
689
+ engine: "smudge",
625
690
  loadsColor: false,
626
- tip: { size: 26, hardness: 0.6, spacing: 0.06, opacity: 1, flow: 0.8, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
627
- 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 }
628
728
  }),
629
729
  builtin({
630
730
  id: "mixer-brush",
@@ -804,7 +904,9 @@ var PenDabWalker = (_class4 = class {
804
904
  opacity: Math.min(1, Math.max(0, opacity)),
805
905
  angle,
806
906
  roundness: Math.min(1, Math.max(0.01, tip.roundness * (1 - dyn.tiltRoundness * s.lean * 0.8))),
807
- pressure: s.pressure
907
+ pressure: s.pressure,
908
+ direction: this.dir,
909
+ index: i
808
910
  };
809
911
  }
810
912
  push(p, out) {
@@ -874,7 +976,7 @@ var DryStroke = (_class5 = class {
874
976
  }
875
977
 
876
978
 
877
- __init12() {this.coverage = new (0, _chunkHUFQD4CBcjs.CoverageMap)()}
979
+ __init12() {this.coverage = new (0, _chunk7VOHOPLWcjs.CoverageMap)()}
878
980
  __init13() {this.dabCount = 0}
879
981
 
880
982
 
@@ -908,11 +1010,11 @@ var DryStroke = (_class5 = class {
908
1010
  const hardness = this.brush.tip.hardness;
909
1011
  const grain = this.options.grain;
910
1012
  for (const d of dabs) {
911
- const rect = _chunkHUFQD4CBcjs.dabRect.call(void 0, d, hardness);
1013
+ const rect = _chunk7VOHOPLWcjs.dabRect.call(void 0, d, hardness);
912
1014
  const n = rect.width * rect.height;
913
1015
  if (this.buf.length < n) this.buf = new Float32Array(n);
914
1016
  const vals = this.buf.subarray(0, n);
915
- _chunkHUFQD4CBcjs.rasterizeDab.call(void 0, d, hardness, rect, vals);
1017
+ _chunk7VOHOPLWcjs.rasterizeDab.call(void 0, d, hardness, rect, vals);
916
1018
  if (this.depth > 0 && grain) {
917
1019
  for (let yy = 0, k = 0; yy < rect.height; yy++) {
918
1020
  const y = rect.y + yy + 0.5;
@@ -935,9 +1037,251 @@ function toStrokePoint(p) {
935
1037
  return { x: p.x, y: p.y, pressure: pressureOf(p), ...typeof p.t === "number" ? { t: p.t } : {} };
936
1038
  }
937
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
+
938
1282
  // src/paint-editor/stroke.ts
939
1283
  function defaultTile(surface) {
940
- const data = new Uint8ClampedArray(_chunkSZ6KMZNMcjs.TILE * _chunkSZ6KMZNMcjs.TILE * surface.channels);
1284
+ const data = new Uint8ClampedArray(_chunkXIOMMGPDcjs.TILE * _chunkXIOMMGPDcjs.TILE * surface.channels);
941
1285
  if (surface.defaultValue) data.fill(surface.defaultValue);
942
1286
  return data;
943
1287
  }
@@ -954,8 +1298,8 @@ function union(a, b) {
954
1298
  const y = Math.min(a.y, b.y);
955
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 };
956
1300
  }
957
- var PaintStrokeSession = (_class6 = class {
958
- 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);
959
1303
  this.host = host;
960
1304
  this.init = init;
961
1305
  this.layerId = init.layerId;
@@ -973,10 +1317,18 @@ var PaintStrokeSession = (_class6 = class {
973
1317
  selection: null,
974
1318
  offset: init.offset
975
1319
  };
976
- if (brush.engine === "mixer") {
977
- this.mixerSmoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
978
- const load = init.mixerLoad;
979
- 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
+ });
980
1332
  } else {
981
1333
  this.dry = new DryStroke(brush, { grain: init.grain, paperDepth: init.paperDepth, seed: init.seed, copies: this.copies });
982
1334
  }
@@ -985,30 +1337,29 @@ var PaintStrokeSession = (_class6 = class {
985
1337
 
986
1338
 
987
1339
  /** The raw input so far (what a replay feeds back in). */
988
- __init17() {this.points = []}
1340
+ __init25() {this.points = []}
989
1341
 
990
1342
 
991
- __init18() {this.touched = /* @__PURE__ */ new Set()}
992
- __init19() {this.fresh = /* @__PURE__ */ new Set()}
993
- __init20() {this.dry = null}
994
- __init21() {this.mixers = []}
995
- __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}
996
1347
 
997
1348
 
998
- __init23() {this.bounds = null}
999
- __init24() {this.lastRaw = null}
1000
- __init25() {this.closed = false}
1001
- __init26() {this.result = null}
1002
- mixerTarget() {
1349
+ __init30() {this.bounds = null}
1350
+ __init31() {this.lastRaw = null}
1351
+ __init32() {this.closed = false}
1352
+ __init33() {this.result = null}
1353
+ tileTarget() {
1003
1354
  return {
1004
1355
  offset: this.init.offset,
1005
1356
  lockTransparency: this.init.lockTransparency,
1006
1357
  selection: null,
1007
- 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)),
1008
1359
  writeTile: (tx, ty) => {
1009
- const key = _chunkSZ6KMZNMcjs.tileKey.call(void 0, tx, ty);
1360
+ const key = _chunkXIOMMGPDcjs.tileKey.call(void 0, tx, ty);
1010
1361
  if (!this.touched.has(key)) {
1011
- 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)));
1012
1363
  this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
1013
1364
  this.touched.add(key);
1014
1365
  }
@@ -1027,15 +1378,7 @@ var PaintStrokeSession = (_class6 = class {
1027
1378
  if (filter) points = points.map(filter);
1028
1379
  for (const p of points) this.points.push(p);
1029
1380
  this.lastRaw = points[points.length - 1];
1030
- if (this.dry) this.update(this.dry.push(points));
1031
- else {
1032
- const rects = [];
1033
- for (const raw of points) {
1034
- const p = toStrokePoint(this.mixerSmoother.push(raw));
1035
- this.copies.forEach((c, i) => rects.push(...this.mixers[i].push([toStrokePoint(c.map(p))])));
1036
- }
1037
- this.update(rects);
1038
- }
1381
+ this.update(this.dry ? this.dry.push(points) : this.wet.push(points));
1039
1382
  }
1040
1383
  update(rects) {
1041
1384
  if (!rects.length) return;
@@ -1044,57 +1387,47 @@ var PaintStrokeSession = (_class6 = class {
1044
1387
  if (!this.dry) {
1045
1388
  for (const key of this.fresh) this.live.set(key, { data: this.live.get(key).data, uniform: null });
1046
1389
  this.fresh.clear();
1047
- 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);
1048
1391
  return;
1049
1392
  }
1050
1393
  const off = this.init.offset;
1051
1394
  const regions = /* @__PURE__ */ new Map();
1052
1395
  for (const r of rects) {
1053
- 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 })) {
1054
- 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);
1055
1398
  const cur = regions.get(key);
1056
1399
  regions.set(key, { tx: t.tx, ty: t.ty, r: cur ? union(cur.r, r) : r });
1057
1400
  }
1058
1401
  }
1059
1402
  for (const [key, { tx, ty, r }] of regions) {
1060
- 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)));
1061
- 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;
1062
1405
  this.touched.add(key);
1063
- 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 } : {});
1064
1407
  this.live.set(key, { data, uniform: null });
1065
1408
  }
1066
- 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);
1067
1410
  }
1068
1411
  /** Ends the stroke (one undo step) and returns what changed. */
1069
1412
  end() {
1070
1413
  if (this.closed) throw new Error("This stroke has already ended.");
1071
- if (this.dry) this.update(this.dry.finish());
1072
- else {
1073
- const rects = [];
1074
- const last = this.lastRaw ? toStrokePoint(this.lastRaw) : void 0;
1075
- this.copies.forEach((c, i) => rects.push(...this.mixers[i].finish(last ? toStrokePoint(c.map(last)) : void 0)));
1076
- this.update(rects);
1077
- }
1414
+ this.update(this.dry ? this.dry.finish() : this.wet.finish());
1078
1415
  this.closed = true;
1079
1416
  const ch = this.base.channels;
1080
1417
  const tiles = new Map(this.base.tiles);
1081
1418
  let changed = false;
1082
1419
  for (const key of this.touched) {
1083
- 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);
1084
1421
  const prev = this.base.tiles.get(key);
1085
1422
  if (tile) tiles.set(key, tile);
1086
1423
  else tiles.delete(key);
1087
1424
  if (!sameTile(prev, tile)) changed = true;
1088
1425
  }
1089
- const surface = changed ? new (0, _chunkSZ6KMZNMcjs.TiledSurface)(ch, tiles, this.base.defaultValue) : null;
1090
- 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;
1091
1428
  this.result = { layerId: this.layerId, bounds: changed ? this.bounds : null, dabs, strokeId: null };
1092
- if (this.mixers.length) {
1093
- const brush = this.init.brush;
1094
- let load = this.mixers[0].load;
1095
- if (brush.mixer.cleanEachStroke) load = _chunkHUFQD4CBcjs.cleanMixer.call(void 0, load);
1096
- this.result.mixerLoad = load;
1097
- }
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);
1098
1431
  this.host.commit(this, surface, false);
1099
1432
  return this.result;
1100
1433
  }
@@ -1104,12 +1437,17 @@ var PaintStrokeSession = (_class6 = class {
1104
1437
  this.closed = true;
1105
1438
  this.host.commit(this, null, true);
1106
1439
  }
1107
- }, _class6);
1440
+ }, _class7);
1108
1441
  function mixerLoadForStroke(brush, current, color) {
1109
1442
  if (!brush.loadsColor) return { reservoir: null, amount: 0, pickup: brush.mixer.cleanEachStroke ? null : current.pickup };
1110
- 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);
1111
1444
  return current;
1112
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
+ }
1113
1451
 
1114
1452
  // src/paint-editor/strokes.ts
1115
1453
  var IDENTITY_AFFINE = [1, 0, 0, 1, 0, 0];
@@ -1155,6 +1493,17 @@ function readPoint(raw) {
1155
1493
  function readAffine(raw) {
1156
1494
  return Array.isArray(raw) && raw.length === 6 && raw.every(finite) ? raw : null;
1157
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
+ }
1158
1507
  function normalizeBrushStroke(raw) {
1159
1508
  if (!raw || typeof raw !== "object") return null;
1160
1509
  const r = raw;
@@ -1171,6 +1520,8 @@ function normalizeBrushStroke(raw) {
1171
1520
  };
1172
1521
  const copies = Array.isArray(r.copies) ? r.copies.map(readAffine).filter((m) => m !== null) : [];
1173
1522
  if (copies.length) out.copies = copies;
1523
+ const load = readLoad(r.load);
1524
+ if (load) out.load = load;
1174
1525
  return out;
1175
1526
  }
1176
1527
  function strokeBounds(points) {
@@ -1207,7 +1558,7 @@ function varyStroke(stroke, m, v) {
1207
1558
  });
1208
1559
  const brush = s !== 1 ? normalizePaintBrush({ ...stroke.brush, tip: { ...stroke.brush.tip, size: stroke.brush.tip.size * s } }) : stroke.brush;
1209
1560
  const out = { ...stroke, brush, points };
1210
- if (_optionalChain([stroke, 'access', _22 => _22.copies, 'optionalAccess', _23 => _23.length])) {
1561
+ if (_optionalChain([stroke, 'access', _30 => _30.copies, 'optionalAccess', _31 => _31.length])) {
1211
1562
  const inv = invertAffine(m);
1212
1563
  out.copies = stroke.copies.map((c) => composeAffine(m, composeAffine(c, inv)));
1213
1564
  }
@@ -1274,7 +1625,7 @@ var PROFILE = {
1274
1625
  }
1275
1626
  };
1276
1627
  function createPaintCompositor() {
1277
- return new (0, _chunkT3QZ6S47cjs.RasterCompositor)(PROFILE);
1628
+ return new (0, _chunkPOGBUYWEcjs.RasterCompositor)(PROFILE);
1278
1629
  }
1279
1630
  function downsampleRgba(data, width, height, tw, th) {
1280
1631
  tw = Math.max(1, Math.round(tw));
@@ -1322,7 +1673,7 @@ function downsampleRgba(data, width, height, tw, th) {
1322
1673
  // src/paint-editor/serialize.ts
1323
1674
  var PAINT_FORMAT = "pixerate-paint";
1324
1675
  var PAINT_SCHEMA_VERSION = 1;
1325
- 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"]);
1326
1677
  function documentExtensions(raw) {
1327
1678
  const out = {};
1328
1679
  for (const [k, v] of Object.entries(raw)) if (!KNOWN.has(k)) out[k] = v;
@@ -1342,14 +1693,15 @@ function header(state, options) {
1342
1693
  if (options.strokes !== false && state.strokes.length) out.strokes = JSON.parse(JSON.stringify(state.strokes));
1343
1694
  if (options.guides !== void 0) out.guides = JSON.parse(JSON.stringify(options.guides));
1344
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));
1345
1697
  return out;
1346
1698
  }
1347
1699
  function serializePaintDocument(state, options = {}) {
1348
- const { layers, assets } = _chunkFRD3FUXIcjs.serializeLayers.call(void 0, state.layers);
1700
+ const { layers, assets } = _chunkMGL4U5WAcjs.serializeLayers.call(void 0, state.layers);
1349
1701
  return { ...header(state, options), layers, assets };
1350
1702
  }
1351
1703
  async function serializePaintDocumentExternal(state, store, options = {}) {
1352
- const { layers, assets } = await _chunkFRD3FUXIcjs.serializeLayersExternal.call(void 0, state.layers, store);
1704
+ const { layers, assets } = await _chunkMGL4U5WAcjs.serializeLayersExternal.call(void 0, state.layers, store);
1353
1705
  return { ...header(state, options), layers, assets };
1354
1706
  }
1355
1707
  function paintLayersOnly(layers, problems) {
@@ -1357,7 +1709,7 @@ function paintLayersOnly(layers, problems) {
1357
1709
  for (const l of layers) {
1358
1710
  if (l.type === "pixel") {
1359
1711
  const { filters, source, ...rest } = l;
1360
- 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.`);
1361
1713
  if (source) problems.push(`Layer "${l.name}" refers to an image editor source image; it loads empty.`);
1362
1714
  out.push(rest);
1363
1715
  } else if (l.type === "group") out.push({ ...l, children: paintLayersOnly(l.children, problems) });
@@ -1386,16 +1738,16 @@ function deserializePaintDocument(raw) {
1386
1738
  const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
1387
1739
  const paper = normalizePaper(_nullishCoalesce(r.paper, () => ( {})));
1388
1740
  const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
1389
- const layers = paintLayersOnly(_chunkFRD3FUXIcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1741
+ const layers = paintLayersOnly(_chunkMGL4U5WAcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1390
1742
  const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
1391
- const activeLayerId = wanted && _chunkT3QZ6S47cjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkT3QZ6S47cjs.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));
1392
- 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 };
1393
1745
  }
1394
1746
  async function deserializePaintDocumentAsync(raw, store) {
1395
1747
  if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
1396
1748
  const r = raw;
1397
1749
  const problems = [];
1398
- const assets = await _chunkFRD3FUXIcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1750
+ const assets = await _chunkMGL4U5WAcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1399
1751
  const loaded = deserializePaintDocument({ ...r, assets });
1400
1752
  return { ...loaded, problems: [...problems, ...loaded.problems] };
1401
1753
  }
@@ -1527,7 +1879,7 @@ async function decodeImageBlob(blob) {
1527
1879
  if (!ctx) throw new Error("No 2D context to decode with.");
1528
1880
  ctx.drawImage(bitmap, 0, 0);
1529
1881
  const data = new Uint8ClampedArray(ctx.getImageData(0, 0, bitmap.width, bitmap.height).data);
1530
- _optionalChain([bitmap, 'access', _30 => _30.close, 'optionalCall', _31 => _31()]);
1882
+ _optionalChain([bitmap, 'access', _38 => _38.close, 'optionalCall', _39 => _39()]);
1531
1883
  return { width: bitmap.width, height: bitmap.height, data };
1532
1884
  }
1533
1885
  function downloadBytes(bytes, fileName, mimeType) {
@@ -1574,7 +1926,7 @@ function paintTestStroke(surface, brush, paper, color, width, height) {
1574
1926
  colorHex: color,
1575
1927
  affineCopies: [],
1576
1928
  copies: [IDENTITY_COPY],
1577
- 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])
1578
1930
  }
1579
1931
  );
1580
1932
  session.push(thumbnailStroke(width, height));
@@ -1587,13 +1939,13 @@ function renderBrushThumbnail(brush, options) {
1587
1939
  const maxSize = height * 0.42;
1588
1940
  const b = brush.tip.size > maxSize ? normalizePaintBrush({ tip: { ...brush.tip, size: maxSize } }, brush) : brush;
1589
1941
  const rgba = [...hexToRgb8(options.color), 255];
1590
- let surface = _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4);
1942
+ let surface = _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4);
1591
1943
  if (b.engine === "eraser") {
1592
- surface = _chunkSZ6KMZNMcjs.fillRect.call(void 0, surface, { x: 0, y: 0, width, height }, rgba);
1593
- } 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) {
1594
1946
  const half = Math.ceil(height / 2);
1595
- surface = _chunkSZ6KMZNMcjs.fillRect.call(void 0, surface, { x: 0, y: 0, width, height: half }, rgba);
1596
- 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]);
1597
1949
  }
1598
1950
  surface = paintTestStroke(surface, b, options.paper, options.color, width, height);
1599
1951
  const layer = { type: "pixel", id: "thumbnail", name: "thumbnail", visible: true, opacity: 1, blend: "normal", surface, offset: { x: 0, y: 0 } };
@@ -1725,13 +2077,13 @@ function perspectiveDirections(g, p) {
1725
2077
  return out;
1726
2078
  }
1727
2079
  var SNAP_DECIDE_DISTANCE = 6;
1728
- var PerspectiveSnapper = (_class7 = class {
1729
- 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);
1730
2082
  this.guide = guide;
1731
2083
  }
1732
2084
 
1733
- __init27() {this.start = null}
1734
- __init28() {this.dir = null}
2085
+ __init34() {this.start = null}
2086
+ __init35() {this.dir = null}
1735
2087
  /** The line the stroke follows, once chosen. */
1736
2088
  get line() {
1737
2089
  return this.start && this.dir ? { from: this.start, direction: this.dir } : null;
@@ -1764,7 +2116,7 @@ var PerspectiveSnapper = (_class7 = class {
1764
2116
  const t = (p.x - s.x) * d.x + (p.y - s.y) * d.y;
1765
2117
  return { ...p, x: s.x + d.x * t, y: s.y + d.y * t };
1766
2118
  }
1767
- }, _class7);
2119
+ }, _class8);
1768
2120
  function perspectiveGuideLines(g, size, count = 12) {
1769
2121
  const out = [];
1770
2122
  const far = Math.hypot(size.width, size.height) * 4;
@@ -1783,9 +2135,9 @@ function perspectiveGuideLines(g, size, count = 12) {
1783
2135
  }
1784
2136
 
1785
2137
  // src/paint-editor/color.ts
1786
- var HEX2 = /^#?([0-9a-f]{6})$/i;
2138
+ var HEX3 = /^#?([0-9a-f]{6})$/i;
1787
2139
  function parseHexColor(value, what = "color") {
1788
- const m = typeof value === "string" ? HEX2.exec(value.trim()) : null;
2140
+ const m = typeof value === "string" ? HEX3.exec(value.trim()) : null;
1789
2141
  if (!m) throw new Error(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
1790
2142
  const n = parseInt(m[1], 16);
1791
2143
  return [n >> 16 & 255, n >> 8 & 255, n & 255];
@@ -1913,17 +2265,25 @@ var PAD_KNIFE = normalizePaintBrush({
1913
2265
  id: "pad-knife",
1914
2266
  name: "Palette knife",
1915
2267
  category: "smudge",
1916
- engine: "mixer",
2268
+ engine: "smudge",
1917
2269
  loadsColor: false,
1918
- tip: { size: 34, hardness: 0.9, spacing: 0.08, roundness: 0.35, angle: 90, opacity: 1, flow: 0.85, pressureSize: false, pressureOpacity: false },
1919
- 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 }
1920
2280
  });
1921
- 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); }
1922
- __init29() {this.width = MIXING_PAD_WIDTH}
1923
- __init30() {this.height = MIXING_PAD_HEIGHT}
1924
- __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)}
1925
2285
  /** Increments on every change (for redrawing). */
1926
- __init32() {this.version = 0}
2286
+ __init39() {this.version = 0}
1927
2287
  /** Paints (`paint`, with `color`) or smears (`knife`) through `points` in pad pixels. */
1928
2288
  stroke(points, options) {
1929
2289
  if (!points.length) throw new Error("points is empty; give at least one {x, y} in pad pixels.");
@@ -1934,7 +2294,11 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1934
2294
  }
1935
2295
  let brush = options.tool === "knife" ? PAD_KNIFE : PAD_PAINT;
1936
2296
  if (options.size !== void 0) brush = normalizePaintBrush({ tip: { ...brush.tip, size: options.size } }, brush);
1937
- 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);
1938
2302
  let result = null;
1939
2303
  const session = new PaintStrokeSession(
1940
2304
  { update: () => {
@@ -1950,10 +2314,10 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1950
2314
  grain: null,
1951
2315
  paperDepth: 0,
1952
2316
  seed: 1,
1953
- colorHex: options.color,
2317
+ colorHex: color,
1954
2318
  affineCopies: [],
1955
2319
  copies: [IDENTITY_COPY],
1956
- 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])
1957
2321
  }
1958
2322
  );
1959
2323
  session.push(points);
@@ -1972,7 +2336,7 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1972
2336
  const y0 = Math.max(0, Math.floor(y) - r);
1973
2337
  const x1 = Math.min(this.width, Math.floor(x) + r + 1);
1974
2338
  const y1 = Math.min(this.height, Math.floor(y) + r + 1);
1975
- 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 });
1976
2340
  let R = 0;
1977
2341
  let G = 0;
1978
2342
  let B = 0;
@@ -1989,12 +2353,88 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
1989
2353
  return `#${hex(R)}${hex(G)}${hex(B)}`;
1990
2354
  }
1991
2355
  clear() {
1992
- 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);
1993
2429
  this.version++;
1994
2430
  }
2431
+ /** Whether nothing is on the pad. */
2432
+ get empty() {
2433
+ return this.surface.tiles.size === 0;
2434
+ }
1995
2435
  /** The pad over white, `width × height` RGBA. */
1996
2436
  pixels() {
1997
- 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 });
1998
2438
  for (let i = 0; i < px.length; i += 4) {
1999
2439
  const a = px[i + 3] / 255;
2000
2440
  px[i] = Math.round(px[i] * a + 255 * (1 - a));
@@ -2004,7 +2444,7 @@ var MixingPad = (_class8 = class {constructor() { _class8.prototype.__init29.cal
2004
2444
  }
2005
2445
  return px;
2006
2446
  }
2007
- }, _class8);
2447
+ }, _class9);
2008
2448
 
2009
2449
  // src/paint-editor/controller.ts
2010
2450
  var PaintEditorError = class extends Error {
@@ -2013,9 +2453,9 @@ var PaintEditorError = class extends Error {
2013
2453
  this.name = "PaintEditorError";
2014
2454
  }
2015
2455
  };
2016
- var HEX3 = /^#[0-9a-f]{6}$/i;
2456
+ var HEX4 = /^#[0-9a-f]{6}$/i;
2017
2457
  function parseColor(value, what = "color") {
2018
- 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".`);
2019
2459
  return value.trim().toLowerCase();
2020
2460
  }
2021
2461
  function rgbaOf(hex) {
@@ -2032,40 +2472,44 @@ function unionRect(a, b) {
2032
2472
  const y = Math.min(a.y, b.y);
2033
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 };
2034
2474
  }
2035
- var isPixelLayer = (l) => _optionalChain([l, 'optionalAccess', _32 => _32.type]) === "pixel";
2036
- var PaintEditorController = (_class9 = class {
2475
+ var isPixelLayer = (l) => _optionalChain([l, 'optionalAccess', _41 => _41.type]) === "pixel";
2476
+ var PaintEditorController = (_class10 = class {
2037
2477
 
2038
- __init33() {this.extensions = {}}
2039
- __init34() {this.undoStack = []}
2040
- __init35() {this.redoStack = []}
2478
+ __init40() {this.extensions = {}}
2479
+ __init41() {this.undoStack = []}
2480
+ __init42() {this.redoStack = []}
2041
2481
 
2042
2482
 
2043
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}
2044
2486
 
2045
- __init36() {this.secondaryColor = "#ffffff"}
2046
- __init37() {this.mixerLoad = _chunkHUFQD4CBcjs.EMPTY_MIXER_LOAD}
2047
- __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 } }}
2048
2490
 
2049
2491
 
2050
- __init39() {this.listeners = /* @__PURE__ */ new Set()}
2051
- __init40() {this.version = 0}
2052
- __init41() {this.cached = null}
2053
- __init42() {this.session = null}
2054
- __init43() {this.live = null}
2055
- __init44() {this.liveLayers = null}
2056
- __init45() {this.dirty = "all"}
2057
- __init46() {this.seedCounter = 1}
2058
- __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}
2059
2501
 
2060
- __init48() {this.swatches = DEFAULT_SWATCHES.map((s) => ({ ...s }))}
2061
- __init49() {this.pad = new MixingPad()}
2062
- __init50() {this.tracingVersion = 1}
2063
- __init51() {this.tracingCache = null}
2064
- __init52() {this.imagePixels = /* @__PURE__ */ new Map()}
2065
- __init53() {this.nextReferenceId = 1}
2066
- __init54() {this.viewCompositor = createPaintCompositor()}
2067
- __init55() {this.exportCompositor = createPaintCompositor()}
2068
- 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);
2069
2513
  this.historyLimit = Math.max(1, _nullishCoalesce(options.historyLimit, () => ( 200)));
2070
2514
  this.doc = createPaintDocument({ width: options.width, height: options.height, dpi: options.dpi, paper: this.resolvePaper(options.paper) });
2071
2515
  this.userBrushes = (_nullishCoalesce(options.brushes, () => ( []))).map((b) => normalizePaintBrush({ ...b, builtin: false }));
@@ -2096,7 +2540,7 @@ var PaintEditorController = (_class9 = class {
2096
2540
  this.cached = {
2097
2541
  document: this.doc,
2098
2542
  brush: this.brush,
2099
- brushes: [...BUILTIN_BRUSHES, ...this.userBrushes],
2543
+ brushes: this.library ??= [...BUILTIN_BRUSHES, ...this.userBrushes],
2100
2544
  color: this.color,
2101
2545
  secondaryColor: this.secondaryColor,
2102
2546
  mixerLoad: this.mixerLoad,
@@ -2104,11 +2548,12 @@ var PaintEditorController = (_class9 = class {
2104
2548
  hand: this.hand,
2105
2549
  canUndo: this.undoStack.length > 0 && !this.session,
2106
2550
  canRedo: this.redoStack.length > 0 && !this.session,
2107
- undoLabel: _nullishCoalesce(_optionalChain([last, 'optionalAccess', _33 => _33.label]), () => ( null)),
2108
- 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)),
2109
2553
  guides: this.guides,
2110
2554
  swatches: this.swatches,
2111
2555
  mixingPadVersion: this.pad.version,
2556
+ savedMixingPads: this.savedPads.map(({ id, name }) => ({ id, name })),
2112
2557
  stroking: Boolean(this.session),
2113
2558
  version: this.version
2114
2559
  };
@@ -2213,7 +2658,7 @@ var PaintEditorController = (_class9 = class {
2213
2658
  }
2214
2659
  // --- Layers --------------------------------------------------------------------
2215
2660
  getLayer(id) {
2216
- return _optionalChain([_chunkT3QZ6S47cjs.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]);
2217
2662
  }
2218
2663
  /** The active pixel layer (or `undefined`). */
2219
2664
  getActiveLayer() {
@@ -2232,12 +2677,12 @@ var PaintEditorController = (_class9 = class {
2232
2677
  walk(this.doc.layers, null, 0);
2233
2678
  return out;
2234
2679
  }
2235
- describeLayer(l, parentId = _nullishCoalesce(_optionalChain([_chunkT3QZ6S47cjs.findLayer.call(void 0, this.doc.layers, l.id), 'optionalAccess', _36 => _36.parent, 'optionalAccess', _37 => _37.id]), () => ( null)), depth = _nullishCoalesce(_optionalChain([_chunkT3QZ6S47cjs.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))) {
2236
2681
  let bounds = null;
2237
2682
  if (l.type === "pixel") {
2238
- const b = _chunkSZ6KMZNMcjs.contentBounds.call(void 0, l.surface);
2683
+ const b = _chunkXIOMMGPDcjs.contentBounds.call(void 0, l.surface);
2239
2684
  bounds = b ? { ...b, x: b.x + l.offset.x, y: b.y + l.offset.y } : null;
2240
- } else for (const c of _chunkT3QZ6S47cjs.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);
2241
2686
  return {
2242
2687
  id: l.id,
2243
2688
  name: l.name,
@@ -2264,7 +2709,7 @@ var PaintEditorController = (_class9 = class {
2264
2709
  if (!id) throw new PaintEditorError(`There is no active layer to ${what}; add one with add_layer.`);
2265
2710
  const l = this.requireLayer(id);
2266
2711
  if (!isPixelLayer(l)) throw new PaintEditorError(`Layer "${l.name}" is a group; ${what} a pixel layer inside it.`);
2267
- if (_chunkT3QZ6S47cjs.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.`);
2268
2713
  if (!l.visible) throw new PaintEditorError(`Layer "${l.name}" is hidden; show it (update_layer visible true) before you ${what} it.`);
2269
2714
  return l;
2270
2715
  }
@@ -2277,9 +2722,9 @@ var PaintEditorController = (_class9 = class {
2277
2722
  /** Adds an empty pixel layer or group above `aboveId` (default: the active layer) and makes it active. */
2278
2723
  addLayer(options = {}) {
2279
2724
  this.requireIdle("adding a layer");
2280
- const count = _chunkT3QZ6S47cjs.flattenLayers.call(void 0, this.doc.layers).length;
2725
+ const count = _chunkPOGBUYWEcjs.flattenLayers.call(void 0, this.doc.layers).length;
2281
2726
  const type = _nullishCoalesce(options.type, () => ( "pixel"));
2282
- 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}`);
2283
2728
  const layer = type === "group" ? { type: "group", id: newPaintLayerId(), name, visible: true, opacity: 1, blend: "pass-through", children: [] } : createPaintLayer(name);
2284
2729
  let parentId = null;
2285
2730
  let index;
@@ -2289,21 +2734,21 @@ var PaintEditorController = (_class9 = class {
2289
2734
  if (g.type !== "group") throw new PaintEditorError(`Layer "${g.name}" is not a group; pass a group id as parentId.`);
2290
2735
  parentId = g.id;
2291
2736
  } else if (above) {
2292
- const loc = _chunkT3QZ6S47cjs.findLayer.call(void 0, this.doc.layers, above);
2737
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, above);
2293
2738
  if (!loc) throw new PaintEditorError(`Unknown layer "${above}". Call list_layers for the ids.`);
2294
- 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));
2295
2740
  index = loc.index + 1;
2296
2741
  }
2297
- const layers = _chunkT3QZ6S47cjs.insertLayer.call(void 0, this.doc.layers, layer, parentId, index);
2742
+ const layers = _chunkPOGBUYWEcjs.insertLayer.call(void 0, this.doc.layers, layer, parentId, index);
2298
2743
  this.commit(type === "group" ? "Add group" : "Add layer", { ...this.doc, layers, activeLayerId: layer.id });
2299
2744
  return this.describeLayer(layer);
2300
2745
  }
2301
2746
  updateLayer(id, update) {
2302
2747
  this.requireIdle("changing a layer");
2303
2748
  const l = this.requireLayer(id);
2304
- if (_chunkT3QZ6S47cjs.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}).`);
2305
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.`);
2306
- 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")) {
2307
2752
  throw new PaintEditorError(`Unknown blend mode "${update.blend}". Call get_canvas_info for the blend modes.`);
2308
2753
  }
2309
2754
  if (update.name !== void 0 && !update.name.trim()) throw new PaintEditorError("name is empty; give the layer a name.");
@@ -2317,66 +2762,66 @@ var PaintEditorController = (_class9 = class {
2317
2762
  const locks = { ..._nullishCoalesce(l.locks, () => ( {})), ...update.locks };
2318
2763
  next.locks = Object.values(locks).some(Boolean) ? locks : void 0;
2319
2764
  }
2320
- this.commit("Layer properties", { ...this.doc, layers: _chunkT3QZ6S47cjs.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) });
2321
2766
  return this.describeLayer(next);
2322
2767
  }
2323
2768
  removeLayer(id) {
2324
2769
  this.requireIdle("removing a layer");
2325
2770
  const l = this.requireLayer(id);
2326
- if (_chunkT3QZ6S47cjs.isLayerLocked.call(void 0, l, "all")) throw new PaintEditorError(`Layer "${l.name}" is fully locked; unlock it before removing it.`);
2327
- const layers = _chunkT3QZ6S47cjs.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);
2328
2773
  let active = this.doc.activeLayerId;
2329
- if (active && !_chunkT3QZ6S47cjs.findLayer.call(void 0, layers, active)) active = _nullishCoalesce(_optionalChain([_chunkT3QZ6S47cjs.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));
2330
2775
  this.commit("Remove layer", { ...this.doc, layers, activeLayerId: active });
2331
2776
  return { removed: id, activeLayerId: active };
2332
2777
  }
2333
2778
  duplicateLayer(id) {
2334
2779
  this.requireIdle("duplicating a layer");
2335
- const loc = _chunkT3QZ6S47cjs.findLayer.call(void 0, this.doc.layers, id);
2780
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id);
2336
2781
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2337
2782
  const copy = (l) => l.type === "group" ? { ...l, id: newPaintLayerId(), children: l.children.map(copy) } : { ...l, id: newPaintLayerId() };
2338
2783
  const dup = { ...copy(loc.layer), name: `${loc.layer.name} copy` };
2339
- const layers = _chunkT3QZ6S47cjs.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);
2340
2785
  this.commit("Duplicate layer", { ...this.doc, layers, activeLayerId: dup.id });
2341
2786
  return this.describeLayer(dup);
2342
2787
  }
2343
2788
  /** Moves a layer to `index` (0 = bottom) within `parentId` (default its own parent; `null` = top level). */
2344
2789
  moveLayer(id, index, parentId) {
2345
2790
  this.requireIdle("moving a layer");
2346
- const loc = _chunkT3QZ6S47cjs.findLayer.call(void 0, this.doc.layers, id);
2791
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id);
2347
2792
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2348
- 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;
2349
2794
  if (target) {
2350
2795
  const g = this.requireLayer(target);
2351
2796
  if (g.type !== "group") throw new PaintEditorError(`Layer "${g.name}" is not a group; pass a group id as parentId.`);
2352
- if (target === id || loc.layer.type === "group" && _chunkT3QZ6S47cjs.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.");
2353
2798
  }
2354
- const without = _chunkT3QZ6S47cjs.mapLayer.call(void 0, this.doc.layers, id, () => null);
2355
- const siblings = target ? _chunkT3QZ6S47cjs.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;
2356
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).`);
2357
- const layers = _chunkT3QZ6S47cjs.insertLayer.call(void 0, without, loc.layer, target, index);
2802
+ const layers = _chunkPOGBUYWEcjs.insertLayer.call(void 0, without, loc.layer, target, index);
2358
2803
  this.commit("Move layer", { ...this.doc, layers });
2359
2804
  return this.describeLayer(loc.layer);
2360
2805
  }
2361
2806
  /** Merges a pixel layer into the pixel layer below it (keeping the lower one's settings). */
2362
2807
  mergeDown(id) {
2363
2808
  this.requireIdle("merging");
2364
- const loc = _chunkT3QZ6S47cjs.findLayer.call(void 0, this.doc.layers, id);
2809
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, this.doc.layers, id);
2365
2810
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2366
2811
  const siblings = loc.parent ? loc.parent.children : this.doc.layers;
2367
2812
  const below = siblings[loc.index - 1];
2368
2813
  const upper = loc.layer;
2369
2814
  if (!isPixelLayer(upper) || !isPixelLayer(below)) throw new PaintEditorError("Merge down needs a pixel layer directly above another pixel layer in the same group.");
2370
- if (_chunkT3QZ6S47cjs.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.`);
2371
2816
  const rect = unionRect(this.describeLayer(below).bounds, this.describeLayer(upper).bounds);
2372
- let surface = _chunkSZ6KMZNMcjs.emptySurface.call(void 0, 4);
2817
+ let surface = _chunkXIOMMGPDcjs.emptySurface.call(void 0, 4);
2373
2818
  if (rect) {
2374
2819
  const base = { ...below, opacity: 1, blend: "normal", visible: true, clipped: void 0, mask: void 0 };
2375
2820
  const px = createPaintCompositor().composite([base, upper.visible ? upper : { ...upper, opacity: 0 }], rect, this.sources());
2376
- 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);
2377
2822
  }
2378
2823
  const merged = { ...below, surface, offset: { x: 0, y: 0 } };
2379
- const layers = _chunkT3QZ6S47cjs.mapLayer.call(void 0, _chunkT3QZ6S47cjs.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);
2380
2825
  this.commit("Merge down", { ...this.doc, layers, activeLayerId: below.id });
2381
2826
  return this.describeLayer(merged);
2382
2827
  }
@@ -2385,7 +2830,7 @@ var PaintEditorController = (_class9 = class {
2385
2830
  this.requireIdle("clearing");
2386
2831
  const l = this.paintTarget(layerId, "clear");
2387
2832
  const bounds = this.describeLayer(l).bounds;
2388
- this.commit("Clear layer", { ...this.doc, layers: _chunkT3QZ6S47cjs.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) })) });
2389
2834
  return { layerId: l.id, bounds };
2390
2835
  }
2391
2836
  /**
@@ -2402,14 +2847,14 @@ var PaintEditorController = (_class9 = class {
2402
2847
  if (!(grainAmount >= 0 && grainAmount <= 1)) throw new PaintEditorError(`grain ${grainAmount} is out of range; use 0..1.`);
2403
2848
  const pressure = _nullishCoalesce(options.pressure, () => ( 0.5));
2404
2849
  const blend = _nullishCoalesce(options.blend, () => ( "normal"));
2405
- 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}".`);
2406
2851
  const { width, height } = this.doc.canvas;
2407
2852
  const rect = { x: 0, y: 0, width, height };
2408
- const coverage = new (0, _chunkHUFQD4CBcjs.CoverageMap)();
2853
+ const coverage = new (0, _chunk7VOHOPLWcjs.CoverageMap)();
2409
2854
  const depth = grainAmount * this.doc.paper.depth;
2410
2855
  const grain = depth > 0 ? paperGrain(this.doc.paper) : null;
2411
- for (const { tx, ty } of _chunkSZ6KMZNMcjs.tilesInRect.call(void 0, rect)) {
2412
- 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) };
2413
2858
  const vals = new Float32Array(r.width * r.height);
2414
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;
2415
2860
  coverage.accumulate(r, vals, 1);
@@ -2420,25 +2865,25 @@ var PaintEditorController = (_class9 = class {
2420
2865
  background: rgbaOf(this.doc.paper.color),
2421
2866
  opacity,
2422
2867
  blend,
2423
- lockTransparency: _chunkT3QZ6S47cjs.isLayerLocked.call(void 0, l, "transparency"),
2868
+ lockTransparency: _chunkPOGBUYWEcjs.isLayerLocked.call(void 0, l, "transparency"),
2424
2869
  selection: null,
2425
2870
  offset: l.offset
2426
2871
  };
2427
2872
  const surface = this.applyCoverage(l, coverage, rect, apply);
2428
- this.commit("Fill layer", { ...this.doc, layers: _chunkT3QZ6S47cjs.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 })) });
2429
2874
  return { layerId: l.id, bounds: rect };
2430
2875
  }
2431
2876
  applyCoverage(l, coverage, rect, apply) {
2432
2877
  const base = l.surface;
2433
2878
  const tiles = new Map(base.tiles);
2434
- 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 })) {
2435
- const key = _chunkSZ6KMZNMcjs.tileKey.call(void 0, tx, ty);
2436
- 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)));
2437
- 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);
2438
2883
  if (tile) tiles.set(key, tile);
2439
2884
  else tiles.delete(key);
2440
2885
  }
2441
- return new (0, _chunkSZ6KMZNMcjs.TiledSurface)(4, tiles, 0);
2886
+ return new (0, _chunkXIOMMGPDcjs.TiledSurface)(4, tiles, 0);
2442
2887
  }
2443
2888
  // --- Brushes -------------------------------------------------------------------
2444
2889
  findBrush(id) {
@@ -2478,6 +2923,7 @@ var PaintEditorController = (_class9 = class {
2478
2923
  const i = this.userBrushes.findIndex((b) => b.id === options.id);
2479
2924
  if (i >= 0) this.userBrushes[i] = saved;
2480
2925
  else this.userBrushes.push(saved);
2926
+ this.library = null;
2481
2927
  this.emit([]);
2482
2928
  return saved;
2483
2929
  }
@@ -2486,6 +2932,7 @@ var PaintEditorController = (_class9 = class {
2486
2932
  const i = this.userBrushes.findIndex((b) => b.id === id);
2487
2933
  if (i < 0) throw new PaintEditorError(`Unknown brush "${id}". Call list_brushes for the ids.`);
2488
2934
  this.userBrushes.splice(i, 1);
2935
+ this.library = null;
2489
2936
  this.emit([]);
2490
2937
  }
2491
2938
  /** The user's saved brushes. */
@@ -2526,7 +2973,10 @@ var PaintEditorController = (_class9 = class {
2526
2973
  else this.userBrushes.push(b);
2527
2974
  added.push(b.id);
2528
2975
  }
2529
- if (added.length) this.emit([]);
2976
+ if (added.length) {
2977
+ this.library = null;
2978
+ this.emit([]);
2979
+ }
2530
2980
  return { added, skipped, problems };
2531
2981
  }
2532
2982
  /** A thumbnail of a brush: a test stroke on the current paper. */
@@ -2558,7 +3008,7 @@ var PaintEditorController = (_class9 = class {
2558
3008
  if (options.layerId !== void 0) {
2559
3009
  const l = this.requireLayer(options.layerId);
2560
3010
  if (!isPixelLayer(l)) throw new PaintEditorError(`Layer "${l.name}" is a group; pick from a pixel layer or the composite.`);
2561
- 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);
2562
3012
  return { color: rgbToHex(r, g, b), alpha: Math.round(a / 255 * 1e3) / 1e3 };
2563
3013
  }
2564
3014
  const d = this.exportCompositor.composite(this.renderLayers({}), { x: px, y: py, width: 1, height: 1 }, this.sources());
@@ -2568,13 +3018,13 @@ var PaintEditorController = (_class9 = class {
2568
3018
  /** Fills the Mixer Brush reservoir with a colour (default the editor colour). */
2569
3019
  loadMixerBrush(color) {
2570
3020
  const [r, g, b] = hexToRgb8(color === void 0 ? this.color : parseColor(color));
2571
- 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]);
2572
3022
  this.emit([]);
2573
3023
  return this.mixerLoad;
2574
3024
  }
2575
3025
  /** Empties the pickup well (and with `emptyReservoir` the loaded paint too). */
2576
3026
  cleanMixerBrush(emptyReservoir = false) {
2577
- 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);
2578
3028
  this.emit([]);
2579
3029
  return this.mixerLoad;
2580
3030
  }
@@ -2600,6 +3050,14 @@ var PaintEditorController = (_class9 = class {
2600
3050
  if (init.brush) brush = normalizePaintBrush({ ...init.brush, id: brush.id }, brush);
2601
3051
  return brush;
2602
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
+ }
2603
3061
  /** Extra copies every new stroke paints (the painting guides add mirror and kaleidoscope copies). */
2604
3062
  strokeCopies() {
2605
3063
  return symmetryCopies(this.guides.symmetry);
@@ -2608,18 +3066,18 @@ var PaintEditorController = (_class9 = class {
2608
3066
  const layer = (() => {
2609
3067
  const id = _nullishCoalesce(init.layerId, () => ( doc.activeLayerId));
2610
3068
  if (!id) throw new PaintEditorError("There is no active layer to paint on; add one with add_layer.");
2611
- const loc = _chunkT3QZ6S47cjs.findLayer.call(void 0, doc.layers, id);
3069
+ const loc = _chunkPOGBUYWEcjs.findLayer.call(void 0, doc.layers, id);
2612
3070
  if (!loc) throw new PaintEditorError(`Unknown layer "${id}". Call list_layers for the ids.`);
2613
3071
  const l = loc.layer;
2614
3072
  if (!isPixelLayer(l)) throw new PaintEditorError(`Layer "${l.name}" is a group; paint on a pixel layer inside it.`);
2615
- if (_chunkT3QZ6S47cjs.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.`);
2616
3074
  if (!l.visible) throw new PaintEditorError(`Layer "${l.name}" is hidden; show it (update_layer visible true) to paint on it.`);
2617
3075
  return l;
2618
3076
  })();
2619
3077
  const brush = this.strokeBrush(init);
2620
3078
  let color = init.color === void 0 ? this.color : parseColor(init.color);
2621
3079
  const tracing = this.guides.tracing;
2622
- 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));
2623
3081
  const perspective = this.guides.perspective;
2624
3082
  const snapper = init.snapToPerspective && perspective.enabled && perspective.snap ? new PerspectiveSnapper(perspective) : null;
2625
3083
  const rgba = rgbaOf(color);
@@ -2629,7 +3087,7 @@ var PaintEditorController = (_class9 = class {
2629
3087
  layerId: layer.id,
2630
3088
  surface: layer.surface,
2631
3089
  offset: layer.offset,
2632
- lockTransparency: _chunkT3QZ6S47cjs.isLayerLocked.call(void 0, layer, "transparency"),
3090
+ lockTransparency: _chunkPOGBUYWEcjs.isLayerLocked.call(void 0, layer, "transparency"),
2633
3091
  brush,
2634
3092
  color: rgba,
2635
3093
  background: rgbaOf(doc.paper.color),
@@ -2640,7 +3098,8 @@ var PaintEditorController = (_class9 = class {
2640
3098
  affineCopies: copies,
2641
3099
  copies: [IDENTITY_AFFINE, ...copies].map(affineCopy),
2642
3100
  inputFilter: snapper ? (p) => snapper.snap(p) : void 0,
2643
- 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)
2644
3103
  });
2645
3104
  return session;
2646
3105
  }
@@ -2655,6 +3114,7 @@ var PaintEditorController = (_class9 = class {
2655
3114
  seed: init.seed
2656
3115
  };
2657
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));
2658
3118
  return stroke;
2659
3119
  }
2660
3120
  /**
@@ -2673,7 +3133,7 @@ var PaintEditorController = (_class9 = class {
2673
3133
  this.live = null;
2674
3134
  this.liveLayers = null;
2675
3135
  const brush = s.init.brush;
2676
- 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);
2677
3137
  if (cancelled || !surface) {
2678
3138
  this.emit();
2679
3139
  return;
@@ -2681,7 +3141,7 @@ var PaintEditorController = (_class9 = class {
2681
3141
  const stroke = this.record(s);
2682
3142
  s.result.strokeId = stroke.id;
2683
3143
  const l = this.getLayer(s.layerId);
2684
- this.commit(`${brush.name} stroke`, { ...this.doc, layers: _chunkT3QZ6S47cjs.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] });
2685
3145
  }
2686
3146
  };
2687
3147
  const session = this.sessionFor(this.doc, { snapToPerspective: true, ...init }, host);
@@ -2703,8 +3163,8 @@ var PaintEditorController = (_class9 = class {
2703
3163
  if (cancelled || !surface) return;
2704
3164
  const stroke = this.record(s);
2705
3165
  s.result.strokeId = stroke.id;
2706
- const l = _chunkT3QZ6S47cjs.findLayer.call(void 0, out.layers, s.layerId).layer;
2707
- out = { ...out, layers: _chunkT3QZ6S47cjs.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] };
2708
3168
  }
2709
3169
  };
2710
3170
  const session = this.sessionFor(doc, init, host);
@@ -2717,7 +3177,7 @@ var PaintEditorController = (_class9 = class {
2717
3177
  this.requireIdle("painting");
2718
3178
  const points = this.checkPoints(init.points);
2719
3179
  const { doc, result, brush } = this.runStroke(this.doc, { ...init, start: _nullishCoalesce(init.start, () => ( points[0])) }, points);
2720
- if (result.mixerLoad && brush.loadsColor) this.mixerLoad = result.mixerLoad;
3180
+ if (result.mixerLoad) this.keepLoad(brush, result.mixerLoad);
2721
3181
  if (doc !== this.doc) this.commit(`${brush.name} stroke`, doc);
2722
3182
  else this.emit([]);
2723
3183
  return result;
@@ -2743,7 +3203,7 @@ var PaintEditorController = (_class9 = class {
2743
3203
  */
2744
3204
  replayStrokes(options) {
2745
3205
  this.requireIdle("replaying");
2746
- 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.");
2747
3207
  const strokes = options.strokeIds.map((id) => {
2748
3208
  const s = this.doc.strokes.find((x) => x.id === id);
2749
3209
  if (!s) throw new PaintEditorError(`Unknown stroke "${id}". Call list_strokes for the ids.`);
@@ -2776,8 +3236,13 @@ var PaintEditorController = (_class9 = class {
2776
3236
  brush = normalizePaintBrush({ tip: { ...b.tip, size: b.tip.size * (v.brush.tip.size / s.brush.tip.size) } }, b);
2777
3237
  }
2778
3238
  const layerId = _nullishCoalesce(options.layerId, () => ( s.layerId));
2779
- if (!_chunkT3QZ6S47cjs.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.`);
2780
- 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);
2781
3246
  doc = run.doc;
2782
3247
  bounds = unionRect(bounds, run.result.bounds);
2783
3248
  if (run.result.strokeId) ids.push(run.result.strokeId);
@@ -2795,8 +3260,8 @@ var PaintEditorController = (_class9 = class {
2795
3260
  currentLayers() {
2796
3261
  const live = this.live;
2797
3262
  if (!live) return this.doc.layers;
2798
- if (_optionalChain([this, 'access', _63 => _63.liveLayers, 'optionalAccess', _64 => _64.key]) === live.surface) return this.liveLayers.layers;
2799
- const layers = _chunkT3QZ6S47cjs.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 }));
2800
3265
  this.liveLayers = { key: live.surface, layers };
2801
3266
  return layers;
2802
3267
  }
@@ -2810,7 +3275,7 @@ var PaintEditorController = (_class9 = class {
2810
3275
  tracingNode(t) {
2811
3276
  const { width, height } = this.doc.canvas;
2812
3277
  const key = this.tracingVersion * 100003 + width * 7 + height;
2813
- 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;
2814
3279
  const src = _nullishCoalesce(this.imagePixels.get(t.src), () => ( this.decodeImage(t.src)));
2815
3280
  const image = resampleInto(src, t.rect, width, height);
2816
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 };
@@ -2835,7 +3300,7 @@ var PaintEditorController = (_class9 = class {
2835
3300
  */
2836
3301
  compositeTiles(rect, onTile, options = {}) {
2837
3302
  const dirty = options.dirty === "all" ? void 0 : options.dirty;
2838
- 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)) });
2839
3304
  }
2840
3305
  /** The canvas as canvas-sized RGBA (paper included by default). */
2841
3306
  compositePixels(options = {}) {
@@ -2856,20 +3321,21 @@ var PaintEditorController = (_class9 = class {
2856
3321
  // --- Save, load and export -------------------------------------------------------
2857
3322
  /** The document as JSON with inline PNG assets. */
2858
3323
  toJSON(options = {}) {
2859
- 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() });
2860
3325
  }
2861
3326
  /** The document as JSON with each PNG stored in `store`. */
2862
3327
  toJSONExternal(store, options = {}) {
2863
- 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() });
2864
3329
  }
2865
3330
  adopt(loaded) {
2866
3331
  this.extensions = loaded.extensions;
3332
+ this.readMixingPad(loaded.mixingPad, loaded.problems);
2867
3333
  this.readGuides(loaded.guides, loaded.state, loaded.problems);
2868
3334
  if (Array.isArray(loaded.swatches)) {
2869
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() });
2870
3336
  }
2871
3337
  for (const s of loaded.state.strokes) {
2872
- 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)));
2873
3339
  if (n >= this.strokeCounter) this.strokeCounter = n + 1;
2874
3340
  }
2875
3341
  this.undoStack = [];
@@ -2900,7 +3366,7 @@ var PaintEditorController = (_class9 = class {
2900
3366
  /** The canvas as PNG bytes (paper included by default; `scale` below 1 shrinks it). */
2901
3367
  exportPng(options = {}) {
2902
3368
  const img = this.scaled(options);
2903
- return _chunkT3QZ6S47cjs.encodePng.call(void 0, img.width, img.height, img.data);
3369
+ return _chunkPOGBUYWEcjs.encodePng.call(void 0, img.width, img.height, img.data);
2904
3370
  }
2905
3371
  scaled(options) {
2906
3372
  const full = this.compositePixels(options);
@@ -2942,22 +3408,22 @@ var PaintEditorController = (_class9 = class {
2942
3408
  opacity: 1,
2943
3409
  blend: "normal",
2944
3410
  locks: { all: true },
2945
- surface: _chunkSZ6KMZNMcjs.surfaceFromPixels.call(void 0, paperPixels, 4),
3411
+ surface: _chunkXIOMMGPDcjs.surfaceFromPixels.call(void 0, paperPixels, 4),
2946
3412
  offset: { x: 0, y: 0 }
2947
3413
  };
2948
3414
  const merged = this.compositePixels({ paper: true }).data;
2949
3415
  const changed = /* @__PURE__ */ new Map();
2950
3416
  const toStandard = (layers2) => layers2.map((l) => {
2951
- 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);
2952
3418
  if (blend !== l.blend) changed.set(l.blend, [..._nullishCoalesce(changed.get(l.blend), () => ( [])), l.name]);
2953
3419
  const next = blend === l.blend ? l : { ...l, blend };
2954
3420
  return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
2955
3421
  });
2956
3422
  const layers = toStandard(this.doc.layers);
2957
- const result = _chunkFRD3FUXIcjs.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 });
2958
3424
  for (const [mode, names] of changed) {
2959
- const label = _nullishCoalesce(_optionalChain([_chunkSZ6KMZNMcjs.BLEND_MODES, 'access', _72 => _72.find, 'call', _73 => _73((m) => m.id === mode), 'optionalAccess', _74 => _74.label]), () => ( mode));
2960
- 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)));
2961
3427
  const list = names.map((n) => `"${n}"`).join(", ");
2962
3428
  const one = names.length === 1;
2963
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).`);
@@ -2967,7 +3433,7 @@ var PaintEditorController = (_class9 = class {
2967
3433
  /** `render_preview` as a base64 PNG. */
2968
3434
  previewPngBase64(options = {}) {
2969
3435
  const img = this.renderPreview(options);
2970
- return { data: _chunkT3QZ6S47cjs.bytesToBase64.call(void 0, _chunkT3QZ6S47cjs.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 };
2971
3437
  }
2972
3438
  // --- Guides --------------------------------------------------------------------
2973
3439
  setGuides(next) {
@@ -3005,17 +3471,17 @@ var PaintEditorController = (_class9 = class {
3005
3471
  pixels = this.decodeImage(input.png.trim().startsWith("data:") ? input.png.trim() : `data:image/png;base64,${input.png.trim()}`, what);
3006
3472
  } else throw new PaintEditorError(`${what}: pass png (a PNG as base64 or a data URL) or pixels.`);
3007
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.`);
3008
- const src = _chunkT3QZ6S47cjs.pngDataUrl.call(void 0, pixels.width, pixels.height, pixels.data);
3474
+ const src = _chunkPOGBUYWEcjs.pngDataUrl.call(void 0, pixels.width, pixels.height, pixels.data);
3009
3475
  this.imagePixels.set(src, pixels);
3010
3476
  return { src, pixels };
3011
3477
  }
3012
3478
  decodeImage(src, what = "Image") {
3013
3479
  const cached = this.imagePixels.get(src);
3014
3480
  if (cached) return cached;
3015
- const parsed = _chunkT3QZ6S47cjs.parseDataUrl.call(void 0, src);
3481
+ const parsed = _chunkPOGBUYWEcjs.parseDataUrl.call(void 0, src);
3016
3482
  if (!parsed) throw new PaintEditorError(`${what} is not a data URL or base64 PNG.`);
3017
3483
  try {
3018
- const png = _chunkT3QZ6S47cjs.decodePng.call(void 0, parsed.bytes);
3484
+ const png = _chunkPOGBUYWEcjs.decodePng.call(void 0, parsed.bytes);
3019
3485
  const px = { width: png.width, height: png.height, data: png.data };
3020
3486
  this.imagePixels.set(src, px);
3021
3487
  return px;
@@ -3073,7 +3539,7 @@ var PaintEditorController = (_class9 = class {
3073
3539
  const { src, pixels } = this.importImage(options, "The reference image");
3074
3540
  const ref = {
3075
3541
  id: `ref${this.nextReferenceId++}`,
3076
- 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}`,
3077
3543
  src,
3078
3544
  width: pixels.width,
3079
3545
  height: pixels.height,
@@ -3152,7 +3618,7 @@ var PaintEditorController = (_class9 = class {
3152
3618
  try {
3153
3619
  const px = this.decodeImage(ref.src, `Reference "${ref.name}"`);
3154
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 });
3155
- 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)));
3156
3622
  if (n >= this.nextReferenceId) this.nextReferenceId = n + 1;
3157
3623
  } catch (e) {
3158
3624
  problems.push(e.message);
@@ -3172,7 +3638,7 @@ var PaintEditorController = (_class9 = class {
3172
3638
  }
3173
3639
  addSwatch(color, name) {
3174
3640
  const c = parseColor(color);
3175
- 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 }];
3176
3642
  this.emit([]);
3177
3643
  return this.swatches;
3178
3644
  }
@@ -3218,12 +3684,105 @@ var PaintEditorController = (_class9 = class {
3218
3684
  this.pad.clear();
3219
3685
  this.emit([]);
3220
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
+ }
3221
3780
  /** A layer's tile surface (live while stroking), for thumbnails. */
3222
3781
  layerSurface(id) {
3223
- const l = _optionalChain([_chunkT3QZ6S47cjs.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]);
3224
3783
  return isPixelLayer(l) ? l.surface : null;
3225
3784
  }
3226
- }, _class9);
3785
+ }, _class10);
3227
3786
  var MAX_IMAGE_SIDE = 8192;
3228
3787
  function resampleInto(src, rect, width, height) {
3229
3788
  const out = new Uint8ClampedArray(width * height * 4);
@@ -3274,8 +3833,8 @@ function strokeSchedule(points) {
3274
3833
  return total > 0 ? acc.map((d) => d / total) : points.map(() => 1);
3275
3834
  }
3276
3835
  function strokeDuration(points, max = 1800) {
3277
- const t0 = _optionalChain([points, 'access', _87 => _87[0], 'optionalAccess', _88 => _88.t]);
3278
- 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]);
3279
3838
  let ms;
3280
3839
  if (points.every((p) => Number.isFinite(p.t)) && t1 > t0) ms = t1 - t0;
3281
3840
  else {
@@ -3329,7 +3888,7 @@ var PaintDirector = class {
3329
3888
  }
3330
3889
  where(id) {
3331
3890
  const a = this.presence.get(id);
3332
- 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)) };
3333
3892
  }
3334
3893
  /** Glides to `to`; an actor that just appeared starts there instead of the corner. */
3335
3894
  async glide(id, to, ms = 380) {
@@ -3376,8 +3935,8 @@ var PaintDirector = class {
3376
3935
  }
3377
3936
  case "replay": {
3378
3937
  await this.say(id, command.note, 500);
3379
- 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)]));
3380
- 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]]);
3381
3940
  if (first) await this.glide(id, first);
3382
3941
  const result = c.replayStrokes(command.replay);
3383
3942
  if (result.bounds) await this.glide(id, centre(result.bounds), 520);
@@ -3675,7 +4234,7 @@ function createPaintGuideTools(c) {
3675
4234
  {
3676
4235
  name: "mixing_pad_stroke",
3677
4236
  title: "Paint on the mixing pad",
3678
- 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.",
3679
4238
  inputSchema: {
3680
4239
  type: "object",
3681
4240
  properties: {
@@ -3730,6 +4289,73 @@ function createPaintGuideTools(c) {
3730
4289
  return { cleared: true };
3731
4290
  }
3732
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
+ },
3733
4359
  {
3734
4360
  name: "render_mixing_pad",
3735
4361
  title: "Render the mixing pad",
@@ -3740,7 +4366,7 @@ function createPaintGuideTools(c) {
3740
4366
  const pad = c.mixingPad;
3741
4367
  return {
3742
4368
  content: [
3743
- { type: "image", data: _chunkT3QZ6S47cjs.bytesToBase64.call(void 0, _chunkT3QZ6S47cjs.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" },
3744
4370
  { type: "text", text: `Mixing pad ${pad.width}\xD7${pad.height}` }
3745
4371
  ]
3746
4372
  };
@@ -3761,7 +4387,7 @@ var TIP = {
3761
4387
  type: "object",
3762
4388
  description: "The tip (the shared brush engine). Missing fields keep their current values.",
3763
4389
  properties: {
3764
- 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." },
3765
4391
  hardness: { type: "number", minimum: 0, maximum: 1, description: "Edge hardness, 0 (soft) .. 1 (hard)." },
3766
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)." },
3767
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." },
@@ -3773,7 +4399,7 @@ var TIP = {
3773
4399
  pressureOpacity: { type: "boolean", description: "Pressure scales the opacity." },
3774
4400
  blend: {
3775
4401
  type: "string",
3776
- enum: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id),
4402
+ enum: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id),
3777
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)."
3778
4404
  }
3779
4405
  },
@@ -3799,28 +4425,79 @@ var DYNAMICS = {
3799
4425
  };
3800
4426
  var MIXER = {
3801
4427
  type: "object",
3802
- 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.",
3803
4429
  properties: {
3804
- 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." },
3805
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." },
3806
- 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." },
3807
4433
  loadEachStroke: { type: "boolean", description: "Refill the reservoir with the colour before each stroke." },
3808
4434
  cleanEachStroke: { type: "boolean", description: "Empty the pickup well after each stroke." },
3809
- 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." }
3810
4480
  },
3811
4481
  additionalProperties: false
3812
4482
  };
3813
4483
  var BRUSH_FIELDS = {
3814
4484
  name: { type: "string", minLength: 1, maxLength: 80, description: "Display name." },
3815
4485
  category: { type: "string", enum: [...PAINT_BRUSH_CATEGORIES], description: "Library category." },
3816
- 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
+ },
3817
4491
  tip: TIP,
3818
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." },
3819
4493
  pressureSmoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Pressure smoothing, separate from the tip position smoothing." },
3820
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)." },
3821
4495
  dynamics: DYNAMICS,
3822
4496
  mixer: MIXER,
3823
- 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
3824
4501
  };
3825
4502
  var POINTS = {
3826
4503
  type: "array",
@@ -3849,7 +4526,20 @@ function loadResult(load) {
3849
4526
  return { reservoir: hex(load.reservoir), amount: Math.round(load.amount * 1e3) / 1e3, pickup: hex(load.pickup) };
3850
4527
  }
3851
4528
  function brushSummary(b) {
3852
- 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
+ };
3853
4543
  }
3854
4544
  function createPaintEditorToolProvider(controller, options = {}) {
3855
4545
  const c = controller;
@@ -3885,7 +4575,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
3885
4575
  strokes: d.strokes.length,
3886
4576
  undo: s.undoLabel,
3887
4577
  redo: s.redoLabel,
3888
- blendModes: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id)
4578
+ blendModes: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id)
3889
4579
  };
3890
4580
  }
3891
4581
  });
@@ -4018,7 +4708,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4018
4708
  opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0..1." },
4019
4709
  blend: {
4020
4710
  type: "string",
4021
- enum: [..._chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => m.id), "pass-through"],
4711
+ enum: [..._chunkXIOMMGPDcjs.BLEND_MODES.map((m) => m.id), "pass-through"],
4022
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."
4023
4713
  },
4024
4714
  clipped: { type: "boolean", description: "Clip to the layer below (paint only shows where it has paint)." },
@@ -4047,12 +4737,12 @@ function createPaintEditorToolProvider(controller, options = {}) {
4047
4737
  inputSchema: EMPTY2,
4048
4738
  annotations: { title: "List blend modes", ...READ2 },
4049
4739
  handler: () => ({
4050
- modes: _chunkSZ6KMZNMcjs.BLEND_MODES.map((m) => ({
4740
+ modes: _chunkXIOMMGPDcjs.BLEND_MODES.map((m) => ({
4051
4741
  id: m.id,
4052
4742
  label: m.label,
4053
4743
  group: m.painterly ? "painterly" : m.id === "pigment" ? "pigment" : "standard",
4054
4744
  ...m.description ? { description: m.description } : {},
4055
- psd: _chunkSZ6KMZNMcjs.standardBlendMode.call(void 0, m.id)
4745
+ psd: _chunkXIOMMGPDcjs.standardBlendMode.call(void 0, m.id)
4056
4746
  }))
4057
4747
  })
4058
4748
  });
@@ -4120,7 +4810,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4120
4810
  opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 1)." },
4121
4811
  grain: { type: "number", minimum: 0, maximum: 1, description: "Paper grain response 0..1 (default 0: flat)." },
4122
4812
  pressure: { type: "number", minimum: 0, maximum: 1, description: "With grain: how far into the valleys, 0..1 (default 0.5)." },
4123
- 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)." }
4124
4814
  },
4125
4815
  additionalProperties: false
4126
4816
  },
@@ -4138,18 +4828,18 @@ function createPaintEditorToolProvider(controller, options = {}) {
4138
4828
  add({
4139
4829
  name: "list_brushes",
4140
4830
  title: "List brushes",
4141
- 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.",
4142
4832
  inputSchema: EMPTY2,
4143
4833
  annotations: { title: "List brushes", ...READ2 },
4144
4834
  handler: () => {
4145
4835
  const s = c.getState();
4146
- 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) };
4147
4837
  }
4148
4838
  });
4149
4839
  add({
4150
4840
  name: "get_brush",
4151
4841
  title: "Get a brush",
4152
- 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.",
4153
4843
  inputSchema: { type: "object", properties: { id: { type: "string", description: "Brush id from list_brushes (default the active brush)." } }, additionalProperties: false },
4154
4844
  annotations: { title: "Get a brush", ...READ2 },
4155
4845
  handler: ({ id }) => {
@@ -4241,7 +4931,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4241
4931
  const t = c.brushThumbnail(_nullishCoalesce(id, () => ( c.getState().brush.id)), { width, height, color });
4242
4932
  return {
4243
4933
  content: [
4244
- { type: "image", data: _chunkT3QZ6S47cjs.bytesToBase64.call(void 0, _chunkT3QZ6S47cjs.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" },
4245
4935
  { type: "text", text: `Brush ${_nullishCoalesce(id, () => ( c.getState().brush.id))} ${t.width}\xD7${t.height}` }
4246
4936
  ]
4247
4937
  };
@@ -4284,7 +4974,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4284
4974
  add({
4285
4975
  name: "get_mixer_brush",
4286
4976
  title: "Get the Mixer Brush load",
4287
- 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.",
4288
4978
  inputSchema: EMPTY2,
4289
4979
  annotations: { title: "Get the Mixer Brush load", ...READ2 },
4290
4980
  handler: () => loadResult(c.getState().mixerLoad)
@@ -4300,7 +4990,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4300
4990
  add({
4301
4991
  name: "clean_mixer_brush",
4302
4992
  title: "Clean the Mixer Brush",
4303
- 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.",
4304
4994
  inputSchema: { type: "object", properties: { emptyReservoir: { type: "boolean", description: "Also drop the loaded paint (default false)." } }, additionalProperties: false },
4305
4995
  annotations: { title: "Clean the Mixer Brush", ...SET2 },
4306
4996
  handler: ({ emptyReservoir }) => loadResult(c.cleanMixerBrush(emptyReservoir === true))
@@ -4308,7 +4998,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4308
4998
  add({
4309
4999
  name: "paint_stroke",
4310
5000
  title: "Paint a brush stroke",
4311
- 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.",
4312
5002
  inputSchema: {
4313
5003
  type: "object",
4314
5004
  properties: {
@@ -4363,7 +5053,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4363
5053
  color: s.color,
4364
5054
  points: ids ? s.points : s.points.length,
4365
5055
  seed: s.seed,
4366
- 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)),
4367
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) }
4368
5058
  };
4369
5059
  })
@@ -4496,7 +5186,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4496
5186
  annotations: { title: "Export the painting", ...READ2 },
4497
5187
  handler: async ({ format, quality, scale, paper }) => {
4498
5188
  const bytes = await c.exportImage({ format, quality, scale, paper });
4499
- return { mimeType: _nullishCoalesce(format, () => ( "image/png")), bytes: bytes.length, data: _chunkT3QZ6S47cjs.bytesToBase64.call(void 0, bytes) };
5189
+ return { mimeType: _nullishCoalesce(format, () => ( "image/png")), bytes: bytes.length, data: _chunkPOGBUYWEcjs.bytesToBase64.call(void 0, bytes) };
4500
5190
  }
4501
5191
  });
4502
5192
  add({
@@ -4507,7 +5197,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4507
5197
  annotations: { title: "Export as PSD", ...READ2 },
4508
5198
  handler: () => {
4509
5199
  const r = c.exportPsd();
4510
- return { data: _chunkT3QZ6S47cjs.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 };
4511
5201
  }
4512
5202
  });
4513
5203
  add({
@@ -4531,7 +5221,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
4531
5221
  }
4532
5222
  });
4533
5223
  tools.push(...createPaintGuideTools(c));
4534
- tools.push(..._chunkHUFQD4CBcjs.createPigmentTools.call(void 0, ));
5224
+ tools.push(..._chunk7VOHOPLWcjs.createPigmentTools.call(void 0, ));
4535
5225
  return { id: _nullishCoalesce(options.id, () => ( "paint-editor")), namespace: options.namespace, tools };
4536
5226
  }
4537
5227
 
@@ -4542,21 +5232,21 @@ var PAINT_TOOLS = [
4542
5232
  { id: "pan", label: "Pan", key: "h" },
4543
5233
  { id: "rotate", label: "Rotate view", key: "r" }
4544
5234
  ];
4545
- var PaintGesture = (_class10 = class {
4546
- 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);
4547
5237
  this.controller = controller;
4548
5238
  this.box = box;
4549
5239
  }
4550
5240
 
4551
5241
 
4552
- __init56() {this.tool = "brush"}
5242
+ __init66() {this.tool = "brush"}
4553
5243
  /** The last refusal (a hidden or locked layer), for the editor to show. */
4554
- __init57() {this.error = null}
5244
+ __init67() {this.error = null}
4555
5245
  /** The predicted tail of the current stroke in document pixels (draw it as a thin preview). */
4556
- __init58() {this.predicted = []}
4557
- __init59() {this.mode = null}
4558
- __init60() {this.penAt = null}
4559
- __init61() {this.penDown = false}
5246
+ __init68() {this.predicted = []}
5247
+ __init69() {this.mode = null}
5248
+ __init70() {this.penAt = null}
5249
+ __init71() {this.penDown = false}
4560
5250
  doc(e) {
4561
5251
  const b = this.box();
4562
5252
  const s = this.controller.getState();
@@ -4637,12 +5327,12 @@ var PaintGesture = (_class10 = class {
4637
5327
  if (!m || m.pointerId !== e.pointerId) return;
4638
5328
  this.mode = null;
4639
5329
  this.predicted = [];
4640
- 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()]);
4641
5331
  }
4642
5332
  cancel() {
4643
5333
  this.penDown = false;
4644
5334
  this.predicted = [];
4645
- 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()]);
4646
5336
  this.mode = null;
4647
5337
  }
4648
5338
  get active() {
@@ -4662,7 +5352,7 @@ var PaintGesture = (_class10 = class {
4662
5352
  const py = e.clientY - b.top - b.height / 2;
4663
5353
  this.controller.setView({ zoom, pan: { x: px - (px - s.view.pan.x) * k, y: py - (py - s.view.pan.y) * k } });
4664
5354
  }
4665
- }, _class10);
5355
+ }, _class11);
4666
5356
  function handlePaintShortcut(controller, gesture, e) {
4667
5357
  const mod = e.ctrlKey || e.metaKey;
4668
5358
  const key = e.key.toLowerCase();
@@ -4702,14 +5392,14 @@ function handlePaintShortcut(controller, gesture, e) {
4702
5392
  }
4703
5393
  return false;
4704
5394
  }
4705
- var PaintCanvasPresenter = (_class11 = class {
4706
- 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);
4707
5397
  this.controller = controller;
4708
5398
  }
4709
5399
 
4710
- __init62() {this.uploaded = /* @__PURE__ */ new Map()}
4711
- __init63() {this.scratch = null}
4712
- __init64() {this.size = ""}
5400
+ __init72() {this.uploaded = /* @__PURE__ */ new Map()}
5401
+ __init73() {this.scratch = null}
5402
+ __init74() {this.size = ""}
4713
5403
  /** Draws what changed into `ctx` (a canvas the size of the document); returns the tiles drawn. */
4714
5404
  render(ctx) {
4715
5405
  const { width, height } = this.controller.document.canvas;
@@ -4726,16 +5416,16 @@ var PaintCanvasPresenter = (_class11 = class {
4726
5416
  const key = `${tx},${ty}`;
4727
5417
  if (this.uploaded.get(key) === data) return;
4728
5418
  this.uploaded.set(key, data);
4729
- if (!this.scratch) this.scratch = ctx.createImageData(_chunkSZ6KMZNMcjs.TILE, _chunkSZ6KMZNMcjs.TILE);
5419
+ if (!this.scratch) this.scratch = ctx.createImageData(_chunkXIOMMGPDcjs.TILE, _chunkXIOMMGPDcjs.TILE);
4730
5420
  this.scratch.data.set(data);
4731
- const ox = tx * _chunkSZ6KMZNMcjs.TILE;
4732
- const oy = ty * _chunkSZ6KMZNMcjs.TILE;
4733
- 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));
4734
5424
  drawn++;
4735
5425
  }, { dirty });
4736
5426
  return drawn;
4737
5427
  }
4738
- }, _class11);
5428
+ }, _class12);
4739
5429
  function guideOverlay(controller) {
4740
5430
  const s = controller.getState();
4741
5431
  const size = s.document.canvas;
@@ -4770,7 +5460,7 @@ var PAPER_SLIDERS = [
4770
5460
  ];
4771
5461
  function readPath(obj, path) {
4772
5462
  let v = obj;
4773
- 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]]);
4774
5464
  return typeof v === "number" ? v : 0;
4775
5465
  }
4776
5466
  function patchPath(obj, path, value) {
@@ -4812,9 +5502,88 @@ function wheelPick(dx, dy, wheel, color) {
4812
5502
  return rgbToHexColor(nr, ng, nb);
4813
5503
  }
4814
5504
  var BLEND_MENU_GROUPS = [
4815
- { label: "Standard", modes: _chunkSZ6KMZNMcjs.BLEND_MODES.filter((m) => !m.painterly && m.id !== "pigment") },
4816
- { 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") }
4817
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
+
4818
5587
 
4819
5588
 
4820
5589
 
@@ -4962,5 +5731,5 @@ var BLEND_MENU_GROUPS = [
4962
5731
 
4963
5732
 
4964
5733
 
4965
- 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;
4966
5735
  //# sourceMappingURL=index.cjs.map