pixi-effects 0.10.0 → 0.11.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.
- package/CHANGELOG.md +21 -0
- package/README.md +6 -6
- package/ai/SKILL.md +3 -3
- package/ai/reference/cheatsheet.md +5 -2
- package/ai/reference/pitfalls.md +2 -2
- package/ai/reference/recipes.md +19 -10
- package/ai/template.html +11 -8
- package/ai/tools/render.mjs +14 -5
- package/dist/Composition-7TEKHZNQ.cjs +14 -0
- package/dist/{Composition-EKVTUYCI.cjs.map → Composition-7TEKHZNQ.cjs.map} +1 -1
- package/dist/Composition-HIISJAX2.js +5 -0
- package/dist/{Composition-NJCOMGD7.js.map → Composition-HIISJAX2.js.map} +1 -1
- package/dist/Controller.d.cts +1 -1
- package/dist/Controller.d.ts +1 -1
- package/dist/{Movie-w3GliuP3.d.cts → Movie-DSSNOF3T.d.cts} +60 -1
- package/dist/{Movie-puD9oZPg.d.ts → Movie-nzmWsxWX.d.ts} +60 -1
- package/dist/{chunk-WDGAVOMS.js → chunk-3IPDNC3E.js} +8 -5
- package/dist/chunk-3IPDNC3E.js.map +1 -0
- package/dist/{chunk-S3ZI4MKS.cjs → chunk-6NWJLVCA.cjs} +3 -3
- package/dist/{chunk-S3ZI4MKS.cjs.map → chunk-6NWJLVCA.cjs.map} +1 -1
- package/dist/{chunk-JAMNDBXW.js → chunk-C6QHKYC5.js} +3 -3
- package/dist/{chunk-JAMNDBXW.js.map → chunk-C6QHKYC5.js.map} +1 -1
- package/dist/{chunk-4WJUSDJJ.cjs → chunk-F7NCHS6Q.cjs} +31 -28
- package/dist/chunk-F7NCHS6Q.cjs.map +1 -0
- package/dist/index.cjs +259 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -2
- package/dist/index.d.ts +74 -2
- package/dist/index.js +237 -24
- package/dist/index.js.map +1 -1
- package/dist/loader.css +110 -0
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +15 -1
- package/docs/dsl.md +29 -0
- package/llms-full.txt +73 -18
- package/package.json +5 -2
- package/dist/Composition-EKVTUYCI.cjs +0 -14
- package/dist/Composition-NJCOMGD7.js +0 -5
- package/dist/chunk-4WJUSDJJ.cjs.map +0 -1
- package/dist/chunk-WDGAVOMS.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkF7NCHS6Q_cjs = require('./chunk-F7NCHS6Q.cjs');
|
|
4
|
+
var chunk6NWJLVCA_cjs = require('./chunk-6NWJLVCA.cjs');
|
|
5
5
|
var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
|
|
6
6
|
var PIXI = require('pixi.js');
|
|
7
7
|
var gsap = require('gsap');
|
|
@@ -296,6 +296,24 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
|
|
|
296
296
|
issues
|
|
297
297
|
};
|
|
298
298
|
}
|
|
299
|
+
|
|
300
|
+
// src/core/motionBlur.ts
|
|
301
|
+
var DEFAULTS = { samples: 8, shutter: 0.5 };
|
|
302
|
+
function resolveMotionBlur(spec, where) {
|
|
303
|
+
if (spec === void 0 || spec === null || spec === false) return null;
|
|
304
|
+
const o = spec === true ? {} : typeof spec === "number" ? { samples: spec } : spec;
|
|
305
|
+
if (typeof spec === "object") chunkUQBGW5DO_cjs.warnUnknownOptions(`${where} motionBlur`, o, ["samples", "shutter"]);
|
|
306
|
+
const samples = o.samples ?? DEFAULTS.samples, shutter = o.shutter ?? DEFAULTS.shutter;
|
|
307
|
+
if (!(Number.isInteger(samples) && samples >= 2 && samples <= 64)) throw new Error(`pixi-effects: ${where}: motionBlur samples must be a whole number from 2 to 64, got ${samples}`);
|
|
308
|
+
if (!(Number.isFinite(shutter) && shutter > 0 && shutter <= 1)) throw new Error(`pixi-effects: ${where}: motionBlur shutter must be a fraction of the frame time above 0 and up to 1 (0.5 is a 180\xB0 shutter), got ${shutter}`);
|
|
309
|
+
return { samples, shutter };
|
|
310
|
+
}
|
|
311
|
+
function blurTimes(frame, frameRate, mb, duration) {
|
|
312
|
+
const t = frame / frameRate, width = mb.shutter / frameRate;
|
|
313
|
+
return Array.from({ length: mb.samples }, (_, k) => Math.min(Math.max(t + ((k + 0.5) / mb.samples - 0.5) * width, 0), duration));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/core/Renderer.ts
|
|
299
317
|
var VIDEO_CODEC_BY_FORMAT = {
|
|
300
318
|
mp4: "avc",
|
|
301
319
|
mov: "avc",
|
|
@@ -368,7 +386,10 @@ async function exportFrames(movie, options = {}) {
|
|
|
368
386
|
format: container,
|
|
369
387
|
target: new mediabunny.BufferTarget()
|
|
370
388
|
});
|
|
371
|
-
const
|
|
389
|
+
const mb = options.motionBlur !== void 0 ? resolveMotionBlur(options.motionBlur, "movie.render()") : movie.motionBlur ?? null;
|
|
390
|
+
const stage = movie.app.canvas;
|
|
391
|
+
const blurCanvas = mb ? Object.assign(document.createElement("canvas"), { width: stage.width, height: stage.height }) : null;
|
|
392
|
+
const canvasSource = new mediabunny.CanvasSource(blurCanvas ?? stage, {
|
|
372
393
|
codec: opts.video.codec,
|
|
373
394
|
bitrate: opts.video.bitrate
|
|
374
395
|
});
|
|
@@ -389,7 +410,8 @@ async function exportFrames(movie, options = {}) {
|
|
|
389
410
|
const keyframeIntervalFrames = Math.max(1, Math.round(2 * movie.frameRate));
|
|
390
411
|
try {
|
|
391
412
|
for (let frame = 0; frame <= movie.totalFrames; frame++) {
|
|
392
|
-
await movie.
|
|
413
|
+
if (mb && blurCanvas) await movie._exposeFrame(frame, mb, blurCanvas, false);
|
|
414
|
+
else await movie.gotoFrame(frame, true);
|
|
393
415
|
const isKey = frame === 0 || frame % keyframeIntervalFrames === 0;
|
|
394
416
|
const addOpts = isKey ? { keyFrame: true } : void 0;
|
|
395
417
|
await canvasSource.add(frame / movie.frameRate, 1 / movie.frameRate, addOpts);
|
|
@@ -398,6 +420,7 @@ async function exportFrames(movie, options = {}) {
|
|
|
398
420
|
}
|
|
399
421
|
await canvasSource.close();
|
|
400
422
|
await output.finalize();
|
|
423
|
+
if (mb) await movie.gotoFrame(movie.totalFrames, true);
|
|
401
424
|
return new Blob([output.target.buffer], { type: output.format.mimeType });
|
|
402
425
|
} finally {
|
|
403
426
|
movie.app.ticker.start();
|
|
@@ -843,16 +866,16 @@ function expandTransitions(spec) {
|
|
|
843
866
|
if (toUses > 1) {
|
|
844
867
|
throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
|
|
845
868
|
}
|
|
846
|
-
const tStart =
|
|
869
|
+
const tStart = chunk6NWJLVCA_cjs.resolveAt(t.at, parentDuration);
|
|
847
870
|
const tEnd = tStart + t.duration;
|
|
848
871
|
const fromSeq = fromEntry.seq;
|
|
849
|
-
const fromStart =
|
|
872
|
+
const fromStart = chunk6NWJLVCA_cjs.resolveAt(fromSeq.at, parentDuration);
|
|
850
873
|
const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
|
|
851
874
|
if (tStart < fromStart || tEnd > fromEnd) {
|
|
852
875
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
|
|
853
876
|
}
|
|
854
877
|
const toSeq = toEntry.seq;
|
|
855
|
-
const toStart =
|
|
878
|
+
const toStart = chunk6NWJLVCA_cjs.resolveAt(toSeq.at, parentDuration);
|
|
856
879
|
const toEnd = toStart + (toSeq.duration ?? parentDuration);
|
|
857
880
|
if (tStart < toStart || tEnd > toEnd) {
|
|
858
881
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
|
|
@@ -1192,9 +1215,9 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1192
1215
|
onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
|
|
1193
1216
|
if (area === 0) onCanvas = "none";
|
|
1194
1217
|
}
|
|
1195
|
-
const keys =
|
|
1218
|
+
const keys = chunk6NWJLVCA_cjs.collectPropKeys(seq.spec);
|
|
1196
1219
|
const carried = carriers.some((c) => {
|
|
1197
|
-
const k =
|
|
1220
|
+
const k = chunk6NWJLVCA_cjs.collectPropKeys(c.spec);
|
|
1198
1221
|
return (c.spec.keyframes ?? []).length > 0 && (k.has("x") || k.has("y") || k.has("rotation") || k.has("scale"));
|
|
1199
1222
|
});
|
|
1200
1223
|
const moving = parentMoving || carried || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
|
|
@@ -1215,7 +1238,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1215
1238
|
const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
|
|
1216
1239
|
if (group) scene.set(info, group);
|
|
1217
1240
|
if (zero || seq.showsNothingYet) scaledToNothing.add(info);
|
|
1218
|
-
if (seq instanceof
|
|
1241
|
+
if (seq instanceof chunkF7NCHS6Q_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
|
|
1219
1242
|
});
|
|
1220
1243
|
};
|
|
1221
1244
|
walk2(root, "", false, true);
|
|
@@ -1275,10 +1298,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
|
|
|
1275
1298
|
const start = parentStart + (seq.at ?? 0);
|
|
1276
1299
|
const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
|
|
1277
1300
|
const span = end - start;
|
|
1278
|
-
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start +
|
|
1301
|
+
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunk6NWJLVCA_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
|
|
1279
1302
|
const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
|
|
1280
1303
|
const inner = [];
|
|
1281
|
-
const isComp = seq instanceof
|
|
1304
|
+
const isComp = seq instanceof chunkF7NCHS6Q_cjs.CompositionSequence;
|
|
1282
1305
|
if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
|
|
1283
1306
|
entries.push({ row, inner, groupable: !isComp });
|
|
1284
1307
|
});
|
|
@@ -1472,6 +1495,41 @@ function normalizePoster(poster, duration, frameRate) {
|
|
|
1472
1495
|
return { seconds, frame: Math.round(seconds * frameRate) };
|
|
1473
1496
|
}
|
|
1474
1497
|
|
|
1498
|
+
// src/core/loader.ts
|
|
1499
|
+
var FADE_FALLBACK_MS = 900;
|
|
1500
|
+
function resolveLoader(option, canvas) {
|
|
1501
|
+
if (option === false) return null;
|
|
1502
|
+
if (option instanceof HTMLElement) return option;
|
|
1503
|
+
if (typeof option === "string") return document.querySelector(option);
|
|
1504
|
+
let box = canvas?.parentElement ?? null;
|
|
1505
|
+
for (let level = 0; box && level < 4; level++, box = box.parentElement) {
|
|
1506
|
+
if (level > 0 && (box === document.body || box === document.documentElement)) break;
|
|
1507
|
+
const found = box.querySelector(".pe-loader");
|
|
1508
|
+
if (found) return found;
|
|
1509
|
+
}
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
function dismissLoader(loader) {
|
|
1513
|
+
if (!loader || loader.classList.contains("pe-loader--done")) return;
|
|
1514
|
+
loader.classList.add("pe-loader--done");
|
|
1515
|
+
loader.setAttribute("aria-hidden", "true");
|
|
1516
|
+
let removed = false;
|
|
1517
|
+
const remove = () => {
|
|
1518
|
+
if (!removed) {
|
|
1519
|
+
removed = true;
|
|
1520
|
+
loader.remove();
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
loader.addEventListener("transitionend", remove, { once: true });
|
|
1524
|
+
setTimeout(remove, FADE_FALLBACK_MS);
|
|
1525
|
+
}
|
|
1526
|
+
function failLoader(loader, message) {
|
|
1527
|
+
if (!loader) return;
|
|
1528
|
+
loader.setAttribute("data-state", "error");
|
|
1529
|
+
if (loader.hasAttribute("data-label")) loader.setAttribute("data-label", message);
|
|
1530
|
+
loader.setAttribute("aria-busy", "false");
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1475
1533
|
// src/core/Movie.ts
|
|
1476
1534
|
PIXI.extensions.add(PIXI.CullerPlugin);
|
|
1477
1535
|
gsap.gsap.registerPlugin(PixiPlugin.PixiPlugin);
|
|
@@ -1487,6 +1545,8 @@ var Movie = class {
|
|
|
1487
1545
|
_cancelAudioStart = null;
|
|
1488
1546
|
/** The poster time in seconds (`movie.init({ poster })`), or null. */
|
|
1489
1547
|
poster = null;
|
|
1548
|
+
/** The motion blur `render()`, `snapshot()` and `contactSheet()` apply unless told otherwise (`movie.init({ motionBlur })`), or null. */
|
|
1549
|
+
motionBlur = null;
|
|
1490
1550
|
_posterFrame = null;
|
|
1491
1551
|
/** The canvas shows the poster picture while the playhead is still at frame 0 (like `<video poster>`): the first seek or play replaces it. */
|
|
1492
1552
|
_atPoster = false;
|
|
@@ -1543,7 +1603,7 @@ var Movie = class {
|
|
|
1543
1603
|
const back = this.currentFrame;
|
|
1544
1604
|
this.currentFrame = this._posterFrame;
|
|
1545
1605
|
try {
|
|
1546
|
-
this.timeline.time(this.
|
|
1606
|
+
this.timeline.time(this._timeOf(this._posterFrame));
|
|
1547
1607
|
await this._awaitVideoFrames();
|
|
1548
1608
|
this._updateSpace();
|
|
1549
1609
|
this._renderNow();
|
|
@@ -1571,7 +1631,7 @@ var Movie = class {
|
|
|
1571
1631
|
return this._audioContext;
|
|
1572
1632
|
}
|
|
1573
1633
|
async init(options = {}) {
|
|
1574
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster"]);
|
|
1634
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster", "motionBlur", "loader"]);
|
|
1575
1635
|
this._initState = "pending";
|
|
1576
1636
|
try {
|
|
1577
1637
|
this.width = options.width ?? 1920;
|
|
@@ -1581,6 +1641,7 @@ var Movie = class {
|
|
|
1581
1641
|
this.totalFrames = Math.round(this.duration * this.frameRate);
|
|
1582
1642
|
const poster = normalizePoster(options.poster, this.duration, this.frameRate);
|
|
1583
1643
|
this.poster = poster.seconds;
|
|
1644
|
+
this.motionBlur = resolveMotionBlur(options.motionBlur, "movie.init()");
|
|
1584
1645
|
this._posterFrame = poster.frame;
|
|
1585
1646
|
this.background = options.background ?? "#000000";
|
|
1586
1647
|
this.timeline = gsap.gsap.timeline({ paused: true, defaults: { ease: "none" } });
|
|
@@ -1618,7 +1679,7 @@ var Movie = class {
|
|
|
1618
1679
|
height: options.composition.height ?? this.height,
|
|
1619
1680
|
duration: options.composition.duration ?? this.duration
|
|
1620
1681
|
} : void 0;
|
|
1621
|
-
await
|
|
1682
|
+
await chunk6NWJLVCA_cjs.ensureFilterLibrary(seededComposition);
|
|
1622
1683
|
const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
|
|
1623
1684
|
const rootSeqSpec = {
|
|
1624
1685
|
type: "composition",
|
|
@@ -1628,7 +1689,7 @@ var Movie = class {
|
|
|
1628
1689
|
...userComposition
|
|
1629
1690
|
};
|
|
1630
1691
|
if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
|
|
1631
|
-
const composition = new
|
|
1692
|
+
const composition = new chunkF7NCHS6Q_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
|
|
1632
1693
|
await composition.build();
|
|
1633
1694
|
this._rootSequence = composition;
|
|
1634
1695
|
if (composition.target) root.addChild(composition.target);
|
|
@@ -1647,7 +1708,9 @@ var Movie = class {
|
|
|
1647
1708
|
this._initState = "ready";
|
|
1648
1709
|
if (this._posterFrame !== null) await this._showPoster();
|
|
1649
1710
|
this.emit("ready");
|
|
1711
|
+
dismissLoader(resolveLoader(options.loader, options.canvas ?? this.app?.canvas ?? null));
|
|
1650
1712
|
} catch (err) {
|
|
1713
|
+
failLoader(resolveLoader(options.loader, options.canvas ?? null), "COULD NOT LOAD");
|
|
1651
1714
|
try {
|
|
1652
1715
|
await this.destroy();
|
|
1653
1716
|
} catch (cleanupErr) {
|
|
@@ -1681,17 +1744,24 @@ var Movie = class {
|
|
|
1681
1744
|
async _goto(frame) {
|
|
1682
1745
|
this._atPoster = false;
|
|
1683
1746
|
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1684
|
-
this.timeline.time(this.
|
|
1747
|
+
this.timeline.time(this._timeOf(this.currentFrame));
|
|
1685
1748
|
await this._awaitVideoFrames();
|
|
1686
1749
|
this._updateSpace();
|
|
1687
1750
|
this._renderNow();
|
|
1688
1751
|
this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1689
1752
|
}
|
|
1690
|
-
|
|
1753
|
+
/**
|
|
1754
|
+
* The moment frame `frame` shows. The very last frame (`totalFrames`) is at the end of the movie, where every layer's lifespan
|
|
1755
|
+
* has just closed: it is drawn a hair before, so a layer that lasts to the end is still on screen in the final frame
|
|
1756
|
+
* (it used to be an empty one, the last frame of every export).
|
|
1757
|
+
*/
|
|
1758
|
+
_timeOf(frame) {
|
|
1759
|
+
return Math.min(frame / this.frameRate, Math.max(0, this.duration - END_MARGIN));
|
|
1760
|
+
}
|
|
1761
|
+
async _awaitVideoFrames(t = this._timeOf(this.currentFrame)) {
|
|
1691
1762
|
if (!this._rootSequence) return;
|
|
1692
1763
|
const collected = [];
|
|
1693
1764
|
collectVideoSequences(this._rootSequence, collected);
|
|
1694
|
-
const t = this.currentFrame / this.frameRate;
|
|
1695
1765
|
await Promise.all(collected.map((v) => {
|
|
1696
1766
|
const local = t - (v.absoluteStart ?? v.at);
|
|
1697
1767
|
if (local < 0 || local > (v.duration ?? 0)) return Promise.resolve();
|
|
@@ -1699,19 +1769,27 @@ var Movie = class {
|
|
|
1699
1769
|
}));
|
|
1700
1770
|
}
|
|
1701
1771
|
async snapshot(frame = this.currentFrame, opts = {}) {
|
|
1702
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
|
|
1772
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as", "motionBlur"]);
|
|
1703
1773
|
this._requireReady("snapshot");
|
|
1704
|
-
|
|
1774
|
+
const mb = this._blurFor(opts.motionBlur, "movie.snapshot()");
|
|
1775
|
+
const f = clampFrame(frame, this.totalFrames);
|
|
1776
|
+
let source = this.app.canvas;
|
|
1777
|
+
if (mb) {
|
|
1778
|
+
source = this._blurCanvas();
|
|
1779
|
+
await this._quietly(() => this._exposeFrame(f, mb, source, true));
|
|
1780
|
+
} else await this._quietly(() => this.gotoFrame(f, true));
|
|
1705
1781
|
const scale = opts.scale ?? 1;
|
|
1706
1782
|
const out = document.createElement("canvas");
|
|
1707
1783
|
out.width = Math.max(1, Math.round(this.width * scale));
|
|
1708
1784
|
out.height = Math.max(1, Math.round(this.height * scale));
|
|
1709
|
-
out.getContext("2d").drawImage(
|
|
1785
|
+
out.getContext("2d").drawImage(source, 0, 0, out.width, out.height);
|
|
1710
1786
|
return encodeCanvas(out, opts.type ?? "image/png", opts.as ?? "blob");
|
|
1711
1787
|
}
|
|
1712
1788
|
async contactSheet(opts = {}) {
|
|
1713
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as"]);
|
|
1789
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as", "motionBlur"]);
|
|
1714
1790
|
this._requireReady("contactSheet");
|
|
1791
|
+
const mb = this._blurFor(opts.motionBlur, "movie.contactSheet()");
|
|
1792
|
+
const blurred = mb ? this._blurCanvas() : null;
|
|
1715
1793
|
const frames = (opts.frames ?? opts.times?.map((t) => Math.round(t * this.frameRate)) ?? pickFrames(this.totalFrames, opts.count ?? 6)).map((f) => clampFrame(f, this.totalFrames));
|
|
1716
1794
|
const LABEL_H = 26;
|
|
1717
1795
|
const L = sheetLayout(frames.length, opts.columns ?? 3, opts.cellWidth ?? 480, this.width, this.height, LABEL_H);
|
|
@@ -1725,9 +1803,10 @@ var Movie = class {
|
|
|
1725
1803
|
try {
|
|
1726
1804
|
for (let i = 0; i < frames.length; i++) {
|
|
1727
1805
|
const f = frames[i];
|
|
1728
|
-
await this._quietly(() => this.
|
|
1806
|
+
if (mb && blurred) await this._quietly(() => this._exposeFrame(f, mb, blurred, true));
|
|
1807
|
+
else await this._quietly(() => this.gotoFrame(f, true));
|
|
1729
1808
|
const { x, y } = L.positions[i];
|
|
1730
|
-
g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1809
|
+
g.drawImage(blurred ?? this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1731
1810
|
g.fillStyle = "#e8ecf8";
|
|
1732
1811
|
g.font = "600 15px system-ui, sans-serif";
|
|
1733
1812
|
g.textBaseline = "middle";
|
|
@@ -1797,9 +1876,46 @@ var Movie = class {
|
|
|
1797
1876
|
}
|
|
1798
1877
|
}
|
|
1799
1878
|
/** Projects every `threeD` layer for the current frame (see src/space). */
|
|
1800
|
-
_updateSpace() {
|
|
1879
|
+
_updateSpace(t = this._timeOf(this.currentFrame)) {
|
|
1801
1880
|
if (!this._rootSequence || !this.app) return;
|
|
1802
|
-
this._rootSequence.updateSpace(
|
|
1881
|
+
this._rootSequence.updateSpace(t, this.app.renderer);
|
|
1882
|
+
}
|
|
1883
|
+
/** The motion blur an operation uses: its own option when given (`false` is off), otherwise the movie's. */
|
|
1884
|
+
_blurFor(option, where) {
|
|
1885
|
+
return option === void 0 ? this.motionBlur : resolveMotionBlur(option, where);
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* @internal Draw `frame` with motion blur into `target` (a canvas the size of the stage): the average of `mb.samples` renders at moments
|
|
1889
|
+
* spread over the shutter interval around the frame time. With `settle` the playhead and the stage are left exactly on the frame.
|
|
1890
|
+
*/
|
|
1891
|
+
async _exposeFrame(frame, mb, target, settle) {
|
|
1892
|
+
const g = target.getContext("2d");
|
|
1893
|
+
const stage = this.app.canvas;
|
|
1894
|
+
this._atPoster = false;
|
|
1895
|
+
const times = blurTimes(frame, this.frameRate, mb, Math.max(0, this.duration - END_MARGIN));
|
|
1896
|
+
for (let k = 0; k < times.length; k++) {
|
|
1897
|
+
const t = times[k];
|
|
1898
|
+
this.timeline.time(t);
|
|
1899
|
+
await this._awaitVideoFrames(t);
|
|
1900
|
+
this._updateSpace(t);
|
|
1901
|
+
this._renderNow();
|
|
1902
|
+
if (k === 0) g.clearRect(0, 0, target.width, target.height);
|
|
1903
|
+
g.globalAlpha = 1 / (k + 1);
|
|
1904
|
+
g.drawImage(stage, 0, 0, target.width, target.height);
|
|
1905
|
+
}
|
|
1906
|
+
g.globalAlpha = 1;
|
|
1907
|
+
if (settle) await this._goto(frame);
|
|
1908
|
+
else {
|
|
1909
|
+
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1910
|
+
this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
_blurCanvas() {
|
|
1914
|
+
const stage = this.app.canvas;
|
|
1915
|
+
const c = document.createElement("canvas");
|
|
1916
|
+
c.width = stage.width;
|
|
1917
|
+
c.height = stage.height;
|
|
1918
|
+
return c;
|
|
1803
1919
|
}
|
|
1804
1920
|
play() {
|
|
1805
1921
|
if (this._initState !== "ready") return;
|
|
@@ -1898,7 +2014,7 @@ var Movie = class {
|
|
|
1898
2014
|
return this.muted;
|
|
1899
2015
|
}
|
|
1900
2016
|
async render(options) {
|
|
1901
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
|
|
2017
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio", "motionBlur"]);
|
|
1902
2018
|
try {
|
|
1903
2019
|
return await this._quietly(() => exportFrames(this, options));
|
|
1904
2020
|
} catch (err) {
|
|
@@ -1923,6 +2039,7 @@ var Movie = class {
|
|
|
1923
2039
|
this._initState = "destroyed";
|
|
1924
2040
|
}
|
|
1925
2041
|
};
|
|
2042
|
+
var END_MARGIN = 1e-4;
|
|
1926
2043
|
var clampFrame = (f, last) => Math.max(0, Math.min(Math.round(f), last));
|
|
1927
2044
|
function encodeCanvas(canvas, type, as) {
|
|
1928
2045
|
if (as === "dataURL") return Promise.resolve(canvas.toDataURL(type));
|
|
@@ -2127,17 +2244,17 @@ function orbit(opts) {
|
|
|
2127
2244
|
throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
|
|
2128
2245
|
}
|
|
2129
2246
|
const [cx, cy] = opts.center ?? [width / 2, height / 2];
|
|
2130
|
-
const fixedR = opts.radius ??
|
|
2247
|
+
const fixedR = opts.radius ?? chunk6NWJLVCA_cjs.homeDistance(height, fov ?? chunk6NWJLVCA_cjs.DEFAULT_FOV);
|
|
2131
2248
|
const start = opts.start ?? -degrees / 2;
|
|
2132
2249
|
const steps = Math.max(1, Math.round(duration * stepsPerSecond));
|
|
2133
2250
|
const easeFn = parseEase(ease);
|
|
2134
2251
|
const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
|
|
2135
2252
|
const pos = (i) => {
|
|
2136
2253
|
const p = i / steps;
|
|
2137
|
-
const a = (start + degrees * easeFn(p)) *
|
|
2254
|
+
const a = (start + degrees * easeFn(p)) * chunk6NWJLVCA_cjs.DEG;
|
|
2138
2255
|
if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
|
|
2139
2256
|
const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
|
|
2140
|
-
const R =
|
|
2257
|
+
const R = chunk6NWJLVCA_cjs.homeDistance(height, f);
|
|
2141
2258
|
return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
|
|
2142
2259
|
};
|
|
2143
2260
|
const keyframes = [];
|
|
@@ -2164,11 +2281,107 @@ function orbit(opts) {
|
|
|
2164
2281
|
return spec;
|
|
2165
2282
|
}
|
|
2166
2283
|
|
|
2284
|
+
// src/presets/particles.ts
|
|
2285
|
+
var KEYS = ["count", "at", "emit", "life", "area", "angle", "speed", "gravity", "wind", "drag", "sway", "size", "scale", "fade", "colors", "shape", "spin", "seed", "name", "blendMode", "template", "sampleRate"];
|
|
2286
|
+
var SHAPES = ["circle", "rect", "star"];
|
|
2287
|
+
var MAX_KEYFRAMES = 3e4;
|
|
2288
|
+
function particles(opts) {
|
|
2289
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("particles()", opts, KEYS);
|
|
2290
|
+
const { count, at = 0, emit = 0, gravity = 0, wind = 0, drag = 0, sway, fade, colors = ["#ffffff"], shape = "circle", seed = 0, name = "particle", template, sampleRate = 20 } = opts;
|
|
2291
|
+
if (!(Number.isInteger(count) && count > 0)) throw new Error(`particles(): count must be a whole number of particles (at least 1), got ${count}`);
|
|
2292
|
+
const life = range("life", opts.life, 1e-6);
|
|
2293
|
+
if (!opts.area || !Number.isFinite(opts.area.x) || !Number.isFinite(opts.area.y)) throw new Error("particles(): area is required: { x, y } (the centre) and optionally width / height to spread over");
|
|
2294
|
+
if (!(Number.isFinite(emit) && emit >= 0)) throw new Error(`particles(): emit must be a number of seconds >= 0, got ${emit}`);
|
|
2295
|
+
if (!(Number.isFinite(sampleRate) && sampleRate > 0)) throw new Error(`particles(): sampleRate must be a positive number (samples per second), got ${sampleRate}`);
|
|
2296
|
+
if (!SHAPES.includes(shape)) {
|
|
2297
|
+
const g = chunkUQBGW5DO_cjs.suggestName(String(shape), SHAPES);
|
|
2298
|
+
throw new Error(`particles(): shape "${shape}" is not one of ${SHAPES.map((s) => `"${s}"`).join(", ")}${g ? `; did you mean "${g}"?` : ""}`);
|
|
2299
|
+
}
|
|
2300
|
+
const angle = range("angle", opts.angle ?? [0, 360], -Infinity);
|
|
2301
|
+
const speed = range("speed", opts.speed ?? [100, 300], 0);
|
|
2302
|
+
const size = range("size", opts.size ?? [3, 6], 0);
|
|
2303
|
+
const spin = opts.spin === void 0 ? null : range("spin", opts.spin, -Infinity);
|
|
2304
|
+
const curved = gravity !== 0 || wind !== 0 || drag !== 0 || !!sway;
|
|
2305
|
+
const worst = curved ? Math.max(1, Math.ceil(life[1] * sampleRate)) : 1;
|
|
2306
|
+
if (count * worst > MAX_KEYFRAMES) {
|
|
2307
|
+
throw new Error(`particles(): too many keyframes (${count} particles \xD7 up to ${worst} samples each): lower count or life, or lower sampleRate (now ${sampleRate})`);
|
|
2308
|
+
}
|
|
2309
|
+
const round2 = (v) => Math.round(v * 1e6) / 1e6;
|
|
2310
|
+
const out = [];
|
|
2311
|
+
for (let i = 0; i < count; i++) {
|
|
2312
|
+
const r = chunk6NWJLVCA_cjs.random(seed * 1000003 + i * 7919 + 17);
|
|
2313
|
+
const pick = ([a2, b]) => a2 + r() * (b - a2);
|
|
2314
|
+
const born = at + r() * emit;
|
|
2315
|
+
const lifetime = pick(life);
|
|
2316
|
+
const ax = (r() - 0.5) * (opts.area.width ?? 0), ay = (r() - 0.5) * (opts.area.height ?? 0);
|
|
2317
|
+
const x0 = opts.area.x + ax, y0 = opts.area.y + ay;
|
|
2318
|
+
const a = pick(angle) * Math.PI / 180, v = pick(speed);
|
|
2319
|
+
const vx = v * Math.cos(a), vy = v * Math.sin(a);
|
|
2320
|
+
const s = pick(size);
|
|
2321
|
+
const color = colors[Math.min(colors.length - 1, Math.floor(r() * colors.length))];
|
|
2322
|
+
const turn = spin ? pick(spin) : 0;
|
|
2323
|
+
const startAngle = spin ? r() * 360 : 0;
|
|
2324
|
+
const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
|
|
2325
|
+
const n0 = sway ? chunk6NWJLVCA_cjs.noise(0, seed * 1000003 + i) : 0;
|
|
2326
|
+
const at_ = (t) => {
|
|
2327
|
+
const axis = (p0, v0, acc) => {
|
|
2328
|
+
if (drag > 0) {
|
|
2329
|
+
const vInf = acc / drag;
|
|
2330
|
+
return p0 + vInf * t + (v0 - vInf) * (1 - Math.exp(-drag * t)) / drag;
|
|
2331
|
+
}
|
|
2332
|
+
return p0 + v0 * t + 0.5 * acc * t * t;
|
|
2333
|
+
};
|
|
2334
|
+
const sw = sway ? swayAmp * (chunk6NWJLVCA_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
|
|
2335
|
+
return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
|
|
2336
|
+
};
|
|
2337
|
+
const kfs = [];
|
|
2338
|
+
const steps = curved ? Math.max(1, Math.ceil(lifetime * sampleRate)) : 1;
|
|
2339
|
+
const step = lifetime / steps;
|
|
2340
|
+
for (let k = 1; k <= steps; k++) {
|
|
2341
|
+
const p = at_(k * step);
|
|
2342
|
+
kfs.push({ at: round2((k - 1) * step), to: { x: round2(p.x), y: round2(p.y) }, duration: round2(step), ease: "none" });
|
|
2343
|
+
}
|
|
2344
|
+
if (opts.scale) kfs.push({ at: 0, to: { scale: opts.scale[1] }, duration: round2(lifetime), ease: "none" });
|
|
2345
|
+
if (turn !== 0) kfs.push({ at: 0, to: { rotation: round2(startAngle + turn * lifetime) }, duration: round2(lifetime), ease: "none" });
|
|
2346
|
+
if (fade?.in) kfs.push({ at: 0, to: { alpha: 1 }, duration: fade.in, ease: "none" });
|
|
2347
|
+
if (fade?.out) kfs.push({ at: -fade.out, from: { alpha: 1 }, to: { alpha: 0 }, duration: fade.out, ease: "none" });
|
|
2348
|
+
const initial = { x: round2(x0), y: round2(y0) };
|
|
2349
|
+
if (opts.scale) initial.scale = opts.scale[0];
|
|
2350
|
+
if (fade?.in) initial.alpha = 0;
|
|
2351
|
+
if (spin) initial.rotation = round2(startAngle);
|
|
2352
|
+
const common = { name: `${name}-${i}`, at: round2(born), duration: round2(lifetime), ...opts.blendMode ? { blendMode: opts.blendMode } : {} };
|
|
2353
|
+
if (template) {
|
|
2354
|
+
const t = template;
|
|
2355
|
+
out.push({ ...t, ...common, initial: { ...t.initial, ...initial }, keyframes: [...t.keyframes ?? [], ...kfs] });
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
const style = { ...initial, fillColor: color };
|
|
2359
|
+
if (shape === "circle") out.push({ type: "shape", shape: "circle", radius: round2(s), ...common, initial: style, keyframes: kfs });
|
|
2360
|
+
else if (shape === "rect") out.push({ type: "shape", shape: "rect", width: round2(s * 2), height: round2(s * 2), ...common, initial: style, keyframes: kfs });
|
|
2361
|
+
else {
|
|
2362
|
+
const points = [];
|
|
2363
|
+
for (let p = 0; p < 10; p++) {
|
|
2364
|
+
const rad = p % 2 === 0 ? s : s * 0.45, ang = -Math.PI / 2 + p * Math.PI / 5;
|
|
2365
|
+
points.push([round2(rad * Math.cos(ang)), round2(rad * Math.sin(ang))]);
|
|
2366
|
+
}
|
|
2367
|
+
out.push({ type: "shape", shape: "polygon", points, ...common, initial: style, keyframes: kfs });
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return out;
|
|
2371
|
+
}
|
|
2372
|
+
function range(name, v, min) {
|
|
2373
|
+
const [a, b] = typeof v === "number" ? [v, v] : v;
|
|
2374
|
+
if (!(Number.isFinite(a) && Number.isFinite(b)) || a > b || a < min || min > 0 && a <= 0) {
|
|
2375
|
+
throw new Error(`particles(): ${name} must be a number or [min, max] with min <= max${min > -Infinity ? ` and min >= ${min}` : ""}, got ${JSON.stringify(v)}`);
|
|
2376
|
+
}
|
|
2377
|
+
return [a, b];
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2167
2380
|
// src/presets/followPath.ts
|
|
2168
|
-
var
|
|
2381
|
+
var KEYS2 = ["d", "at", "duration", "ease", "from", "to", "orient", "rotate", "frameRate"];
|
|
2169
2382
|
var MAX_STEPS = 2e3;
|
|
2170
2383
|
function followPath(opts) {
|
|
2171
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("followPath()", opts,
|
|
2384
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("followPath()", opts, KEYS2);
|
|
2172
2385
|
const { d, at = 0, duration, ease = "none", from = 0, to = 1, orient = false, rotate = 0, frameRate = 30 } = opts;
|
|
2173
2386
|
if (typeof d !== "string" || d.trim() === "") throw new Error('followPath(): d must be SVG path data, e.g. "M 100 600 C 300 100 900 100 1180 600"');
|
|
2174
2387
|
if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`followPath(): duration must be a positive number of seconds, got ${duration}`);
|
|
@@ -2180,7 +2393,7 @@ function followPath(opts) {
|
|
|
2180
2393
|
if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
|
|
2181
2394
|
const segments = [];
|
|
2182
2395
|
let total = 0;
|
|
2183
|
-
for (const line of
|
|
2396
|
+
for (const line of chunk6NWJLVCA_cjs.flattenSvgPath(d)) {
|
|
2184
2397
|
const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
|
|
2185
2398
|
for (let i = 2; i < p.length; i += 2) {
|
|
2186
2399
|
const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
|
|
@@ -2261,7 +2474,7 @@ function distances(n, from, grid, seed) {
|
|
|
2261
2474
|
if (n === 0) return [];
|
|
2262
2475
|
if (from === "random") {
|
|
2263
2476
|
const order = Array.from({ length: n }, (_, i) => i);
|
|
2264
|
-
const r =
|
|
2477
|
+
const r = chunk6NWJLVCA_cjs.random(seed);
|
|
2265
2478
|
for (let i = n - 1; i > 0; i--) {
|
|
2266
2479
|
const j = Math.floor(r() * (i + 1));
|
|
2267
2480
|
[order[i], order[j]] = [order[j], order[i]];
|
|
@@ -2323,7 +2536,7 @@ function wiggle(opts) {
|
|
|
2323
2536
|
to[n] = p.around;
|
|
2324
2537
|
return;
|
|
2325
2538
|
}
|
|
2326
|
-
const off = (
|
|
2539
|
+
const off = (chunk6NWJLVCA_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
|
|
2327
2540
|
to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
|
|
2328
2541
|
});
|
|
2329
2542
|
out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
|
|
@@ -2332,7 +2545,7 @@ function wiggle(opts) {
|
|
|
2332
2545
|
}
|
|
2333
2546
|
|
|
2334
2547
|
// src/presets/animateText.ts
|
|
2335
|
-
var
|
|
2548
|
+
var KEYS3 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
|
|
2336
2549
|
var PRESETS = {
|
|
2337
2550
|
rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
|
|
2338
2551
|
drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
|
|
@@ -2344,7 +2557,7 @@ var PRESETS = {
|
|
|
2344
2557
|
};
|
|
2345
2558
|
var PRESET_NAMES = Object.keys(PRESETS);
|
|
2346
2559
|
function animateText(text, style, options) {
|
|
2347
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("animateText()", options,
|
|
2560
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("animateText()", options, KEYS3);
|
|
2348
2561
|
const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
|
|
2349
2562
|
if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`animateText(): duration must be a positive number of seconds (how long until the last piece is gone), got ${duration}`);
|
|
2350
2563
|
const pieces = splitText(text, style, { by, x, y, align });
|
|
@@ -2449,26 +2662,30 @@ function withFade(spec, opts) {
|
|
|
2449
2662
|
|
|
2450
2663
|
Object.defineProperty(exports, "noise", {
|
|
2451
2664
|
enumerable: true,
|
|
2452
|
-
get: function () { return
|
|
2665
|
+
get: function () { return chunk6NWJLVCA_cjs.noise; }
|
|
2453
2666
|
});
|
|
2454
2667
|
Object.defineProperty(exports, "rand", {
|
|
2455
2668
|
enumerable: true,
|
|
2456
|
-
get: function () { return
|
|
2669
|
+
get: function () { return chunk6NWJLVCA_cjs.rand; }
|
|
2457
2670
|
});
|
|
2458
2671
|
Object.defineProperty(exports, "random", {
|
|
2459
2672
|
enumerable: true,
|
|
2460
|
-
get: function () { return
|
|
2673
|
+
get: function () { return chunk6NWJLVCA_cjs.random; }
|
|
2461
2674
|
});
|
|
2462
2675
|
Object.defineProperty(exports, "registerSequenceType", {
|
|
2463
2676
|
enumerable: true,
|
|
2464
|
-
get: function () { return
|
|
2677
|
+
get: function () { return chunk6NWJLVCA_cjs.registerSequenceType; }
|
|
2465
2678
|
});
|
|
2466
2679
|
exports.Movie = Movie;
|
|
2467
2680
|
exports.animateText = animateText;
|
|
2681
|
+
exports.dismissLoader = dismissLoader;
|
|
2682
|
+
exports.failLoader = failLoader;
|
|
2468
2683
|
exports.followPath = followPath;
|
|
2469
2684
|
exports.kenBurns = kenBurns;
|
|
2470
2685
|
exports.measureText = measureText;
|
|
2471
2686
|
exports.orbit = orbit;
|
|
2687
|
+
exports.particles = particles;
|
|
2688
|
+
exports.resolveLoader = resolveLoader;
|
|
2472
2689
|
exports.splitText = splitText;
|
|
2473
2690
|
exports.stagger = stagger;
|
|
2474
2691
|
exports.wiggle = wiggle;
|