pixi-effects 0.35.0 → 0.37.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 (77) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +38 -9
  5. package/ai/tools/lookup.mjs +15 -2
  6. package/dist/{Base-pNq6sVGl.d.cts → Base-BlA6SeqJ.d.cts} +1 -1
  7. package/dist/{Base-CXsMdmJB.d.ts → Base-YqM0Ljvp.d.ts} +1 -1
  8. package/dist/Composition-5KZETM4J.js +7 -0
  9. package/dist/{Composition-NDXKQHB4.js.map → Composition-5KZETM4J.js.map} +1 -1
  10. package/dist/Composition-MHDI7UNZ.cjs +16 -0
  11. package/dist/{Composition-OLH7RC5Z.cjs.map → Composition-MHDI7UNZ.cjs.map} +1 -1
  12. package/dist/Controller.d.cts +2 -2
  13. package/dist/Controller.d.ts +2 -2
  14. package/dist/{Movie-VYtUN4TS.d.ts → Movie-CJN_gvLB.d.cts} +26 -2
  15. package/dist/{Movie-Db4QnMIH.d.cts → Movie-CfHaGmUP.d.ts} +26 -2
  16. package/dist/{Spans-QHHWC2PB.js → Spans-FSSO2HGX.js} +6 -6
  17. package/dist/{Spans-QHHWC2PB.js.map → Spans-FSSO2HGX.js.map} +1 -1
  18. package/dist/{Spans-QRMN2EZO.cjs → Spans-WVJH77BW.cjs} +11 -11
  19. package/dist/{Spans-QRMN2EZO.cjs.map → Spans-WVJH77BW.cjs.map} +1 -1
  20. package/dist/{blendModes-WIUCYIMC.cjs → blendModes-FD3THLNC.cjs} +5 -5
  21. package/dist/{blendModes-WIUCYIMC.cjs.map → blendModes-FD3THLNC.cjs.map} +1 -1
  22. package/dist/{blendModes-PP5NPBXY.js → blendModes-FMYO6SQN.js} +4 -4
  23. package/dist/{blendModes-PP5NPBXY.js.map → blendModes-FMYO6SQN.js.map} +1 -1
  24. package/dist/{chunk-ITDYFBB6.js → chunk-32MMNDDN.js} +7 -6
  25. package/dist/chunk-32MMNDDN.js.map +1 -0
  26. package/dist/{chunk-GL2AQFLQ.cjs → chunk-3SAS3ZE5.cjs} +7 -6
  27. package/dist/chunk-3SAS3ZE5.cjs.map +1 -0
  28. package/dist/{chunk-7TTCPHAV.cjs → chunk-E26M4NTL.cjs} +235 -49
  29. package/dist/chunk-E26M4NTL.cjs.map +1 -0
  30. package/dist/{chunk-XV4L72FW.js → chunk-IPQNT65V.js} +6 -6
  31. package/dist/chunk-IPQNT65V.js.map +1 -0
  32. package/dist/{chunk-IMQJVRMT.cjs → chunk-JN7JHNZR.cjs} +5 -5
  33. package/dist/{chunk-IMQJVRMT.cjs.map → chunk-JN7JHNZR.cjs.map} +1 -1
  34. package/dist/{chunk-2GNK6DPV.js → chunk-KNDEKWQO.js} +203 -18
  35. package/dist/chunk-KNDEKWQO.js.map +1 -0
  36. package/dist/{chunk-5IJENZOG.cjs → chunk-MJMOPWMK.cjs} +92 -92
  37. package/dist/chunk-MJMOPWMK.cjs.map +1 -0
  38. package/dist/{chunk-LEECUCTG.cjs → chunk-QHIOOK5S.cjs} +436 -29
  39. package/dist/chunk-QHIOOK5S.cjs.map +1 -0
  40. package/dist/{chunk-Y253JQOH.js → chunk-SBR75G73.js} +416 -11
  41. package/dist/chunk-SBR75G73.js.map +1 -0
  42. package/dist/{chunk-RTJD3ZSW.js → chunk-VNP7QCJE.js} +4 -4
  43. package/dist/{chunk-RTJD3ZSW.js.map → chunk-VNP7QCJE.js.map} +1 -1
  44. package/dist/index.cjs +41 -33
  45. package/dist/index.d.cts +47 -7
  46. package/dist/index.d.ts +47 -7
  47. package/dist/index.js +5 -5
  48. package/dist/loader.css +1 -1
  49. package/dist/present.cjs +5 -5
  50. package/dist/present.d.cts +2 -2
  51. package/dist/present.d.ts +2 -2
  52. package/dist/present.js +5 -5
  53. package/dist/three.cjs +6 -6
  54. package/dist/three.d.cts +2 -2
  55. package/dist/three.d.ts +2 -2
  56. package/dist/three.js +2 -2
  57. package/dist/{types-CavnUvd5.d.cts → types-DvnXXm33.d.cts} +67 -3
  58. package/dist/{types-CavnUvd5.d.ts → types-DvnXXm33.d.ts} +67 -3
  59. package/docs/api.md +5 -4
  60. package/docs/dsl.md +42 -5
  61. package/llms-full.txt +305 -22
  62. package/package.json +1 -1
  63. package/skills/pixi-effects/SKILL.md +5 -4
  64. package/skills/pixi-effects/reference/cheatsheet.md +9 -6
  65. package/skills/pixi-effects/reference/pitfalls.md +7 -0
  66. package/skills/pixi-effects/reference/recipes.md +237 -3
  67. package/skills/pixi-effects/template.html +6 -6
  68. package/dist/Composition-NDXKQHB4.js +0 -7
  69. package/dist/Composition-OLH7RC5Z.cjs +0 -16
  70. package/dist/chunk-2GNK6DPV.js.map +0 -1
  71. package/dist/chunk-5IJENZOG.cjs.map +0 -1
  72. package/dist/chunk-7TTCPHAV.cjs.map +0 -1
  73. package/dist/chunk-GL2AQFLQ.cjs.map +0 -1
  74. package/dist/chunk-ITDYFBB6.js.map +0 -1
  75. package/dist/chunk-LEECUCTG.cjs.map +0 -1
  76. package/dist/chunk-XV4L72FW.js.map +0 -1
  77. package/dist/chunk-Y253JQOH.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { describeLayer, collectPropKeys } from './chunk-ITDYFBB6.js';
