@pixerate/editor 0.249.0 → 0.251.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 (158) hide show
  1. package/dist/agent/index.cjs +40 -41
  2. package/dist/agent/index.js +9 -10
  3. package/dist/animation/index.cjs +6 -7
  4. package/dist/animation/index.js +5 -6
  5. package/dist/animation-editor/index.cjs +124 -123
  6. package/dist/animation-editor/index.js +15 -14
  7. package/dist/brushes/index.cjs +1406 -0
  8. package/dist/brushes/index.d.cts +12 -0
  9. package/dist/brushes/index.d.ts +12 -0
  10. package/dist/brushes/index.js +1406 -0
  11. package/dist/brushes/migrate.d.cts +17 -0
  12. package/dist/brushes/migrate.d.ts +17 -0
  13. package/dist/brushes/normalize.d.cts +15 -0
  14. package/dist/brushes/normalize.d.ts +15 -0
  15. package/dist/brushes/presets.d.cts +12 -0
  16. package/dist/brushes/presets.d.ts +12 -0
  17. package/dist/brushes/resolve.d.cts +56 -0
  18. package/dist/brushes/resolve.d.ts +56 -0
  19. package/dist/brushes/schema.d.cts +29 -0
  20. package/dist/brushes/schema.d.ts +29 -0
  21. package/dist/brushes/types.d.cts +323 -0
  22. package/dist/brushes/types.d.ts +323 -0
  23. package/dist/{brushstrokeRender-T3JZST44.js → brushstrokeRender-5PYSKZIJ.js} +9 -8
  24. package/dist/{brushstrokeRender-GCKK7V2I.cjs → brushstrokeRender-7FLLSQEC.cjs} +15 -14
  25. package/dist/canvas/index.cjs +120 -65
  26. package/dist/canvas/index.d.cts +1 -0
  27. package/dist/canvas/index.d.ts +1 -0
  28. package/dist/canvas/index.js +88 -33
  29. package/dist/canvas/stickers.d.cts +35 -0
  30. package/dist/canvas/stickers.d.ts +35 -0
  31. package/dist/chart/index.cjs +159 -160
  32. package/dist/chart/index.js +9 -10
  33. package/dist/{chunk-RXZL4NG5.js → chunk-226QDRO3.js} +1 -1
  34. package/dist/{chunk-DOIU7VJG.js → chunk-2ZJ4DT3M.js} +1 -1
  35. package/dist/{chunk-XZNU4DUX.cjs → chunk-3NS5C7FB.cjs} +76 -76
  36. package/dist/{chunk-UTI5IITZ.cjs → chunk-4ZC5QJE2.cjs} +3 -3
  37. package/dist/{chunk-L3JFOTBQ.js → chunk-5E2Q7ZHI.js} +1 -1
  38. package/dist/{chunk-6BIDVPED.js → chunk-5JNA6LLY.js} +1 -1
  39. package/dist/{chunk-RD7RX52G.cjs → chunk-5UEFBM2M.cjs} +207 -18
  40. package/dist/{chunk-WA44NKPF.js → chunk-62Z5WLRA.js} +1 -1
  41. package/dist/{chunk-BVRRIW7F.js → chunk-6GRUVGKU.js} +3 -3
  42. package/dist/{chunk-QARCM66V.js → chunk-6ZZKRH7R.js} +3 -3
  43. package/dist/{chunk-BP6KSGVD.js → chunk-7272D3K2.js} +4 -6
  44. package/dist/{chunk-O75V4ZS3.js → chunk-7P6VF3FG.js} +2 -2
  45. package/dist/{chunk-AU6MWQKZ.js → chunk-ABL7IWFZ.js} +1 -1
  46. package/dist/{chunk-5RH3VXAK.js → chunk-APX63ILU.js} +4 -100
  47. package/dist/{chunk-W7L4HUT2.cjs → chunk-CJ6TBL2N.cjs} +16 -16
  48. package/dist/{chunk-7PT4RJL5.cjs → chunk-EUIDTZNQ.cjs} +111 -113
  49. package/dist/{chunk-CXXXAZ7H.js → chunk-F6R67I3A.js} +1 -1
  50. package/dist/{chunk-I7NJPHWV.cjs → chunk-FQPUF2W2.cjs} +35 -606
  51. package/dist/{chunk-D5KGAO4Z.js → chunk-GCVFEPJG.js} +2 -573
  52. package/dist/{chunk-LEQKZGQI.js → chunk-GOBOCZZI.js} +1 -1
  53. package/dist/{chunk-PZP2SZIZ.js → chunk-GYMMNCFT.js} +3 -3
  54. package/dist/{chunk-NUTMINJZ.cjs → chunk-H7ET2ISK.cjs} +52 -52
  55. package/dist/chunk-HS6QDWDF.cjs +1029 -0
  56. package/dist/{chunk-T7KQEMAE.cjs → chunk-HUAISCUX.cjs} +18 -18
  57. package/dist/{chunk-YGQVCQTG.js → chunk-ILP77UD6.js} +1 -1
  58. package/dist/{chunk-GHCSC2WP.cjs → chunk-ISIO63Q5.cjs} +21 -15
  59. package/dist/{chunk-S6OT6CTS.cjs → chunk-IWEVV63O.cjs} +31 -33
  60. package/dist/{chunk-OFH6Q67U.js → chunk-JA57JIT5.js} +9 -3
  61. package/dist/{chunk-LP2MCSBE.cjs → chunk-JFHBXNR2.cjs} +88 -88
  62. package/dist/{chunk-IOFVM3L3.cjs → chunk-JPEHXIB5.cjs} +10 -10
  63. package/dist/chunk-JSDND7DF.cjs +173 -0
  64. package/dist/{chunk-36ZTV7WO.js → chunk-L6Y7TSQ4.js} +1 -1
  65. package/dist/{chunk-KYX7NZ5V.cjs → chunk-LFMKWGFL.cjs} +1 -1
  66. package/dist/{chunk-V6DT2CDH.js → chunk-M2TPMYJE.js} +1 -1
  67. package/dist/{chunk-LJZRHVZV.cjs → chunk-M5GLQQWG.cjs} +2 -2
  68. package/dist/{chunk-JEGJP4OW.js → chunk-MHLU6GRJ.js} +196 -7
  69. package/dist/{chunk-KMPM5JM5.js → chunk-MONXNVZK.js} +4 -6
  70. package/dist/chunk-MTKY6K3Q.js +1029 -0
  71. package/dist/{chunk-MOW3JMNG.cjs → chunk-N76AMOKE.cjs} +42 -42
  72. package/dist/{chunk-75K3W4D6.cjs → chunk-NKGB64CW.cjs} +16 -16
  73. package/dist/{chunk-SGPIVHJB.js → chunk-P3K2JCJA.js} +3 -3
  74. package/dist/chunk-PXDIAYB6.cjs +594 -0
  75. package/dist/{chunk-UBRJOIR5.js → chunk-QC3OXRE4.js} +9 -9
  76. package/dist/{chunk-QOBCBGGI.cjs → chunk-QKXO6JPP.cjs} +14 -14
  77. package/dist/{chunk-WLJZZHUZ.cjs → chunk-QLSGTCYT.cjs} +23 -23
  78. package/dist/{chunk-7YW7T5TJ.js → chunk-RHPSE2YR.js} +2 -2
  79. package/dist/{chunk-44BMJJVA.cjs → chunk-RUONPPMC.cjs} +8 -8
  80. package/dist/{chunk-LJRCU3BJ.cjs → chunk-SABJMH6M.cjs} +150 -15
  81. package/dist/{chunk-XWOLPNBW.cjs → chunk-SOSBOS5T.cjs} +22 -22
  82. package/dist/chunk-SZTI6EER.js +13 -0
  83. package/dist/{chunk-PPFJHTW5.cjs → chunk-TFF2GTGN.cjs} +22 -24
  84. package/dist/{chunk-2SMMZDMG.cjs → chunk-TLOGFIIV.cjs} +37 -37
  85. package/dist/chunk-TXFWZ5ZJ.js +594 -0
  86. package/dist/chunk-U67EN7GG.cjs +13 -0
  87. package/dist/{chunk-GXAK6Q5J.js → chunk-UFEDHACQ.js} +2 -2
  88. package/dist/{chunk-GA4EU4P3.cjs → chunk-UXETV2NC.cjs} +4 -4
  89. package/dist/{chunk-N4DFDI73.js → chunk-WKMUBATU.js} +1 -1
  90. package/dist/{chunk-3R5P3DVY.cjs → chunk-WRZ6C4TU.cjs} +6 -6
  91. package/dist/{chunk-EJLRBS5Q.js → chunk-WZOGDKCQ.js} +137 -2
  92. package/dist/{chunk-6ADHGQ73.js → chunk-YBAPT7NL.js} +2 -4
  93. package/dist/{chunk-K5DS2JTA.cjs → chunk-Z3BRH4JK.cjs} +6 -6
  94. package/dist/{chunk-XNZFVYC2.cjs → chunk-ZBCT5MEA.cjs} +7 -7
  95. package/dist/color/index.cjs +4 -6
  96. package/dist/color/index.js +7 -9
  97. package/dist/effects/helpers.d.cts +6 -0
  98. package/dist/effects/helpers.d.ts +6 -0
  99. package/dist/effects/index.cjs +62 -60
  100. package/dist/effects/index.js +5 -3
  101. package/dist/effects/tools.d.cts +6 -0
  102. package/dist/effects/tools.d.ts +6 -0
  103. package/dist/export/index.cjs +7 -8
  104. package/dist/export/index.js +6 -7
  105. package/dist/extensions/index.cjs +10 -11
  106. package/dist/extensions/index.js +9 -10
  107. package/dist/gradient/index.cjs +33 -34
  108. package/dist/gradient/index.js +4 -5
  109. package/dist/image-editor/effects.d.cts +1 -1
  110. package/dist/image-editor/effects.d.ts +1 -1
  111. package/dist/image-editor/index.cjs +887 -863
  112. package/dist/image-editor/index.d.cts +1 -0
  113. package/dist/image-editor/index.d.ts +1 -0
  114. package/dist/image-editor/index.js +69 -45
  115. package/dist/image-editor/stickers.d.cts +9 -0
  116. package/dist/image-editor/stickers.d.ts +9 -0
  117. package/dist/index.cjs +16 -17
  118. package/dist/index.js +10 -11
  119. package/dist/paint/index.cjs +6 -4
  120. package/dist/paint/index.js +21 -19
  121. package/dist/paint-editor/brushes.d.cts +1 -2
  122. package/dist/paint-editor/brushes.d.ts +1 -2
  123. package/dist/paint-editor/index.cjs +2199 -699
  124. package/dist/paint-editor/index.js +1577 -77
  125. package/dist/paint-editor/mark-settings.d.cts +104 -0
  126. package/dist/paint-editor/mark-settings.d.ts +104 -0
  127. package/dist/paint-editor/procedural.d.cts +2 -75
  128. package/dist/paint-editor/procedural.d.ts +2 -75
  129. package/dist/paint-editor/stickers.d.cts +1 -22
  130. package/dist/paint-editor/stickers.d.ts +1 -22
  131. package/dist/raster/index.cjs +2 -2
  132. package/dist/raster/index.js +9 -9
  133. package/dist/stickers/index.cjs +22 -16
  134. package/dist/stickers/index.d.cts +1 -1
  135. package/dist/stickers/index.d.ts +1 -1
  136. package/dist/stickers/index.js +14 -8
  137. package/dist/stickers/tools.d.cts +57 -1
  138. package/dist/stickers/tools.d.ts +57 -1
  139. package/dist/tokens/index.cjs +3 -4
  140. package/dist/tokens/index.js +2 -3
  141. package/dist/typography/index.cjs +7 -8
  142. package/dist/typography/index.js +2 -3
  143. package/dist/vector/index.cjs +1011 -977
  144. package/dist/vector/index.d.cts +1 -0
  145. package/dist/vector/index.d.ts +1 -0
  146. package/dist/vector/index.js +47 -13
  147. package/dist/vector/stickers.d.cts +11 -0
  148. package/dist/vector/stickers.d.ts +11 -0
  149. package/dist/video-editor/index.cjs +349 -350
  150. package/dist/video-editor/index.js +9 -10
  151. package/package.json +11 -1
  152. package/skills/pixerate-image-editor/SKILL.md +1 -1
  153. package/skills/pixerate-stickers/SKILL.md +13 -1
  154. package/dist/chunk-7AHBW2CQ.cjs +0 -200
  155. package/dist/chunk-F67XMVDI.js +0 -200
  156. package/dist/chunk-H4AOUUBE.cjs +0 -2635
  157. package/dist/chunk-J7T5QZ45.js +0 -2635
  158. package/dist/chunk-ZEYMKDYS.cjs +0 -269
