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