1
+ import { describeLayer, collectPropKeys } from './chunk-32MMNDDN.js';
2
2
  import { suggestName, warnUnknownOptions } from './chunk-BXOCEZ4U.js';
3
3
  import * as PIXI from 'pixi.js';
4
4
  import { Rectangle, Container, FillGradient, Text, Color, GraphicsPath, Sprite, Texture, Graphics, Assets, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, TextStyle, CanvasTextMetrics } from 'pixi.js';
@@ -971,6 +971,17 @@ function noise(x, seed = 0) {
971
971
  const u = f * f * f * (f * (f * 6 - 15) + 10);
972
972
  return a + (b - a) * u;
973
973
  }
974
+ function noise2(x, y, seed = 0) {
975
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return 0;
976
+ const i = Math.floor(x), j = Math.floor(y);
977
+ const fx = x - i, fy = y - j;
978
+ const s = fold(seed) * 668265261;
979
+ const corner = (a, b) => mix2(Math.imul((a | 0) + s, 2654435761) ^ Math.imul((b | 0) + 2135587861, 2246822507)) / 2147483648 - 1;
980
+ const ux = fx * fx * fx * (fx * (fx * 6 - 15) + 10), uy = fy * fy * fy * (fy * (fy * 6 - 15) + 10);
981
+ const top = corner(i, j) + (corner(i + 1, j) - corner(i, j)) * ux;
982
+ const bottom = corner(i, j + 1) + (corner(i + 1, j + 1) - corner(i, j + 1)) * ux;
983
+ return top + (bottom - top) * uy;
984
+ }
974
985
 
975
986
  // src/expr/Parser.ts
