@dnyantra/dtly 0.2.1 → 0.3.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/README.md +21 -8
- package/dist/agents.d.ts +1 -0
- package/dist/agents.js +9 -4
- package/dist/agents.js.map +1 -1
- package/dist/cli.js +35 -5
- package/dist/cli.js.map +1 -1
- package/dist/core/anim.js +20 -0
- package/dist/core/anim.js.map +1 -1
- package/dist/core/captions.d.ts +13 -0
- package/dist/core/captions.js +57 -0
- package/dist/core/captions.js.map +1 -0
- package/dist/core/geo.d.ts +13 -0
- package/dist/core/geo.js +41 -0
- package/dist/core/geo.js.map +1 -0
- package/dist/core/model.d.ts +112 -9
- package/dist/core/model.js.map +1 -1
- package/dist/core/path.d.ts +35 -0
- package/dist/core/path.js +324 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/svg.d.ts +22 -0
- package/dist/core/svg.js +217 -0
- package/dist/core/svg.js.map +1 -0
- package/dist/core/text.d.ts +3 -1
- package/dist/core/text.js +49 -3
- package/dist/core/text.js.map +1 -1
- package/dist/mcp/server.js +28 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/ops/ops.d.ts +7 -0
- package/dist/ops/ops.js +11 -1
- package/dist/ops/ops.js.map +1 -1
- package/dist/presets/app.d.ts +2 -0
- package/dist/presets/app.js +82 -0
- package/dist/presets/app.js.map +1 -0
- package/dist/presets/captions.d.ts +2 -0
- package/dist/presets/captions.js +48 -0
- package/dist/presets/captions.js.map +1 -0
- package/dist/presets/charts.d.ts +2 -0
- package/dist/presets/charts.js +162 -0
- package/dist/presets/charts.js.map +1 -0
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +62 -1
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/maps.d.ts +2 -0
- package/dist/presets/maps.js +79 -0
- package/dist/presets/maps.js.map +1 -0
- package/dist/render/fx.d.ts +11 -0
- package/dist/render/fx.js +87 -0
- package/dist/render/fx.js.map +1 -0
- package/dist/render/grade.d.ts +20 -0
- package/dist/render/grade.js +178 -0
- package/dist/render/grade.js.map +1 -0
- package/dist/render/lottie.d.ts +36 -0
- package/dist/render/lottie.js +523 -0
- package/dist/render/lottie.js.map +1 -0
- package/dist/render/node-host.d.ts +1 -0
- package/dist/render/node-host.js +15 -4
- package/dist/render/node-host.js.map +1 -1
- package/dist/render/perspective.d.ts +5 -0
- package/dist/render/perspective.js +71 -0
- package/dist/render/perspective.js.map +1 -0
- package/dist/render/renderer.d.ts +6 -0
- package/dist/render/renderer.js +156 -95
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/shapes.d.ts +6 -0
- package/dist/render/shapes.js +129 -0
- package/dist/render/shapes.js.map +1 -0
- package/dist/render/tiles.d.ts +11 -0
- package/dist/render/tiles.js +42 -0
- package/dist/render/tiles.js.map +1 -0
- package/dist/render/transcribe.d.ts +14 -0
- package/dist/render/transcribe.js +55 -0
- package/dist/render/transcribe.js.map +1 -0
- package/dist/studio/server.js +14 -1
- package/dist/studio/server.js.map +1 -1
- package/dist/studio-app/app.js +5 -4
- package/dist/studio-app/app.js.map +4 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/plugin/skills/{dtly → moti}/SKILL.md +26 -6
package/dist/render/node-host.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Node host: skia-canvas for drawing, FFmpeg for decoding video frames.
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { Canvas, FontLibrary, ImageData, loadImage } from 'skia-canvas';
|
|
6
7
|
import { isAnimated } from '../core/model.js';
|
|
8
|
+
import { GRADE_TYPES } from './grade.js';
|
|
7
9
|
// CPU rendering by default: deterministic, and the GPU backend can drop draws when a shader
|
|
8
10
|
// compile times out (seen with JPEG/YUV images on Metal). DTLY_GPU=1 opts back in.
|
|
9
11
|
export const newCanvas = (w, h) => {
|
|
@@ -130,7 +132,8 @@ export class NodeHost {
|
|
|
130
132
|
constructor(baseDir) {
|
|
131
133
|
this.baseDir = baseDir;
|
|
132
134
|
}
|
|
133
|
-
resolve(src) { return path.resolve(this.baseDir, src); }
|
|
135
|
+
resolve(src) { return src.startsWith('data:') ? src : path.resolve(this.baseDir, src); }
|
|
136
|
+
data(src) { return fs.promises.readFile(this.resolve(src), 'utf8'); }
|
|
134
137
|
createCanvas(w, h) { return newCanvas(w, h); }
|
|
135
138
|
image(src) {
|
|
136
139
|
if (!this.images.has(src))
|
|
@@ -142,19 +145,27 @@ export class NodeHost {
|
|
|
142
145
|
const key = [layer.id, layer.src, w, h, layer.fit, rate].join('|');
|
|
143
146
|
let r = this.readers.get(key);
|
|
144
147
|
if (!r)
|
|
145
|
-
this.readers.set(key, (r = new VideoReader(this.resolve(layer.src), w, h, layer.fit ?? 'cover', rate, [...this.bakedVideoEffects(layer)].map(ffmpegFilter))));
|
|
148
|
+
this.readers.set(key, (r = new VideoReader(this.resolve(layer.src), w, h, layer.fit ?? 'cover', rate, [...this.bakedVideoEffects(layer)].map((e) => ffmpegFilter(e, (src) => this.resolve(src))))));
|
|
146
149
|
return r.frame(sourceTime);
|
|
147
150
|
}
|
|
148
151
|
// Static colour/blur effects on video run inside FFmpeg, in parallel with drawing.
|
|
149
152
|
bakedVideoEffects(layer) {
|
|
150
|
-
|
|
153
|
+
const effects = layer.effects ?? [];
|
|
154
|
+
const baked = effects.filter((e) => BAKEABLE.has(e.type) && !isAnimated(e.radius ?? e.amount));
|
|
155
|
+
// A full-strength LUT that is the only grade on the clip runs inside FFmpeg (lut3d), at no extra cost.
|
|
156
|
+
const grades = effects.filter((e) => GRADE_TYPES.has(e.type));
|
|
157
|
+
if (grades.length === 1 && grades[0].type === 'lut' && (grades[0].intensity === undefined || grades[0].intensity === 100))
|
|
158
|
+
baked.push(grades[0]);
|
|
159
|
+
return new Set(baked);
|
|
151
160
|
}
|
|
152
161
|
close() { this.readers.forEach((r) => r.close()); this.readers.clear(); }
|
|
153
162
|
}
|
|
154
163
|
const BAKEABLE = new Set(['blur', 'brightness', 'saturate', 'contrast']);
|
|
155
164
|
// FFmpeg equivalents of the CSS filters the renderer would otherwise apply.
|
|
156
|
-
const
|
|
165
|
+
const escapeFilterPath = (p) => p.replace(/\\/g, '/').replace(/([:'\\,\[\];])/g, '\\$1');
|
|
166
|
+
const ffmpegFilter = (e, resolve) => {
|
|
157
167
|
switch (e.type) {
|
|
168
|
+
case 'lut': return `lut3d=file=${escapeFilterPath(resolve(e.src))}:interp=trilinear`;
|
|
158
169
|
case 'blur': return `gblur=sigma=${Number(e.radius)}`;
|
|
159
170
|
case 'brightness': {
|
|
160
171
|
const k = Number(e.amount) / 100;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-host.js","sourceRoot":"","sources":["../../src/render/node-host.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EAAC,KAAK,EAAsC,MAAM,oBAAoB,CAAC;AAC9E,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAC,aAAa,EAAC,MAAM,UAAU,CAAC;AACvC,OAAO,EAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,aAAa,CAAC;AACtE,OAAO,EAAC,UAAU,EAA6C,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"node-host.js","sourceRoot":"","sources":["../../src/render/node-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Vec2 } from '../core/model.js';
|
|
2
|
+
type Ctx = CanvasRenderingContext2D;
|
|
3
|
+
export declare const drawPerspective: (ctx: Ctx, src: CanvasImageSource, w: number, h: number, quad: [Vec2, Vec2, Vec2, Vec2], n?: number) => void;
|
|
4
|
+
export declare const tiltCorners: (w: number, h: number, rotX: number, rotY: number, rotZ?: number, perspective?: number) => [Vec2, Vec2, Vec2, Vec2];
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Homography mapping the unit square (0,0)-(1,1) to the quad tl, tr, br, bl.
|
|
2
|
+
const squareToQuad = ([x0, y0], [x1, y1], [x2, y2], [x3, y3]) => {
|
|
3
|
+
const dx1 = x1 - x2, dx2 = x3 - x2, dx3 = x0 - x1 + x2 - x3;
|
|
4
|
+
const dy1 = y1 - y2, dy2 = y3 - y2, dy3 = y0 - y1 + y2 - y3;
|
|
5
|
+
let a13 = 0, a23 = 0;
|
|
6
|
+
const den = dx1 * dy2 - dx2 * dy1;
|
|
7
|
+
if (Math.abs(den) > 1e-12) {
|
|
8
|
+
a13 = (dx3 * dy2 - dx2 * dy3) / den;
|
|
9
|
+
a23 = (dx1 * dy3 - dx3 * dy1) / den;
|
|
10
|
+
}
|
|
11
|
+
const a11 = x1 - x0 + a13 * x1, a21 = x3 - x0 + a23 * x3, a31 = x0;
|
|
12
|
+
const a12 = y1 - y0 + a13 * y1, a22 = y3 - y0 + a23 * y3, a32 = y0;
|
|
13
|
+
return (u, v) => {
|
|
14
|
+
const w = a13 * u + a23 * v + 1;
|
|
15
|
+
return [(a11 * u + a21 * v + a31) / w, (a12 * u + a22 * v + a32) / w];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
// Draws `src` (w×h) so its corners land on the quad. `n` subdivisions per side (16 is seamless at 1080p).
|
|
19
|
+
export const drawPerspective = (ctx, src, w, h, quad, n = 16) => {
|
|
20
|
+
const map = squareToQuad(...quad);
|
|
21
|
+
const tri = (s0, s1, s2) => {
|
|
22
|
+
const d0 = map(s0[0] / w, s0[1] / h), d1 = map(s1[0] / w, s1[1] / h), d2 = map(s2[0] / w, s2[1] / h);
|
|
23
|
+
// Affine map taking the source triangle onto the destination triangle.
|
|
24
|
+
const den = (s1[0] - s0[0]) * (s2[1] - s0[1]) - (s2[0] - s0[0]) * (s1[1] - s0[1]);
|
|
25
|
+
if (Math.abs(den) < 1e-9)
|
|
26
|
+
return;
|
|
27
|
+
const a = ((d1[0] - d0[0]) * (s2[1] - s0[1]) - (d2[0] - d0[0]) * (s1[1] - s0[1])) / den;
|
|
28
|
+
const b = ((d1[1] - d0[1]) * (s2[1] - s0[1]) - (d2[1] - d0[1]) * (s1[1] - s0[1])) / den;
|
|
29
|
+
const c = ((d2[0] - d0[0]) * (s1[0] - s0[0]) - (d1[0] - d0[0]) * (s2[0] - s0[0])) / den;
|
|
30
|
+
const d = ((d2[1] - d0[1]) * (s1[0] - s0[0]) - (d1[1] - d0[1]) * (s2[0] - s0[0])) / den;
|
|
31
|
+
const e = d0[0] - a * s0[0] - c * s0[1], f = d0[1] - b * s0[0] - d * s0[1];
|
|
32
|
+
// Grow the clip triangle slightly so neighbouring triangles overlap and leave no hairline seams.
|
|
33
|
+
const cx = (d0[0] + d1[0] + d2[0]) / 3, cy = (d0[1] + d1[1] + d2[1]) / 3;
|
|
34
|
+
const grow = (p) => { const vx = p[0] - cx, vy = p[1] - cy, l = Math.hypot(vx, vy) || 1; return [p[0] + (vx / l) * 0.6, p[1] + (vy / l) * 0.6]; };
|
|
35
|
+
ctx.save();
|
|
36
|
+
ctx.beginPath();
|
|
37
|
+
ctx.moveTo(...grow(d0));
|
|
38
|
+
ctx.lineTo(...grow(d1));
|
|
39
|
+
ctx.lineTo(...grow(d2));
|
|
40
|
+
ctx.closePath();
|
|
41
|
+
ctx.clip();
|
|
42
|
+
ctx.transform(a, b, c, d, e, f);
|
|
43
|
+
ctx.drawImage(src, 0, 0);
|
|
44
|
+
ctx.restore();
|
|
45
|
+
};
|
|
46
|
+
for (let j = 0; j < n; j++) {
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const x0 = (w * i) / n, x1 = (w * (i + 1)) / n, y0 = (h * j) / n, y1 = (h * (j + 1)) / n;
|
|
49
|
+
tri([x0, y0], [x1, y0], [x0, y1]);
|
|
50
|
+
tri([x1, y0], [x1, y1], [x0, y1]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// Corners of a w×h rectangle rotated in 3D about its centre and projected (for device tilts).
|
|
55
|
+
export const tiltCorners = (w, h, rotX, rotY, rotZ = 0, perspective = 2000) => {
|
|
56
|
+
const rx = (rotX * Math.PI) / 180, ry = (rotY * Math.PI) / 180, rz = (rotZ * Math.PI) / 180;
|
|
57
|
+
const pts = [[-w / 2, -h / 2], [w / 2, -h / 2], [w / 2, h / 2], [-w / 2, h / 2]];
|
|
58
|
+
return pts.map(([x, y]) => {
|
|
59
|
+
// rotate Z, then X, then Y
|
|
60
|
+
let X = x * Math.cos(rz) - y * Math.sin(rz), Y = x * Math.sin(rz) + y * Math.cos(rz), Z = 0;
|
|
61
|
+
const y1 = Y * Math.cos(rx) - Z * Math.sin(rx), z1 = Y * Math.sin(rx) + Z * Math.cos(rx);
|
|
62
|
+
Y = y1;
|
|
63
|
+
Z = z1;
|
|
64
|
+
const x2 = X * Math.cos(ry) + Z * Math.sin(ry), z2 = -X * Math.sin(ry) + Z * Math.cos(ry);
|
|
65
|
+
X = x2;
|
|
66
|
+
Z = z2;
|
|
67
|
+
const k = perspective / (perspective + Z);
|
|
68
|
+
return [X * k + w / 2, Y * k + h / 2];
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=perspective.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perspective.js","sourceRoot":"","sources":["../../src/render/perspective.ts"],"names":[],"mappings":"AAKA,6EAA6E;AAC7E,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAO,EAAE,EAAE;IACtF,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC5D,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC5D,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC;IACrB,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAClC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC;QAC1B,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QACpC,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IACtC,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,CAAC;IACnE,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,CAAC;IACnE,OAAO,CAAC,CAAS,EAAE,CAAS,EAAQ,EAAE;QACpC,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,0GAA0G;AAC1G,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAQ,EAAE,GAAsB,EAAE,CAAS,EAAE,CAAS,EAAE,IAA8B,EAAE,CAAC,GAAG,EAAE,EAAE,EAAE;IAChI,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,EAAQ,EAAE,EAAQ,EAAE,EAAQ,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrG,uEAAuE;QACvE,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI;YAAE,OAAO;QACjC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QACxF,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QACxF,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QACxF,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QACxF,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3E,iGAAiG;QACjG,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CA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"}
|
|
@@ -14,6 +14,7 @@ export interface Host {
|
|
|
14
14
|
image(src: string): Promise<Drawable>;
|
|
15
15
|
videoFrame(layer: VideoLayer, sourceTime: number, w: number, h: number, fps: number): Promise<Drawable>;
|
|
16
16
|
bakedVideoEffects?(layer: VideoLayer): Set<Effect>;
|
|
17
|
+
data(src: string): Promise<string>;
|
|
17
18
|
defaultFont: string;
|
|
18
19
|
}
|
|
19
20
|
type M = [number, number, number, number, number, number];
|
|
@@ -27,9 +28,14 @@ export declare class Renderer {
|
|
|
27
28
|
private release;
|
|
28
29
|
layerSize(layer: Layer, comp: Composition, t: number): Vec2;
|
|
29
30
|
matrix(layer: Layer, comp: Composition, t: number, depth?: number): M;
|
|
31
|
+
private lotties;
|
|
32
|
+
private preload;
|
|
30
33
|
renderComp(ctx: Ctx, comp: Composition, t: number, withBackground?: boolean): Promise<void>;
|
|
31
34
|
isActive(layer: Layer, comp: Composition, t: number): boolean;
|
|
32
35
|
renderLayer(target: Ctx, layer: Layer, comp: Composition, t: number): Promise<void>;
|
|
36
|
+
private applyMasks;
|
|
37
|
+
private grade;
|
|
38
|
+
private renderAdjustment;
|
|
33
39
|
private applyShadow;
|
|
34
40
|
private drawContent;
|
|
35
41
|
}
|
package/dist/render/renderer.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
import { val } from '../core/anim.js';
|
|
4
4
|
import { parseColor, rgbaString } from '../core/color.js';
|
|
5
5
|
import { drawText } from '../core/text.js';
|
|
6
|
+
import { drawShape } from './shapes.js';
|
|
7
|
+
import { drawWithFx } from './fx.js';
|
|
8
|
+
import { applyGrade, drawVignette, GRADE_TYPES, preloadLuts } from './grade.js';
|
|
9
|
+
import { drawPerspective, tiltCorners } from './perspective.js';
|
|
10
|
+
import { parsePath, tracePath } from '../core/path.js';
|
|
11
|
+
import { LottiePlayer } from './lottie.js';
|
|
6
12
|
const I = [1, 0, 0, 1, 0, 0];
|
|
7
13
|
const mul = (m, n) => [
|
|
8
14
|
m[0] * n[0] + m[2] * n[1], m[1] * n[0] + m[3] * n[1],
|
|
@@ -31,7 +37,16 @@ export class Renderer {
|
|
|
31
37
|
const list = this.pool.get(`${w}x${h}`);
|
|
32
38
|
const c = list?.pop() ?? this.host.createCanvas(w, h);
|
|
33
39
|
const ctx = c.getContext('2d');
|
|
40
|
+
// Pooled canvases come back with whatever state their last user left: reset all of it.
|
|
34
41
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
42
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
43
|
+
ctx.globalAlpha = 1;
|
|
44
|
+
ctx.filter = 'none';
|
|
45
|
+
ctx.shadowColor = 'transparent';
|
|
46
|
+
ctx.shadowBlur = 0;
|
|
47
|
+
ctx.shadowOffsetX = 0;
|
|
48
|
+
ctx.shadowOffsetY = 0;
|
|
49
|
+
ctx.setLineDash([]);
|
|
35
50
|
ctx.clearRect(0, 0, w, h);
|
|
36
51
|
return c;
|
|
37
52
|
}
|
|
@@ -51,6 +66,10 @@ export class Renderer {
|
|
|
51
66
|
const c = this.comp(layer.comp);
|
|
52
67
|
return [c.width, c.height];
|
|
53
68
|
}
|
|
69
|
+
case 'lottie': {
|
|
70
|
+
const p = this.lotties.get(layer.src);
|
|
71
|
+
return layer.size ?? (p ? [p.w, p.h] : [comp.width, comp.width]);
|
|
72
|
+
}
|
|
54
73
|
default: return [0, 0];
|
|
55
74
|
}
|
|
56
75
|
}
|
|
@@ -76,7 +95,23 @@ export class Renderer {
|
|
|
76
95
|
}
|
|
77
96
|
return m;
|
|
78
97
|
}
|
|
98
|
+
// Lottie players, keyed by src; loaded before a frame draws so sizes are known synchronously.
|
|
99
|
+
lotties = new Map();
|
|
100
|
+
async preload(comp) {
|
|
101
|
+
for (const l of comp.layers) {
|
|
102
|
+
if (l.type === 'lottie' && !this.lotties.has(l.src)) {
|
|
103
|
+
const p = new LottiePlayer(JSON.parse(await this.host.data(l.src)), this.host);
|
|
104
|
+
await p.preload();
|
|
105
|
+
this.lotties.set(l.src, p);
|
|
106
|
+
}
|
|
107
|
+
if (l.type === 'precomp')
|
|
108
|
+
await this.preload(this.comp(l.comp));
|
|
109
|
+
if (l.effects?.some((e) => e.type === 'lut'))
|
|
110
|
+
await preloadLuts(l.effects, { loadText: (src) => this.host.data(src) });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
79
113
|
async renderComp(ctx, comp, t, withBackground = true) {
|
|
114
|
+
await this.preload(comp);
|
|
80
115
|
if (withBackground && comp.background) {
|
|
81
116
|
ctx.save();
|
|
82
117
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
@@ -91,6 +126,10 @@ export class Renderer {
|
|
|
91
126
|
continue;
|
|
92
127
|
if (matteSources.has(layer.id))
|
|
93
128
|
continue; // mattes only show through the layer that uses them
|
|
129
|
+
if (layer.type === 'adjustment') {
|
|
130
|
+
this.renderAdjustment(ctx, layer, comp, t);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
94
133
|
await this.renderLayer(ctx, layer, comp, t);
|
|
95
134
|
}
|
|
96
135
|
}
|
|
@@ -137,35 +176,32 @@ export class Renderer {
|
|
|
137
176
|
b.restore();
|
|
138
177
|
}
|
|
139
178
|
}
|
|
179
|
+
else if (effects.some((e) => e.type === 'cornerPin' || e.type === 'tilt')) {
|
|
180
|
+
// Perspective: draw the content flat at its own size, then map it through the corner pin.
|
|
181
|
+
const [w, h] = this.layerSize(layer, comp, t);
|
|
182
|
+
if (w > 0 && h > 0) {
|
|
183
|
+
const src = this.acquire(Math.ceil(w), Math.ceil(h));
|
|
184
|
+
await this.drawContent(src.getContext('2d'), layer, comp, t);
|
|
185
|
+
const pin = effects.find((e) => e.type === 'cornerPin');
|
|
186
|
+
const tilt = effects.find((e) => e.type === 'tilt');
|
|
187
|
+
const quad = pin
|
|
188
|
+
? val(pin.corners, t, [[0, 0], [w, 0], [w, h], [0, h]])
|
|
189
|
+
: tiltCorners(w, h, val(tilt.rotateX, t, 0), val(tilt.rotateY, t, 0), val(tilt.rotateZ, t, 0), tilt.perspective ?? Math.max(w, h) * 2);
|
|
190
|
+
b.save();
|
|
191
|
+
b.setTransform(...this.matrix(layer, comp, t));
|
|
192
|
+
drawPerspective(b, src, w, h, quad);
|
|
193
|
+
b.restore();
|
|
194
|
+
this.release(src);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
140
197
|
else {
|
|
141
198
|
b.save();
|
|
142
199
|
b.setTransform(...this.matrix(layer, comp, t));
|
|
143
200
|
await this.drawContent(b, layer, comp, t);
|
|
144
201
|
b.restore();
|
|
145
202
|
}
|
|
146
|
-
if (layer.masks?.length)
|
|
147
|
-
|
|
148
|
-
for (const mask of layer.masks) {
|
|
149
|
-
b.save();
|
|
150
|
-
b.setTransform(...m);
|
|
151
|
-
b.globalCompositeOperation = mask.inverted ? 'destination-out' : 'destination-in';
|
|
152
|
-
b.beginPath();
|
|
153
|
-
if (mask.shape === 'path' && mask.points?.length) {
|
|
154
|
-
mask.points.forEach(([x, y], i) => (i ? b.lineTo(x, y) : b.moveTo(x, y)));
|
|
155
|
-
b.closePath();
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
const [x, y, w, h] = val(mask.rect, t, [0, 0, 0, 0]);
|
|
159
|
-
if (mask.shape === 'ellipse')
|
|
160
|
-
b.ellipse(x + w / 2, y + h / 2, Math.abs(w / 2), Math.abs(h / 2), 0, 0, Math.PI * 2);
|
|
161
|
-
else
|
|
162
|
-
b.roundRect(x, y, w, h, mask.radius ?? 0);
|
|
163
|
-
}
|
|
164
|
-
b.fillStyle = '#000';
|
|
165
|
-
b.fill();
|
|
166
|
-
b.restore();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
203
|
+
if (layer.masks?.length)
|
|
204
|
+
this.applyMasks(b, layer, comp, t);
|
|
169
205
|
for (const e of effects) {
|
|
170
206
|
if (e.type === 'fill') {
|
|
171
207
|
b.save();
|
|
@@ -176,6 +212,7 @@ export class Renderer {
|
|
|
176
212
|
b.restore();
|
|
177
213
|
}
|
|
178
214
|
}
|
|
215
|
+
this.grade(b, effects, comp, t);
|
|
179
216
|
if (layer.matte) {
|
|
180
217
|
const src = comp.layers.find((l) => l.id === layer.matte.layer);
|
|
181
218
|
if (src) {
|
|
@@ -218,7 +255,85 @@ export class Renderer {
|
|
|
218
255
|
target.drawImage(buf, 0, 0);
|
|
219
256
|
target.restore();
|
|
220
257
|
}
|
|
221
|
-
target.
|
|
258
|
+
if (!drawWithFx(target, buf, effects, t, comp.fps, (w, h) => this.acquire(w, h), (c) => this.release(c)))
|
|
259
|
+
target.drawImage(buf, 0, 0);
|
|
260
|
+
target.restore();
|
|
261
|
+
this.release(buf);
|
|
262
|
+
}
|
|
263
|
+
// Masks are drawn into their own full-frame buffer, then applied with one composite:
|
|
264
|
+
// this also hides everything when a mask has zero size (some backends skip empty paths).
|
|
265
|
+
applyMasks(b, layer, comp, t) {
|
|
266
|
+
const masks = layer.masks ?? [];
|
|
267
|
+
const m = this.matrix(layer, comp, t);
|
|
268
|
+
const mk = this.acquire(comp.width, comp.height);
|
|
269
|
+
const mc = mk.getContext('2d');
|
|
270
|
+
masks.forEach((mask, i) => {
|
|
271
|
+
mc.save();
|
|
272
|
+
if (i === 0 && mask.inverted) {
|
|
273
|
+
mc.fillStyle = '#000';
|
|
274
|
+
mc.fillRect(0, 0, comp.width, comp.height);
|
|
275
|
+
}
|
|
276
|
+
mc.setTransform(...m);
|
|
277
|
+
mc.globalCompositeOperation = mask.inverted ? 'destination-out' : 'source-over';
|
|
278
|
+
mc.beginPath();
|
|
279
|
+
if (mask.shape === 'path' && mask.points?.length) {
|
|
280
|
+
mask.points.forEach(([x, y], k) => (k ? mc.lineTo(x, y) : mc.moveTo(x, y)));
|
|
281
|
+
mc.closePath();
|
|
282
|
+
}
|
|
283
|
+
else if (mask.shape === 'path' && mask.path)
|
|
284
|
+
tracePath(mc, parsePath(val(mask.path, t, '')));
|
|
285
|
+
else {
|
|
286
|
+
const [x, y, w, h] = val(mask.rect, t, [0, 0, 0, 0]);
|
|
287
|
+
if (Math.abs(w) > 0.01 && Math.abs(h) > 0.01) {
|
|
288
|
+
if (mask.shape === 'ellipse')
|
|
289
|
+
mc.ellipse(x + w / 2, y + h / 2, Math.abs(w / 2), Math.abs(h / 2), 0, 0, Math.PI * 2);
|
|
290
|
+
else
|
|
291
|
+
mc.roundRect(x, y, w, h, mask.radius ?? 0);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
mc.fillStyle = '#000';
|
|
295
|
+
mc.fill();
|
|
296
|
+
mc.restore();
|
|
297
|
+
});
|
|
298
|
+
b.save();
|
|
299
|
+
b.setTransform(1, 0, 0, 1, 0, 0);
|
|
300
|
+
b.globalCompositeOperation = 'destination-in';
|
|
301
|
+
b.drawImage(mk, 0, 0);
|
|
302
|
+
b.restore();
|
|
303
|
+
this.release(mk);
|
|
304
|
+
}
|
|
305
|
+
// Grade effects (curves, wheels, exposure, temperature, LUT) and vignette on a comp-size buffer.
|
|
306
|
+
grade(b, effects, comp, t) {
|
|
307
|
+
const grades = effects.filter((e) => GRADE_TYPES.has(e.type));
|
|
308
|
+
if (grades.length)
|
|
309
|
+
applyGrade(b, comp.width, comp.height, grades, t);
|
|
310
|
+
for (const e of effects)
|
|
311
|
+
if (e.type === 'vignette')
|
|
312
|
+
drawVignette(b, comp.width, comp.height, val(e.amount, t, 50), e.softness, e.color);
|
|
313
|
+
}
|
|
314
|
+
// An adjustment layer re-renders everything already drawn below it through its effects.
|
|
315
|
+
renderAdjustment(target, layer, comp, t) {
|
|
316
|
+
if (!this.isActive(layer, comp, t))
|
|
317
|
+
return;
|
|
318
|
+
const opacity = val(layer.transform?.opacity, t, 100) / 100;
|
|
319
|
+
const effects = layer.effects ?? [];
|
|
320
|
+
if (opacity <= 0 || !effects.length)
|
|
321
|
+
return;
|
|
322
|
+
const W = comp.width, H = comp.height;
|
|
323
|
+
const buf = this.acquire(W, H), b = buf.getContext('2d');
|
|
324
|
+
const filters = filterString(effects, t);
|
|
325
|
+
if (filters)
|
|
326
|
+
b.filter = filters;
|
|
327
|
+
b.drawImage(target.canvas, 0, 0);
|
|
328
|
+
b.filter = 'none';
|
|
329
|
+
this.grade(b, effects, comp, t);
|
|
330
|
+
if (layer.masks?.length)
|
|
331
|
+
this.applyMasks(b, layer, comp, t);
|
|
332
|
+
target.save();
|
|
333
|
+
target.setTransform(1, 0, 0, 1, 0, 0);
|
|
334
|
+
target.globalAlpha *= opacity;
|
|
335
|
+
if (!drawWithFx(target, buf, effects, t, comp.fps, (w, h) => this.acquire(w, h), (c) => this.release(c)))
|
|
336
|
+
target.drawImage(buf, 0, 0);
|
|
222
337
|
target.restore();
|
|
223
338
|
this.release(buf);
|
|
224
339
|
}
|
|
@@ -257,6 +372,23 @@ export class Renderer {
|
|
|
257
372
|
ctx.drawImage(frame, 0, 0, w, h);
|
|
258
373
|
return;
|
|
259
374
|
}
|
|
375
|
+
case 'lottie': {
|
|
376
|
+
const p = this.lotties.get(layer.src);
|
|
377
|
+
if (!p)
|
|
378
|
+
return;
|
|
379
|
+
const [w, h] = layer.size ?? [p.w, p.h];
|
|
380
|
+
const s = Math.min(w / p.w, h / p.h);
|
|
381
|
+
const [f0, f1] = layer.frames ?? [p.ip, p.op];
|
|
382
|
+
const span = Math.max(1, f1 - f0);
|
|
383
|
+
let f = ((t - inP) * (layer.speed ?? 1) + (layer.timeOffset ?? 0)) * p.fr;
|
|
384
|
+
f = layer.loop === false ? Math.min(f, span - 0.001) : ((f % span) + span) % span;
|
|
385
|
+
ctx.save();
|
|
386
|
+
ctx.translate((w - p.w * s) / 2, (h - p.h * s) / 2);
|
|
387
|
+
ctx.scale(s, s);
|
|
388
|
+
p.draw(ctx, f0 + f);
|
|
389
|
+
ctx.restore();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
260
392
|
case 'precomp': {
|
|
261
393
|
const child = this.comp(layer.comp);
|
|
262
394
|
const ct = (t - inP) * (layer.speed ?? 1) + (layer.timeOffset ?? 0);
|
|
@@ -298,75 +430,4 @@ const drawFitted = (ctx, img, w, h, fit) => {
|
|
|
298
430
|
ctx.drawImage(img, (w - dw) / 2, (h - dh) / 2, dw, dh);
|
|
299
431
|
ctx.restore();
|
|
300
432
|
};
|
|
301
|
-
// Shape outline as a polyline (layer space, centred on the origin), used for trim paths.
|
|
302
|
-
const outline = (s, t) => {
|
|
303
|
-
const [w, h] = val(s.size, t, [200, 200]);
|
|
304
|
-
switch (s.shape) {
|
|
305
|
-
case 'rect': return { pts: [[-w / 2, -h / 2], [w / 2, -h / 2], [w / 2, h / 2], [-w / 2, h / 2]], closed: true };
|
|
306
|
-
case 'ellipse': return { pts: [...Array(96).keys()].map((i) => { const a = (i / 96) * Math.PI * 2 - Math.PI / 2; return [Math.cos(a) * w / 2, Math.sin(a) * h / 2]; }), closed: true };
|
|
307
|
-
case 'polygon': {
|
|
308
|
-
const r = val(s.radius, t, 100), n = s.sides ?? 6;
|
|
309
|
-
return { pts: [...Array(n).keys()].map((i) => { const a = (i / n) * Math.PI * 2 - Math.PI / 2; return [Math.cos(a) * r, Math.sin(a) * r]; }), closed: true };
|
|
310
|
-
}
|
|
311
|
-
default: return { pts: s.points ?? [], closed: s.closed ?? s.shape === 'path' };
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
const drawShape = (ctx, s, t) => {
|
|
315
|
-
const start = val(s.trim?.start, t, 0) / 100, end = val(s.trim?.end, t, 100) / 100;
|
|
316
|
-
ctx.beginPath();
|
|
317
|
-
if (s.trim && (start > 0 || end < 1)) {
|
|
318
|
-
const { pts, closed } = outline(s, t);
|
|
319
|
-
const all = closed ? [...pts, pts[0]] : pts;
|
|
320
|
-
const lens = all.slice(1).map((p, i) => Math.hypot(p[0] - all[i][0], p[1] - all[i][1]));
|
|
321
|
-
const total = lens.reduce((a, b) => a + b, 0);
|
|
322
|
-
const a = Math.min(start, end) * total, z = Math.max(start, end) * total;
|
|
323
|
-
let acc = 0, started = false;
|
|
324
|
-
for (let i = 0; i < lens.length; i++) {
|
|
325
|
-
const s0 = acc, s1 = acc + lens[i];
|
|
326
|
-
acc = s1;
|
|
327
|
-
if (s1 < a || s0 > z)
|
|
328
|
-
continue;
|
|
329
|
-
const p = (d) => { const k = lens[i] ? (d - s0) / lens[i] : 0; return [all[i][0] + (all[i + 1][0] - all[i][0]) * k, all[i][1] + (all[i + 1][1] - all[i][1]) * k]; };
|
|
330
|
-
const from = p(Math.max(a, s0)), to = p(Math.min(z, s1));
|
|
331
|
-
if (!started) {
|
|
332
|
-
ctx.moveTo(...from);
|
|
333
|
-
started = true;
|
|
334
|
-
}
|
|
335
|
-
ctx.lineTo(...to);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
else if (s.shape === 'rect') {
|
|
339
|
-
const [w, h] = val(s.size, t, [200, 200]);
|
|
340
|
-
ctx.roundRect(-w / 2, -h / 2, w, h, val(s.radius, t, 0));
|
|
341
|
-
}
|
|
342
|
-
else if (s.shape === 'ellipse') {
|
|
343
|
-
const [w, h] = val(s.size, t, [200, 200]);
|
|
344
|
-
ctx.ellipse(0, 0, Math.abs(w / 2), Math.abs(h / 2), 0, 0, Math.PI * 2);
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
const { pts, closed } = outline(s, t);
|
|
348
|
-
pts.forEach((p, i) => (i ? ctx.lineTo(...p) : ctx.moveTo(...p)));
|
|
349
|
-
if (closed)
|
|
350
|
-
ctx.closePath();
|
|
351
|
-
}
|
|
352
|
-
const trimmed = !!s.trim && (start > 0 || end < 1);
|
|
353
|
-
if (s.fill && !trimmed && s.shape !== 'line') {
|
|
354
|
-
if (typeof s.fill === 'object' && 'linear' in s.fill) {
|
|
355
|
-
const g = ctx.createLinearGradient(...s.fill.linear.from, ...s.fill.linear.to);
|
|
356
|
-
for (const [o, c] of s.fill.linear.stops)
|
|
357
|
-
g.addColorStop(o, c);
|
|
358
|
-
ctx.fillStyle = g;
|
|
359
|
-
}
|
|
360
|
-
else
|
|
361
|
-
ctx.fillStyle = val(s.fill, t, '#fff');
|
|
362
|
-
ctx.fill();
|
|
363
|
-
}
|
|
364
|
-
if (s.stroke) {
|
|
365
|
-
ctx.strokeStyle = val(s.stroke, t, '#fff');
|
|
366
|
-
ctx.lineWidth = val(s.strokeWidth, t, 4);
|
|
367
|
-
ctx.lineCap = s.lineCap ?? 'round';
|
|
368
|
-
ctx.lineJoin = 'round';
|
|
369
|
-
ctx.stroke();
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
433
|
//# sourceMappingURL=renderer.js.map
|