@@ -1,2635 +0,0 @@
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;
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
- var _chunkZEYMKDYScjs = require('./chunk-ZEYMKDYS.cjs');
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
- var _chunkI7NJPHWVcjs = require('./chunk-I7NJPHWV.cjs');
30
-
31
-
32
-
33
-
34
- var _chunk3K6HWEXZcjs = require('./chunk-3K6HWEXZ.cjs');
35
-
36
-
37
-
38
- var _chunkW5M3J7PVcjs = require('./chunk-W5M3J7PV.cjs');
39
-
40
-
41
-
42
-
43
-
44
-
45
- var _chunk474IQG74cjs = require('./chunk-474IQG74.cjs');
46
-
47
-
48
-
49
-
50
-
51
- var _chunk7AHBW2CQcjs = require('./chunk-7AHBW2CQ.cjs');
52
-
53
- // src/paint-editor/effect-paint.ts
54
- var EFFECT_STRIP_HEIGHT = 256;
55
- var EFFECT_MAP_REPEATS = ["repeat", "mirror", "clamp"];
56
- function effectPaintSize(p, canvas) {
57
- return p.map ? { width: p.map.length, height: EFFECT_STRIP_HEIGHT } : { width: canvas.width, height: canvas.height };
58
- }
59
- function readEffectStrokeMap(raw) {
60
- if (raw === void 0 || raw === null || raw.by === "canvas") return void 0;
61
- const r = raw;
62
- if (r.by !== "stroke") throw new Error(`map.by ${JSON.stringify(r.by)} is not a mapping; use "canvas" (fixed to the canvas) or "stroke" (along each stroke).`);
63
- const length = r.length === void 0 ? 300 : Number(r.length);
64
- if (!(Number.isFinite(length) && length >= 16 && length <= 4096)) throw new Error(`map.length ${JSON.stringify(r.length)} is out of range; use 16..4096 px of stroke per repeat.`);
65
- const repeat = r.repeat === void 0 ? "repeat" : r.repeat;
66
- if (!EFFECT_MAP_REPEATS.includes(repeat)) throw new Error(`map.repeat ${JSON.stringify(r.repeat)} is not a repeat mode; use ${EFFECT_MAP_REPEATS.join(", ")}.`);
67
- return { by: "stroke", length: Math.round(length), repeat };
68
- }
69
- function stripIndex(img, map, dab, x, y) {
70
- const ca = Math.cos(dab.direction);
71
- const sa = Math.sin(dab.direction);
72
- const dx = x - dab.x;
73
- const dy = y - dab.y;
74
- let u = (dab.travel + dx * ca - dy * sa) / map.length;
75
- if (map.repeat === "clamp") u = Math.min(1, Math.max(0, u));
76
- else if (map.repeat === "mirror") {
77
- const m = (u % 2 + 2) % 2;
78
- u = m > 1 ? 2 - m : m;
79
- } else u = (u % 1 + 1) % 1;
80
- const v = Math.min(1, Math.max(0, ((dx * sa + dy * ca) / Math.max(0.5, dab.radius) + 1) / 2));
81
- const px = Math.min(img.width - 1, Math.floor(u * img.width));
82
- const py = Math.min(img.height - 1, Math.floor(v * img.height));
83
- return (py * img.width + px) * 4;
84
- }
85
- function effectPaintKey(p) {
86
- return JSON.stringify({ type: "effect", effect: p.effect, provider: p.provider, values: p.values, time: p.time, ...p.map ? { map: p.map } : {} });
87
- }
88
- function isPaintEffectPaint(raw) {
89
- return Boolean(raw) && typeof raw === "object" && raw.type === "effect";
90
- }
91
- function readMapLeniently(raw) {
92
- try {
93
- return readEffectStrokeMap(raw);
94
- } catch (e2) {
95
- return void 0;
96
- }
97
- }
98
- function readPaintEffectPaint(raw) {
99
- if (!isPaintEffectPaint(raw)) return null;
100
- const r = raw;
101
- if (typeof r.effect !== "string" || !r.effect || !r.values || typeof r.values !== "object" || Array.isArray(r.values)) return null;
102
- return {
103
- type: "effect",
104
- effect: r.effect,
105
- provider: typeof r.provider === "string" ? r.provider : "",
106
- values: JSON.parse(JSON.stringify(r.values)),
107
- time: typeof r.time === "number" && Number.isFinite(r.time) && r.time > 0 ? r.time : 0,
108
- ...readMapLeniently(r.map) ? { map: readMapLeniently(r.map) } : {},
109
- ...r.live === true ? { live: true } : {}
110
- };
111
- }
112
- function sampleEffectField(img, rect, vals, field, dab) {
113
- const { width, height, data } = img;
114
- if (img.map && dab) {
115
- for (let yy = 0, k = 0; yy < rect.height; yy++) {
116
- for (let xx = 0; xx < rect.width; xx++, k++) {
117
- const i = stripIndex(img, img.map, dab, rect.x + xx + 0.5, rect.y + yy + 0.5);
118
- const f = k * 3;
119
- const a = data[i + 3];
120
- if (a < 255) vals[k] *= a / 255;
121
- field[f] = data[i] / 255;
122
- field[f + 1] = data[i + 1] / 255;
123
- field[f + 2] = data[i + 2] / 255;
124
- }
125
- }
126
- return;
127
- }
128
- for (let yy = 0, k = 0; yy < rect.height; yy++) {
129
- const y = rect.y + yy;
130
- const inY = y >= 0 && y < height;
131
- for (let xx = 0; xx < rect.width; xx++, k++) {
132
- const x = rect.x + xx;
133
- const f = k * 3;
134
- if (!inY || x < 0 || x >= width) {
135
- vals[k] = 0;
136
- field[f] = field[f + 1] = field[f + 2] = 0;
137
- continue;
138
- }
139
- const i = (y * width + x) * 4;
140
- const a = data[i + 3];
141
- if (a < 255) vals[k] *= a / 255;
142
- field[f] = data[i] / 255;
143
- field[f + 1] = data[i + 1] / 255;
144
- field[f + 2] = data[i + 2] / 255;
145
- }
146
- }
147
- }
148
- function sampleEffectAt(img, x, y, out, dab) {
149
- if (img.map && dab) {
150
- const i2 = stripIndex(img, img.map, dab, x, y);
151
- out[0] = img.data[i2] / 255;
152
- out[1] = img.data[i2 + 1] / 255;
153
- out[2] = img.data[i2 + 2] / 255;
154
- return img.data[i2 + 3] / 255;
155
- }
156
- const px = Math.floor(x);
157
- const py = Math.floor(y);
158
- if (px < 0 || py < 0 || px >= img.width || py >= img.height) return 0;
159
- const i = (py * img.width + px) * 4;
160
- out[0] = img.data[i] / 255;
161
- out[1] = img.data[i + 1] / 255;
162
- out[2] = img.data[i + 2] / 255;
163
- return img.data[i + 3] / 255;
164
- }
165
- function effectDabField(img, rect, cov, centre, mixed, field, steps = 0, dab = null) {
166
- sampleEffectField(img, rect, cov, field, dab);
167
- const d0 = mixed[0] - centre[0];
168
- const d1 = mixed[1] - centre[1];
169
- const d2 = mixed[2] - centre[2];
170
- const n = rect.width * rect.height;
171
- for (let k = 0; k < n; k++) {
172
- if (steps) cov[k] = Math.round(cov[k] * steps) / steps;
173
- const f = k * 3;
174
- field[f] = Math.min(1, Math.max(0, field[f] + d0));
175
- field[f + 1] = Math.min(1, Math.max(0, field[f + 1] + d1));
176
- field[f + 2] = Math.min(1, Math.max(0, field[f + 2] + d2));
177
- }
178
- }
179
-
180
- // src/paint-editor/paper.ts
181
- var PAPER_FAMILIES = ["watercolor", "drawing", "pastel", "canvas", "panel", "smooth"];
182
- var unit = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
183
- var clampNum = (v, lo, hi, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : d;
184
- var HEX = /^#[0-9a-f]{6}$/i;
185
- var tuning = (contrast = 1, invert = false) => ({ contrast, invert });
186
- function preset(id, name, family, pattern, color, o) {
187
- return {
188
- preset: id,
189
- name,
190
- family,
191
- pattern,
192
- seed: _nullishCoalesce(o.seed, () => ( 1)),
193
- color,
194
- scale: _nullishCoalesce(o.scale, () => ( 1)),
195
- tiling: "mirror",
196
- depth: o.depth,
197
- shading: _nullishCoalesce(o.shading, () => ( 0.5)),
198
- dry: tuning(_nullishCoalesce(o.contrast, () => ( 1))),
199
- wet: tuning(1),
200
- absorption: o.absorption,
201
- permeability: o.permeability,
202
- roughness: o.roughness,
203
- ...o.backlight ? { backlight: o.backlight, backlightColor: _nullishCoalesce(o.backlightColor, () => ( "#fff1d6")) } : {}
204
- };
205
- }
206
- var PAPER_LIBRARY = [
207
- preset("cold-pressed", "Cold pressed watercolour", "watercolor", "cold-pressed", "#f7f3ea", { depth: 0.5, absorption: 0.7, permeability: 0.5, roughness: 0.6 }),
208
- preset("hot-pressed", "Hot pressed watercolour", "watercolor", "hot-pressed", "#f8f6ef", { depth: 0.25, absorption: 0.55, permeability: 0.4, roughness: 0.25 }),
209
- preset("rough-watercolor", "Rough watercolour", "watercolor", "rough", "#f5f0e4", { depth: 0.75, absorption: 0.8, permeability: 0.6, roughness: 0.85 }),
210
- preset("cartridge", "Cartridge", "drawing", "vellum", "#fbfaf6", { depth: 0.3, absorption: 0.4, permeability: 0.3, roughness: 0.35 }),
211
- preset("laid", "Laid paper", "drawing", "laid", "#f3ecdc", { depth: 0.45, absorption: 0.45, permeability: 0.35, roughness: 0.45 }),
212
- preset("ingres", "Charcoal paper", "drawing", "ingres", "#ede6d6", { depth: 0.6, absorption: 0.4, permeability: 0.3, roughness: 0.65 }),
213
- preset("sanded-pastel", "Sanded pastel paper", "pastel", "sanded", "#d8cfc0", { depth: 0.7, absorption: 0.3, permeability: 0.2, roughness: 0.9, shading: 0.35 }),
214
- preset("pastel-card", "Grey pastel card", "pastel", "sanded", "#9c978e", { depth: 0.55, absorption: 0.3, permeability: 0.2, roughness: 0.75, scale: 1.5, shading: 0.3 }),
215
- preset("cotton-canvas", "Cotton canvas", "canvas", "canvas", "#efe9dc", { depth: 0.55, absorption: 0.35, permeability: 0.25, roughness: 0.6 }),
216
- preset("fine-linen", "Fine linen", "canvas", "linen", "#e9e2d2", { depth: 0.45, absorption: 0.3, permeability: 0.25, roughness: 0.5 }),
217
- preset("gesso", "Gesso panel", "panel", "gesso", "#f6f4ef", { depth: 0.35, absorption: 0.2, permeability: 0.15, roughness: 0.4 }),
218
- preset("smooth", "Smooth bristol", "smooth", "smooth", "#ffffff", { depth: 0, absorption: 0.3, permeability: 0.2, roughness: 0.05, shading: 0 }),
219
- // Backlit: smooth paper on a lightbox, for glazes that glow like stained glass.
220
- preset("stained-glass", "Stained glass (backlit)", "smooth", "smooth", "#ffffff", { depth: 0, absorption: 0.35, permeability: 0.25, roughness: 0.05, shading: 0, backlight: 0.8, backlightColor: "#fff1d6" })
221
- ];
222
- var DEFAULT_PAPER = PAPER_LIBRARY[0];
223
- function paperPreset(id) {
224
- const p = PAPER_LIBRARY.find((x) => x.preset === id);
225
- return p ? clonePaper(p) : void 0;
226
- }
227
- function clonePaper(p) {
228
- return { ...p, dry: { ...p.dry }, wet: { ...p.wet }, ...p.offset ? { offset: { ...p.offset } } : {} };
229
- }
230
- function readOffset(raw, d) {
231
- const o = raw && typeof raw === "object" ? raw : null;
232
- const x = o && typeof o.x === "number" && Number.isFinite(o.x) ? Math.round(o.x) : _nullishCoalesce(_optionalChain([d, 'optionalAccess', _3 => _3.x]), () => ( 0));
233
- const y = o && typeof o.y === "number" && Number.isFinite(o.y) ? Math.round(o.y) : _nullishCoalesce(_optionalChain([d, 'optionalAccess', _4 => _4.y]), () => ( 0));
234
- return x || y ? { offset: { x, y } } : {};
235
- }
236
- function readTuning(raw, d) {
237
- const r = raw && typeof raw === "object" ? raw : {};
238
- return { contrast: clampNum(r.contrast, 0, 3, d.contrast), invert: typeof r.invert === "boolean" ? r.invert : d.invert };
239
- }
240
- function normalizePaper(raw = {}, base) {
241
- const r = raw;
242
- const b = _nullishCoalesce(_nullishCoalesce(base, () => ( (typeof r.preset === "string" ? paperPreset(r.preset) : void 0))), () => ( clonePaper(DEFAULT_PAPER)));
243
- return {
244
- preset: typeof r.preset === "string" && r.preset ? r.preset : b.preset,
245
- name: typeof r.name === "string" && r.name.trim() ? r.name.trim() : b.name,
246
- family: PAPER_FAMILIES.includes(r.family) ? r.family : b.family,
247
- pattern: _chunkI7NJPHWVcjs.GRAIN_PATTERNS.includes(r.pattern) ? r.pattern : b.pattern,
248
- seed: typeof r.seed === "number" && Number.isFinite(r.seed) ? Math.round(Math.abs(r.seed)) % 2147483647 : b.seed,
249
- color: typeof r.color === "string" && HEX.test(r.color) ? r.color.toLowerCase() : b.color,
250
- scale: clampNum(r.scale, 0.25, 8, b.scale),
251
- tiling: r.tiling === "repeat" || r.tiling === "mirror" ? r.tiling : b.tiling,
252
- depth: unit(r.depth, b.depth),
253
- shading: unit(r.shading, b.shading),
254
- dry: readTuning(r.dry, b.dry),
255
- wet: readTuning(r.wet, b.wet),
256
- absorption: unit(r.absorption, b.absorption),
257
- permeability: unit(r.permeability, b.permeability),
258
- roughness: unit(r.roughness, b.roughness),
259
- ...unit(r.metallic, _nullishCoalesce(b.metallic, () => ( 0))) > 0 ? { metallic: unit(r.metallic, _nullishCoalesce(b.metallic, () => ( 0))) } : {},
260
- ...unit(r.backlight, _nullishCoalesce(b.backlight, () => ( 0))) > 0 ? { backlight: unit(r.backlight, _nullishCoalesce(b.backlight, () => ( 0))) } : {},
261
- ...typeof r.backlightColor === "string" && HEX.test(r.backlightColor) ? { backlightColor: r.backlightColor.toLowerCase() } : b.backlightColor ? { backlightColor: b.backlightColor } : {},
262
- ...readOffset(r.offset, b.offset)
263
- };
264
- }
265
- function hexToRgb8(hex) {
266
- const n = parseInt(hex.slice(1, 7), 16);
267
- return [n >> 16 & 255, n >> 8 & 255, n & 255];
268
- }
269
- function renderPaper(paper, rect, grain = new (0, _chunkI7NJPHWVcjs.PaperGrain)(paper), light) {
270
- const [r, g, b] = hexToRgb8(paper.color);
271
- const out = new Uint8ClampedArray(rect.width * rect.height * 4);
272
- const shade = grain.flat ? 0 : paper.shading * 0.16;
273
- const metal = light ? _nullishCoalesce(paper.metallic, () => ( 0)) : 0;
274
- let hx = 0;
275
- let hy = 0;
276
- let hz = 1;
277
- if (metal > 0 && light) {
278
- const az = light.azimuth * Math.PI / 180;
279
- const el = light.elevation * Math.PI / 180;
280
- const lx = Math.cos(el) * Math.sin(az);
281
- const ly = -Math.cos(el) * Math.cos(az);
282
- const lz = Math.sin(el) + 1;
283
- const n = Math.hypot(lx, ly, lz);
284
- hx = lx / n;
285
- hy = ly / n;
286
- hz = lz / n;
287
- }
288
- for (let y = 0; y < rect.height; y++) {
289
- for (let x = 0; x < rect.width; x++) {
290
- const i = (y * rect.width + x) * 4;
291
- const px = rect.x + x + 0.5;
292
- const py = rect.y + y + 0.5;
293
- const k = shade ? 1 - shade * (1 - grain.at(px, py)) : 1;
294
- let spec = 0;
295
- if (metal > 0) {
296
- const gx = (grain.at(px + 1, py) - grain.at(px - 1, py)) * 4;
297
- const gy = (grain.at(px, py + 1) - grain.at(px, py - 1)) * 4;
298
- const nl = Math.hypot(gx, gy, 1);
299
- const nh = Math.max(0, (-gx * hx - gy * hy + hz) / nl);
300
- spec = metal * Math.pow(nh, 24) * 0.9;
301
- }
302
- out[i] = Math.min(255, r * k * (1 - 0.25 * metal) + spec * (r * 0.6 + 102));
303
- out[i + 1] = Math.min(255, g * k * (1 - 0.25 * metal) + spec * (g * 0.6 + 102));
304
- out[i + 2] = Math.min(255, b * k * (1 - 0.25 * metal) + spec * (b * 0.6 + 102));
305
- out[i + 3] = 255;
306
- }
307
- }
308
- return out;
309
- }
310
-
311
- // src/paint-editor/engine.ts
312
- function penOrientation(p) {
313
- if (typeof p.altitude === "number" && Number.isFinite(p.altitude)) {
314
- const altitude = Math.min(Math.PI / 2, Math.max(0, p.altitude));
315
- const azimuth = typeof p.azimuth === "number" && Number.isFinite(p.azimuth) ? p.azimuth : 0;
316
- return { altitude, azimuth };
317
- }
318
- const hasX = typeof p.tiltX === "number" && Number.isFinite(p.tiltX);
319
- const hasY = typeof p.tiltY === "number" && Number.isFinite(p.tiltY);
320
- if (!hasX && !hasY) return null;
321
- return tiltToOrientation(hasX ? p.tiltX : 0, hasY ? p.tiltY : 0);
322
- }
323
- function tiltToOrientation(tiltX, tiltY) {
324
- const clampT = (v) => Math.min(89.999, Math.max(-89.999, v));
325
- const tx = Math.tan(clampT(tiltX) * Math.PI / 180);
326
- const ty = Math.tan(clampT(tiltY) * Math.PI / 180);
327
- const r = Math.hypot(tx, ty);
328
- if (r < 1e-9) return { altitude: Math.PI / 2, azimuth: 0 };
329
- let azimuth = Math.atan2(ty, tx);
330
- if (azimuth < 0) azimuth += 2 * Math.PI;
331
- return { altitude: Math.atan(1 / r), azimuth };
332
- }
333
- var pressureOf = (p) => typeof p.pressure === "number" && Number.isFinite(p.pressure) ? Math.min(1, Math.max(0, p.pressure)) : 1;
334
- var PenSmoother = (_class = class {
335
- __init() {this.pos = null}
336
- __init2() {this.pressure = null}
337
-
338
-
339
- constructor(position, pressure) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);
340
- this.a = 1 - Math.min(0.95, Math.max(0, position));
341
- this.ap = 1 - Math.min(0.95, Math.max(0, pressure));
342
- }
343
- push(p) {
344
- const pr = pressureOf(p);
345
- if (!this.pos) {
346
- this.pos = { x: p.x, y: p.y };
347
- this.pressure = pr;
348
- return p;
349
- }
350
- this.pos = { x: this.pos.x + (p.x - this.pos.x) * this.a, y: this.pos.y + (p.y - this.pos.y) * this.a };
351
- this.pressure = this.pressure + (pr - this.pressure) * this.ap;
352
- return { ...p, x: this.pos.x, y: this.pos.y, pressure: this.pressure };
353
- }
354
- /** Where the smoothed position is now (or `null` before the first sample). */
355
- get position() {
356
- return this.pos;
357
- }
358
- }, _class);
359
- var IDENTITY_COPY = { map: (p) => p, linear: [1, 0, 0, 1] };
360
- var FULL_SPEED = 2.5;
361
- var PenDabWalker = (_class2 = class {
362
- constructor(brush, seed = 0, linear = [1, 0, 0, 1]) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);
363
- this.brush = brush;
364
- this.seed = seed;
365
- this.linear = linear;
366
- }
367
-
368
-
369
-
370
- __init3() {this.last = null}
371
- __init4() {this.nextAt = 0}
372
- __init5() {this.pendingFirst = null}
373
- __init6() {this.index = 0}
374
- __init7() {this.dir = 0}
375
- __init8() {this.lastT = null}
376
- __init9() {this.firstT = null}
377
- __init10() {this.speed = null}
378
- __init11() {this.travelled = 0}
379
- sample(p) {
380
- const o = penOrientation(p);
381
- let speed = null;
382
- let time = null;
383
- if (this.last) this.travelled += Math.hypot(p.x - this.last.x, p.y - this.last.y);
384
- if (typeof p.t === "number" && Number.isFinite(p.t)) {
385
- if (this.firstT === null) this.firstT = p.t;
386
- time = p.t - this.firstT;
387
- if (this.last && this.lastT !== null && p.t > this.lastT) {
388
- const v = Math.hypot(p.x - this.last.x, p.y - this.last.y) / (p.t - this.lastT);
389
- this.speed = this.speed === null ? v : this.speed + (v - this.speed) * 0.35;
390
- }
391
- this.lastT = p.t;
392
- speed = _nullishCoalesce(this.speed, () => ( 0));
393
- }
394
- return {
395
- x: p.x,
396
- y: p.y,
397
- pressure: pressureOf(p),
398
- lean: o ? 1 - o.altitude / (Math.PI / 2) : 0,
399
- azimuth: o ? o.azimuth : 0,
400
- twist: typeof p.twist === "number" && Number.isFinite(p.twist) ? p.twist : null,
401
- speed,
402
- travel: this.travelled,
403
- time
404
- };
405
- }
406
- diameter(s) {
407
- const tip = this.brush.tip;
408
- const dyn = this.brush.dynamics;
409
- let d = tip.size;
410
- if (tip.pressureSize) d *= dyn.minSize + (1 - dyn.minSize) * s.pressure;
411
- d *= 1 + dyn.tiltSize * s.lean * 1.5;
412
- if (s.speed !== null && dyn.velocitySize) d *= Math.max(0.05, 1 + dyn.velocitySize * Math.min(1, s.speed / FULL_SPEED) * (dyn.velocitySize < 0 ? 0.85 : 1));
413
- return d;
414
- }
415
- stepLen(s) {
416
- return Math.max(0.5, Math.max(1, this.diameter(s)) * Math.max(0.01, this.brush.tip.spacing));
417
- }
418
- /** A rasterizer angle (CCW, y up) mapped through this copy's linear part. */
419
- mapAngle(theta) {
420
- const [a, b, c, d] = this.linear;
421
- if (a === 1 && b === 0 && c === 0 && d === 1) return theta;
422
- const vx = Math.cos(theta);
423
- const vy = -Math.sin(theta);
424
- return Math.atan2(-(c * vx + d * vy), a * vx + b * vy);
425
- }
426
- dab(s) {
427
- const { tip, dynamics: dyn, rotation } = this.brush;
428
- const i = this.index++;
429
- const seed = this.seed;
430
- let diameter = this.diameter(s);
431
- if (dyn.sizeJitter) diameter *= Math.max(0.05, 1 + dyn.sizeJitter * _chunk3K6HWEXZcjs.randSigned.call(void 0, seed, i, 1 /* Size */));
432
- const base = tip.angle * Math.PI / 180;
433
- let angle;
434
- if (rotation === "stroke") angle = this.dir + base;
435
- else if (rotation === "barrel" && s.twist !== null) angle = this.mapAngle(base - s.twist * Math.PI / 180);
436
- else if (dyn.tiltRoundness > 0 && s.lean > 0.02) angle = this.mapAngle(base - s.azimuth);
437
- else angle = this.mapAngle(base);
438
- if (dyn.angleJitter) angle += dyn.angleJitter * Math.PI * _chunk3K6HWEXZcjs.randSigned.call(void 0, seed, i, 2 /* Angle */);
439
- let opacity = tip.pressureOpacity ? s.pressure : 1;
440
- opacity *= 1 - dyn.tiltOpacity * s.lean * 0.8;
441
- if (s.speed !== null && dyn.velocityOpacity) opacity *= Math.max(0, 1 + dyn.velocityOpacity * (Math.min(1, s.speed / FULL_SPEED) - 0.5));
442
- if (dyn.opacityJitter) opacity *= 1 - dyn.opacityJitter * (_chunk3K6HWEXZcjs.randSigned.call(void 0, seed, i, 3 /* Opacity */) * 0.5 + 0.5);
443
- const flow = tip.flow * (1 - dyn.pressureFlow + dyn.pressureFlow * s.pressure);
444
- let { x, y } = s;
445
- if (dyn.scatter) {
446
- const off = dyn.scatter * diameter * _chunk3K6HWEXZcjs.randSigned.call(void 0, seed, i, 4 /* Scatter */);
447
- x += Math.sin(this.dir) * off;
448
- y += Math.cos(this.dir) * off;
449
- }
450
- return {
451
- x,
452
- y,
453
- radius: Math.max(0.5, diameter / 2),
454
- alpha: Math.min(1, Math.max(0, flow)),
455
- opacity: Math.min(1, Math.max(0, opacity)),
456
- angle,
457
- roundness: Math.min(1, Math.max(0.01, tip.roundness * (1 - dyn.tiltRoundness * s.lean * 0.8))),
458
- pressure: s.pressure,
459
- direction: this.dir,
460
- index: i,
461
- travel: s.travel,
462
- time: s.time,
463
- lean: s.lean,
464
- speed: s.speed
465
- };
466
- }
467
- push(p, out) {
468
- const a = this.last;
469
- const s = this.sample(p);
470
- if (!a) {
471
- this.last = s;
472
- this.pendingFirst = s;
473
- this.nextAt = this.stepLen(s);
474
- return;
475
- }
476
- const dx = s.x - a.x;
477
- const dy = s.y - a.y;
478
- const len = Math.hypot(dx, dy);
479
- if (len > 1e-9) this.dir = Math.atan2(-dy, dx);
480
- if (this.pendingFirst) {
481
- out.push(this.dab(this.pendingFirst));
482
- this.pendingFirst = null;
483
- }
484
- if (len > 1e-9) {
485
- while (this.nextAt <= len + 1e-9) {
486
- const f = Math.min(1, this.nextAt / len);
487
- const lerp = (u, v) => u + (v - u) * f;
488
- const q = {
489
- x: a.x + dx * f,
490
- y: a.y + dy * f,
491
- pressure: lerp(a.pressure, s.pressure),
492
- lean: lerp(a.lean, s.lean),
493
- azimuth: lerpAngle(a.azimuth, s.azimuth, f),
494
- twist: a.twist !== null && s.twist !== null ? lerpDegrees(a.twist, s.twist, f) : s.twist,
495
- speed: a.speed !== null && s.speed !== null ? lerp(a.speed, s.speed) : s.speed,
496
- travel: a.travel + this.nextAt,
497
- time: a.time !== null && s.time !== null ? lerp(a.time, s.time) : s.time
498
- };
499
- out.push(this.dab(q));
500
- this.nextAt += this.stepLen(q);
501
- }
502
- this.nextAt -= len;
503
- }
504
- this.last = s;
505
- }
506
- finish(out) {
507
- if (this.pendingFirst) {
508
- out.push(this.dab(this.pendingFirst));
509
- this.pendingFirst = null;
510
- }
511
- }
512
- }, _class2);
513
- function lerpAngle(a, b, t) {
514
- let d = b - a;
515
- while (d > Math.PI) d -= 2 * Math.PI;
516
- while (d < -Math.PI) d += 2 * Math.PI;
517
- return a + d * t;
518
- }
519
- function lerpDegrees(a, b, t) {
520
- let d = b - a;
521
- while (d > 180) d -= 360;
522
- while (d < -180) d += 360;
523
- return a + d * t;
524
- }
525
- var DryStroke = (_class3 = class {
526
- constructor(brush, options) {;_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);
527
- this.brush = brush;
528
- this.options = options;
529
- this.smoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
530
- this.copies = _optionalChain([options, 'access', _5 => _5.copies, 'optionalAccess', _6 => _6.length]) ? options.copies : [IDENTITY_COPY];
531
- this.walkers = this.copies.map((c, i) => new PenDabWalker(brush, (_nullishCoalesce(options.seed, () => ( 0))) + i * 7919 | 0, c.linear));
532
- this.depth = options.grain && !options.grain.flat ? Math.min(1, Math.max(0, options.paperDepth * brush.grain)) : 0;
533
- }
534
-
535
-
536
- __init12() {this.coverage = new (0, _chunkI7NJPHWVcjs.CoverageMap)()}
537
- __init13() {this.dabCount = 0}
538
-
539
-
540
-
541
-
542
- __init14() {this.buf = new Float32Array(0)}
543
- __init15() {this.field = new Float32Array(0)}
544
- __init16() {this.lastRaw = null}
545
- __init17() {this.lastFed = null}
546
- /** Feeds raw pen samples; returns the document rectangles the new dabs touched. */
547
- push(points) {
548
- const dabs = [];
549
- for (const raw of points) {
550
- this.lastRaw = raw;
551
- const p = this.smoother.push(raw);
552
- this.lastFed = p;
553
- this.copies.forEach((c, i) => this.walkers[i].push(c.map(p), dabs));
554
- }
555
- return this.paint(dabs);
556
- }
557
- /** Ends the stroke: the smoothed path catches up with the last raw sample; a single tap paints one dab. */
558
- finish() {
559
- const dabs = [];
560
- const raw = this.lastRaw;
561
- const fed = this.lastFed;
562
- if (raw && fed && (raw.x !== fed.x || raw.y !== fed.y)) this.copies.forEach((c, i) => this.walkers[i].push(c.map({ ...raw, pressure: fed.pressure }), dabs));
563
- for (const w of this.walkers) w.finish(dabs);
564
- return this.paint(dabs);
565
- }
566
- paint(dabs) {
567
- const rects = [];
568
- const hardness = this.brush.tip.hardness;
569
- const grain = this.options.grain;
570
- for (const d of dabs) {
571
- const rect = _chunkI7NJPHWVcjs.dabRect.call(void 0, d, hardness);
572
- const n = rect.width * rect.height;
573
- if (this.buf.length < n) this.buf = new Float32Array(n);
574
- const vals = this.buf.subarray(0, n);
575
- _chunkI7NJPHWVcjs.rasterizeDab.call(void 0, d, hardness, rect, vals);
576
- if (this.depth > 0 && grain) {
577
- for (let yy = 0, k = 0; yy < rect.height; yy++) {
578
- const y = rect.y + yy + 0.5;
579
- for (let xx = 0; xx < rect.width; xx++, k++) {
580
- if (vals[k] > 0) vals[k] *= _chunkI7NJPHWVcjs.grainCatch.call(void 0, grain.at(rect.x + xx + 0.5, y), this.depth, d.pressure);
581
- }
582
- }
583
- }
584
- let field = null;
585
- if (this.options.effect) {
586
- if (this.field.length < n * 3) this.field = new Float32Array(n * 3);
587
- field = this.field.subarray(0, n * 3);
588
- sampleEffectField(this.options.effect, rect, vals, field, d);
589
- }
590
- this.coverage.accumulate(rect, vals, d.opacity, null, field);
591
- rects.push(rect);
592
- this.dabCount++;
593
- }
594
- return rects;
595
- }
596
- }, _class3);
597
- function mixerTip(brush) {
598
- return { ...brush.tip, smoothing: 0, pressureSize: brush.tip.pressureSize };
599
- }
600
- function toStrokePoint(p) {
601
- return { x: p.x, y: p.y, pressure: pressureOf(p), ...typeof p.t === "number" ? { t: p.t } : {} };
602
- }
603
-
604
- // src/paint-editor/sticker-art.ts
605
- var STICKER_ART_SIZE = 256;
606
- var defaultStickerRasterizer = async (asset, size) => {
607
- if (asset.kind === "image") {
608
- const parsed = _chunkW5M3J7PVcjs.parseDataUrl.call(void 0, asset.url);
609
- if (parsed && parsed.mime === "image/png") {
610
- const png = _chunkW5M3J7PVcjs.decodePng.call(void 0, parsed.bytes);
611
- return { width: png.width, height: png.height, data: new Uint8ClampedArray(png.data) };
612
- }
613
- }
614
- const g = globalThis;
615
- if (!g.createImageBitmap || !g.OffscreenCanvas) {
616
- throw new (0, _chunkZEYMKDYScjs.StickerError)("unsupported_format", `This environment can't draw ${asset.kind === "svg" ? "SVG" : "non-PNG"} sticker art; pass rasterizeSticker in the paint editor's options (or use a provider that offers png).`);
617
- }
618
- const blob = asset.kind === "svg" ? new Blob([asset.svg], { type: "image/svg+xml" }) : await (await g.fetch(asset.url)).blob();
619
- const bitmap = await g.createImageBitmap(blob);
620
- const scale = size / Math.max(bitmap.width, bitmap.height, 1);
621
- const w = Math.max(1, Math.round(bitmap.width * scale));
622
- const h = Math.max(1, Math.round(bitmap.height * scale));
623
- const canvas = new g.OffscreenCanvas(w, h);
624
- const ctx = canvas.getContext("2d");
625
- ctx.drawImage(bitmap, 0, 0, w, h);
626
- const img = ctx.getImageData(0, 0, w, h);
627
- return { width: w, height: h, data: img.data };
628
- };
629
- var StickerArtCache = (_class4 = class {constructor() { _class4.prototype.__init18.call(this);_class4.prototype.__init19.call(this); }
630
- __init18() {this.entries = /* @__PURE__ */ new Map()}
631
- /** The art for `ref` when it has loaded (editor stamps never need any). */
632
- __init19() {this.get = (ref) => {
633
- const e = this.entries.get(ref);
634
- return _optionalChain([e, 'optionalAccess', _8 => _8.status]) === "ready" ? e.art : void 0;
635
- }}
636
- /** `ready`, `loading`, `failed` (with the reason) or `none` (never asked for). Editor stamps are always `ready`. */
637
- status(ref) {
638
- if (_chunkZEYMKDYScjs.editorStampOf.call(void 0, ref)) return { status: "ready" };
639
- const e = this.entries.get(ref);
640
- if (!e) return { status: "none" };
641
- return e.status === "failed" ? { status: "failed", error: e.error } : { status: e.status };
642
- }
643
- /**
644
- * Loads and rasterises `ref` from `provider` once (later calls share the load). Unknown refs
645
- * reject with the closest known ones; failures are kept so they are reported, and retried on
646
- * the next call.
647
- */
648
- ensure(provider, ref, rasterize) {
649
- if (_chunkZEYMKDYScjs.editorStampOf.call(void 0, ref)) return Promise.resolve(null);
650
- const e = this.entries.get(ref);
651
- if (_optionalChain([e, 'optionalAccess', _9 => _9.status]) === "ready") return Promise.resolve(e.art);
652
- if (_optionalChain([e, 'optionalAccess', _10 => _10.status]) === "loading") return e.promise;
653
- const promise = (async () => {
654
- const d = await _chunkZEYMKDYScjs.describeStickerRef.call(void 0, provider, ref);
655
- const id = _chunkZEYMKDYScjs.stickerIdFor.call(void 0, provider, ref);
656
- const max = _nullishCoalesce(provider.capabilities.maxSize, () => ( STICKER_ART_SIZE));
657
- const size = Math.min(STICKER_ART_SIZE, max);
658
- const asset = await provider.load(id, d.formats.includes("png") ? { format: "png", size } : { format: _nullishCoalesce(d.formats[0], () => ( "svg")), size });
659
- const px = await rasterize(asset, size);
660
- if (!(px.width > 0 && px.height > 0) || px.data.length !== px.width * px.height * 4) throw new (0, _chunkZEYMKDYScjs.StickerError)("load_failed", `The art for "${ref}" came back empty or the wrong size.`);
661
- return { ...px, tintable: d.tintable };
662
- })();
663
- this.entries.set(ref, { status: "loading", promise });
664
- return promise.then(
665
- (art) => {
666
- this.entries.set(ref, { status: "ready", art });
667
- return art;
668
- },
669
- (err) => {
670
- const message = err instanceof Error ? err.message : String(err);
671
- this.entries.set(ref, { status: "failed", error: message });
672
- throw err;
673
- }
674
- );
675
- }
676
- /** Forgets every load (a new provider). */
677
- clear() {
678
- this.entries.clear();
679
- }
680
- }, _class4);
681
- function sampleArt(art, x, y, out) {
682
- const { width: w, height: h, data } = art;
683
- const fx = x - 0.5;
684
- const fy = y - 0.5;
685
- const x0 = Math.floor(fx);
686
- const y0 = Math.floor(fy);
687
- const tx = fx - x0;
688
- const ty = fy - y0;
689
- let r = 0;
690
- let g = 0;
691
- let b = 0;
692
- let a = 0;
693
- for (let k = 0; k < 4; k++) {
694
- const xx = x0 + (k & 1);
695
- const yy = y0 + (k >> 1);
696
- if (xx < 0 || yy < 0 || xx >= w || yy >= h) continue;
697
- const wgt = (k & 1 ? tx : 1 - tx) * (k >> 1 ? ty : 1 - ty);
698
- const i = (yy * w + xx) * 4;
699
- const al = data[i + 3] * wgt;
700
- r += data[i] * al;
701
- g += data[i + 1] * al;
702
- b += data[i + 2] * al;
703
- a += al;
704
- }
705
- out[0] = a > 0 ? r / a / 255 : 0;
706
- out[1] = a > 0 ? g / a / 255 : 0;
707
- out[2] = a > 0 ? b / a / 255 : 0;
708
- out[3] = a / 255;
709
- return out;
710
- }
711
-
712
- // src/paint-editor/stickers.ts
713
- var MAX_STICKER_STAMPS = 16;
714
- var DEFAULT_STICKER = { stamps: ["editor:star"], density: 0.4, sizeJitter: 0.4, rotationJitter: 1, scatter: 0.5, colorJitter: 0.2 };
715
- var unit2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
716
- function normalizeStickerSettings(raw = {}, base = DEFAULT_STICKER) {
717
- const refs = (list) => list.flatMap((s) => {
718
- try {
719
- return [_chunkZEYMKDYScjs.normalizeStickerRef.call(void 0, s)];
720
- } catch (e3) {
721
- return [];
722
- }
723
- });
724
- const stamps = Array.isArray(raw.stamps) ? refs(raw.stamps) : refs(base.stamps);
725
- return {
726
- stamps: stamps.length ? [...new Set(stamps)].slice(0, MAX_STICKER_STAMPS) : refs(base.stamps.length ? base.stamps : DEFAULT_STICKER.stamps),
727
- density: unit2(raw.density, base.density),
728
- sizeJitter: unit2(raw.sizeJitter, base.sizeJitter),
729
- rotationJitter: unit2(raw.rotationJitter, base.rotationJitter),
730
- scatter: typeof raw.scatter === "number" && Number.isFinite(raw.scatter) ? Math.min(2, Math.max(0, raw.scatter)) : base.scatter,
731
- colorJitter: unit2(raw.colorJitter, base.colorJitter)
732
- };
733
- }
734
- function hexRgb(hex) {
735
- const n = parseInt(hex.slice(1, 7), 16);
736
- return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
737
- }
738
- function stickerRect(s) {
739
- const r = s.size / 2 + 1;
740
- const x0 = Math.floor(s.x - r);
741
- const y0 = Math.floor(s.y - r);
742
- return { x: x0, y: y0, width: Math.ceil(s.x + r) - x0, height: Math.ceil(s.y + r) - y0 };
743
- }
744
- function stickerStamp(s) {
745
- if (s.ref) {
746
- try {
747
- return _chunkZEYMKDYScjs.editorStampOf.call(void 0, s.ref);
748
- } catch (e4) {
749
- return null;
750
- }
751
- }
752
- return s.stamp && _chunkZEYMKDYScjs.STAMP_KINDS.includes(s.stamp) ? s.stamp : null;
753
- }
754
- function drawSticker(medium, s, art) {
755
- const kind = stickerStamp(s);
756
- if (!kind) {
757
- const a2 = _optionalChain([art, 'optionalCall', _11 => _11(s.ref)]);
758
- return a2 ? drawArtSticker(medium, s, a2) : stickerRect(s);
759
- }
760
- const rect = stickerRect(s);
761
- const n = rect.width * rect.height;
762
- const cov = new Float32Array(n);
763
- const field = new Float32Array(n * 3);
764
- const rad = s.size / 2;
765
- const a = s.rotation * Math.PI / 180;
766
- const ca = Math.cos(a);
767
- const sa = Math.sin(a);
768
- const [r, g, b] = hexRgb(s.color);
769
- for (let yy = 0, p = 0; yy < rect.height; yy++) {
770
- for (let xx = 0, px = rect.x; xx < rect.width; xx++, p++, px++) {
771
- const dx = px + 0.5 - s.x;
772
- const dy = rect.y + yy + 0.5 - s.y;
773
- const u = (dx * ca + dy * sa) / rad;
774
- const v = (-dx * sa + dy * ca) / rad;
775
- if (u < -1.05 || u > 1.05 || v < -1.05 || v > 1.05) continue;
776
- let c = 0;
777
- for (let k = 0; k < 4; k++) c += _chunkZEYMKDYScjs.stampCoverage.call(void 0, kind, u + ((k & 1) - 0.5) / rad / 2, v + ((k >> 1) - 0.5) / rad / 2, s.seed);
778
- c /= 4;
779
- if (c <= 0) continue;
780
- cov[p] = c;
781
- const shade = 1 + 0.12 * (-(u + v) / 2) + 0.06 * (_chunk3K6HWEXZcjs.hash2.call(void 0, px, rect.y + yy, s.seed) - 0.5);
782
- field[p * 3] = Math.min(1, Math.max(0, r * shade));
783
- field[p * 3 + 1] = Math.min(1, Math.max(0, g * shade));
784
- field[p * 3 + 2] = Math.min(1, Math.max(0, b * shade));
785
- }
786
- }
787
- medium.deposit(rect, cov, s.opacity, [r, g, b], null, field);
788
- return rect;
789
- }
790
- function drawArtSticker(medium, s, art) {
791
- const rect = stickerRect(s);
792
- const n = rect.width * rect.height;
793
- const cov = new Float32Array(n);
794
- const field = new Float32Array(n * 3);
795
- const a = s.rotation * Math.PI / 180;
796
- const ca = Math.cos(a);
797
- const sa = Math.sin(a);
798
- const scale = Math.max(art.width, art.height) / s.size;
799
- const [r, g, b] = hexRgb(s.color);
800
- const px = [0, 0, 0, 0];
801
- for (let yy = 0, p = 0; yy < rect.height; yy++) {
802
- for (let xx = 0; xx < rect.width; xx++, p++) {
803
- const dx = rect.x + xx + 0.5 - s.x;
804
- const dy = rect.y + yy + 0.5 - s.y;
805
- const ax = (dx * ca + dy * sa) * scale + art.width / 2;
806
- const ay = (-dx * sa + dy * ca) * scale + art.height / 2;
807
- if (ax < -1 || ay < -1 || ax > art.width + 1 || ay > art.height + 1) continue;
808
- sampleArt(art, ax, ay, px);
809
- if (!(px[3] > 0)) continue;
810
- cov[p] = px[3];
811
- field[p * 3] = art.tintable ? r : px[0];
812
- field[p * 3 + 1] = art.tintable ? g : px[1];
813
- field[p * 3 + 2] = art.tintable ? b : px[2];
814
- }
815
- }
816
- medium.deposit(rect, cov, s.opacity, [r, g, b], null, field);
817
- return rect;
818
- }
819
- function jitterColor(hex, amount, seed, i) {
820
- const [r, g, b] = hexRgb(hex);
821
- const lch = _chunk7AHBW2CQcjs.oklabToOklch.call(void 0, _chunk7AHBW2CQcjs.rgbToOklab.call(void 0, { r, g, b, a: 1 }));
822
- const l = Math.min(1, Math.max(0, lch.l + (_chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 61) - 0.5) * amount * 0.3));
823
- const h = lch.h + (_chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 63) - 0.5) * amount * 80;
824
- const at = (c) => _chunk7AHBW2CQcjs.oklabToRgb.call(void 0, _chunk7AHBW2CQcjs.oklchToOklab.call(void 0, { l, c, h, alpha: 1 }));
825
- const inGamut = (o) => [o.r, o.g, o.b].every((v) => v >= -1e-4 && v <= 1 + 1e-4);
826
- let out = at(lch.c);
827
- if (!inGamut(out)) {
828
- let lo = 0;
829
- let hi = lch.c;
830
- for (let k = 0; k < 12; k++) {
831
- const mid = (lo + hi) / 2;
832
- if (inGamut(at(mid))) lo = mid;
833
- else hi = mid;
834
- }
835
- out = at(lo);
836
- }
837
- return "#" + [out.r, out.g, out.b].map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255).toString(16).padStart(2, "0")).join("");
838
- }
839
- var StickerSpray = (_class5 = class {
840
- constructor(init) {;_class5.prototype.__init20.call(this);_class5.prototype.__init21.call(this);_class5.prototype.__init22.call(this);_class5.prototype.__init23.call(this);_class5.prototype.__init24.call(this);
841
- this.init = init;
842
- const b = init.brush;
843
- this.smoother = new PenSmoother(b.tip.smoothing, b.pressureSmoothing);
844
- this.copies = init.copies.length ? init.copies : [IDENTITY_COPY];
845
- const spacing = Math.max(0.05, 1 / Math.max(0.05, b.sticker.density * 4));
846
- const walk = { ...b, tip: { ...b.tip, spacing }, dynamics: { ...b.dynamics, scatter: 0, sizeJitter: 0, angleJitter: 0 } };
847
- this.walkers = this.copies.map((c, i) => new PenDabWalker(walk, init.seed + i * 7919 | 0, c.linear));
848
- this.medium = new (0, _chunkI7NJPHWVcjs.TileMedium)(init.target, {});
849
- this.next = init.nextId;
850
- }
851
-
852
- __init20() {this.dabCount = 0}
853
- __init21() {this.stickers = []}
854
- /** Refs the spray picked but could not draw because their art had not loaded. */
855
- __init22() {this.skipped = /* @__PURE__ */ new Set()}
856
-
857
-
858
-
859
-
860
- __init23() {this.lastRaw = null}
861
- __init24() {this.lastFed = null}
862
-
863
- push(points) {
864
- const rects = [];
865
- for (const raw of points) {
866
- this.lastRaw = raw;
867
- const p = this.smoother.push(raw);
868
- this.lastFed = p;
869
- this.copies.forEach((c, i) => {
870
- const dabs = [];
871
- this.walkers[i].push(c.map(p), dabs);
872
- for (const d of dabs) this.drop(d, rects);
873
- });
874
- }
875
- return rects;
876
- }
877
- finish() {
878
- const rects = [];
879
- const raw = this.lastRaw;
880
- const fed = this.lastFed;
881
- this.copies.forEach((c, i) => {
882
- const dabs = [];
883
- if (raw && fed && (raw.x !== fed.x || raw.y !== fed.y)) this.walkers[i].push(c.map({ ...raw, pressure: fed.pressure }), dabs);
884
- this.walkers[i].finish(dabs);
885
- for (const d of dabs) this.drop(d, rects);
886
- });
887
- return rects;
888
- }
889
- drop(d, rects) {
890
- const st = this.init.brush.sticker;
891
- const i = this.next;
892
- const seed = this.init.seed;
893
- const size = d.radius * 2 * (1 - st.sizeJitter * 0.7 * _chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 51));
894
- const off = st.scatter * d.radius * 2;
895
- const a = _chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 53) * Math.PI * 2;
896
- const dist = _chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 55) * off;
897
- const base = this.colorAt(d, Math.cos(a) * dist, Math.sin(a) * dist);
898
- if (!(base.alpha > 0)) {
899
- this.next++;
900
- return;
901
- }
902
- const ref = _nullishCoalesce(st.stamps[Math.floor(_chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 57) * st.stamps.length)], () => ( "editor:dot"));
903
- const stamp = stickerStamp({ ref });
904
- if (!stamp && !_optionalChain([this, 'access', _12 => _12.init, 'access', _13 => _13.art, 'optionalCall', _14 => _14(ref)])) {
905
- this.skipped.add(ref);
906
- this.next++;
907
- return;
908
- }
909
- const sticker = {
910
- id: `st${this.next++}`,
911
- ref,
912
- ...stamp ? { stamp } : {},
913
- x: Math.round((d.x + Math.cos(a) * dist) * 100) / 100,
914
- y: Math.round((d.y + Math.sin(a) * dist) * 100) / 100,
915
- size: Math.max(2, Math.round(size * 10) / 10),
916
- rotation: Math.round((-d.angle * 180 / Math.PI + (_chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 59) * 2 - 1) * 180 * st.rotationJitter) * 10) / 10,
917
- color: st.colorJitter > 0 ? jitterColor(base.color, st.colorJitter, seed, i) : base.color,
918
- opacity: Math.round(Math.min(1, d.opacity * this.init.brush.tip.opacity * base.alpha) * 1e3) / 1e3,
919
- seed: Math.floor(_chunk3K6HWEXZcjs.rand01.call(void 0, seed, i, 65) * 1e9)
920
- };
921
- this.stickers.push(sticker);
922
- rects.push(drawSticker(this.medium, sticker, this.init.art));
923
- this.dabCount++;
924
- }
925
-
926
- /** A sticker's colour: the paint colour, or gradient paint at its dab (`dx`, `dy` from the dab's centre). */
927
- colorAt(d, dx, dy) {
928
- const effect = this.init.effect;
929
- if (effect) {
930
- const rgb = [0, 0, 0];
931
- const alpha = sampleEffectAt(effect, d.x + dx, d.y + dy, rgb, d);
932
- return { color: "#" + rgb.map((c) => Math.round(c * 255).toString(16).padStart(2, "0")).join(""), alpha };
933
- }
934
- const g = this.init.paint;
935
- if (!g) return { color: this.init.color, alpha: 1 };
936
- this.sampler ??= new (0, _chunkI7NJPHWVcjs.PaintGradientSampler)(g, this.init.seed);
937
- const s = g.map.by === "across-tip" ? this.sampler.sample(((dx * Math.sin(d.direction) + dy * Math.cos(d.direction)) / Math.max(0.5, d.radius) + 1) / 2) : this.sampler.dab(d);
938
- return { color: "#" + s.rgb.map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255).toString(16).padStart(2, "0")).join(""), alpha: s.alpha };
939
- }
940
- }, _class5);
941
- function renderStickers(stickers, target, art) {
942
- const medium = new (0, _chunkI7NJPHWVcjs.TileMedium)(target, {});
943
- for (const s of stickers) drawSticker(medium, s, art);
944
- }
945
- function normalizeStickerUpdate(u) {
946
- const out = {};
947
- if (u.ref !== void 0 && u.stamp !== void 0) throw new Error("Give ref or stamp, not both (stamp is the older name for an editor stamp).");
948
- if (u.stamp !== void 0) {
949
- if (!_chunkZEYMKDYScjs.STAMP_KINDS.includes(u.stamp)) throw new Error(`stamp ${JSON.stringify(u.stamp)} is not a stamp; use ${_chunkZEYMKDYScjs.STAMP_KINDS.join(", ")}, or ref for any sticker (find_stickers).`);
950
- out.ref = `editor:${u.stamp}`;
951
- out.stamp = u.stamp;
952
- }
953
- if (u.ref !== void 0) {
954
- out.ref = _chunkZEYMKDYScjs.normalizeStickerRef.call(void 0, u.ref);
955
- const stamp = stickerStamp({ ref: out.ref });
956
- if (stamp) out.stamp = stamp;
957
- else out.stamp = void 0;
958
- }
959
- for (const k of ["x", "y"]) if (u[k] !== void 0) {
960
- if (!Number.isFinite(u[k])) throw new Error(`${k} must be a number of pixels.`);
961
- out[k] = u[k];
962
- }
963
- if (u.size !== void 0) {
964
- if (!(u.size >= 2 && u.size <= 2048)) throw new Error(`size ${u.size} is out of range; use 2..2048 px.`);
965
- out.size = u.size;
966
- }
967
- if (u.rotation !== void 0) {
968
- if (!Number.isFinite(u.rotation)) throw new Error("rotation must be degrees.");
969
- out.rotation = u.rotation;
970
- }
971
- if (u.color !== void 0) {
972
- if (!/^#[0-9a-f]{6}$/i.test(u.color)) throw new Error(`color ${JSON.stringify(u.color)} must be #rrggbb.`);
973
- out.color = u.color.toLowerCase();
974
- }
975
- if (u.opacity !== void 0) {
976
- if (!(u.opacity >= 0 && u.opacity <= 1)) throw new Error(`opacity ${u.opacity} is out of range; use 0..1.`);
977
- out.opacity = u.opacity;
978
- }
979
- return out;
980
- }
981
- function stickerSurface(stickers, base, dirty, art) {
982
- const only = base && dirty ? new Set(dirty.flatMap((r) => _chunk474IQG74cjs.tilesInRect.call(void 0, r).map(({ tx, ty }) => _chunk474IQG74cjs.tileKey.call(void 0, tx, ty)))) : null;
983
- const live = /* @__PURE__ */ new Map();
984
- let scratch = null;
985
- const target = {
986
- offset: { x: 0, y: 0 },
987
- lockTransparency: false,
988
- selection: null,
989
- readTile: (tx, ty) => _nullishCoalesce(live.get(_chunk474IQG74cjs.tileKey.call(void 0, tx, ty)), () => ( null)),
990
- writeTile: (tx, ty) => {
991
- const k = _chunk474IQG74cjs.tileKey.call(void 0, tx, ty);
992
- if (only && !only.has(k)) return (scratch ??= new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE * 4)).fill(0);
993
- let t = live.get(k);
994
- if (!t) live.set(k, t = new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE * 4));
995
- return t;
996
- }
997
- };
998
- const reaches = (s) => !only || _chunk474IQG74cjs.tilesInRect.call(void 0, stickerRect(s)).some(({ tx, ty }) => only.has(_chunk474IQG74cjs.tileKey.call(void 0, tx, ty)));
999
- renderStickers(stickers.filter(reaches), target, art);
1000
- const tiles = /* @__PURE__ */ new Map();
1001
- if (only && base) {
1002
- for (const [k, t] of base.tiles) if (!only.has(k)) tiles.set(k, t);
1003
- }
1004
- for (const [k, d] of live) {
1005
- const t = _chunk474IQG74cjs.finishTile.call(void 0, d, 4, 0);
1006
- if (t) tiles.set(k, t);
1007
- }
1008
- return new (0, _chunk474IQG74cjs.TiledSurface)(4, tiles, 0);
1009
- }
1010
- function stickerAt(stickers, x, y, art) {
1011
- for (let i = stickers.length - 1; i >= 0; i--) {
1012
- const s = stickers[i];
1013
- const rad = s.size / 2;
1014
- const a = s.rotation * Math.PI / 180;
1015
- const dx = x - s.x;
1016
- const dy = y - s.y;
1017
- const u = (dx * Math.cos(a) + dy * Math.sin(a)) / rad;
1018
- const v = (-dx * Math.sin(a) + dy * Math.cos(a)) / rad;
1019
- if (Math.abs(u) > 1.05 || Math.abs(v) > 1.05) continue;
1020
- if (s.size < 16) return s;
1021
- const kind = stickerStamp(s);
1022
- if (kind) {
1023
- if (_chunkZEYMKDYScjs.stampCoverage.call(void 0, kind, u, v, s.seed) > 0.25) return s;
1024
- continue;
1025
- }
1026
- const pic = _optionalChain([art, 'optionalCall', _15 => _15(s.ref)]);
1027
- if (!pic) return s;
1028
- const scale = Math.max(pic.width, pic.height) / 2;
1029
- if (sampleArt(pic, u * scale + pic.width / 2, v * scale + pic.height / 2, [0, 0, 0, 0])[3] > 0.25) return s;
1030
- }
1031
- return null;
1032
- }
1033
-
1034
- // src/paint-editor/procedural.ts
1035
- var STEP_PX = 1.5;
1036
- var MarkStroke = (_class6 = class {
1037
- constructor(brush, options) {;_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);
1038
- this.brush = brush;
1039
- this.options = options;
1040
- this.sampler = options.paint ? new (0, _chunkI7NJPHWVcjs.PaintGradientSampler)(options.paint, _nullishCoalesce(options.seed, () => ( 0))) : null;
1041
- this.tipTable = _optionalChain([this, 'access', _16 => _16.sampler, 'optionalAccess', _17 => _17.acrossTip]) ? this.sampler.tipTable(_chunkI7NJPHWVcjs.TIP_GRADIENT_STEPS) : null;
1042
- this.smoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
1043
- this.copies = _optionalChain([options, 'access', _18 => _18.copies, 'optionalAccess', _19 => _19.length]) ? options.copies : [IDENTITY_COPY];
1044
- this.seed = _nullishCoalesce(options.seed, () => ( 0));
1045
- const fine = { ...brush, tip: { ...brush.tip, spacing: Math.max(0.01, Math.min(0.25, STEP_PX / Math.max(1, brush.tip.size))) } };
1046
- this.walkers = this.copies.map((c, i) => new PenDabWalker(fine, (_nullishCoalesce(options.seed, () => ( 0))) + i * 7919 | 0, c.linear));
1047
- this.depth = options.grain && !options.grain.flat ? Math.min(1, Math.max(0, options.paperDepth * brush.grain)) : 0;
1048
- }
1049
-
1050
-
1051
- __init25() {this.coverage = new (0, _chunkI7NJPHWVcjs.CoverageMap)()}
1052
- __init26() {this.dabCount = 0}
1053
-
1054
-
1055
-
1056
-
1057
- __init27() {this.buf = new Float32Array(0)}
1058
- __init28() {this.field = new Float32Array(0)}
1059
- __init29() {this.lastRaw = null}
1060
- __init30() {this.lastFed = null}
1061
-
1062
-
1063
-
1064
- /** The pen dab the marks being made belong to, and its gradient colour. */
1065
- __init31() {this.pen = null}
1066
- __init32() {this.penColor = { rgb: [0, 0, 0], alpha: 1 }}
1067
- push(points) {
1068
- const rects = [];
1069
- for (const raw of points) {
1070
- this.lastRaw = raw;
1071
- const p = this.smoother.push(raw);
1072
- this.lastFed = p;
1073
- this.copies.forEach((c, i) => {
1074
- const dabs = [];
1075
- this.walkers[i].push(c.map(p), dabs);
1076
- for (const d of dabs) this.step(i, d, rects);
1077
- });
1078
- }
1079
- return rects;
1080
- }
1081
- step(copy, d, rects) {
1082
- this.pen = d;
1083
- if (this.sampler && !this.tipTable) this.sampler.dab(d, this.penColor);
1084
- this.advance(copy, d, rects);
1085
- }
1086
- /** The colour of a mark at (x, y): its pen dab's, or across the tip by its offset from the pen. */
1087
- markColor(x, y) {
1088
- if (!this.sampler || !this.pen) return null;
1089
- if (!this.tipTable) return this.penColor;
1090
- const d = this.pen;
1091
- const u = ((x - d.x) * Math.sin(d.direction) + (y - d.y) * Math.cos(d.direction)) / Math.max(0.5, d.radius);
1092
- const last = this.tipTable.length - 1;
1093
- return this.tipTable[Math.min(last, Math.max(0, Math.round((u + 1) / 2 * last)))];
1094
- }
1095
- finish() {
1096
- const rects = [];
1097
- const raw = this.lastRaw;
1098
- const fed = this.lastFed;
1099
- this.copies.forEach((c, i) => {
1100
- const dabs = [];
1101
- if (raw && fed && (raw.x !== fed.x || raw.y !== fed.y)) this.walkers[i].push(c.map({ ...raw, pressure: fed.pressure }), dabs);
1102
- this.walkers[i].finish(dabs);
1103
- for (const d of dabs) this.step(i, d, rects);
1104
- });
1105
- return rects;
1106
- }
1107
- /** Stamps one round mark of radius `r` at (x, y) with flow `alpha` towards `opacity`. */
1108
- mark(x, y, r, alpha, opacity, pressure, rects) {
1109
- if (!(r > 0.05) || !(alpha > 0)) return;
1110
- const dab = { x, y, radius: Math.max(0.5, r), alpha: Math.min(1, alpha), opacity: Math.min(1, opacity), angle: 0, roundness: 1 };
1111
- const hardness = this.brush.tip.hardness;
1112
- const rect = _chunkI7NJPHWVcjs.dabRect.call(void 0, dab, hardness);
1113
- const n = rect.width * rect.height;
1114
- if (n <= 0) return;
1115
- const vals = this.scratch(n);
1116
- _chunkI7NJPHWVcjs.rasterizeDab.call(void 0, dab, hardness, rect, vals);
1117
- this.lay(rect, vals, dab.opacity, pressure, x, y, rects);
1118
- }
1119
- /**
1120
- * Stamps a textured mark: `kind` from the sticker stamps over a square of half-size `r`, turned
1121
- * `rotation` radians clockwise, with flow `alpha` towards `opacity`. Small marks are supersampled.
1122
- */
1123
- stamp(x, y, r, kind, rotation, seed, alpha, opacity, pressure, rects) {
1124
- if (!(r > 0.05) || !(alpha > 0)) return;
1125
- const reach = Math.max(0.5, r) * Math.SQRT2 + 1;
1126
- const rect = { x: Math.floor(x - reach), y: Math.floor(y - reach), width: 0, height: 0 };
1127
- rect.width = Math.ceil(x + reach) - rect.x;
1128
- rect.height = Math.ceil(y + reach) - rect.y;
1129
- const n = rect.width * rect.height;
1130
- const vals = this.scratch(n);
1131
- const ss = r < 10 ? 3 : 1;
1132
- const c = Math.cos(rotation);
1133
- const s = Math.sin(rotation);
1134
- const inv = 1 / Math.max(0.5, r);
1135
- const a = Math.min(1, alpha);
1136
- for (let yy = 0, k = 0; yy < rect.height; yy++) {
1137
- for (let xx = 0; xx < rect.width; xx++, k++) {
1138
- let sum = 0;
1139
- for (let sy = 0; sy < ss; sy++) {
1140
- for (let sx = 0; sx < ss; sx++) {
1141
- const dx = rect.x + xx + (sx + 0.5) / ss - x;
1142
- const dy = rect.y + yy + (sy + 0.5) / ss - y;
1143
- sum += _chunkZEYMKDYScjs.stampCoverage.call(void 0, kind, (dx * c + dy * s) * inv, (dy * c - dx * s) * inv, seed);
1144
- }
1145
- }
1146
- vals[k] = sum / (ss * ss) * a;
1147
- }
1148
- }
1149
- this.lay(rect, vals, Math.min(1, opacity), pressure, x, y, rects);
1150
- }
1151
- /** A scratch buffer of `n` values (reused; valid until the next mark). */
1152
- scratch(n) {
1153
- if (this.buf.length < n) this.buf = new Float32Array(n);
1154
- const vals = this.buf.subarray(0, n);
1155
- vals.fill(0);
1156
- return vals;
1157
- }
1158
- /**
1159
- * Lays coverage `vals` over `rect` towards `opacity`: paper grain, then effect paint or the
1160
- * colour of a mark at (x, y) (gradient paint), into the stroke's coverage.
1161
- */
1162
- lay(rect, vals, opacity, pressure, x, y, rects) {
1163
- const n = rect.width * rect.height;
1164
- if (n <= 0) return;
1165
- const grain = this.options.grain;
1166
- if (this.depth > 0 && grain) {
1167
- for (let yy = 0, k = 0; yy < rect.height; yy++)
1168
- for (let xx = 0; xx < rect.width; xx++, k++) if (vals[k] > 0) vals[k] *= _chunkI7NJPHWVcjs.grainCatch.call(void 0, grain.at(rect.x + xx + 0.5, rect.y + yy + 0.5), this.depth, pressure);
1169
- }
1170
- if (this.options.effect) {
1171
- if (this.field.length < n * 3) this.field = new Float32Array(n * 3);
1172
- const field = this.field.subarray(0, n * 3);
1173
- sampleEffectField(this.options.effect, rect, vals, field, this.pen);
1174
- this.coverage.accumulate(rect, vals, opacity, null, field);
1175
- } else {
1176
- const color = this.markColor(x, y);
1177
- if (color && color.alpha < 1) for (let k = 0; k < n; k++) vals[k] *= color.alpha;
1178
- this.coverage.accumulate(rect, vals, opacity, color ? color.rgb : null);
1179
- }
1180
- rects.push(rect);
1181
- this.dabCount++;
1182
- }
1183
- /** Marks along a segment, spaced by a fraction of the radius. */
1184
- segment(x0, y0, x1, y1, r, alpha, opacity, pressure, rects) {
1185
- const len = Math.hypot(x1 - x0, y1 - y0);
1186
- const step = Math.max(0.35, r * 0.5);
1187
- const n = Math.max(1, Math.ceil(len / step));
1188
- for (let i = 1; i <= n; i++) this.mark(x0 + (x1 - x0) * i / n, y0 + (y1 - y0) * i / n, r, alpha, opacity, pressure, rects);
1189
- }
1190
- }, _class6);
1191
- var PARTICLE_KINDS = ["string", "spring", "orbit", "flow", "elastic", "fuzzy"];
1192
- var PARTICLE_RENDERS = ["dot", "line", "sequence", "ribbon"];
1193
- var RIBBON_PAIRS = ["adjacent", "first-last"];
1194
- var DOT_TEXTURE_ORDERS = ["sequence", "random"];
1195
- var DOT_ROTATIONS = ["follow", "random", "fixed"];
1196
- var MAX_DOT_TEXTURES = 4;
1197
- var DEFAULT_PARTICLE = {
1198
- kind: "spring",
1199
- render: "line",
1200
- count: 12,
1201
- size: 0.08,
1202
- spread: 0.5,
1203
- stiffness: 0.4,
1204
- damping: 0.25,
1205
- speed: 0.5,
1206
- gravity: 0,
1207
- life: 0.5,
1208
- interval: 0.3,
1209
- ribbonFill: 0.5,
1210
- ribbonPairs: "adjacent",
1211
- waveAmplitude: 0,
1212
- waveLength: 60,
1213
- waveSpeed: 0,
1214
- wavePhaseJitter: 0,
1215
- dotTextures: [],
1216
- dotTextureOrder: "sequence",
1217
- dotRotation: "random"
1218
- };
1219
- var unit3 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
1220
- var oneOf = (v, all, d) => all.includes(v) ? v : d;
1221
- function normalizeParticle(raw = {}, base = DEFAULT_PARTICLE) {
1222
- const textures = Array.isArray(raw.dotTextures) ? raw.dotTextures.filter((t) => _chunkZEYMKDYScjs.STAMP_KINDS.includes(t)).slice(0, MAX_DOT_TEXTURES) : [..._nullishCoalesce(base.dotTextures, () => ( []))];
1223
- return {
1224
- kind: oneOf(raw.kind, PARTICLE_KINDS, base.kind),
1225
- render: oneOf(raw.render, PARTICLE_RENDERS, base.render),
1226
- count: typeof raw.count === "number" && Number.isFinite(raw.count) ? Math.min(64, Math.max(1, Math.round(raw.count))) : base.count,
1227
- size: unit3(raw.size, base.size),
1228
- spread: unit3(raw.spread, base.spread),
1229
- stiffness: unit3(raw.stiffness, base.stiffness),
1230
- damping: unit3(raw.damping, base.damping),
1231
- speed: unit3(raw.speed, base.speed),
1232
- gravity: typeof raw.gravity === "number" && Number.isFinite(raw.gravity) ? Math.min(1, Math.max(-1, raw.gravity)) : base.gravity,
1233
- life: unit3(raw.life, base.life),
1234
- interval: unit3(raw.interval, base.interval),
1235
- ribbonFill: unit3(raw.ribbonFill, _nullishCoalesce(base.ribbonFill, () => ( DEFAULT_PARTICLE.ribbonFill))),
1236
- ribbonPairs: oneOf(raw.ribbonPairs, RIBBON_PAIRS, _nullishCoalesce(base.ribbonPairs, () => ( DEFAULT_PARTICLE.ribbonPairs))),
1237
- waveAmplitude: unit3(raw.waveAmplitude, _nullishCoalesce(base.waveAmplitude, () => ( 0))),
1238
- waveLength: typeof raw.waveLength === "number" && Number.isFinite(raw.waveLength) ? Math.min(2e3, Math.max(4, raw.waveLength)) : _nullishCoalesce(base.waveLength, () => ( DEFAULT_PARTICLE.waveLength)),
1239
- waveSpeed: unit3(raw.waveSpeed, _nullishCoalesce(base.waveSpeed, () => ( 0))),
1240
- wavePhaseJitter: unit3(raw.wavePhaseJitter, _nullishCoalesce(base.wavePhaseJitter, () => ( 0))),
1241
- dotTextures: textures,
1242
- dotTextureOrder: oneOf(raw.dotTextureOrder, DOT_TEXTURE_ORDERS, _nullishCoalesce(base.dotTextureOrder, () => ( DEFAULT_PARTICLE.dotTextureOrder))),
1243
- dotRotation: oneOf(raw.dotRotation, DOT_ROTATIONS, _nullishCoalesce(base.dotRotation, () => ( DEFAULT_PARTICLE.dotRotation)))
1244
- };
1245
- }
1246
- var FUZZY_AMPLITUDE = 0.12;
1247
- var NOMINAL_MS_PER_PX = 2;
1248
- var RIBBON_SUB = 4;
1249
- function chainDamp(damp, k) {
1250
- return Math.min(damp, 1 / (1 + 0.8 * Math.sqrt(Math.max(0, k))) ** 2);
1251
- }
1252
- function addPolygonArea(pts, rect, out, weight = 1) {
1253
- const n = pts.length >> 1;
1254
- if (n < 3) return;
1255
- let y0 = Infinity;
1256
- let y1 = -Infinity;
1257
- for (let i = 1; i < pts.length; i += 2) {
1258
- if (pts[i] < y0) y0 = pts[i];
1259
- if (pts[i] > y1) y1 = pts[i];
1260
- }
1261
- const r0 = Math.max(0, Math.floor(y0) - rect.y);
1262
- const r1 = Math.min(rect.height - 1, Math.floor(y1) - rect.y);
1263
- const xs = [];
1264
- const w = weight / RIBBON_SUB;
1265
- for (let row = r0; row <= r1; row++) {
1266
- const base = row * rect.width;
1267
- for (let sub = 0; sub < RIBBON_SUB; sub++) {
1268
- const y = rect.y + row + (sub + 0.5) / RIBBON_SUB;
1269
- xs.length = 0;
1270
- for (let i = 0, j = n - 1; i < n; j = i++) {
1271
- const yi = pts[i * 2 + 1];
1272
- const yj = pts[j * 2 + 1];
1273
- if (yi <= y !== yj <= y) xs.push(pts[i * 2] + (y - yi) / (yj - yi) * (pts[j * 2] - pts[i * 2]));
1274
- }
1275
- if (xs.length < 2) continue;
1276
- xs.sort((a, b) => a - b);
1277
- for (let k = 0; k + 1 < xs.length; k += 2) {
1278
- const a = Math.max(rect.x, xs[k]);
1279
- const b = Math.min(rect.x + rect.width, xs[k + 1]);
1280
- if (!(b > a)) continue;
1281
- for (let px = Math.floor(a); px < b; px++) {
1282
- const cover = Math.min(b, px + 1) - Math.max(a, px);
1283
- if (cover > 0) out[base + px - rect.x] += cover * w;
1284
- }
1285
- }
1286
- }
1287
- }
1288
- }
1289
- function curl(x, y, seed) {
1290
- const n = (u, v) => {
1291
- const x0 = Math.floor(u);
1292
- const y0 = Math.floor(v);
1293
- const fx = u - x0;
1294
- const fy = v - y0;
1295
- const sx = fx * fx * (3 - 2 * fx);
1296
- const sy = fy * fy * (3 - 2 * fy);
1297
- const h = (a2, b2) => _chunk3K6HWEXZcjs.rand01.call(void 0, seed, a2 * 73856093 + b2 * 19349663, 41);
1298
- const a = h(x0, y0);
1299
- const b = h(x0 + 1, y0);
1300
- const c = h(x0, y0 + 1);
1301
- const d = h(x0 + 1, y0 + 1);
1302
- return a + (b - a) * sx + (c - a) * sy + (a - b - c + d) * sx * sy;
1303
- };
1304
- const e = 0.01;
1305
- return [(n(x, y + e) - n(x, y - e)) / (2 * e), -(n(x + e, y) - n(x - e, y)) / (2 * e)];
1306
- }
1307
- var ParticleStroke = (_class7 = class extends MarkStroke {
1308
-
1309
- /** Ribbon pairs per copy: particles in their order around the pen (non-flow kinds). */
1310
-
1311
-
1312
- __init33() {this.spawn = 0}
1313
- /** Textured dots drawn so far, per copy (sequence order, random picks). */
1314
-
1315
- /** Ribbon fill: the area each pixel has had so far this stroke (telescoped into the coverage). */
1316
- __init34() {this.filled = /* @__PURE__ */ new Map()}
1317
- __init35() {this.pts = []}
1318
- constructor(brush, options) {
1319
- super(brush, options);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);;
1320
- this.s = brush.particle;
1321
- this.sets = this.copies.map(() => []);
1322
- this.orders = this.copies.map(() => []);
1323
- this.dots = this.copies.map(() => 0);
1324
- }
1325
- /** Strand kinds: a chain of masses behind each particle. */
1326
- get strands() {
1327
- return this.s.kind === "string" || this.s.kind === "elastic" || this.s.kind === "fuzzy";
1328
- }
1329
- newParticle(x, y, ox, oy, k, phase, i, copy) {
1330
- const s = this.s;
1331
- const fuzzy = s.kind === "fuzzy";
1332
- const jitter = fuzzy ? 1 : s.wavePhaseJitter;
1333
- return {
1334
- x,
1335
- y,
1336
- vx: 0,
1337
- vy: 0,
1338
- ox,
1339
- oy,
1340
- k,
1341
- phase,
1342
- age: 0,
1343
- run: 0,
1344
- bead: 0,
1345
- alive: true,
1346
- wavePhase: jitter > 0 ? jitter * Math.PI * 2 * _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 43 + copy) : 0,
1347
- waveScale: fuzzy ? 0.6 + 0.8 * _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 47 + copy) : 1,
1348
- dx: x,
1349
- dy: y,
1350
- px: NaN,
1351
- py: NaN,
1352
- fade: 1
1353
- };
1354
- }
1355
- init(copy, d) {
1356
- const s = this.s;
1357
- const list = this.sets[copy];
1358
- const size = Math.max(1, d.radius * 2);
1359
- for (let i = 0; i < (s.kind === "flow" ? 0 : s.count); i++) {
1360
- const r = _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 3 + copy) * s.spread * size * 0.5;
1361
- const a = _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 5 + copy) * Math.PI * 2;
1362
- const ox = Math.cos(a) * r;
1363
- const oy = Math.sin(a) * r;
1364
- const p = this.newParticle(d.x + ox, d.y + oy, ox, oy, 0.6 + 0.8 * _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 7), a, i, copy);
1365
- if (this.strands) p.strand = Array.from({ length: 6 }, () => ({ x: d.x + ox, y: d.y + oy, vx: 0, vy: 0 }));
1366
- if (s.kind === "orbit") {
1367
- p.ox = Math.max(1, r + size * 0.15);
1368
- p.oy = 0;
1369
- }
1370
- list.push(p);
1371
- }
1372
- this.orders[copy] = list.map((_, i) => i).sort((i, j) => list[i].phase - list[j].phase || i - j);
1373
- }
1374
- /** The sideways wave of particle `p` at pen dab `d` (px along the normal to the pen's direction). */
1375
- wave(p, d, size) {
1376
- const s = this.s;
1377
- const amp = s.kind === "fuzzy" ? Math.max(FUZZY_AMPLITUDE, s.waveAmplitude) : s.waveAmplitude;
1378
- if (!(amp > 0)) return 0;
1379
- const ms = _nullishCoalesce(d.time, () => ( d.travel * NOMINAL_MS_PER_PX));
1380
- const phase = 2 * Math.PI * d.travel / (s.waveLength * p.waveScale) - s.waveSpeed * 0.025 * ms + p.wavePhase;
1381
- return amp * size * Math.sin(phase);
1382
- }
1383
- advance(copy, d, rects) {
1384
- const s = this.s;
1385
- let list = this.sets[copy];
1386
- if (d.index === 0 && !list.length) this.init(copy, d);
1387
- const size = Math.max(1, d.radius * 2);
1388
- const markR = Math.max(0.5, size * s.size / 2);
1389
- const k = 0.02 + 0.3 * s.stiffness;
1390
- const damp = 0.6 + 0.38 * (1 - s.damping);
1391
- const g = s.gravity * 0.15;
1392
- const op = d.opacity;
1393
- const al = Math.max(0.05, d.alpha);
1394
- const nx = Math.sin(d.direction);
1395
- const ny = Math.cos(d.direction);
1396
- if (s.kind === "flow") {
1397
- this.spawn += s.count / 8;
1398
- while (this.spawn >= 1) {
1399
- this.spawn -= 1;
1400
- const i = this.dabCount + list.length * 31 + Math.floor(d.index);
1401
- const a = _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 11 + copy) * Math.PI * 2;
1402
- const r = _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 13 + copy) * s.spread * size * 0.5;
1403
- list.push(this.newParticle(d.x + Math.cos(a) * r, d.y + Math.sin(a) * r, 0, 0, 1, a, i, copy));
1404
- }
1405
- }
1406
- const lifeSteps = 8 + 120 * s.life;
1407
- for (let i = 0; i < list.length; i++) {
1408
- const p = list[i];
1409
- if (!p.alive) continue;
1410
- switch (s.kind) {
1411
- case "spring": {
1412
- p.vx = (p.vx + (d.x + p.ox - p.x) * k * p.k) * damp;
1413
- p.vy = (p.vy + (d.y + p.oy - p.y) * k * p.k + g) * damp;
1414
- p.x += p.vx;
1415
- p.y += p.vy;
1416
- break;
1417
- }
1418
- case "string":
1419
- case "fuzzy": {
1420
- const strand = p.strand;
1421
- const dm = s.kind === "fuzzy" ? chainDamp(damp, k * p.k) : damp;
1422
- let ax = d.x + p.ox;
1423
- let ay = d.y + p.oy;
1424
- for (const m of strand) {
1425
- m.vx = (m.vx + (ax - m.x) * k * p.k + 0) * dm;
1426
- m.vy = (m.vy + (ay - m.y) * k * p.k + g) * dm;
1427
- m.x += m.vx;
1428
- m.y += m.vy;
1429
- ax = m.x;
1430
- ay = m.y;
1431
- }
1432
- p.x = strand[strand.length - 1].x;
1433
- p.y = strand[strand.length - 1].y;
1434
- break;
1435
- }
1436
- case "elastic": {
1437
- const strand = p.strand;
1438
- const rest = size * 0.12;
1439
- const ke = Math.min(0.9, (0.1 + 0.6 * s.stiffness) * p.k);
1440
- const dm = chainDamp(damp, ke);
1441
- let ax = d.x + p.ox;
1442
- let ay = d.y + p.oy;
1443
- for (const m of strand) {
1444
- const ex = ax - m.x;
1445
- const ey = ay - m.y;
1446
- const len = Math.hypot(ex, ey);
1447
- const pull = len > rest ? (len - rest) / len : 0;
1448
- m.vx = (m.vx + ex * pull * ke) * dm;
1449
- m.vy = (m.vy + ey * pull * ke + g) * dm;
1450
- m.x += m.vx;
1451
- m.y += m.vy;
1452
- ax = m.x;
1453
- ay = m.y;
1454
- }
1455
- p.x = strand[strand.length - 1].x;
1456
- p.y = strand[strand.length - 1].y;
1457
- break;
1458
- }
1459
- case "orbit": {
1460
- p.phase += (0.05 + 0.3 * s.speed) * (i % 2 ? -1 : 1) * (0.7 + 0.6 * _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 17));
1461
- p.x = d.x + Math.cos(p.phase) * p.ox;
1462
- p.y = d.y + Math.sin(p.phase) * p.ox + g * p.ox;
1463
- break;
1464
- }
1465
- case "flow": {
1466
- const [fx, fy] = curl(p.x / Math.max(8, size * 1.5), p.y / Math.max(8, size * 1.5), this.seed);
1467
- const push = size * 0.012 * (0.3 + 1.5 * s.speed);
1468
- const fl = Math.hypot(fx, fy) || 1;
1469
- p.vx = p.vx * 0.8 + fx / fl * push;
1470
- p.vy = p.vy * 0.8 + fy / fl * push + g * 0.2;
1471
- p.x += p.vx;
1472
- p.y += p.vy;
1473
- p.age++;
1474
- if (p.age > lifeSteps) p.alive = false;
1475
- break;
1476
- }
1477
- }
1478
- const w = this.wave(p, d, size);
1479
- const qx = w ? p.x + nx * w : p.x;
1480
- const qy = w ? p.y + ny * w : p.y;
1481
- const px = p.dx;
1482
- const py = p.dy;
1483
- p.px = p.dx;
1484
- p.py = p.dy;
1485
- p.dx = qx;
1486
- p.dy = qy;
1487
- const fade = s.kind === "flow" ? 1 - p.age / lifeSteps : 1;
1488
- p.fade = fade;
1489
- const moved = Math.hypot(qx - px, qy - py);
1490
- if (d.index === 0 && s.kind !== "flow") continue;
1491
- if (s.render === "dot") this.dot(copy, qx, qy, qx - px, qy - py, markR * fade, al * fade, op, d, rects);
1492
- else if (s.render === "line" || s.render === "ribbon") this.segment(px, py, qx, qy, markR * fade, al * fade, op, d.pressure, rects);
1493
- else {
1494
- p.run += moved;
1495
- const gap = Math.max(markR * 2, size * (0.05 + s.interval));
1496
- while (p.run >= gap) {
1497
- p.run -= gap;
1498
- p.bead++;
1499
- const t = moved > 0 ? 1 - p.run / Math.max(moved, 1e-6) : 1;
1500
- const bx = px + (qx - px) * Math.min(1, Math.max(0, t));
1501
- const by = py + (qy - py) * Math.min(1, Math.max(0, t));
1502
- this.dot(copy, bx, by, qx - px, qy - py, markR * (0.7 + 0.6 * (p.bead % 3 / 2)) * fade, al * fade, op, d, rects);
1503
- }
1504
- }
1505
- }
1506
- if (s.render === "ribbon" && !(d.index === 0 && s.kind !== "flow")) this.ribbon(copy, list, op, al, d, rects);
1507
- if (s.kind === "flow" && list.length > 512) {
1508
- list = list.filter((p) => p.alive);
1509
- this.sets[copy] = list;
1510
- }
1511
- }
1512
- /** A dot (or bead): round, or a textured stamp turned by the particle's motion (mx, my), at random or to the tip angle. */
1513
- dot(copy, x, y, mx, my, r, alpha, opacity, d, rects) {
1514
- const s = this.s;
1515
- const textures = s.dotTextures;
1516
- if (!textures.length) return this.mark(x, y, r, alpha, opacity, d.pressure, rects);
1517
- const n = this.dots[copy]++;
1518
- const pick = s.dotTextureOrder === "random" ? Math.min(textures.length - 1, Math.floor(_chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, n, 31 + copy) * textures.length)) : n % textures.length;
1519
- const rotation = s.dotRotation === "random" ? _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, n, 37 + copy) * Math.PI * 2 : s.dotRotation === "follow" ? mx || my ? Math.atan2(my, mx) : -d.direction : -this.brush.tip.angle * Math.PI / 180;
1520
- this.stamp(x, y, r, textures[pick], rotation, this.seed + n * 2654435761 >>> 0, alpha, opacity, d.pressure, rects);
1521
- }
1522
- /**
1523
- * Ribbon fill for one step: the band between each pair of joined particles, from where they
1524
- * were drawn last step to where they are drawn now. Bands of one step, and of consecutive steps,
1525
- * share edges exactly, so the fill has no seams; it is telescoped into the coverage
1526
- * (`telescope`), so it does not build up at the joins.
1527
- */
1528
- ribbon(copy, list, opacity, alpha, d, rects) {
1529
- const s = this.s;
1530
- if (!(s.ribbonFill > 0)) return;
1531
- const live = (p) => !!p && p.alive && !Number.isNaN(p.px);
1532
- let order;
1533
- if (s.kind === "flow") order = list.filter(live);
1534
- else order = this.orders[copy].map((i) => list[i]).filter(live);
1535
- if (order.length < 2) return;
1536
- const pairs = [];
1537
- if (s.ribbonPairs === "first-last") pairs.push([order[0], order[order.length - 1]]);
1538
- else for (let i = 0; i + 1 < order.length; i++) pairs.push([order[i], order[i + 1]]);
1539
- const pts = this.pts;
1540
- const ceil = Math.min(1, opacity * s.ribbonFill);
1541
- for (const [a, b] of pairs) {
1542
- pts.length = 0;
1543
- pts.push(a.px, a.py, a.dx, a.dy, b.dx, b.dy, b.px, b.py);
1544
- let x0 = Infinity;
1545
- let y0 = Infinity;
1546
- let x1 = -Infinity;
1547
- let y1 = -Infinity;
1548
- for (let i = 0; i < 8; i += 2) {
1549
- x0 = Math.min(x0, pts[i]);
1550
- x1 = Math.max(x1, pts[i]);
1551
- y0 = Math.min(y0, pts[i + 1]);
1552
- y1 = Math.max(y1, pts[i + 1]);
1553
- }
1554
- const rect = { x: Math.floor(x0), y: Math.floor(y0), width: Math.floor(x1) - Math.floor(x0) + 1, height: Math.floor(y1) - Math.floor(y0) + 1 };
1555
- const area = this.scratch(rect.width * rect.height);
1556
- addPolygonArea(pts, rect, area, Math.min(a.fade, b.fade) * alpha);
1557
- if (this.telescope(rect, area)) this.lay(rect, area, ceil, d.pressure, (a.dx + b.dx) / 2, (a.dy + b.dy) / 2, rects);
1558
- }
1559
- }
1560
- /**
1561
- * Turns the area newly filled over `rect` into coverage to lay towards the fill's ceiling: the
1562
- * share of what was left unfilled, so after any number of bands (in any grouping) a pixel's
1563
- * coverage is the ceiling times the area filled there. False when nothing was added.
1564
- */
1565
- telescope(rect, area) {
1566
- let any = false;
1567
- for (let yy = 0, k = 0; yy < rect.height; yy++) {
1568
- const gy = rect.y + yy;
1569
- const ty = Math.floor(gy / _chunk474IQG74cjs.TILE);
1570
- let t;
1571
- let tx = NaN;
1572
- for (let xx = 0; xx < rect.width; xx++, k++) {
1573
- const add = area[k];
1574
- if (!(add > 0)) continue;
1575
- const gx = rect.x + xx;
1576
- const ntx = Math.floor(gx / _chunk474IQG74cjs.TILE);
1577
- if (ntx !== tx || !t) {
1578
- tx = ntx;
1579
- const key = _chunk474IQG74cjs.tileKey.call(void 0, tx, ty);
1580
- t = this.filled.get(key);
1581
- if (!t) {
1582
- t = new Float32Array(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE);
1583
- this.filled.set(key, t);
1584
- }
1585
- }
1586
- const ti = (gy - ty * _chunk474IQG74cjs.TILE) * _chunk474IQG74cjs.TILE + (gx - tx * _chunk474IQG74cjs.TILE);
1587
- const before = t[ti];
1588
- const after = Math.min(1, before + add);
1589
- t[ti] = after;
1590
- area[k] = before < 1 ? (after - before) / (1 - before) : 0;
1591
- any = true;
1592
- }
1593
- }
1594
- return any;
1595
- }
1596
- }, _class7);
1597
- var DEFAULT_BRISTLE = { count: 32, stiffness: 0.5, splay: 0.5, thickness: 0.06, load: 0.6, jitter: 0.4 };
1598
- function normalizeBristle(raw = {}, base = DEFAULT_BRISTLE) {
1599
- return {
1600
- count: typeof raw.count === "number" && Number.isFinite(raw.count) ? Math.min(128, Math.max(3, Math.round(raw.count))) : base.count,
1601
- stiffness: unit3(raw.stiffness, base.stiffness),
1602
- splay: unit3(raw.splay, base.splay),
1603
- thickness: unit3(raw.thickness, base.thickness),
1604
- load: unit3(raw.load, base.load),
1605
- jitter: unit3(raw.jitter, base.jitter)
1606
- };
1607
- }
1608
- var BristleStroke = class extends MarkStroke {
1609
-
1610
-
1611
- constructor(brush, options) {
1612
- super(brush, options);
1613
- this.s = brush.bristle;
1614
- const s = this.s;
1615
- this.tufts = this.copies.map(
1616
- (_, c) => Array.from({ length: s.count }, (_2, i) => {
1617
- const a = _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 21 + c) * Math.PI * 2;
1618
- const r = Math.sqrt(_chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 23 + c));
1619
- return {
1620
- u: Math.cos(a) * r,
1621
- v: Math.sin(a) * r,
1622
- x: 0,
1623
- y: 0,
1624
- paint: 1 - s.jitter * 0.4 * _chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 25 + c),
1625
- thirst: 1 + s.jitter * (_chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 27 + c) * 2 - 1),
1626
- w: 1 + s.jitter * 0.6 * (_chunk3K6HWEXZcjs.rand01.call(void 0, this.seed, i, 29 + c) - 0.5),
1627
- started: false
1628
- };
1629
- })
1630
- );
1631
- }
1632
- advance(copy, d, rects) {
1633
- const s = this.s;
1634
- const tuft = this.tufts[copy];
1635
- const spread = d.radius * (0.55 + 0.9 * s.splay * d.pressure);
1636
- const ca = Math.cos(d.angle);
1637
- const sa = Math.sin(d.angle);
1638
- const ro = Math.max(0.15, d.roundness);
1639
- const tx = Math.cos(d.direction);
1640
- const ty = -Math.sin(d.direction);
1641
- const follow = 0.15 + 0.85 * s.stiffness;
1642
- const bend = d.radius * (1 - s.stiffness) * d.pressure * 0.8;
1643
- const lineR = Math.max(0.5, d.radius * 2 * s.thickness * 0.5);
1644
- const dryRate = 4e-3 * (1 - s.load);
1645
- for (const b of tuft) {
1646
- const lu = b.u * spread;
1647
- const lv = b.v * spread * ro;
1648
- const ox = lu * ca - lv * sa;
1649
- const oy = -(lu * sa + lv * ca);
1650
- const lag = bend * (1 - 0.5 * b.v);
1651
- const wx = d.x + ox - tx * lag;
1652
- const wy = d.y + oy - ty * lag;
1653
- if (!b.started) {
1654
- b.x = wx;
1655
- b.y = wy;
1656
- b.started = true;
1657
- continue;
1658
- }
1659
- const nx = b.x + (wx - b.x) * follow;
1660
- const ny = b.y + (wy - b.y) * follow;
1661
- const paint = Math.max(0, b.paint);
1662
- if (paint > 0.02) this.segment(b.x, b.y, nx, ny, lineR * b.w * (0.6 + 0.4 * d.pressure), d.alpha * Math.min(1, paint * 1.4), d.opacity, d.pressure, rects);
1663
- b.paint -= dryRate * b.thirst * Math.hypot(nx - b.x, ny - b.y);
1664
- b.x = nx;
1665
- b.y = ny;
1666
- }
1667
- }
1668
- };
1669
-
1670
- // src/paint-editor/brushes.ts
1671
- var PAINT_BRUSH_ENGINES = ["dab", "eraser", "mixer", "blender", "smudge", "wet", "impasto", "particle", "bristle", "sticker"];
1672
- var PAINT_BRUSH_CATEGORIES = ["pencil", "pastel", "charcoal", "airbrush", "marker", "ink", "paint", "eraser", "blender", "smudge", "mixer", "watercolor", "wet-tool", "oil", "impasto", "procedural", "bristle", "sticker", "light", "custom"];
1673
- var WET_TOOLS = ["paint", "water", "dry", "blow", "blend", "smudge", "blot"];
1674
- var WET_MEDIA = ["watercolor", "gouache", "oil"];
1675
- var DEFAULT_WET_BRUSH = { tool: "paint", water: 0.5, pigment: 0.5, body: 0, strength: 0.5, rewet: 0, medium: "watercolor", load: 1, thinner: 0 };
1676
- function normalizeWetBrush(raw = {}, base = DEFAULT_WET_BRUSH) {
1677
- return {
1678
- tool: WET_TOOLS.includes(raw.tool) ? raw.tool : base.tool,
1679
- water: unit4(raw.water, base.water),
1680
- pigment: unit4(raw.pigment, base.pigment),
1681
- body: unit4(raw.body, base.body),
1682
- strength: unit4(raw.strength, base.strength),
1683
- rewet: unit4(raw.rewet, base.rewet),
1684
- medium: WET_MEDIA.includes(raw.medium) ? raw.medium : _nullishCoalesce(base.medium, () => ( "watercolor")),
1685
- load: unit4(raw.load, _nullishCoalesce(base.load, () => ( 1))),
1686
- thinner: unit4(raw.thinner, _nullishCoalesce(base.thinner, () => ( 0)))
1687
- };
1688
- }
1689
- var IMPASTO_TOOLS = ["brush", "tube", "knife", "roller", "gloop"];
1690
- var DEFAULT_IMPASTO = { tool: "brush", thickness: 0.6, gloss: 0.4, metallic: 0, load: 0.9, thinner: 0, drag: 0.4, rim: 0, clarity: 0 };
1691
- var DEFAULT_LIGHT = { amount: 0, pressure: false };
1692
- function normalizeLight(raw = {}, base = DEFAULT_LIGHT) {
1693
- return { amount: unit4(raw.amount, base.amount), pressure: typeof raw.pressure === "boolean" ? raw.pressure : base.pressure };
1694
- }
1695
- function normalizeImpasto(raw = {}, base = DEFAULT_IMPASTO) {
1696
- return {
1697
- tool: IMPASTO_TOOLS.includes(raw.tool) ? raw.tool : base.tool,
1698
- thickness: unit4(raw.thickness, base.thickness),
1699
- gloss: unit4(raw.gloss, base.gloss),
1700
- metallic: unit4(raw.metallic, base.metallic),
1701
- load: unit4(raw.load, base.load),
1702
- thinner: unit4(raw.thinner, base.thinner),
1703
- drag: unit4(raw.drag, base.drag),
1704
- rim: unit4(raw.rim, _nullishCoalesce(base.rim, () => ( 0))),
1705
- clarity: unit4(raw.clarity, _nullishCoalesce(base.clarity, () => ( 0)))
1706
- };
1707
- }
1708
- var MULTI_COLOR_MODES = ["off", "tip", "dab"];
1709
- var DEFAULT_MULTI_COLOR = { mode: "off", colors: [], order: "sequence", length: 12, softness: 1 };
1710
- var DEFAULT_BEHAVIOR = { pickup: 0, mix: 0.5, dirty: false, mixing: "pigment", multicolor: DEFAULT_MULTI_COLOR };
1711
- var BRUSH_ROTATIONS = ["fixed", "stroke", "barrel"];
1712
- var DEFAULT_DYNAMICS = {
1713
- minSize: 0,
1714
- pressureFlow: 0,
1715
- tiltSize: 0,
1716
- tiltRoundness: 0,
1717
- tiltOpacity: 0,
1718
- velocitySize: 0,
1719
- velocityOpacity: 0,
1720
- sizeJitter: 0,
1721
- angleJitter: 0,
1722
- opacityJitter: 0,
1723
- scatter: 0
1724
- };
1725
- var unit4 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
1726
- var range = (v, lo, hi, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : d;
1727
- function normalizeDynamics(raw = {}, base = DEFAULT_DYNAMICS) {
1728
- return {
1729
- minSize: unit4(raw.minSize, base.minSize),
1730
- pressureFlow: unit4(raw.pressureFlow, base.pressureFlow),
1731
- tiltSize: unit4(raw.tiltSize, base.tiltSize),
1732
- tiltRoundness: unit4(raw.tiltRoundness, base.tiltRoundness),
1733
- tiltOpacity: unit4(raw.tiltOpacity, base.tiltOpacity),
1734
- velocitySize: range(raw.velocitySize, -1, 1, base.velocitySize),
1735
- velocityOpacity: range(raw.velocityOpacity, -1, 1, base.velocityOpacity),
1736
- sizeJitter: unit4(raw.sizeJitter, base.sizeJitter),
1737
- angleJitter: unit4(raw.angleJitter, base.angleJitter),
1738
- opacityJitter: unit4(raw.opacityJitter, base.opacityJitter),
1739
- scatter: range(raw.scatter, 0, 4, base.scatter)
1740
- };
1741
- }
1742
- var BRUSH_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
1743
- var HEX2 = /^#[0-9a-f]{6}$/i;
1744
- function normalizeMultiColor(raw = {}, base = DEFAULT_MULTI_COLOR) {
1745
- 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;
1746
- return {
1747
- mode: MULTI_COLOR_MODES.includes(raw.mode) ? raw.mode : base.mode,
1748
- colors: [...colors],
1749
- order: raw.order === "random" || raw.order === "sequence" ? raw.order : base.order,
1750
- length: typeof raw.length === "number" && Number.isFinite(raw.length) ? Math.min(500, Math.max(1, Math.round(raw.length))) : base.length,
1751
- softness: unit4(raw.softness, base.softness)
1752
- };
1753
- }
1754
- function normalizeBehavior(raw = {}, base = DEFAULT_BEHAVIOR) {
1755
- return {
1756
- pickup: unit4(raw.pickup, base.pickup),
1757
- mix: unit4(raw.mix, base.mix),
1758
- dirty: typeof raw.dirty === "boolean" ? raw.dirty : base.dirty,
1759
- mixing: raw.mixing === "rgb" || raw.mixing === "pigment" ? raw.mixing : base.mixing,
1760
- multicolor: normalizeMultiColor(_nullishCoalesce(raw.multicolor, () => ( {})), base.multicolor)
1761
- };
1762
- }
1763
- function isWetCapable(brush) {
1764
- return brush.engine === "dab" || brush.engine === "mixer" || brush.engine === "wet" || brush.engine === "impasto";
1765
- }
1766
- function isWetBrush(brush) {
1767
- return brush.engine === "wet";
1768
- }
1769
- function usesPaintExchange(brush, gradient = brush.paint !== null) {
1770
- return brush.engine === "dab" && (brush.behavior.pickup > 0 || gradient);
1771
- }
1772
- function brushTakesPaint(brush) {
1773
- if (brush.engine === "eraser" || brush.engine === "blender" || brush.engine === "smudge") return false;
1774
- if (brush.engine === "wet") return brush.wet.tool === "paint";
1775
- if (brush.engine === "mixer") return brush.loadsColor;
1776
- return true;
1777
- }
1778
- function paintToMultiColor(g) {
1779
- if (!g || g.space === "oklab" || g.stops.some((s) => s.opacity !== void 0 || s.midpoint !== void 0)) return null;
1780
- const colors = g.stops.map((s) => s.color);
1781
- const own = (list) => list.length === 2 && list[0] === "paint" && list[1] === "secondary" ? [] : list.some((c) => c === "paint" || c === "secondary") ? null : list;
1782
- const uniform = (n) => g.stops.every((s, k) => s.offset === k / n);
1783
- const softness = _nullishCoalesce(g.softness, () => ( 1));
1784
- if (g.map.by === "across-tip" && colors.length <= 8 && uniform(colors.length - 1)) {
1785
- const c = own(colors);
1786
- return c ? { mode: "tip", colors: c, order: "sequence", length: DEFAULT_MULTI_COLOR.length, softness } : null;
1787
- }
1788
- if (g.map.by === "random" && colors.length % 2 === 0 && colors.length <= 16) {
1789
- const n = colors.length / 2;
1790
- const pairs = Array.from({ length: n }, (_, k) => colors[2 * k]);
1791
- const hard = g.stops.every((s, i) => s.offset === (Math.floor(i / 2) + i % 2) / n && s.color === pairs[Math.floor(i / 2)]);
1792
- const c = hard ? own(pairs) : null;
1793
- return c ? { mode: "dab", colors: c, order: "random", length: _nullishCoalesce(g.map.every, () => ( 1)), softness } : null;
1794
- }
1795
- if (g.map.by === "distance" && g.map.unit === "dabs" && g.map.repeat === "repeat" && colors.length >= 3 && colors.length <= 9 && colors[0] === colors[colors.length - 1] && uniform(colors.length - 1)) {
1796
- const n = colors.length - 1;
1797
- const c = own(colors.slice(0, n));
1798
- const length = g.map.length / n;
1799
- return c && Number.isInteger(length) ? { mode: "dab", colors: c, order: "sequence", length, softness } : null;
1800
- }
1801
- return null;
1802
- }
1803
- function multiColorToPaint(mc, mixing) {
1804
- if (mc.mode === "off") return null;
1805
- const colors = mc.colors.length >= 2 ? mc.colors : ["paint", "secondary"];
1806
- const n = colors.length;
1807
- const space = mixing === "rgb" ? "srgb" : "pigment";
1808
- const soft = mc.softness < 1 ? { softness: mc.softness } : {};
1809
- if (mc.mode === "tip") return { type: "gradient", stops: colors.map((color, k) => ({ offset: k / (n - 1), color })), space, map: { by: "across-tip" }, ...soft };
1810
- if (mc.order === "random") {
1811
- const stops = colors.flatMap((color, k) => [
1812
- { offset: k / n, color },
1813
- { offset: (k + 1) / n, color }
1814
- ]);
1815
- return { type: "gradient", stops, space, map: { by: "random", every: mc.length } };
1816
- }
1817
- return { type: "gradient", stops: [...colors, colors[0]].map((color, k) => ({ offset: k / n, color })), space, map: { by: "distance", length: mc.length * n, repeat: "repeat", unit: "dabs" }, ...soft };
1818
- }
1819
- function normalizePaintBrush(raw = {}, base) {
1820
- const b = _nullishCoalesce(base, () => ( BASE_BRUSH));
1821
- const id = typeof raw.id === "string" && BRUSH_ID.test(raw.id) ? raw.id : b.id;
1822
- const engine = PAINT_BRUSH_ENGINES.includes(raw.engine) ? raw.engine : b.engine;
1823
- const rb = _nullishCoalesce(raw.behavior, () => ( {}));
1824
- const mixerIn = { ...b.mixer, ..._nullishCoalesce(raw.mixer, () => ( {})) };
1825
- if (engine === "mixer") {
1826
- if (rb.pickup !== void 0) mixerIn.wet = rb.pickup;
1827
- if (rb.mix !== void 0) mixerIn.mix = rb.mix;
1828
- if (rb.dirty !== void 0) mixerIn.cleanEachStroke = !rb.dirty;
1829
- if (rb.mixing !== void 0) mixerIn.mixing = rb.mixing;
1830
- }
1831
- const mixer = _chunkI7NJPHWVcjs.normalizeMixer.call(void 0, mixerIn);
1832
- let behavior = normalizeBehavior(rb, b.behavior);
1833
- if (engine === "mixer") behavior = { ...behavior, pickup: mixer.wet, mix: mixer.mix, dirty: !mixer.cleanEachStroke, mixing: mixer.mixing };
1834
- let paint = _nullishCoalesce(b.paint, () => ( null));
1835
- if (raw.paint !== void 0) {
1836
- const p = _chunkI7NJPHWVcjs.readPaintSource.call(void 0, raw.paint);
1837
- paint = p && p.type === "gradient" ? p : null;
1838
- } else if (rb.multicolor !== void 0) {
1839
- const mc = normalizeMultiColor(rb.multicolor, _nullishCoalesce(paintToMultiColor(paint), () => ( DEFAULT_MULTI_COLOR)));
1840
- paint = mc.mode === "off" ? paintToMultiColor(paint) ? null : paint : multiColorToPaint(mc, behavior.mixing);
1841
- }
1842
- behavior = { ...behavior, multicolor: _nullishCoalesce(paintToMultiColor(paint), () => ( { ...DEFAULT_MULTI_COLOR, colors: [] })) };
1843
- return {
1844
- id,
1845
- name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, 80) : b.name,
1846
- category: PAINT_BRUSH_CATEGORIES.includes(raw.category) ? raw.category : b.category,
1847
- engine,
1848
- tip: _chunkI7NJPHWVcjs.normalizeBrush.call(void 0, { ...b.tip, ..._nullishCoalesce(raw.tip, () => ( {})) }),
1849
- grain: unit4(raw.grain, b.grain),
1850
- pressureSmoothing: range(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
1851
- rotation: BRUSH_ROTATIONS.includes(raw.rotation) ? raw.rotation : b.rotation,
1852
- dynamics: normalizeDynamics(_nullishCoalesce(raw.dynamics, () => ( {})), b.dynamics),
1853
- mixer,
1854
- loadsColor: typeof raw.loadsColor === "boolean" ? raw.loadsColor : b.loadsColor,
1855
- behavior,
1856
- blender: _chunkI7NJPHWVcjs.normalizeBlender.call(void 0, _nullishCoalesce(raw.blender, () => ( {})), b.blender),
1857
- smudge: _chunkI7NJPHWVcjs.normalizeSmudge.call(void 0, _nullishCoalesce(raw.smudge, () => ( {})), b.smudge),
1858
- wet: normalizeWetBrush(_nullishCoalesce(raw.wet, () => ( {})), _nullishCoalesce(b.wet, () => ( DEFAULT_WET_BRUSH))),
1859
- impasto: normalizeImpasto(_nullishCoalesce(raw.impasto, () => ( {})), _nullishCoalesce(b.impasto, () => ( DEFAULT_IMPASTO))),
1860
- particle: normalizeParticle(_nullishCoalesce(raw.particle, () => ( {})), _nullishCoalesce(b.particle, () => ( DEFAULT_PARTICLE))),
1861
- bristle: normalizeBristle(_nullishCoalesce(raw.bristle, () => ( {})), _nullishCoalesce(b.bristle, () => ( DEFAULT_BRISTLE))),
1862
- sticker: normalizeStickerSettings(_nullishCoalesce(raw.sticker, () => ( {})), _nullishCoalesce(b.sticker, () => ( DEFAULT_STICKER))),
1863
- light: normalizeLight(_nullishCoalesce(raw.light, () => ( {})), _nullishCoalesce(b.light, () => ( DEFAULT_LIGHT))),
1864
- paint,
1865
- ...raw.builtin ? { builtin: true } : {}
1866
- };
1867
- }
1868
- var BASE_BRUSH = {
1869
- id: "round",
1870
- name: "Round",
1871
- category: "custom",
1872
- engine: "dab",
1873
- tip: { ..._chunkI7NJPHWVcjs.DEFAULT_BRUSH },
1874
- grain: 0,
1875
- pressureSmoothing: 0,
1876
- rotation: "fixed",
1877
- dynamics: { ...DEFAULT_DYNAMICS },
1878
- mixer: { ..._chunkI7NJPHWVcjs.DEFAULT_MIXER },
1879
- loadsColor: true,
1880
- behavior: { ...DEFAULT_BEHAVIOR, multicolor: { ...DEFAULT_MULTI_COLOR, colors: [] } },
1881
- blender: { ..._chunkI7NJPHWVcjs.DEFAULT_BLENDER },
1882
- smudge: { ..._chunkI7NJPHWVcjs.DEFAULT_SMUDGE },
1883
- wet: { ...DEFAULT_WET_BRUSH },
1884
- impasto: { ...DEFAULT_IMPASTO },
1885
- particle: { ...DEFAULT_PARTICLE },
1886
- bristle: { ...DEFAULT_BRISTLE },
1887
- sticker: { ...DEFAULT_STICKER, stamps: [...DEFAULT_STICKER.stamps] },
1888
- light: { ...DEFAULT_LIGHT },
1889
- paint: null
1890
- };
1891
- var builtin = (init) => ({ ...normalizePaintBrush(init), builtin: true });
1892
- var BUILTIN_BRUSHES = [
1893
- builtin({
1894
- id: "pencil-hb",
1895
- name: "HB pencil",
1896
- category: "pencil",
1897
- tip: { size: 4, hardness: 0.7, spacing: 0.08, opacity: 0.85, flow: 0.55, smoothing: 0.25, pressureSize: true, pressureOpacity: true },
1898
- grain: 0.85,
1899
- pressureSmoothing: 0.3,
1900
- dynamics: { minSize: 0.55, tiltSize: 0.9, tiltRoundness: 0.7, tiltOpacity: 0.5 }
1901
- }),
1902
- builtin({
1903
- id: "pencil-6b",
1904
- name: "6B pencil",
1905
- category: "pencil",
1906
- tip: { size: 7, hardness: 0.55, spacing: 0.07, opacity: 1, flow: 0.75, smoothing: 0.2, pressureSize: true, pressureOpacity: true },
1907
- grain: 0.7,
1908
- pressureSmoothing: 0.3,
1909
- dynamics: { minSize: 0.5, tiltSize: 1, tiltRoundness: 0.75, tiltOpacity: 0.35 }
1910
- }),
1911
- builtin({
1912
- id: "soft-pastel",
1913
- name: "Soft pastel",
1914
- category: "pastel",
1915
- rotation: "stroke",
1916
- tip: { size: 32, hardness: 0.55, spacing: 0.05, angle: 90, roundness: 0.55, opacity: 1, flow: 0.7, smoothing: 0.15, pressureSize: false, pressureOpacity: true },
1917
- grain: 1,
1918
- pressureSmoothing: 0.25,
1919
- dynamics: { sizeJitter: 0.06, angleJitter: 0.02 }
1920
- }),
1921
- builtin({
1922
- id: "oil-pastel",
1923
- name: "Oil pastel",
1924
- category: "pastel",
1925
- rotation: "stroke",
1926
- tip: { size: 26, hardness: 0.8, spacing: 0.05, angle: 90, roundness: 0.7, opacity: 1, flow: 0.9, smoothing: 0.15, pressureSize: true, pressureOpacity: false, blend: "pigment" },
1927
- grain: 0.5,
1928
- pressureSmoothing: 0.25,
1929
- dynamics: { minSize: 0.6 }
1930
- }),
1931
- builtin({
1932
- id: "charcoal-stick",
1933
- name: "Charcoal stick",
1934
- category: "charcoal",
1935
- rotation: "stroke",
1936
- tip: { size: 26, hardness: 0.4, spacing: 0.05, angle: 90, roundness: 0.35, opacity: 0.95, flow: 0.5, smoothing: 0.15, pressureSize: false, pressureOpacity: true },
1937
- grain: 0.95,
1938
- pressureSmoothing: 0.2,
1939
- dynamics: { sizeJitter: 0.1, angleJitter: 0.03, tiltSize: 0.6 }
1940
- }),
1941
- builtin({
1942
- id: "vine-charcoal",
1943
- name: "Vine charcoal",
1944
- category: "charcoal",
1945
- tip: { size: 10, hardness: 0.3, spacing: 0.06, opacity: 0.75, flow: 0.45, smoothing: 0.2, pressureSize: true, pressureOpacity: true },
1946
- grain: 0.9,
1947
- pressureSmoothing: 0.2,
1948
- dynamics: { minSize: 0.4, tiltSize: 0.8, tiltRoundness: 0.5, tiltOpacity: 0.3, sizeJitter: 0.08 }
1949
- }),
1950
- builtin({
1951
- id: "airbrush",
1952
- name: "Airbrush",
1953
- category: "airbrush",
1954
- tip: { size: 90, hardness: 0, spacing: 0.04, opacity: 1, flow: 0.06, smoothing: 0.3, pressureSize: false, pressureOpacity: false },
1955
- grain: 0,
1956
- pressureSmoothing: 0.3,
1957
- dynamics: { pressureFlow: 1, tiltSize: 0.5, tiltRoundness: 0.6 }
1958
- }),
1959
- builtin({
1960
- id: "marker",
1961
- name: "Chisel marker",
1962
- category: "marker",
1963
- tip: { size: 24, hardness: 0.85, spacing: 0.04, angle: 45, roundness: 0.35, opacity: 0.6, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: false, blend: "multiply" },
1964
- grain: 0.1
1965
- }),
1966
- builtin({
1967
- id: "ink-pen",
1968
- name: "Ink pen",
1969
- category: "ink",
1970
- tip: { size: 4, hardness: 1, spacing: 0.05, opacity: 1, flow: 1, smoothing: 0.5, pressureSize: true, pressureOpacity: false },
1971
- pressureSmoothing: 0.4,
1972
- dynamics: { minSize: 0.25, velocitySize: -0.45 }
1973
- }),
1974
- builtin({
1975
- id: "brush-pen",
1976
- name: "Brush pen",
1977
- category: "ink",
1978
- rotation: "stroke",
1979
- tip: { size: 14, hardness: 0.9, spacing: 0.04, roundness: 0.8, opacity: 1, flow: 1, smoothing: 0.5, pressureSize: true, pressureOpacity: false },
1980
- pressureSmoothing: 0.45,
1981
- dynamics: { minSize: 0.05, velocitySize: -0.3 }
1982
- }),
1983
- builtin({
1984
- id: "eraser-soft",
1985
- name: "Soft eraser",
1986
- category: "eraser",
1987
- engine: "eraser",
1988
- tip: { size: 40, hardness: 0.2, spacing: 0.08, opacity: 1, flow: 0.6, pressureSize: false, pressureOpacity: true }
1989
- }),
1990
- builtin({
1991
- id: "eraser-hard",
1992
- name: "Hard eraser",
1993
- category: "eraser",
1994
- engine: "eraser",
1995
- tip: { size: 16, hardness: 0.9, spacing: 0.06, opacity: 1, flow: 1, pressureSize: true, pressureOpacity: false },
1996
- grain: 0.3,
1997
- dynamics: { minSize: 0.5 }
1998
- }),
1999
- builtin({
2000
- id: "blender",
2001
- name: "Blender",
2002
- category: "blender",
2003
- engine: "blender",
2004
- loadsColor: false,
2005
- tip: { size: 34, hardness: 0.4, spacing: 0.1, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
2006
- blender: { strength: 0.5, distance: 0.35, mixing: "pigment" }
2007
- }),
2008
- builtin({
2009
- id: "smudge",
2010
- name: "Smudge",
2011
- category: "smudge",
2012
- engine: "smudge",
2013
- loadsColor: false,
2014
- tip: { size: 26, hardness: 0.6, spacing: 0.06, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
2015
- smudge: { strength: 0.75, length: 0.85, mixing: "rgb" }
2016
- }),
2017
- builtin({
2018
- id: "wet-smudge",
2019
- name: "Pigment smudge",
2020
- category: "smudge",
2021
- engine: "smudge",
2022
- loadsColor: false,
2023
- tip: { size: 30, hardness: 0.5, spacing: 0.06, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: true, pressureOpacity: true },
2024
- smudge: { strength: 0.7, length: 0.9, mixing: "pigment" },
2025
- dynamics: { minSize: 0.6 }
2026
- }),
2027
- builtin({
2028
- id: "dirty-round",
2029
- name: "Dirty round",
2030
- category: "paint",
2031
- tip: { size: 22, hardness: 0.65, spacing: 0.06, opacity: 1, flow: 0.7, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2032
- behavior: { pickup: 0.55, mix: 0.6, dirty: true, mixing: "pigment" },
2033
- dynamics: { minSize: 0.5 }
2034
- }),
2035
- builtin({
2036
- id: "multicolor-flat",
2037
- name: "Multi-colour flat",
2038
- category: "paint",
2039
- rotation: "stroke",
2040
- 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 },
2041
- behavior: { pickup: 0.15, mix: 0.4, mixing: "pigment" },
2042
- // The paint and second colours side by side across the tip.
2043
- paint: { type: "gradient", stops: [{ offset: 0, color: "paint" }, { offset: 1, color: "secondary" }], space: "pigment", map: { by: "across-tip" }, softness: 0.7 },
2044
- dynamics: { minSize: 0.6 }
2045
- }),
2046
- builtin({
2047
- id: "palette-dabber",
2048
- name: "Palette dabber",
2049
- category: "paint",
2050
- tip: { size: 18, hardness: 0.7, spacing: 0.35, opacity: 1, flow: 0.9, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
2051
- // From the paint colour to the second colour and back, every 6 dabs.
2052
- paint: { type: "gradient", stops: [{ offset: 0, color: "paint" }, { offset: 0.5, color: "secondary" }, { offset: 1, color: "paint" }], space: "pigment", map: { by: "distance", length: 12, repeat: "repeat", unit: "dabs" }, softness: 0.4 },
2053
- dynamics: { minSize: 0.5, sizeJitter: 0.15, scatter: 0.3 }
2054
- }),
2055
- builtin({
2056
- id: "gradient-round",
2057
- name: "Gradient round",
2058
- category: "paint",
2059
- tip: { size: 24, hardness: 0.8, spacing: 0.05, opacity: 1, flow: 0.9, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2060
- // Changes colour as it is dragged out: paint → second colour over 300 px, and back.
2061
- paint: { type: "gradient", stops: [{ offset: 0, color: "paint" }, { offset: 1, color: "secondary" }], space: "pigment", map: { by: "distance", length: 300, repeat: "mirror" } },
2062
- dynamics: { minSize: 0.5 }
2063
- }),
2064
- builtin({
2065
- id: "pressure-ombre",
2066
- name: "Pressure ombr\xE9",
2067
- category: "paint",
2068
- tip: { size: 26, hardness: 0.6, spacing: 0.05, opacity: 1, flow: 0.8, smoothing: 0.2, pressureSize: false, pressureOpacity: false },
2069
- // Light pressure lays the second colour, full pressure the paint colour.
2070
- paint: { type: "gradient", stops: [{ offset: 0, color: "secondary" }, { offset: 1, color: "paint" }], space: "pigment", map: { by: "pressure" } }
2071
- }),
2072
- builtin({
2073
- id: "rainbow-marker",
2074
- name: "Rainbow marker",
2075
- category: "marker",
2076
- rotation: "stroke",
2077
- tip: { size: 18, hardness: 0.9, spacing: 0.04, angle: 90, roundness: 0.4, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: false, pressureOpacity: false },
2078
- paint: {
2079
- type: "gradient",
2080
- stops: [
2081
- { offset: 0, color: "#e11d48" },
2082
- { offset: 0.2, color: "#f59e0b" },
2083
- { offset: 0.4, color: "#facc15" },
2084
- { offset: 0.6, color: "#22c55e" },
2085
- { offset: 0.8, color: "#3b82f6" },
2086
- { offset: 1, color: "#8b5cf6" }
2087
- ],
2088
- space: "oklab",
2089
- map: { by: "distance", length: 600, repeat: "repeat" }
2090
- }
2091
- }),
2092
- builtin({
2093
- id: "mixer-brush",
2094
- name: "Mixer Brush",
2095
- category: "mixer",
2096
- engine: "mixer",
2097
- tip: { size: 30, hardness: 0.7, spacing: 0.1, opacity: 1, flow: 0.5, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2098
- mixer: { wet: 0.5, load: 0.95, mix: 0.5, loadEachStroke: true, cleanEachStroke: true, mixing: "pigment" },
2099
- dynamics: { minSize: 0.5 }
2100
- }),
2101
- builtin({
2102
- id: "watercolor-round",
2103
- name: "Watercolour round",
2104
- category: "watercolor",
2105
- engine: "wet",
2106
- tip: { size: 28, hardness: 0.6, spacing: 0.12, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: true },
2107
- dynamics: { minSize: 0.35 },
2108
- behavior: { pickup: 0.55, mix: 0.5, mixing: "pigment" },
2109
- wet: { tool: "paint", water: 0.55, pigment: 0.5, body: 0, rewet: 0.2 }
2110
- }),
2111
- builtin({
2112
- id: "watercolor-wash",
2113
- name: "Wash brush",
2114
- category: "watercolor",
2115
- engine: "wet",
2116
- rotation: "stroke",
2117
- tip: { size: 90, hardness: 0.35, spacing: 0.1, angle: 90, roundness: 0.45, opacity: 1, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: true },
2118
- behavior: { pickup: 0.45, mix: 0.45, mixing: "pigment" },
2119
- wet: { tool: "paint", water: 0.8, pigment: 0.45, body: 0, rewet: 0.2 }
2120
- }),
2121
- builtin({
2122
- id: "gradient-wash",
2123
- name: "Gradient wash",
2124
- category: "watercolor",
2125
- engine: "wet",
2126
- rotation: "stroke",
2127
- tip: { size: 80, hardness: 0.35, spacing: 0.1, angle: 90, roundness: 0.5, opacity: 1, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: true },
2128
- behavior: { pickup: 0.4, mix: 0.4, mixing: "pigment" },
2129
- wet: { tool: "paint", water: 0.8, pigment: 0.45, body: 0, rewet: 0.2 },
2130
- // A graded wash: the paint colour runs into the second colour as the brush travels.
2131
- paint: { type: "gradient", stops: [{ offset: 0, color: "paint" }, { offset: 1, color: "secondary" }], space: "pigment", map: { by: "distance", length: 500, repeat: "mirror" } }
2132
- }),
2133
- builtin({
2134
- id: "watercolor-detail",
2135
- name: "Watercolour rigger",
2136
- category: "watercolor",
2137
- engine: "wet",
2138
- tip: { size: 7, hardness: 0.75, spacing: 0.1, opacity: 1, flow: 1, smoothing: 0.4, pressureSize: true, pressureOpacity: false },
2139
- dynamics: { minSize: 0.2 },
2140
- behavior: { pickup: 0.4, mix: 0.45, mixing: "pigment" },
2141
- wet: { tool: "paint", water: 0.4, pigment: 0.7, body: 0, rewet: 0.2 }
2142
- }),
2143
- builtin({
2144
- id: "gouache",
2145
- name: "Gouache",
2146
- category: "watercolor",
2147
- engine: "wet",
2148
- tip: { size: 26, hardness: 0.7, spacing: 0.1, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2149
- dynamics: { minSize: 0.5 },
2150
- behavior: { pickup: 0.65, mix: 0.6, mixing: "pigment" },
2151
- wet: { tool: "paint", water: 0.35, pigment: 0.8, body: 1, medium: "gouache", rewet: 0.3 }
2152
- }),
2153
- builtin({
2154
- id: "oil-round",
2155
- name: "Wet oil round",
2156
- category: "oil",
2157
- engine: "wet",
2158
- tip: { size: 24, hardness: 0.75, spacing: 0.08, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2159
- dynamics: { minSize: 0.5 },
2160
- behavior: { pickup: 0.6, mix: 0.55, mixing: "pigment" },
2161
- wet: { tool: "paint", water: 0.4, pigment: 0.9, body: 1, medium: "oil", load: 0.97 }
2162
- }),
2163
- builtin({
2164
- id: "oil-flat",
2165
- name: "Wet oil flat",
2166
- category: "oil",
2167
- engine: "wet",
2168
- rotation: "stroke",
2169
- tip: { size: 40, hardness: 0.85, spacing: 0.06, angle: 90, roundness: 0.35, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2170
- dynamics: { minSize: 0.6 },
2171
- behavior: { pickup: 0.65, mix: 0.6, mixing: "pigment" },
2172
- wet: { tool: "paint", water: 0.45, pigment: 0.9, body: 1, medium: "oil", load: 0.95 }
2173
- }),
2174
- builtin({
2175
- id: "impasto-brush",
2176
- name: "Impasto brush",
2177
- category: "impasto",
2178
- engine: "impasto",
2179
- rotation: "stroke",
2180
- tip: { size: 36, hardness: 0.8, spacing: 0.06, angle: 90, roundness: 0.55, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2181
- dynamics: { minSize: 0.55 },
2182
- behavior: { pickup: 0.4, mix: 0.5, mixing: "pigment" },
2183
- impasto: { tool: "brush", thickness: 0.6, gloss: 0.45, load: 0.92, drag: 0.4 }
2184
- }),
2185
- builtin({
2186
- id: "paint-tube",
2187
- name: "Paint tube",
2188
- category: "impasto",
2189
- engine: "impasto",
2190
- tip: { size: 22, hardness: 0.9, spacing: 0.05, opacity: 1, flow: 1, smoothing: 0.35, pressureSize: true, pressureOpacity: false },
2191
- dynamics: { minSize: 0.4 },
2192
- impasto: { tool: "tube", thickness: 0.9, gloss: 0.5, load: 1, drag: 0.1 }
2193
- }),
2194
- builtin({
2195
- id: "palette-knife",
2196
- name: "Palette knife",
2197
- category: "impasto",
2198
- engine: "impasto",
2199
- rotation: "stroke",
2200
- tip: { size: 48, hardness: 1, spacing: 0.05, angle: 90, roundness: 0.18, opacity: 1, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: false },
2201
- impasto: { tool: "knife", thickness: 0.3, gloss: 0.55, load: 1, drag: 0.85 }
2202
- }),
2203
- builtin({
2204
- id: "paint-roller",
2205
- name: "Roller",
2206
- category: "impasto",
2207
- engine: "impasto",
2208
- rotation: "stroke",
2209
- tip: { size: 70, hardness: 0.95, spacing: 0.04, angle: 90, roundness: 0.12, opacity: 1, flow: 1, smoothing: 0.4, pressureSize: false, pressureOpacity: false },
2210
- impasto: { tool: "roller", thickness: 0.25, gloss: 0.3, load: 0.9, drag: 0.15 }
2211
- }),
2212
- builtin({
2213
- id: "gloop-pen",
2214
- name: "Gloop pen",
2215
- category: "impasto",
2216
- engine: "impasto",
2217
- tip: { size: 14, hardness: 0.9, spacing: 0.05, opacity: 1, flow: 1, smoothing: 0.5, pressureSize: true, pressureOpacity: false },
2218
- dynamics: { minSize: 0.5 },
2219
- impasto: { tool: "gloop", thickness: 1, gloss: 0.9, load: 1, drag: 0 }
2220
- }),
2221
- builtin({
2222
- id: "metallic-paint",
2223
- name: "Metallic paint",
2224
- category: "impasto",
2225
- engine: "impasto",
2226
- rotation: "stroke",
2227
- tip: { size: 30, hardness: 0.8, spacing: 0.06, angle: 90, roundness: 0.6, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2228
- dynamics: { minSize: 0.5 },
2229
- behavior: { pickup: 0.2, mix: 0.4, mixing: "pigment" },
2230
- impasto: { tool: "brush", thickness: 0.45, gloss: 0.8, metallic: 1, load: 0.95, drag: 0.3 }
2231
- }),
2232
- builtin({
2233
- id: "hair-strands",
2234
- name: "Hair strands",
2235
- category: "procedural",
2236
- engine: "particle",
2237
- tip: { size: 40, hardness: 0.8, opacity: 0.9, flow: 0.6, smoothing: 0.2, pressureSize: true, pressureOpacity: true },
2238
- particle: { kind: "string", render: "line", count: 22, size: 0.025, spread: 1, stiffness: 0.08, damping: 0.3 }
2239
- }),
2240
- builtin({
2241
- id: "spring-ink",
2242
- name: "Spring ink",
2243
- category: "procedural",
2244
- engine: "particle",
2245
- tip: { size: 30, hardness: 0.9, opacity: 1, flow: 0.8, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
2246
- particle: { kind: "spring", render: "line", count: 5, size: 0.08, spread: 0.6, stiffness: 0.2, damping: 0.1 }
2247
- }),
2248
- builtin({
2249
- id: "orbit-curls",
2250
- name: "Orbit curls",
2251
- category: "procedural",
2252
- engine: "particle",
2253
- tip: { size: 36, hardness: 0.85, opacity: 0.9, flow: 0.7, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2254
- particle: { kind: "orbit", render: "line", count: 4, size: 0.05, spread: 0.6, speed: 0.45 }
2255
- }),
2256
- builtin({
2257
- id: "flow-fur",
2258
- name: "Flow fur",
2259
- category: "procedural",
2260
- engine: "particle",
2261
- tip: { size: 50, hardness: 0.7, opacity: 0.8, flow: 0.5, smoothing: 0.2, pressureSize: true, pressureOpacity: true },
2262
- particle: { kind: "flow", render: "line", count: 10, size: 0.03, spread: 0.6, speed: 0.5, life: 0.25 }
2263
- }),
2264
- builtin({
2265
- id: "bead-trail",
2266
- name: "Bead trail",
2267
- category: "procedural",
2268
- engine: "particle",
2269
- tip: { size: 34, hardness: 0.9, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2270
- particle: { kind: "spring", render: "sequence", count: 3, size: 0.18, spread: 0.5, stiffness: 0.3, damping: 0.3, interval: 0.4 }
2271
- }),
2272
- builtin({
2273
- id: "sparkle-dust",
2274
- name: "Sparkle dust",
2275
- category: "procedural",
2276
- engine: "particle",
2277
- tip: { size: 60, hardness: 0.6, opacity: 1, flow: 0.9, smoothing: 0.1, pressureSize: true, pressureOpacity: true },
2278
- particle: { kind: "flow", render: "dot", count: 6, size: 0.05, spread: 0.9, speed: 0.7, life: 0.4 }
2279
- }),
2280
- builtin({
2281
- id: "bristle-round",
2282
- name: "Bristle round",
2283
- category: "bristle",
2284
- engine: "bristle",
2285
- rotation: "stroke",
2286
- tip: { size: 30, hardness: 0.85, opacity: 1, flow: 0.9, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2287
- dynamics: { minSize: 0.4, tiltSize: 0.6, tiltRoundness: 0.6 },
2288
- bristle: { count: 28, stiffness: 0.45, splay: 0.6, thickness: 0.035, load: 0.7, jitter: 0.5 }
2289
- }),
2290
- builtin({
2291
- id: "bristle-flat",
2292
- name: "Bristle flat",
2293
- category: "bristle",
2294
- engine: "bristle",
2295
- rotation: "stroke",
2296
- tip: { size: 46, hardness: 0.85, angle: 90, roundness: 0.3, opacity: 1, flow: 0.9, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2297
- dynamics: { minSize: 0.5, tiltSize: 0.5 },
2298
- bristle: { count: 40, stiffness: 0.6, splay: 0.4, thickness: 0.025, load: 0.65, jitter: 0.45 }
2299
- }),
2300
- builtin({
2301
- id: "dry-brush",
2302
- name: "Dry brush",
2303
- category: "bristle",
2304
- engine: "bristle",
2305
- rotation: "stroke",
2306
- tip: { size: 44, hardness: 0.9, angle: 90, roundness: 0.45, opacity: 1, flow: 0.8, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2307
- grain: 0.6,
2308
- bristle: { count: 48, stiffness: 0.35, splay: 0.7, thickness: 0.05, load: 0.15, jitter: 0.8 }
2309
- }),
2310
- // Light paint: brushes that lay light with their colour (glow and spill; see light.ts).
2311
- builtin({
2312
- id: "glow-gel",
2313
- name: "Glow gel",
2314
- category: "light",
2315
- engine: "impasto",
2316
- tip: { size: 16, hardness: 0.9, spacing: 0.05, opacity: 1, flow: 1, smoothing: 0.5, pressureSize: true, pressureOpacity: false },
2317
- dynamics: { minSize: 0.6 },
2318
- impasto: { tool: "gloop", thickness: 1, gloss: 0.95, load: 1, drag: 0, rim: 0.8, clarity: 0.5 },
2319
- light: { amount: 0.85 }
2320
- }),
2321
- builtin({
2322
- id: "neon-marker",
2323
- name: "Neon marker",
2324
- category: "light",
2325
- tip: { size: 10, hardness: 0.9, spacing: 0.04, opacity: 1, flow: 1, smoothing: 0.45, pressureSize: false, pressureOpacity: false },
2326
- light: { amount: 1 }
2327
- }),
2328
- builtin({
2329
- id: "light-airbrush",
2330
- name: "Light airbrush",
2331
- category: "light",
2332
- tip: { size: 90, hardness: 0, spacing: 0.04, opacity: 1, flow: 0.06, smoothing: 0.3, pressureSize: false, pressureOpacity: false },
2333
- pressureSmoothing: 0.3,
2334
- dynamics: { pressureFlow: 1 },
2335
- light: { amount: 0.7, pressure: true }
2336
- }),
2337
- builtin({
2338
- id: "spark-trail",
2339
- name: "Spark trail",
2340
- category: "light",
2341
- engine: "particle",
2342
- tip: { size: 50, hardness: 0.7, opacity: 1, flow: 1, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
2343
- particle: { kind: "flow", render: "dot", count: 8, size: 0.06, spread: 0.7, speed: 0.9, life: 0.3 },
2344
- light: { amount: 1 }
2345
- }),
2346
- builtin({
2347
- // Silk of light: strands dragged behind the pen on slow waves, the bands between them filled.
2348
- id: "light-ribbon",
2349
- name: "Light ribbon",
2350
- category: "light",
2351
- engine: "particle",
2352
- tip: { size: 80, hardness: 0.8, opacity: 0.9, flow: 1, smoothing: 0.35, pressureSize: false, pressureOpacity: false },
2353
- particle: { kind: "string", render: "ribbon", count: 5, size: 0.015, spread: 1, stiffness: 0.05, damping: 0.3, ribbonFill: 0.45, waveAmplitude: 0.25, waveLength: 220, wavePhaseJitter: 0.25 },
2354
- light: { amount: 0.8 }
2355
- }),
2356
- builtin({
2357
- // Flames: short-lived bands thrown off the pen, rising and curling in the flow.
2358
- id: "flame-ribbon",
2359
- name: "Flame ribbon",
2360
- category: "light",
2361
- engine: "particle",
2362
- tip: { size: 50, hardness: 0.6, opacity: 0.85, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2363
- particle: { kind: "flow", render: "ribbon", count: 4, size: 0.012, spread: 0.5, speed: 0.6, gravity: -0.6, life: 0.2, ribbonFill: 0.5 },
2364
- light: { amount: 1 }
2365
- }),
2366
- builtin({
2367
- // A neon tube that lags and snaps back, with a slow shimmer.
2368
- id: "neon-strand",
2369
- name: "Neon strand",
2370
- category: "light",
2371
- engine: "particle",
2372
- tip: { size: 40, hardness: 0.9, opacity: 1, flow: 1, smoothing: 0.4, pressureSize: false, pressureOpacity: false },
2373
- particle: { kind: "elastic", render: "line", count: 3, size: 0.05, spread: 0.25, stiffness: 0.6, damping: 0.3, waveAmplitude: 0.06, waveLength: 120 },
2374
- light: { amount: 1 }
2375
- }),
2376
- builtin({
2377
- // Smoke lit from within: thin bands thrown into a slow, rising flow.
2378
- id: "smoke-ribbon",
2379
- name: "Smoke ribbon",
2380
- category: "light",
2381
- engine: "particle",
2382
- tip: { size: 70, hardness: 0.3, opacity: 0.45, flow: 1, smoothing: 0.3, pressureSize: true, pressureOpacity: false },
2383
- particle: { kind: "flow", render: "ribbon", count: 3, size: 5e-3, spread: 0.5, speed: 0.15, gravity: -0.15, life: 0.35, ribbonFill: 0.25, waveAmplitude: 0.1, waveLength: 140, wavePhaseJitter: 1 },
2384
- light: { amount: 0.3 }
2385
- }),
2386
- builtin({
2387
- // Out-of-focus lights: soft discs and rings spaced along springy paths.
2388
- id: "bokeh-dots",
2389
- name: "Bokeh dots",
2390
- category: "light",
2391
- engine: "particle",
2392
- tip: { size: 80, hardness: 0.7, opacity: 0.6, flow: 1, smoothing: 0.3, pressureSize: true, pressureOpacity: false },
2393
- particle: { kind: "spring", render: "sequence", count: 3, size: 0.3, spread: 1, stiffness: 0.15, damping: 0.4, interval: 1, dotTextures: ["dot", "dot", "ring"], dotTextureOrder: "random" },
2394
- light: { amount: 0.9 }
2395
- }),
2396
- builtin({
2397
- // Sparks: twinkles and stars thrown into the flow, spaced along it.
2398
- id: "twinkle-sparks",
2399
- name: "Twinkle sparks",
2400
- category: "light",
2401
- engine: "particle",
2402
- tip: { size: 50, hardness: 0.8, opacity: 1, flow: 1, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
2403
- particle: { kind: "flow", render: "sequence", count: 3, size: 0.25, spread: 0.8, speed: 0.9, life: 0.35, interval: 0.6, dotTextures: ["sparkle", "sparkle", "star"], dotTextureOrder: "random", dotRotation: "random" },
2404
- light: { amount: 1 }
2405
- }),
2406
- builtin({
2407
- id: "glow-wash",
2408
- name: "Glow wash",
2409
- category: "light",
2410
- engine: "wet",
2411
- tip: { size: 40, hardness: 0.55, spacing: 0.12, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: true },
2412
- dynamics: { minSize: 0.35 },
2413
- behavior: { pickup: 0.4, mix: 0.5, mixing: "pigment" },
2414
- wet: { tool: "paint", water: 0.65, pigment: 0.75, body: 0, rewet: 0.1 },
2415
- light: { amount: 0.7 }
2416
- }),
2417
- builtin({
2418
- // Fluorescent ink: a transparent chisel marker that multiplies (text under it stays dark) and
2419
- // glows a little brighter than the paper. Turn the layer's spill off (set_light_spill radius 0).
2420
- id: "highlighter",
2421
- name: "Highlighter",
2422
- category: "light",
2423
- tip: { size: 30, hardness: 0.85, spacing: 0.04, angle: 45, roundness: 0.35, opacity: 0.6, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: false, blend: "multiply" },
2424
- grain: 0.05,
2425
- light: { amount: 0.5 }
2426
- }),
2427
- builtin({
2428
- id: "sticker-stars",
2429
- name: "Star stickers",
2430
- category: "sticker",
2431
- engine: "sticker",
2432
- tip: { size: 36, opacity: 1, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2433
- dynamics: { minSize: 0.5 },
2434
- sticker: { stamps: ["star", "sparkle"], density: 0.35, sizeJitter: 0.5, rotationJitter: 1, scatter: 0.6, colorJitter: 0.3 }
2435
- }),
2436
- builtin({
2437
- id: "sticker-leaves",
2438
- name: "Leaf stickers",
2439
- category: "sticker",
2440
- engine: "sticker",
2441
- rotation: "stroke",
2442
- tip: { size: 40, opacity: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
2443
- dynamics: { minSize: 0.5 },
2444
- sticker: { stamps: ["leaf"], density: 0.5, sizeJitter: 0.4, rotationJitter: 0.35, scatter: 0.4, colorJitter: 0.35 }
2445
- }),
2446
- builtin({
2447
- id: "sticker-confetti",
2448
- name: "Confetti",
2449
- category: "sticker",
2450
- engine: "sticker",
2451
- tip: { size: 22, opacity: 1, smoothing: 0.1, pressureSize: false, pressureOpacity: false },
2452
- sticker: { stamps: ["confetti", "dot", "ring"], density: 0.7, sizeJitter: 0.5, rotationJitter: 1, scatter: 1.5, colorJitter: 1 }
2453
- }),
2454
- builtin({
2455
- id: "sticker-flowers",
2456
- name: "Flower stickers",
2457
- category: "sticker",
2458
- engine: "sticker",
2459
- tip: { size: 44, opacity: 1, smoothing: 0.2, pressureSize: true, pressureOpacity: false },
2460
- sticker: { stamps: ["flower", "heart"], density: 0.3, sizeJitter: 0.5, rotationJitter: 1, scatter: 0.7, colorJitter: 0.4 }
2461
- }),
2462
- builtin({
2463
- id: "sticker-splats",
2464
- name: "Paint splats",
2465
- category: "sticker",
2466
- engine: "sticker",
2467
- tip: { size: 50, opacity: 0.95, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
2468
- sticker: { stamps: ["splat"], density: 0.25, sizeJitter: 0.6, rotationJitter: 1, scatter: 0.8, colorJitter: 0.15 }
2469
- }),
2470
- builtin({
2471
- id: "add-water",
2472
- name: "Add water",
2473
- category: "wet-tool",
2474
- engine: "wet",
2475
- tip: { size: 40, hardness: 0.4, spacing: 0.12, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: true },
2476
- dynamics: { minSize: 0.5 },
2477
- wet: { tool: "water", water: 0.6, pigment: 0, rewet: 0.3 }
2478
- }),
2479
- builtin({
2480
- id: "hair-dryer",
2481
- name: "Dry",
2482
- category: "wet-tool",
2483
- engine: "wet",
2484
- tip: { size: 80, hardness: 0.1, spacing: 0.15, opacity: 1, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: true },
2485
- wet: { tool: "dry", strength: 0.5 }
2486
- }),
2487
- builtin({
2488
- id: "blow",
2489
- name: "Blow",
2490
- category: "wet-tool",
2491
- engine: "wet",
2492
- tip: { size: 50, hardness: 0.2, spacing: 0.1, opacity: 1, flow: 1, smoothing: 0.3, pressureSize: false, pressureOpacity: true },
2493
- wet: { tool: "blow", strength: 0.6 }
2494
- }),
2495
- builtin({
2496
- id: "wet-blend",
2497
- name: "Wet blend",
2498
- category: "wet-tool",
2499
- engine: "wet",
2500
- tip: { size: 36, hardness: 0.3, spacing: 0.1, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: false, pressureOpacity: true },
2501
- wet: { tool: "blend", strength: 0.4 }
2502
- }),
2503
- builtin({
2504
- id: "wet-drag",
2505
- name: "Wet smudge",
2506
- category: "wet-tool",
2507
- engine: "wet",
2508
- tip: { size: 30, hardness: 0.5, spacing: 0.08, opacity: 1, flow: 1, smoothing: 0.25, pressureSize: false, pressureOpacity: true },
2509
- wet: { tool: "smudge", strength: 0.6 }
2510
- }),
2511
- builtin({
2512
- id: "blot",
2513
- name: "Blot (erase wet)",
2514
- category: "wet-tool",
2515
- engine: "wet",
2516
- tip: { size: 40, hardness: 0.5, spacing: 0.12, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
2517
- wet: { tool: "blot", strength: 0.7 }
2518
- })
2519
- ];
2520
- var DEFAULT_BRUSH_ID = "pencil-6b";
2521
- function brushToJSON(brush) {
2522
- const { builtin: _b, ...rest } = brush;
2523
- return JSON.parse(JSON.stringify(rest));
2524
- }
2525
- function parseBrushLibrary(raw, problems = []) {
2526
- const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.brushes) ? raw.brushes : null;
2527
- if (!list) throw new Error('Not a brush library: expected {"format":"pixerate-brushes","version":1,"brushes":[...]} or an array of brushes.');
2528
- const out = [];
2529
- list.forEach((item, i) => {
2530
- if (!item || typeof item !== "object") {
2531
- problems.push(`brushes[${i}] is not an object; skipped.`);
2532
- return;
2533
- }
2534
- const r = item;
2535
- if (typeof r.id !== "string" || !BRUSH_ID.test(r.id)) {
2536
- problems.push(`brushes[${i}] has no valid id (lowercase letters, digits and dashes); skipped.`);
2537
- return;
2538
- }
2539
- if (typeof r.name !== "string" || !r.name.trim()) {
2540
- problems.push(`brushes[${i}] ("${r.id}") has no name; skipped.`);
2541
- return;
2542
- }
2543
- const { builtin: _b, ...rest } = r;
2544
- out.push(normalizePaintBrush(rest));
2545
- });
2546
- return out;
2547
- }
2548
-
2549
-
2550
-
2551
-
2552
-
2553
-
2554
-
2555
-
2556
-
2557
-
2558
-
2559
-
2560
-
2561
-
2562
-
2563
-
2564
-
2565
-
2566
-
2567
-
2568
-
2569
-
2570
-
2571
-
2572
-
2573
-
2574
-
2575
-
2576
-
2577
-
2578
-
2579
-
2580
-
2581
-
2582
-
2583
-
2584
-
2585
-
2586
-
2587
-
2588
-
2589
-
2590
-
2591
-
2592
-
2593
-
2594
-
2595
-
2596
-
2597
-
2598
-
2599
-
2600
-
2601
-
2602
-
2603
-
2604
-
2605
-
2606
-
2607
-
2608
-
2609
-
2610
-
2611
-
2612
-
2613
-
2614
-
2615
-
2616
-
2617
-
2618
-
2619
-
2620
-
2621
-
2622
-
2623
-
2624
-
2625
-
2626
-
2627
-
2628
-
2629
-
2630
-
2631
-
2632
-
2633
-
2634
-
2635
- exports.EFFECT_STRIP_HEIGHT = EFFECT_STRIP_HEIGHT; exports.EFFECT_MAP_REPEATS = EFFECT_MAP_REPEATS; exports.effectPaintSize = effectPaintSize; exports.readEffectStrokeMap = readEffectStrokeMap; exports.effectPaintKey = effectPaintKey; exports.isPaintEffectPaint = isPaintEffectPaint; exports.readPaintEffectPaint = readPaintEffectPaint; exports.sampleEffectField = sampleEffectField; exports.sampleEffectAt = sampleEffectAt; exports.effectDabField = effectDabField; exports.PAPER_FAMILIES = PAPER_FAMILIES; exports.PAPER_LIBRARY = PAPER_LIBRARY; exports.DEFAULT_PAPER = DEFAULT_PAPER; exports.paperPreset = paperPreset; exports.clonePaper = clonePaper; exports.normalizePaper = normalizePaper; exports.hexToRgb8 = hexToRgb8; exports.renderPaper = renderPaper; exports.penOrientation = penOrientation; exports.tiltToOrientation = tiltToOrientation; exports.PenSmoother = PenSmoother; exports.IDENTITY_COPY = IDENTITY_COPY; exports.PenDabWalker = PenDabWalker; exports.DryStroke = DryStroke; exports.mixerTip = mixerTip; exports.toStrokePoint = toStrokePoint; exports.STICKER_ART_SIZE = STICKER_ART_SIZE; exports.defaultStickerRasterizer = defaultStickerRasterizer; exports.StickerArtCache = StickerArtCache; exports.sampleArt = sampleArt; exports.MAX_STICKER_STAMPS = MAX_STICKER_STAMPS; exports.DEFAULT_STICKER = DEFAULT_STICKER; exports.normalizeStickerSettings = normalizeStickerSettings; exports.stickerRect = stickerRect; exports.stickerStamp = stickerStamp; exports.drawSticker = drawSticker; exports.StickerSpray = StickerSpray; exports.renderStickers = renderStickers; exports.normalizeStickerUpdate = normalizeStickerUpdate; exports.stickerSurface = stickerSurface; exports.stickerAt = stickerAt; exports.PARTICLE_KINDS = PARTICLE_KINDS; exports.PARTICLE_RENDERS = PARTICLE_RENDERS; exports.RIBBON_PAIRS = RIBBON_PAIRS; exports.DOT_TEXTURE_ORDERS = DOT_TEXTURE_ORDERS; exports.DOT_ROTATIONS = DOT_ROTATIONS; exports.MAX_DOT_TEXTURES = MAX_DOT_TEXTURES; exports.DEFAULT_PARTICLE = DEFAULT_PARTICLE; exports.normalizeParticle = normalizeParticle; exports.addPolygonArea = addPolygonArea; exports.ParticleStroke = ParticleStroke; exports.DEFAULT_BRISTLE = DEFAULT_BRISTLE; exports.normalizeBristle = normalizeBristle; exports.BristleStroke = BristleStroke; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.WET_TOOLS = WET_TOOLS; exports.WET_MEDIA = WET_MEDIA; exports.DEFAULT_WET_BRUSH = DEFAULT_WET_BRUSH; exports.normalizeWetBrush = normalizeWetBrush; exports.IMPASTO_TOOLS = IMPASTO_TOOLS; exports.DEFAULT_IMPASTO = DEFAULT_IMPASTO; exports.DEFAULT_LIGHT = DEFAULT_LIGHT; exports.normalizeLight = normalizeLight; exports.normalizeImpasto = normalizeImpasto; exports.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.normalizeDynamics = normalizeDynamics; exports.BRUSH_ID = BRUSH_ID; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizeBehavior = normalizeBehavior; exports.isWetCapable = isWetCapable; exports.isWetBrush = isWetBrush; exports.usesPaintExchange = usesPaintExchange; exports.brushTakesPaint = brushTakesPaint; exports.paintToMultiColor = paintToMultiColor; exports.multiColorToPaint = multiColorToPaint; exports.normalizePaintBrush = normalizePaintBrush; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.brushToJSON = brushToJSON; exports.parseBrushLibrary = parseBrushLibrary;