976
987
  var FUNCS = {
@@ -2326,6 +2337,11 @@ function createFilter(spec) {
2326
2337
  return named;
2327
2338
  }
2328
2339
  const { type, name, ...given } = spec;
2340
+ const unset = Object.keys(given).filter((k) => given[k] === void 0);
2341
+ if (unset.length > 0) {
2342
+ console.warn(`pixi-effects: the "${type}" filter has ${unset.map((k) => `"${k}"`).join(", ")} set to undefined (a misspelt variable or a missing field?): the filter's default is used instead.`);
2343
+ for (const k of unset) delete given[k];
2344
+ }
2329
2345
  const params = copyPlain(given);
2330
2346
  const Cls = registry[type];
2331
2347
  const inst = Cls ? new Cls(params) : createNamedFilter(type, params);
@@ -2841,7 +2857,37 @@ var TEXT_STYLE_DEFAULTS = {
2841
2857
  };
2842
2858
 
2843
2859
  // src/sequences/Text.ts
2844
- function formatValue(v, f) {
2860
+ var TIME_PATTERNS = ["hh:mm:ss", "h:mm:ss", "hh:mm", "mm:ss", "m:ss"];
2861
+ function formatTime(v, pattern, warned2) {
2862
+ if (!TIME_PATTERNS.includes(pattern)) {
2863
+ if (!warned2.has(pattern)) {
2864
+ warned2.add(pattern);
2865
+ console.warn(`pixi-effects: format.time "${pattern}" is not one of ${TIME_PATTERNS.map((p) => `"${p}"`).join(", ")}: the value is printed as a number`);
2866
+ }
2867
+ return null;
2868
+ }
2869
+ const total = Math.floor(Math.abs(v));
2870
+ const h = Math.floor(total / 3600), m = Math.floor(total % 3600 / 60), sec = total % 60;
2871
+ const two = (n) => String(n).padStart(2, "0");
2872
+ const sign = v < 0 && total > 0 ? "-" : "";
2873
+ switch (pattern) {
2874
+ case "hh:mm:ss":
2875
+ return `${sign}${two(h)}:${two(m)}:${two(sec)}`;
2876
+ case "h:mm:ss":
2877
+ return `${sign}${h}:${two(m)}:${two(sec)}`;
2878
+ case "hh:mm":
2879
+ return `${sign}${two(h)}:${two(m)}`;
2880
+ case "mm:ss":
2881
+ return `${sign}${two(Math.floor(total / 60))}:${two(sec)}`;
2882
+ default:
2883
+ return `${sign}${Math.floor(total / 60)}:${two(sec)}`;
2884
+ }
2885
+ }
2886
+ function formatValue(v, f, warned2 = /* @__PURE__ */ new Set()) {
2887
+ if (f?.time !== void 0) {
2888
+ const t = formatTime(v, String(f.time), warned2);
2889
+ if (t !== null) return t;
2890
+ }
2845
2891
  const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
2846
2892
  const pad = Math.max(0, Math.floor(f?.pad ?? 0));
2847
2893
  if (!f?.grouping && !pad) return fixed;
@@ -2860,6 +2906,8 @@ var TextSequence = class extends Sequence {
2860
2906
  /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
2861
2907
  _typed = false;
2862
2908
  _lastText = "";
2909
+ /** The `format.time` patterns already said to be wrong on this layer. */
2910
+ _timeWarned = /* @__PURE__ */ new Set();
2863
2911
  /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
2864
2912
  _grad = null;
2865
2913
  _fillGradient = null;
@@ -2886,7 +2934,7 @@ var TextSequence = class extends Sequence {
2886
2934
  }
2887
2935
  /** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
2888
2936
  _refreshText() {
2889
- let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
2937
+ let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format, this._timeWarned)) : this._template;
2890
2938
  if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
2891
2939
  if (next === this._lastText) return;
2892
2940
  this._lastText = next;
@@ -2902,6 +2950,11 @@ var TextSequence = class extends Sequence {
2902
2950
  label: this.spec.name
2903
2951
  });
2904
2952
  text.cullable = true;
2953
+ if (this.spec.resolution !== void 0) {
2954
+ const r = this.spec.resolution;
2955
+ if (typeof r === "number" && r >= 1 && r <= 8) text.resolution = r;
2956
+ else console.warn(`pixi-effects: ${describeLayer(this.spec)}: resolution must be a number from 1 to 8 (the text is drawn at that many times the pixels), got ${JSON.stringify(r)}: ignored`);
2957
+ }
2905
2958
  this.target = text;
2906
2959
  this.intrinsicWidth = text.width;
2907
2960
  this.intrinsicHeight = text.height;
@@ -2926,6 +2979,8 @@ var TextSequence = class extends Sequence {
2926
2979
  this.intrinsicWidth = text.width;
2927
2980
  this.intrinsicHeight = text.height;
2928
2981
  }
2982
+ const startFill = initialProps.fill;
2983
+ if (typeof startFill === "string" || typeof startFill === "number") text.style.fill = startFill;
2929
2984
  validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
2930
2985
  const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
2931
2986
  if (gradientSpec) {
@@ -3730,7 +3785,7 @@ function resolveSpansStyle(style, scope, who, warn = defaultWarn) {
3730
3785
  }
3731
3786
  return out;
3732
3787
  }
3733
- var TEXT_ONLY = ["type", "spans", "text", "style", "format", "colorSpace", "fillGradient", "initial", "keyframes"];
3788
+ var TEXT_ONLY = ["type", "spans", "text", "style", "format", "colorSpace", "fillGradient", "initial", "keyframes", "resolution"];
3734
3789
  var NOT_FOR_A_GROUP = ["fill", "text", "value", "fillGradient"];
3735
3790
  function expandSpans(spec, warn = defaultWarn, scope) {
3736
3791
  const who = describeLayer(spec);
@@ -3775,7 +3830,9 @@ function expandSpans(spec, warn = defaultWarn, scope) {
3775
3830
  name: `${baseName}.${i}`,
3776
3831
  text: p.text,
3777
3832
  style: { ...style, ...p.style },
3778
- initial: { x: p.x, y: p.y, ...typed ? { visibleChars: p.length } : {} }
3833
+ initial: { x: p.x, y: p.y, ...typed ? { visibleChars: p.length } : {} },
3834
+ ...s.resolution !== void 0 ? { resolution: s.resolution } : {}
3835
+ // the pieces are what is drawn
3779
3836
  }));
3780
3837
  const rest = {};
3781
3838
  for (const [k, v] of Object.entries(s)) if (!TEXT_ONLY.includes(k)) rest[k] = v;
@@ -3783,9 +3840,15 @@ function expandSpans(spec, warn = defaultWarn, scope) {
3783
3840
  spansMeta.set(group, { typed, pieces: drawn.map((p) => ({ start: p.start, length: p.length })), preview: layout.pieces.map((p, i) => (i > 0 && p.line !== layout.pieces[i - 1].line ? " " : "") + p.text).join("") });
3784
3841
  return group;
3785
3842
  }
3843
+ var ORIGINAL_SCALE_MODE = /* @__PURE__ */ new WeakMap();
3786
3844
  var ImageSequence = class extends Sequence {
3787
3845
  async build() {
3788
3846
  const texture = await Assets.get(this.spec.asset);
3847
+ const source = texture.source;
3848
+ if (source && !ORIGINAL_SCALE_MODE.has(source)) ORIGINAL_SCALE_MODE.set(source, source.scaleMode);
3849
+ const mode = this.spec.scaleMode;
3850
+ if (mode !== void 0 && mode !== "nearest" && mode !== "linear") console.warn(`pixi-effects: ${describeLayer(this.spec)}: scaleMode "${String(mode)}" is not "nearest" or "linear": ignored`);
3851
+ if (source) source.scaleMode = mode === "nearest" || mode === "linear" ? mode : ORIGINAL_SCALE_MODE.get(source);
3789
3852
  const sprite = new Sprite({ texture, label: this.spec.name });
3790
3853
  sprite.cullable = true;
3791
3854
  this.target = sprite;
@@ -4642,6 +4705,21 @@ function slice(line, a, b) {
4642
4705
  }
4643
4706
  return out;
4644
4707
  }
4708
+ function splitEvenOdd(d) {
4709
+ const parts = d.split(/(?=[Mm])/).map((x) => x.trim()).filter(Boolean);
4710
+ if (parts.length < 2) return { main: d.trim(), holes: [] };
4711
+ const area = (sub2) => flattenSvgPath(sub2).reduce((sum, line) => {
4712
+ let a = 0;
4713
+ for (let i = 0; i < line.pts.length; i += 2) {
4714
+ const j = (i + 2) % line.pts.length;
4715
+ a += line.pts[i] * line.pts[j + 1] - line.pts[j] * line.pts[i + 1];
4716
+ }
4717
+ return sum + Math.abs(a) / 2;
4718
+ }, 0);
4719
+ const areas = parts.map(area);
4720
+ const big = areas.indexOf(Math.max(...areas));
4721
+ return { main: parts[big], holes: parts.filter((_, i) => i !== big) };
4722
+ }
4645
4723
 
4646
4724
  // src/sequences/morphPath.ts
4647
4725
  function resample(line, n) {
@@ -4704,6 +4782,11 @@ function buildMorph(from, to, n = 128) {
4704
4782
  for (let i = 0; i < count; i++) {
4705
4783
  const fa = from[i], fb = to[i];
4706
4784
  const closed = (fa?.closed ?? fb.closed) && (fb?.closed ?? fa.closed);
4785
+ if (fa && fb && fa.closed === fb.closed && fa.pts.length === fb.pts.length && fa.pts.length >= 6) {
4786
+ const a2 = [...fa.pts];
4787
+ pairs.push({ a: a2, b: closed ? align(a2, [...fb.pts]) : [...fb.pts], closed });
4788
+ continue;
4789
+ }
4707
4790
  const a = fa ? resample({ pts: fa.pts, closed }, n) : collapsed(resample({ pts: fb.pts, closed }, n));
4708
4791
  let b = fb ? resample({ pts: fb.pts, closed }, n) : collapsed(a);
4709
4792
  if (closed && fa && fb) b = align(a, b);
@@ -4787,6 +4870,8 @@ var ShapeSequence = class extends Sequence {
4787
4870
  const morph = this._setupMorph();
4788
4871
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state, morph);
4789
4872
  this._outline = makeOutline(this.spec, scope, this._state, morph);
4873
+ const eo = this.spec.shape === "path" && this.spec.fillRule === "evenodd" && !morph ? splitEvenOdd(this.spec.d) : null;
4874
+ this._evenOdd = eo && eo.holes.length > 0 ? eo : null;
4790
4875
  const { trimStart, trimEnd } = this._state;
4791
4876
  this._state.trimStart = 0;
4792
4877
  this._state.trimEnd = 1;
@@ -4852,6 +4937,27 @@ var ShapeSequence = class extends Sequence {
4852
4937
  _painter = null;
4853
4938
  _gradDirty = false;
4854
4939
  _outline = null;
4940
+ /** fillRule 'evenodd' on a path: the shape and the sub-paths cut out of it after it is filled (null: not used). */
4941
+ _evenOdd = null;
4942
+ /**
4943
+ * fillRule 'evenodd': the shape is filled, each hole is cut out of that fill, and the stroke goes round the shape and every hole. (PIXI's `cut()` only reaches the
4944
+ * LAST instruction, so it has to come between the fill and the stroke.)
4945
+ */
4946
+ _fillWithHoles(g) {
4947
+ const s = this._state, { main, holes } = this._evenOdd;
4948
+ applyFill(g, s);
4949
+ for (const hole of holes) {
4950
+ g.beginPath();
4951
+ g.path(new GraphicsPath(hole));
4952
+ g.cut();
4953
+ }
4954
+ if (s.strokeColor !== void 0 && s.strokeWidth > 0) {
4955
+ g.beginPath();
4956
+ g.path(new GraphicsPath(main));
4957
+ for (const hole of holes) g.path(new GraphicsPath(hole));
4958
+ applyStroke(g, s);
4959
+ }
4960
+ }
4855
4961
  _redraw() {
4856
4962
  const graphics = this.target;
4857
4963
  const s = this._state;
@@ -4863,6 +4969,10 @@ var ShapeSequence = class extends Sequence {
4863
4969
  const trimmed = (s.trimStart ?? 0) > 0 || (s.trimEnd ?? 1) < 1;
4864
4970
  if (!trimmed || !this._outline || !(s.strokeColor !== void 0 && s.strokeWidth > 0)) {
4865
4971
  this._drawGeometry(graphics);
4972
+ if (this._evenOdd) {
4973
+ this._fillWithHoles(graphics);
4974
+ return;
4975
+ }
4866
4976
  applyState(graphics, s);
4867
4977
  return;
4868
4978
  }
@@ -5086,8 +5196,9 @@ function makePolygonDraw(spec, scope) {
5086
5196
  };
5087
5197
  }
5088
5198
  function makePathDraw(spec) {
5199
+ const d = spec.fillRule === "evenodd" ? splitEvenOdd(spec.d).main : spec.d;
5089
5200
  return (g) => {
5090
- g.path(new GraphicsPath(spec.d));
5201
+ g.path(new GraphicsPath(d));
5091
5202
  };
5092
5203
  }
5093
5204
  var STATE_DRAWERS = {
@@ -5618,18 +5729,311 @@ function resolveDim(v, scope) {
5618
5729
  if (typeof v === "number") return v;
5619
5730
  return evaluateExpr(v, scope);
5620
5731
  }
5732
+ var MAX_SIZE = 8192;
5733
+ var MIN_RESOLUTION = 0.25;
5734
+ var MAX_RESOLUTION = 4;
5735
+ var NOT_PURE = /\bMath\s*\.\s*random\s*\(|\bDate\s*\.\s*now\s*\(|\bperformance\s*\.\s*now\s*\(|\bnew\s+Date\s*\(/;
5736
+ function seeded(seed) {
5737
+ let a = seed >>> 0;
5738
+ return () => {
5739
+ a = a + 1831565813 >>> 0;
5740
+ let t = a;
5741
+ t = Math.imul(t ^ t >>> 15, t | 1);
5742
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
5743
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
5744
+ };
5745
+ }
5746
+ function offscreen(width, height) {
5747
+ const c = document.createElement("canvas");
5748
+ c.width = Math.max(1, Math.round(width));
5749
+ c.height = Math.max(1, Math.round(height));
5750
+ c.getContext("2d", { willReadFrequently: true });
5751
+ return c;
5752
+ }
5753
+ var CanvasSequence = class extends Sequence {
5754
+ canvas = null;
5755
+ w = 1;
5756
+ h = 1;
5757
+ res = 1;
5758
+ fps = 30;
5759
+ layerW = 1;
5760
+ layerH = 1;
5761
+ seed = 0;
5762
+ smoothing = true;
5763
+ drawFn = null;
5764
+ state;
5765
+ /** The current value of every param: what keyframes move (`params.amount`). */
5766
+ carrier = {};
5767
+ failed = false;
5768
+ warnedDraw = false;
5769
+ warnedPaths = /* @__PURE__ */ new Set();
5770
+ lastKey = "";
5771
+ lastLocal = 0;
5772
+ get canvasSpec() {
5773
+ return this.spec;
5774
+ }
5775
+ pathRouters() {
5776
+ return { params: (path) => this.resolveParam(path) };
5777
+ }
5778
+ async build() {
5779
+ const spec = this.canvasSpec;
5780
+ const who = describeLayer(spec);
5781
+ if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
5782
+ const scope = buildScope(this, this.parent, this.root);
5783
+ this.layerW = this.size("width", spec.width, scope, this.parent?.width ?? this.root.width, who);
5784
+ this.layerH = this.size("height", spec.height, scope, this.parent?.height ?? this.root.height, who);
5785
+ const r = spec.resolution;
5786
+ if (r === void 0) this.res = 1;
5787
+ else if (typeof r === "number" && Number.isFinite(r) && r >= MIN_RESOLUTION && r <= MAX_RESOLUTION) this.res = r;
5788
+ else {
5789
+ this.res = typeof r === "number" && Number.isFinite(r) ? Math.min(MAX_RESOLUTION, Math.max(MIN_RESOLUTION, r)) : 1;
5790
+ console.warn(`pixi-effects: ${who}: resolution must be a number from ${MIN_RESOLUTION} to ${MAX_RESOLUTION} (canvas pixels per layer pixel), got ${JSON.stringify(r)}: ${this.res} is used`);
5791
+ }
5792
+ const longest = Math.max(this.layerW, this.layerH);
5793
+ if (longest * this.res > MAX_SIZE) {
5794
+ const fitted = MAX_SIZE / longest;
5795
+ console.warn(`pixi-effects: ${who}: ${this.layerW}\xD7${this.layerH} at resolution ${this.res} would be a canvas of ${Math.round(longest * this.res)} pixels, more than ${MAX_SIZE}: resolution ${Math.round(fitted * 1e3) / 1e3} is used (lower the size or the resolution)`);
5796
+ this.res = fitted;
5797
+ }
5798
+ this.w = Math.max(1, Math.round(this.layerW * this.res));
5799
+ this.h = Math.max(1, Math.round(this.layerH * this.res));
5800
+ this.fps = this.root.frameRate ?? 30;
5801
+ this.seed = typeof spec.seed === "number" && Number.isFinite(spec.seed) ? Math.floor(spec.seed) : 0;
5802
+ this.smoothing = spec.smoothing !== false;
5803
+ const params = spec.params;
5804
+ if (params !== void 0) {
5805
+ if (params === null || typeof params !== "object" || Array.isArray(params)) {
5806
+ console.warn(`pixi-effects: ${who}: params must be an object of numbers, got ${JSON.stringify(params)}: ignored`);
5807
+ } else {
5808
+ const bad = [];
5809
+ for (const [k, v] of Object.entries(params)) {
5810
+ if (typeof v === "number" && Number.isFinite(v)) this.carrier[k] = v;
5811
+ else bad.push(`"${k}"`);
5812
+ }
5813
+ if (bad.length) console.warn(`pixi-effects: ${who}: params ${bad.join(", ")} ${bad.length > 1 ? "are" : "is"} not a number, so ${bad.length > 1 ? "they are" : "it is"} ignored (params holds numbers that keyframes can animate as 'params.<name>')`);
5814
+ }
5815
+ }
5816
+ if (typeof spec.draw === "function") {
5817
+ this.drawFn = spec.draw;
5818
+ if (NOT_PURE.test(spec.draw.toString())) {
5819
+ console.warn(`pixi-effects: ${who}: draw calls Math.random, Date.now, performance.now or new Date, so it is not deterministic: the same frame would draw differently in playback, in a seek and in an export. Use info.random() (seeded), or derive the value from t`);
5820
+ }
5821
+ } else {
5822
+ this.failed = true;
5823
+ console.warn(`pixi-effects: ${who}: a canvas layer needs draw: (ctx, t, info) => { \u2026 } (a function that draws the picture at time t); the layer is drawn as a checkerboard`);
5824
+ }
5825
+ this.canvas = document.createElement("canvas");
5826
+ this.canvas.width = this.w;
5827
+ this.canvas.height = this.h;
5828
+ const sprite = new Sprite({ texture: Texture.from(this.canvas), label: spec.name });
5829
+ if (!this.smoothing) sprite.texture.source.scaleMode = "nearest";
5830
+ sprite.width = this.layerW;
5831
+ sprite.height = this.layerH;
5832
+ this.target = sprite;
5833
+ this.intrinsicWidth = this.layerW;
5834
+ this.intrinsicHeight = this.layerH;
5835
+ this.buildFilters();
5836
+ if (typeof spec.setup === "function" && !this.failed) {
5837
+ try {
5838
+ this.state = await spec.setup(this.info(0));
5839
+ } catch (e) {
5840
+ this.failed = true;
5841
+ this.say(`setup failed: ${e instanceof Error ? e.message : String(e)}; the layer is drawn as a checkerboard`);
5842
+ }
5843
+ }
5844
+ this.drawAt(0);
5845
+ }
5846
+ async awaitFrameAt(local) {
5847
+ this.drawAt(Math.max(0, Math.min(local, this.duration ?? local)));
5848
+ }
5849
+ size(name, v, scope, fallback, who) {
5850
+ if (v === void 0) return fallback;
5851
+ const n = typeof v === "number" ? v : evaluateExpr(v, scope);
5852
+ if (!Number.isFinite(n) || n < 1) {
5853
+ console.warn(`pixi-effects: ${who}: ${name} must be a number of pixels of at least 1 (or an expression), got ${JSON.stringify(v)}: ${fallback} is used`);
5854
+ return fallback;
5855
+ }
5856
+ if (n > MAX_SIZE) {
5857
+ console.warn(`pixi-effects: ${who}: ${name} ${n} is more than ${MAX_SIZE}: ${MAX_SIZE} is used`);
5858
+ return MAX_SIZE;
5859
+ }
5860
+ return n;
5861
+ }
5862
+ say(why) {
5863
+ if (this.warnedDraw) return;
5864
+ this.warnedDraw = true;
5865
+ console.warn(`pixi-effects: ${describeLayer(this.spec)}: ${why}`);
5866
+ }
5867
+ info(local) {
5868
+ const duration = this.duration ?? 0;
5869
+ const start = this.absoluteStart ?? this.at;
5870
+ return {
5871
+ frame: Math.round(local * this.fps),
5872
+ duration,
5873
+ progress: duration > 0 ? local / duration : 0,
5874
+ width: this.layerW,
5875
+ height: this.layerH,
5876
+ resolution: this.res,
5877
+ frameRate: this.fps,
5878
+ params: { ...this.carrier },
5879
+ state: this.state,
5880
+ random: seeded(this.seed),
5881
+ start,
5882
+ time: start + local,
5883
+ canvas: offscreen
5884
+ };
5885
+ }
5886
+ /** Resolve `params.amount` for the keyframe pipeline; a name nobody declared is said once and dropped. */
5887
+ resolveParam(path) {
5888
+ if (path in this.carrier && !path.includes(".")) return { target: this.carrier, prop: path };
5889
+ if (!this.warnedPaths.has(path)) {
5890
+ this.warnedPaths.add(path);
5891
+ const guess = suggestName(path.split(".")[0], Object.keys(this.carrier));
5892
+ console.warn(`pixi-effects: ${describeLayer(this.spec)}: keyframe path params.${path} names no param "${path}"${guess ? `; did you mean "${guess}"?` : ""} (declare it in params with a starting value); skipped`);
5893
+ }
5894
+ return null;
5895
+ }
5896
+ drawAt(local) {
5897
+ const c = this.canvas;
5898
+ if (!c) return;
5899
+ const ctx = c.getContext("2d");
5900
+ if (!ctx) {
5901
+ this.say("this browser gave no 2D context for the layer's canvas, so nothing can be drawn");
5902
+ return;
5903
+ }
5904
+ const key = `${local}|${this.absoluteStart ?? this.at}|${JSON.stringify(this.carrier)}`;
5905
+ if (key === this.lastKey) return;
5906
+ this.lastKey = key;
5907
+ this.lastLocal = local;
5908
+ if (!this.paint(ctx, c, local)) this.lastKey = "";
5909
+ this.target?.texture.source.update();
5910
+ }
5911
+ /** Draws one frame of the layer into `ctx` (the layer's own canvas, or the scratch one of the determinism check). false: `draw` threw. */
5912
+ paint(ctx, c, local) {
5913
+ const hasReset = "reset" in ctx && typeof ctx.reset === "function";
5914
+ const fresh = () => {
5915
+ if (hasReset) ctx.reset();
5916
+ else {
5917
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
5918
+ ctx.clearRect(0, 0, c.width, c.height);
5919
+ }
5920
+ };
5921
+ fresh();
5922
+ if (this.failed || !this.drawFn) {
5923
+ this.checkerboard(ctx, c);
5924
+ return true;
5925
+ }
5926
+ if (!hasReset) ctx.save();
5927
+ let ok = true;
5928
+ try {
5929
+ ctx.scale(this.res, this.res);
5930
+ ctx.imageSmoothingEnabled = this.smoothing;
5931
+ this.drawFn(ctx, local, this.info(local));
5932
+ } catch (e) {
5933
+ if (!hasReset) {
5934
+ ctx.restore();
5935
+ ctx.save();
5936
+ }
5937
+ fresh();
5938
+ this.say(`draw threw at ${local.toFixed(2)} s: ${e instanceof Error ? e.message : String(e)}; that frame is drawn as a checkerboard (the other frames are tried again)`);
5939
+ this.checkerboard(ctx, c);
5940
+ ok = false;
5941
+ }
5942
+ if (!hasReset) ctx.restore();
5943
+ return ok;
5944
+ }
5945
+ /**
5946
+ * Draws each of `times` in two orders and returns the moments where the pictures differ (a draw that uses Math.random, the clock or state that outlives the call), with how much
5947
+ * (channel values that differ, the largest difference out of 255), and how long a draw took. One draw of every moment comes first and is thrown away: a browser can draw a canvas
5948
+ * made in `setup` differently the first time it is used as a source, and a cache a draw fills on its first call is fine. The layer's own canvas is not touched (everything is drawn in a scratch canvas).
5949
+ * A layer that failed, or a draw that throws, is reported in `threw`.
5950
+ */
5951
+ checkDeterminism(times) {
5952
+ const none = { differs: [], drawMs: 0, threw: [] };
5953
+ if (!this.canvas) return none;
5954
+ if (this.failed || !this.drawFn) return { ...none, threw: [...times] };
5955
+ const scratch = document.createElement("canvas");
5956
+ scratch.width = this.w;
5957
+ scratch.height = this.h;
5958
+ const ctx = scratch.getContext("2d", { willReadFrequently: true });
5959
+ if (!ctx) return none;
5960
+ const keep = this.w * this.h * 4 * times.length <= 96e6;
5961
+ const read2 = () => ctx.getImageData(0, 0, scratch.width, scratch.height).data;
5962
+ const hash = (d) => {
5963
+ let h = 2166136261;
5964
+ for (let i = 0; i < d.length; i++) {
5965
+ h ^= d[i];
5966
+ h = Math.imul(h, 16777619);
5967
+ }
5968
+ return h >>> 0;
5969
+ };
5970
+ const ms = [];
5971
+ const threw = /* @__PURE__ */ new Set();
5972
+ const once = (t, timed) => {
5973
+ const from = performance.now();
5974
+ if (!this.paint(ctx, scratch, t)) threw.add(t);
5975
+ if (timed) {
5976
+ ctx.getImageData(0, 0, 1, 1);
5977
+ ms.push(performance.now() - from);
5978
+ }
5979
+ return read2();
5980
+ };
5981
+ for (const t of times) once(t, false);
5982
+ const first = times.map((t) => {
5983
+ const d = once(t, true);
5984
+ return keep ? d.slice() : hash(d);
5985
+ });
5986
+ const second = [...times].reverse().map((t) => once(t, false)).reverse();
5987
+ scratch.width = scratch.height = 1;
5988
+ const differs = [];
5989
+ times.forEach((t, i) => {
5990
+ const a = first[i], b = second[i];
5991
+ if (typeof a === "number") {
5992
+ if (a !== hash(b)) differs.push({ t, pixels: -1, maxDiff: -1 });
5993
+ return;
5994
+ }
5995
+ let pixels = 0, maxDiff = 0;
5996
+ for (let k = 0; k < a.length; k++) {
5997
+ const d = Math.abs(a[k] - b[k]);
5998
+ if (d) {
5999
+ pixels++;
6000
+ if (d > maxDiff) maxDiff = d;
6001
+ }
6002
+ }
6003
+ if (pixels) differs.push({ t, pixels, maxDiff });
6004
+ });
6005
+ ms.sort((x, y) => x - y);
6006
+ return { differs: differs.filter((d) => !threw.has(d.t)), drawMs: ms.length ? ms[Math.floor(ms.length / 2)] : 0, threw: times.filter((t) => threw.has(t)) };
6007
+ }
6008
+ checkerboard(ctx, c) {
6009
+ const s = 32;
6010
+ for (let y = 0; y < c.height; y += s) for (let x = 0; x < c.width; x += s) {
6011
+ ctx.fillStyle = x / s + y / s & 1 ? "#ff00ff" : "#220022";
6012
+ ctx.fillRect(x, y, s, s);
6013
+ }
6014
+ }
6015
+ destroy() {
6016
+ const texture = this.target?.texture;
6017
+ if (this.canvas) {
6018
+ this.canvas.width = this.canvas.height = 1;
6019
+ this.canvas = null;
6020
+ }
6021
+ super.destroy();
6022
+ texture?.destroy(true);
6023
+ }
6024
+ };
5621
6025
 
5622
6026
  // src/core/Composition.ts
5623
6027
  var _CompositionSequence = null;
5624
6028
  async function getCompositionSequence() {
5625
6029
  if (!_CompositionSequence) {
5626
- _CompositionSequence = (await import('./Composition-NDXKQHB4.js')).CompositionSequence;
6030
+ _CompositionSequence = (await import('./Composition-5KZETM4J.js')).CompositionSequence;
5627
6031
  }
5628
6032
  return _CompositionSequence;
5629
6033
  }
5630
6034
  var _SpansSequence = null;
5631
6035
  async function getSpansSequence() {
5632
- if (!_SpansSequence) _SpansSequence = (await import('./Spans-QHHWC2PB.js')).SpansSequence;
6036
+ if (!_SpansSequence) _SpansSequence = (await import('./Spans-FSSO2HGX.js')).SpansSequence;
5633
6037
  return _SpansSequence;
5634
6038
  }
5635
6039
  var staticTypes = {
@@ -5641,6 +6045,7 @@ var staticTypes = {
5641
6045
  camera: CameraSequence,
5642
6046
  light: LightSequence,
5643
6047
  shader: ShaderSequence,
6048
+ canvas: CanvasSequence,
5644
6049
  null: NullSequence
5645
6050
  };
5646
6051
  var registry2 = {};
@@ -5674,6 +6079,6 @@ async function buildSequenceTree(specs, parent, root) {
5674
6079
  return out;
5675
6080
  }
5676
6081
 
5677
- export { CameraSequence, DEFAULT_FOV, DEG3 as DEG, EXPRESSION_NAMES, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, TextSequence, bindClock, bindCounterKeyframes, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, createTimeline, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lengthen, lightLevel, noise, packLights, parseEase, parseSpring, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, resolveNumber, spansMeta, springDuration, timeRemapOf };
5678
- //# sourceMappingURL=chunk-Y253JQOH.js.map
5679
- //# sourceMappingURL=chunk-Y253JQOH.js.map
6082
+ export { CameraSequence, DEFAULT_FOV, DEG3 as DEG, EXPRESSION_NAMES, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, TextSequence, bindClock, bindCounterKeyframes, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, createTimeline, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lengthen, lightLevel, noise, noise2, packLights, parseEase, parseSpring, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, resolveNumber, spansMeta, springDuration, timeRemapOf, trimPolylines };
6083
+ //# sourceMappingURL=chunk-SBR75G73.js.map
6084
+ //# sourceMappingURL=chunk-SBR75G73.js.map