@pixerate/editor 0.135.0 → 0.137.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 (164) hide show
  1. package/dist/animation-editor/index.cjs +63 -55
  2. package/dist/animation-editor/index.cjs.map +1 -1
  3. package/dist/animation-editor/index.d.cts +8 -5
  4. package/dist/animation-editor/index.d.ts +8 -5
  5. package/dist/animation-editor/index.js +10 -2
  6. package/dist/animation-editor/index.js.map +1 -1
  7. package/dist/{bezier-J8i4uqSH.d.cts → bezier-CZ2AhjID.d.cts} +1 -1
  8. package/dist/{bezier-lNEOp0Jx.d.ts → bezier-Dywv3Bw8.d.ts} +1 -1
  9. package/dist/blending-D7jCmjO6.d.cts +1026 -0
  10. package/dist/blending-NgF3qo3d.d.ts +1026 -0
  11. package/dist/brushes-CnvZSYYU.d.cts +744 -0
  12. package/dist/brushes-DJmUbIgz.d.ts +744 -0
  13. package/dist/{brushstrokeRender-SII6MG4R.js → brushstrokeRender-2FAXA3XC.js} +13 -16
  14. package/dist/{brushstrokeRender-SII6MG4R.js.map → brushstrokeRender-2FAXA3XC.js.map} +1 -1
  15. package/dist/{brushstrokeRender-JNHVEUFG.cjs → brushstrokeRender-IMFY3FF6.cjs} +16 -19
  16. package/dist/brushstrokeRender-IMFY3FF6.cjs.map +1 -0
  17. package/dist/canvas/index.cjs +10 -10
  18. package/dist/canvas/index.cjs.map +1 -1
  19. package/dist/canvas/index.d.cts +10 -7
  20. package/dist/canvas/index.d.ts +10 -7
  21. package/dist/canvas/index.js +4 -4
  22. package/dist/{chunk-W4LNKTHT.cjs → chunk-2XS6LTUP.cjs} +63 -63
  23. package/dist/{chunk-W4LNKTHT.cjs.map → chunk-2XS6LTUP.cjs.map} +1 -1
  24. package/dist/{chunk-HHTCDFZC.js → chunk-3D77LOBM.js} +234 -136
  25. package/dist/chunk-3D77LOBM.js.map +1 -0
  26. package/dist/{chunk-BR7EKQUP.js → chunk-3G6J7UFV.js} +322 -2
  27. package/dist/chunk-3G6J7UFV.js.map +1 -0
  28. package/dist/{chunk-62XUO5LB.js → chunk-3X6IAJUQ.js} +2 -2
  29. package/dist/{chunk-EM3FMJWE.js → chunk-5FSRTP6E.js} +320 -3
  30. package/dist/chunk-5FSRTP6E.js.map +1 -0
  31. package/dist/{chunk-Z2H7KECK.cjs → chunk-5OOIUXNJ.cjs} +345 -247
  32. package/dist/chunk-5OOIUXNJ.cjs.map +1 -0
  33. package/dist/{chunk-7RJ6P7MI.js → chunk-6S6362D2.js} +2 -2
  34. package/dist/chunk-A3BVTI3G.js +150 -0
  35. package/dist/chunk-A3BVTI3G.js.map +1 -0
  36. package/dist/{chunk-24ATT2PE.cjs → chunk-AEAAN4AD.cjs} +120 -120
  37. package/dist/{chunk-24ATT2PE.cjs.map → chunk-AEAAN4AD.cjs.map} +1 -1
  38. package/dist/chunk-ECCXGELC.cjs +727 -0
  39. package/dist/chunk-ECCXGELC.cjs.map +1 -0
  40. package/dist/{chunk-SVYE5HIM.js → chunk-EQQMGO42.js} +2 -2
  41. package/dist/{chunk-FHPVDTI2.cjs → chunk-F2Y4E4GU.cjs} +22 -22
  42. package/dist/{chunk-FHPVDTI2.cjs.map → chunk-F2Y4E4GU.cjs.map} +1 -1
  43. package/dist/{chunk-OXSV64JX.js → chunk-G6IKY6PL.js} +4 -4
  44. package/dist/{chunk-UYQUPUR6.js → chunk-HUO3TI4C.js} +3 -3
  45. package/dist/chunk-I52DRGGJ.js +727 -0
  46. package/dist/chunk-I52DRGGJ.js.map +1 -0
  47. package/dist/{chunk-DDSO4HS7.cjs → chunk-KEENUAFN.cjs} +2 -2
  48. package/dist/{chunk-DDSO4HS7.cjs.map → chunk-KEENUAFN.cjs.map} +1 -1
  49. package/dist/{chunk-2HC4DRF4.js → chunk-LLFHCSU2.js} +2 -2
  50. package/dist/{chunk-J5BA3BQ6.cjs → chunk-O7LUD7MH.cjs} +323 -3
  51. package/dist/chunk-O7LUD7MH.cjs.map +1 -0
  52. package/dist/{chunk-ILYFOJUN.js → chunk-OIBIRXRG.js} +3 -3
  53. package/dist/{chunk-FWEO3HSL.js → chunk-PQQF5KOW.js} +8 -8
  54. package/dist/{chunk-T6TV6HY6.cjs → chunk-PRSRYHQD.cjs} +36 -36
  55. package/dist/{chunk-T6TV6HY6.cjs.map → chunk-PRSRYHQD.cjs.map} +1 -1
  56. package/dist/chunk-Q6OUXEK3.cjs +150 -0
  57. package/dist/chunk-Q6OUXEK3.cjs.map +1 -0
  58. package/dist/{chunk-6GFRKLLS.cjs → chunk-QBWEH3IM.cjs} +429 -112
  59. package/dist/chunk-QBWEH3IM.cjs.map +1 -0
  60. package/dist/{chunk-ESIWZMMK.js → chunk-SYT2TLQF.js} +35 -37
  61. package/dist/{chunk-ESIWZMMK.js.map → chunk-SYT2TLQF.js.map} +1 -1
  62. package/dist/{chunk-YT3JL7YM.cjs → chunk-TLCTTQFF.cjs} +53 -53
  63. package/dist/{chunk-YT3JL7YM.cjs.map → chunk-TLCTTQFF.cjs.map} +1 -1
  64. package/dist/{chunk-P3RQ5UJE.cjs → chunk-UTQKC66X.cjs} +4 -4
  65. package/dist/{chunk-P3RQ5UJE.cjs.map → chunk-UTQKC66X.cjs.map} +1 -1
  66. package/dist/{chunk-J3B45HTA.cjs → chunk-XB25WOC2.cjs} +6 -6
  67. package/dist/{chunk-J3B45HTA.cjs.map → chunk-XB25WOC2.cjs.map} +1 -1
  68. package/dist/{chunk-YAFS2QFZ.cjs → chunk-XN2NBLV5.cjs} +313 -315
  69. package/dist/chunk-XN2NBLV5.cjs.map +1 -0
  70. package/dist/effects/index.d.cts +7 -4
  71. package/dist/effects/index.d.ts +7 -4
  72. package/dist/{export-DMeG6E06.d.ts → export-41rL6Cne.d.ts} +1 -1
  73. package/dist/{export-AZSwX087.d.cts → export-CyJdfP_Z.d.cts} +5 -4
  74. package/dist/{export-KzubBKo7.d.cts → export-D9mXp05Q.d.cts} +1 -1
  75. package/dist/{export-BDkyDNM8.d.ts → export-DwnZ_hvQ.d.ts} +5 -4
  76. package/dist/extensions/index.cjs +3 -3
  77. package/dist/extensions/index.cjs.map +1 -1
  78. package/dist/extensions/index.js +2 -2
  79. package/dist/generation/index.cjs +150 -945
  80. package/dist/generation/index.cjs.map +1 -1
  81. package/dist/generation/index.d.cts +3 -423
  82. package/dist/generation/index.d.ts +3 -423
  83. package/dist/generation/index.js +59 -854
  84. package/dist/generation/index.js.map +1 -1
  85. package/dist/grammar/index.cjs +3 -3
  86. package/dist/grammar/index.cjs.map +1 -1
  87. package/dist/grammar/index.js +2 -2
  88. package/dist/{helpers-Coa4e2j_.d.ts → helpers-C-RgeyvH.d.ts} +25 -8
  89. package/dist/{helpers-DHl28yLO.d.cts → helpers-v6GMazzg.d.cts} +25 -8
  90. package/dist/image-editor/index.cjs +986 -986
  91. package/dist/image-editor/index.cjs.map +1 -1
  92. package/dist/image-editor/index.d.cts +15 -11
  93. package/dist/image-editor/index.d.ts +15 -11
  94. package/dist/image-editor/index.js +94 -94
  95. package/dist/index.cjs +28 -28
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.js +36 -36
  98. package/dist/{opentype-BWR7Qv8W.d.cts → opentype-KR_jj6b_.d.cts} +1 -1
  99. package/dist/{opentype-Dfcx6wFx.d.ts → opentype-a1KAW-qs.d.ts} +1 -1
  100. package/dist/paint/index.cjs +5 -8
  101. package/dist/paint/index.cjs.map +1 -1
  102. package/dist/paint/index.d.cts +4 -2
  103. package/dist/paint/index.d.ts +4 -2
  104. package/dist/paint/index.js +15 -18
  105. package/dist/paint-editor/index.cjs +405 -403
  106. package/dist/paint-editor/index.cjs.map +1 -1
  107. package/dist/paint-editor/index.d.cts +15 -748
  108. package/dist/paint-editor/index.d.ts +15 -748
  109. package/dist/paint-editor/index.js +79 -77
  110. package/dist/paint-editor/index.js.map +1 -1
  111. package/dist/{pathfinder-D8KQ6blQ.d.cts → pathfinder-BHS4vQNm.d.cts} +1 -1
  112. package/dist/{pathfinder-Celbi3e6.d.ts → pathfinder-BVvTIBMo.d.ts} +1 -1
  113. package/dist/{pool-8JZGWHvR.d.ts → pool-CZQOrSXZ.d.ts} +1 -1
  114. package/dist/{pool-BfuxBKTd.d.cts → pool-FjZZmZ80.d.cts} +1 -1
  115. package/dist/provenance-DmlcgdzT.d.cts +426 -0
  116. package/dist/provenance-uh5r2TQ8.d.ts +426 -0
  117. package/dist/rand-8xbUb6ur.d.ts +1118 -0
  118. package/dist/rand-Cw9VLrVC.d.cts +1118 -0
  119. package/dist/raster/index.cjs +17 -19
  120. package/dist/raster/index.cjs.map +1 -1
  121. package/dist/raster/index.js +9 -11
  122. package/dist/raster/index.js.map +1 -1
  123. package/dist/{svg-CxxqG3_W.d.ts → svg-CSz3mzkw.d.ts} +2 -2
  124. package/dist/{svg-pwXWq6s2.d.cts → svg-DTb40ICU.d.cts} +2 -2
  125. package/dist/{tools-y1M6O3UT.d.ts → tools-B0wXP5cF.d.ts} +1 -1
  126. package/dist/{tools-3EBjR91b.d.cts → tools-Ca0kI1qo.d.cts} +1 -1
  127. package/dist/vector/index.cjs +2698 -1674
  128. package/dist/vector/index.cjs.map +1 -1
  129. package/dist/vector/index.d.cts +390 -72
  130. package/dist/vector/index.d.ts +390 -72
  131. package/dist/vector/index.js +1665 -641
  132. package/dist/vector/index.js.map +1 -1
  133. package/dist/video-editor/index.d.cts +7 -4
  134. package/dist/video-editor/index.d.ts +7 -4
  135. package/dist/{warp-Ceqdz7kj.d.cts → warp-DTOp0lON.d.cts} +1 -1
  136. package/dist/{warp-C1gwu2G_.d.ts → warp-pd7hrH4T.d.ts} +1 -1
  137. package/package.json +1 -1
  138. package/skills/pixerate-vector/SKILL.md +22 -0
  139. package/dist/brushstrokeRender-JNHVEUFG.cjs.map +0 -1
  140. package/dist/chunk-64PYNEHJ.js +0 -340
  141. package/dist/chunk-64PYNEHJ.js.map +0 -1
  142. package/dist/chunk-6GFRKLLS.cjs.map +0 -1
  143. package/dist/chunk-7QN76ESJ.js +0 -323
  144. package/dist/chunk-7QN76ESJ.js.map +0 -1
  145. package/dist/chunk-BR7EKQUP.js.map +0 -1
  146. package/dist/chunk-DCLJNVZS.cjs +0 -323
  147. package/dist/chunk-DCLJNVZS.cjs.map +0 -1
  148. package/dist/chunk-EM3FMJWE.js.map +0 -1
  149. package/dist/chunk-HHTCDFZC.js.map +0 -1
  150. package/dist/chunk-J5BA3BQ6.cjs.map +0 -1
  151. package/dist/chunk-YAFS2QFZ.cjs.map +0 -1
  152. package/dist/chunk-Z2H7KECK.cjs.map +0 -1
  153. package/dist/chunk-Z6OUCS2K.cjs +0 -340
  154. package/dist/chunk-Z6OUCS2K.cjs.map +0 -1
  155. package/dist/rand-BszBST4s.d.ts +0 -2140
  156. package/dist/rand-DOH_aMvm.d.cts +0 -2140
  157. /package/dist/{chunk-62XUO5LB.js.map → chunk-3X6IAJUQ.js.map} +0 -0
  158. /package/dist/{chunk-7RJ6P7MI.js.map → chunk-6S6362D2.js.map} +0 -0
  159. /package/dist/{chunk-SVYE5HIM.js.map → chunk-EQQMGO42.js.map} +0 -0
  160. /package/dist/{chunk-OXSV64JX.js.map → chunk-G6IKY6PL.js.map} +0 -0
  161. /package/dist/{chunk-UYQUPUR6.js.map → chunk-HUO3TI4C.js.map} +0 -0
  162. /package/dist/{chunk-2HC4DRF4.js.map → chunk-LLFHCSU2.js.map} +0 -0
  163. /package/dist/{chunk-ILYFOJUN.js.map → chunk-OIBIRXRG.js.map} +0 -0
  164. /package/dist/{chunk-FWEO3HSL.js.map → chunk-PQQF5KOW.js.map} +0 -0
@@ -1,18 +1,28 @@
1
+ import {
2
+ BRUSHSTROKE_MAX_SCALE,
3
+ BRUSHSTROKE_MIN_SCALE
4
+ } from "./chunk-63AZAUSN.js";
1
5
  import {
2
6
  PIGMENT_BANDS,
3
7
  TILE,
8
+ TiledSurface,
4
9
  blendPixel,
5
10
  createPigment,
11
+ emptySurface,
12
+ finishTile,
6
13
  lerpPigmentInto,
7
14
  mixPigment8,
8
15
  mixPigmentRgb,
9
16
  pigment8,
17
+ pngDataUrl,
18
+ readRegion,
10
19
  setPigmentColor,
11
20
  srgbDecode,
12
21
  srgbEncode,
13
22
  tileCoord,
14
- tileKey
15
- } from "./chunk-BR7EKQUP.js";
23
+ tileKey,
24
+ tilesInRect
25
+ } from "./chunk-3G6J7UFV.js";
16
26
 
17
27
  // src/paint/rand.ts
18
28
  function mix32(h) {
@@ -1090,6 +1100,302 @@ function coverageTileCoord(key) {
1090
1100
  return tileCoord(key);
1091
1101
  }
1092
1102
 
1103
+ // src/paint/path-stroke.ts
1104
+ function lean(s) {
1105
+ if (typeof s.altitude === "number" && Number.isFinite(s.altitude)) {
1106
+ return { amount: 1 - Math.min(1, Math.max(0, s.altitude / (Math.PI / 2))), azimuth: s.azimuth ?? 0 };
1107
+ }
1108
+ if (typeof s.tiltX === "number" || typeof s.tiltY === "number") {
1109
+ const tx = (s.tiltX ?? 0) * Math.PI / 180;
1110
+ const ty = (s.tiltY ?? 0) * Math.PI / 180;
1111
+ if (tx === 0 && ty === 0) return { amount: 0, azimuth: 0 };
1112
+ const az = Math.atan2(Math.tan(ty), Math.tan(tx));
1113
+ const alt = Math.atan(1 / Math.hypot(Math.tan(tx), Math.tan(ty)));
1114
+ return { amount: 1 - alt / (Math.PI / 2), azimuth: az };
1115
+ }
1116
+ return null;
1117
+ }
1118
+ function pathStrokeDabs(samples, options) {
1119
+ const brush = normalizeBrush(options.brush);
1120
+ const pts = options.closed && samples.length > 2 ? [...samples, samples[0]] : [...samples];
1121
+ if (!pts.length) return [];
1122
+ const tilt = options.tilt ?? {};
1123
+ const minSize = Math.min(1, Math.max(0, options.minSize ?? 0));
1124
+ let index = 0;
1125
+ const t0 = pts.find((s) => typeof s.t === "number" && Number.isFinite(s.t))?.t ?? null;
1126
+ const dab = (x, y, p, l, dir, travel = 0, time = null, speed = null) => {
1127
+ const pr = Math.min(1, Math.max(0, p));
1128
+ const lean0 = l?.amount ?? 0;
1129
+ const size = brush.size * (brush.pressureSize ? minSize + (1 - minSize) * pr : 1) * (1 + 1.5 * (tilt.size ?? 0) * lean0);
1130
+ let angle = brush.angle * Math.PI / 180;
1131
+ if (options.followPath) angle += dir;
1132
+ if (tilt.followAzimuth && l) angle = -l.azimuth;
1133
+ const roundness = Math.max(0.05, brush.roundness * (1 - 0.85 * (tilt.roundness ?? 0) * lean0));
1134
+ const op = brush.pressureOpacity ? pr : 1;
1135
+ const params = { travel, index: index++, pressure: pr, lean: lean0, speed, direction: dir, time: time === null || t0 === null ? null : time - t0 };
1136
+ return { x, y, radius: Math.max(0.5, size / 2), alpha: brush.flow, opacity: brush.opacity * op, angle, roundness, params };
1137
+ };
1138
+ const out = [];
1139
+ const pressure = (s) => typeof s.pressure === "number" && Number.isFinite(s.pressure) ? s.pressure : 1;
1140
+ if (pts.length === 1) {
1141
+ out.push(dab(pts[0].x, pts[0].y, pressure(pts[0]), lean(pts[0]), 0));
1142
+ return out;
1143
+ }
1144
+ let carry = 0;
1145
+ let along = 0;
1146
+ const timeOf = (s) => typeof s.t === "number" && Number.isFinite(s.t) ? s.t : null;
1147
+ out.push(dab(pts[0].x, pts[0].y, pressure(pts[0]), lean(pts[0]), Math.atan2(-(pts[1].y - pts[0].y), pts[1].x - pts[0].x), 0, timeOf(pts[0])));
1148
+ for (let i = 0; i + 1 < pts.length; i++) {
1149
+ const a = pts[i];
1150
+ const b = pts[i + 1];
1151
+ const dx = b.x - a.x;
1152
+ const dy = b.y - a.y;
1153
+ const len = Math.hypot(dx, dy);
1154
+ if (len === 0) continue;
1155
+ const dir = Math.atan2(-dy, dx);
1156
+ const la = lean(a);
1157
+ const lb = lean(b);
1158
+ const ta = timeOf(a);
1159
+ const tb = timeOf(b);
1160
+ const speed = ta !== null && tb !== null && tb > ta ? len / (tb - ta) : null;
1161
+ let t = carry;
1162
+ for (; ; ) {
1163
+ const f = t / len;
1164
+ if (f > 1) break;
1165
+ const p = pressure(a) + (pressure(b) - pressure(a)) * f;
1166
+ const l = la && lb ? { amount: la.amount + (lb.amount - la.amount) * f, azimuth: la.azimuth + (lb.azimuth - la.azimuth) * f } : la ?? lb;
1167
+ const size = brush.size * (brush.pressureSize ? minSize + (1 - minSize) * p : 1);
1168
+ const step = Math.max(0.5, size * brush.spacing);
1169
+ if (t > 0 || i > 0) out.push(dab(a.x + dx * f, a.y + dy * f, p, l, dir, along + t, ta !== null && tb !== null ? ta + (tb - ta) * f : null, speed));
1170
+ t += step;
1171
+ }
1172
+ carry = t - len;
1173
+ along += len;
1174
+ }
1175
+ return out;
1176
+ }
1177
+ function pathStrokeCoverage(samples, options) {
1178
+ const brush = normalizeBrush(options.brush);
1179
+ const coverage = new CoverageMap();
1180
+ const dabs = pathStrokeDabs(samples, options);
1181
+ const g = options.gradient?.paint.type === "gradient" ? options.gradient : null;
1182
+ const sampler = g ? new PaintGradientSampler(resolvePaintGradient(g.paint, g.colors ?? { paint: "#000000", secondary: "#ffffff" }), g.seed ?? 0) : null;
1183
+ const tipTable = sampler?.acrossTip ? sampler.tipTable(TIP_GRADIENT_STEPS) : null;
1184
+ const scratch = { field: new Float32Array(0), sample: { rgb: [0, 0, 0], alpha: 1 } };
1185
+ let buf = new Float32Array(0);
1186
+ let bounds = null;
1187
+ for (const d of dabs) {
1188
+ const rect = dabRect(d, brush.hardness);
1189
+ const n = rect.width * rect.height;
1190
+ if (n <= 0) continue;
1191
+ if (buf.length < n) buf = new Float32Array(n);
1192
+ const vals = buf.subarray(0, n);
1193
+ rasterizeDab(d, brush.hardness, rect, vals);
1194
+ if (sampler) {
1195
+ const { color, field } = gradientDabColor(sampler, tipTable, d, d.params, rect, vals, scratch);
1196
+ coverage.accumulate(rect, vals, d.opacity, color, field);
1197
+ } else coverage.accumulate(rect, vals, d.opacity);
1198
+ if (!bounds) bounds = { ...rect };
1199
+ else {
1200
+ const x = Math.min(bounds.x, rect.x);
1201
+ const y = Math.min(bounds.y, rect.y);
1202
+ bounds = { x, y, width: Math.max(bounds.x + bounds.width, rect.x + rect.width) - x, height: Math.max(bounds.y + bounds.height, rect.y + rect.height) - y };
1203
+ }
1204
+ }
1205
+ return { coverage, bounds, dabs: dabs.length };
1206
+ }
1207
+ function paintPathStroke(surface, samples, options) {
1208
+ if (surface.channels !== 4) throw new Error("paintPathStroke paints RGBA surfaces (4 channels).");
1209
+ const brush = normalizeBrush(options.brush);
1210
+ const hex = (c) => "#" + [0, 1, 2].map((i) => Math.round(Math.min(255, Math.max(0, c[i]))).toString(16).padStart(2, "0")).join("");
1211
+ let color = options.color;
1212
+ if (options.paint?.paint.type === "color") {
1213
+ const rgb = hexToPaintRgb(options.paint.paint.color).map((v) => Math.round(v * 255));
1214
+ color = [rgb[0], rgb[1], rgb[2], color[3]];
1215
+ }
1216
+ const gradient = options.paint?.paint.type === "gradient" ? { ...options.paint, colors: options.paint.colors ?? { paint: hex(color), secondary: "#ffffff" } } : null;
1217
+ const { coverage, bounds, dabs } = pathStrokeCoverage(samples, { ...options, gradient });
1218
+ if (!bounds) return { surface, bounds: null, dabs };
1219
+ const offset = options.offset ?? { x: 0, y: 0 };
1220
+ const apply = {
1221
+ mode: options.mode ?? "paint",
1222
+ color,
1223
+ background: [0, 0, 0, 0],
1224
+ opacity: brush.opacity,
1225
+ blend: options.blend ?? brush.blend,
1226
+ lockTransparency: options.lockTransparency ?? false,
1227
+ selection: options.selection ?? null,
1228
+ offset
1229
+ };
1230
+ const tiles = new Map(surface.tiles);
1231
+ for (const { tx, ty } of tilesInRect({ x: bounds.x - offset.x, y: bounds.y - offset.y, width: bounds.width, height: bounds.height })) {
1232
+ const key = tileKey(tx, ty);
1233
+ const src = surface.tiles.get(key)?.data;
1234
+ const base = src && src.length === TILE * TILE * 4 ? src : new Uint8ClampedArray(TILE * TILE * 4);
1235
+ const t = finishTile(applyStrokeToTile(coverage, base, 4, tx, ty, apply), 4, surface.defaultValue);
1236
+ if (t) tiles.set(key, t);
1237
+ else tiles.delete(key);
1238
+ }
1239
+ return { surface: new TiledSurface(4, tiles, surface.defaultValue), bounds, dabs };
1240
+ }
1241
+
1242
+ // src/paint/brushstroke.ts
1243
+ var DEFAULT_MAX_PIXELS = 4096 * 4096;
1244
+ var isProfilePath = (input) => !Array.isArray(input) && typeof input === "object" && input !== null && "points" in input;
1245
+ function profileAt(profile, f) {
1246
+ if (!profile || !profile.length) return void 0;
1247
+ if (profile.length === 1) return profile[0];
1248
+ const u = Math.min(1, Math.max(0, f)) * (profile.length - 1);
1249
+ const i = Math.min(profile.length - 2, Math.floor(u));
1250
+ return profile[i] + (profile[i + 1] - profile[i]) * (u - i);
1251
+ }
1252
+ function brushstrokeSamples(input) {
1253
+ if (!isProfilePath(input)) return input.map((s) => ({ ...s }));
1254
+ const pts = input.points;
1255
+ const lengths = [0];
1256
+ for (let i = 1; i < pts.length; i++) lengths.push(lengths[i - 1] + Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y));
1257
+ const total = lengths.at(-1) ?? 0;
1258
+ return pts.map((p, i) => {
1259
+ const f = total > 0 ? lengths[i] / total : pts.length > 1 ? i / (pts.length - 1) : 0;
1260
+ const s = { x: p.x, y: p.y };
1261
+ const pressure = profileAt(input.pressure, f);
1262
+ const tiltX = profileAt(input.tiltX, f);
1263
+ const tiltY = profileAt(input.tiltY, f);
1264
+ if (pressure !== void 0) s.pressure = pressure;
1265
+ if (tiltX !== void 0) s.tiltX = tiltX;
1266
+ if (tiltY !== void 0) s.tiltY = tiltY;
1267
+ return s;
1268
+ });
1269
+ }
1270
+ var REFUSED_ENGINES = {
1271
+ wet: "is a wet brush, and wet paint needs an area to flow in (paint it on a wet layer in the paint editor)",
1272
+ mixer: "is a Mixer Brush, which picks up paint from under the tip and has none on a standalone stroke",
1273
+ blender: "is a blender, which only moves paint that is already there",
1274
+ smudge: "is a smudge brush, which only moves paint that is already there",
1275
+ eraser: "is an eraser, which has nothing to remove on a standalone stroke"
1276
+ };
1277
+ function brushstrokeStrokeOptions(brush) {
1278
+ if (!brush || typeof brush !== "object") throw new Error("A brushstroke needs a brush: tip settings ({ size, hardness, \u2026 }) or a paint brush preset.");
1279
+ if (!("tip" in brush) || !brush.tip) return { brush: normalizeBrush(brush) };
1280
+ const engine = brush.engine ?? "dab";
1281
+ if (engine !== "dab") {
1282
+ const label = brush.name ?? brush.id ?? "This brush";
1283
+ const why = REFUSED_ENGINES[engine] ?? `uses the '${engine}' engine, which renderBrushstroke does not draw`;
1284
+ throw new Error(`Brushstrokes render dry media only: '${label}' ${why}. Pick a dry brush (a dab-engine preset such as a pencil, pastel, charcoal, marker, ink or airbrush) or plain tip settings.`);
1285
+ }
1286
+ const d = brush.dynamics ?? {};
1287
+ const tilt = { size: d.tiltSize ?? 0, roundness: d.tiltRoundness ?? 0 };
1288
+ return {
1289
+ brush: normalizeBrush(brush.tip),
1290
+ followPath: brush.rotation === "stroke",
1291
+ minSize: d.minSize ?? 0,
1292
+ ...tilt.size || tilt.roundness ? { tilt } : {}
1293
+ };
1294
+ }
1295
+ function isBrushstrokeBrush(brush) {
1296
+ return !("tip" in brush) || !brush.tip || (brush.engine ?? "dab") === "dab";
1297
+ }
1298
+ var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
1299
+ function renderBrushstroke(input, brush, options = {}) {
1300
+ const strokeOptions = brushstrokeStrokeOptions(brush);
1301
+ const samples = brushstrokeSamples(input);
1302
+ for (const s of samples) if (!finite2(s.x) || !finite2(s.y)) throw new Error("Brushstroke samples need finite x and y.");
1303
+ const solid = options.paint && !("type" in options.paint) ? options.paint.color : null;
1304
+ const source = options.paint && "type" in options.paint ? options.paint : null;
1305
+ const color = solid ?? options.color ?? [0, 0, 0, 255];
1306
+ let scale = finite2(options.scale) && options.scale > 0 ? Math.min(BRUSHSTROKE_MAX_SCALE, Math.max(BRUSHSTROKE_MIN_SCALE, options.scale)) : 1;
1307
+ if (!samples.length) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs: 0 };
1308
+ const tip = strokeOptions.brush;
1309
+ const reach = tip.size * (1 + 1.5 * (strokeOptions.tilt?.size ?? 0)) + 4;
1310
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
1311
+ for (const s of samples) {
1312
+ x0 = Math.min(x0, s.x);
1313
+ y0 = Math.min(y0, s.y);
1314
+ x1 = Math.max(x1, s.x);
1315
+ y1 = Math.max(y1, s.y);
1316
+ }
1317
+ const maxPixels = finite2(options.maxPixels) && options.maxPixels > 0 ? options.maxPixels : DEFAULT_MAX_PIXELS;
1318
+ const area = (x1 - x0 + reach) * (y1 - y0 + reach);
1319
+ if (area * scale * scale > maxPixels) scale = Math.max(BRUSHSTROKE_MIN_SCALE, Math.sqrt(maxPixels / area));
1320
+ const scaled = samples.map((s) => ({ ...s, x: s.x * scale, y: s.y * scale }));
1321
+ const { surface, bounds, dabs } = paintPathStroke(emptySurface(4), scaled, {
1322
+ ...strokeOptions,
1323
+ brush: { ...tip, size: tip.size * scale },
1324
+ closed: options.closed ?? strokeOptions.closed,
1325
+ color,
1326
+ // Gradient lengths are in path units: scale them with the image.
1327
+ ...source ? { paint: { paint: source.type === "gradient" ? scalePaintGradient(source, scale) : source, seed: options.seed ?? 0 } } : {}
1328
+ });
1329
+ if (!bounds) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs };
1330
+ const rect = bounds;
1331
+ const data = readRegion(surface, rect);
1332
+ return {
1333
+ image: { width: rect.width, height: rect.height, data },
1334
+ bounds: { x: rect.x / scale, y: rect.y / scale, width: rect.width / scale, height: rect.height / scale },
1335
+ scale,
1336
+ dabs
1337
+ };
1338
+ }
1339
+ function brushstrokeDataUrl(stroke) {
1340
+ const { width, height, data } = stroke.image;
1341
+ return width && height ? pngDataUrl(width, height, data) : "";
1342
+ }
1343
+ function brushstrokeToCanvas(stroke, createCanvas) {
1344
+ const { width, height, data } = stroke.image;
1345
+ if (!width || !height) return null;
1346
+ const canvas = createCanvas(width, height);
1347
+ const ctx = canvas.getContext("2d");
1348
+ if (!ctx) return null;
1349
+ const img = ctx.createImageData(width, height);
1350
+ img.data.set(data);
1351
+ ctx.putImageData(img, 0, 0);
1352
+ return canvas;
1353
+ }
1354
+ function hashString(s, h = 2166136261) {
1355
+ for (let i = 0; i < s.length; i++) {
1356
+ h ^= s.charCodeAt(i);
1357
+ h = Math.imul(h, 16777619) >>> 0;
1358
+ }
1359
+ return h;
1360
+ }
1361
+ function brushstrokeKey(input, brush, options = {}) {
1362
+ const path = JSON.stringify(input);
1363
+ const { brush: tip, ...rest } = brushstrokeStrokeOptions(brush);
1364
+ const rest2 = JSON.stringify([tip, rest, options.paint ?? options.color ?? null, options.seed ?? 0, options.closed ?? false, options.maxPixels ?? null]);
1365
+ const scale = finite2(options.scale) ? options.scale : 1;
1366
+ return `${hashString(path).toString(16)}:${path.length}:${hashString(rest2).toString(16)}:${scale}`;
1367
+ }
1368
+ var BrushstrokeCache = class {
1369
+ constructor(capacity = 64) {
1370
+ this.capacity = capacity;
1371
+ }
1372
+ capacity;
1373
+ entries = /* @__PURE__ */ new Map();
1374
+ /** Renders that missed the cache (for tests and stats). */
1375
+ misses = 0;
1376
+ get size() {
1377
+ return this.entries.size;
1378
+ }
1379
+ /** The cached stroke for these arguments, rendering (and caching) it on a miss. */
1380
+ render(input, brush, options = {}) {
1381
+ const key = brushstrokeKey(input, brush, options);
1382
+ const hit = this.entries.get(key);
1383
+ if (hit) {
1384
+ this.entries.delete(key);
1385
+ this.entries.set(key, hit);
1386
+ return hit;
1387
+ }
1388
+ this.misses++;
1389
+ const out = renderBrushstroke(input, brush, options);
1390
+ this.entries.set(key, out);
1391
+ while (this.entries.size > Math.max(1, this.capacity)) this.entries.delete(this.entries.keys().next().value);
1392
+ return out;
1393
+ }
1394
+ clear() {
1395
+ this.entries.clear();
1396
+ }
1397
+ };
1398
+
1093
1399
  // src/paint/exchange.ts
1094
1400
  var MIXING_MODELS = ["rgb", "pigment"];
1095
1401
  var EMPTY_MIXER_LOAD = { reservoir: null, amount: 0, pickup: null };
@@ -1756,6 +2062,17 @@ export {
1756
2062
  applyStrokeToTile,
1757
2063
  applyStrokeToDeepTile,
1758
2064
  coverageTileCoord,
2065
+ pathStrokeDabs,
2066
+ pathStrokeCoverage,
2067
+ paintPathStroke,
2068
+ brushstrokeSamples,
2069
+ brushstrokeStrokeOptions,
2070
+ isBrushstrokeBrush,
2071
+ renderBrushstroke,
2072
+ brushstrokeDataUrl,
2073
+ brushstrokeToCanvas,
2074
+ brushstrokeKey,
2075
+ BrushstrokeCache,
1759
2076
  MIXING_MODELS,
1760
2077
  EMPTY_MIXER_LOAD,
1761
2078
  MIXER_PICKUP_RATE,
@@ -1779,4 +2096,4 @@ export {
1779
2096
  cleanMixer,
1780
2097
  MixerStroke
1781
2098
  };
1782
- //# sourceMappingURL=chunk-EM3FMJWE.js.map
2099
+ //# sourceMappingURL=chunk-5FSRTP6E.js.map