reframe-video 0.6.9 → 0.6.10
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/dist/browserEntry.js +43 -14
- package/dist/index.js +5 -2
- package/dist/renderer-canvas.js +38 -12
- package/dist/types/evaluate.d.ts +4 -0
- package/dist/types/ir.d.ts +4 -2
- package/guides/edsl-guide.md +26 -4
- package/package.json +1 -1
- package/preview/src/main.ts +2 -0
package/dist/browserEntry.js
CHANGED
|
@@ -737,15 +737,18 @@
|
|
|
737
737
|
switch (node.type) {
|
|
738
738
|
case "group": {
|
|
739
739
|
const childClips = node.props.clip ? [...clips, { transform: matrix, shape: node.props.clip }] : clips;
|
|
740
|
+
const hasFx = fx.blur !== void 0 || fx.shadowColor !== void 0 || fx.blend !== void 0;
|
|
741
|
+
if (hasFx) ops.push({ type: "group-fx-push", id, transform: matrix, opacity, ...fx, ...clipSpread });
|
|
740
742
|
if (node.props.matte && node.children.length >= 2) {
|
|
741
743
|
ops.push({ type: "matte-push", id, transform: matrix, opacity, mode: node.props.matte, ...clipSpread });
|
|
742
744
|
walk(node.children[0], matrix, opacity, childClips);
|
|
743
745
|
ops.push({ type: "matte-sep", id, transform: matrix, opacity });
|
|
744
746
|
for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips);
|
|
745
747
|
ops.push({ type: "matte-pop", id, transform: matrix, opacity });
|
|
746
|
-
|
|
748
|
+
} else {
|
|
749
|
+
for (const child of node.children) walk(child, matrix, opacity, childClips);
|
|
747
750
|
}
|
|
748
|
-
|
|
751
|
+
if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
|
|
749
752
|
return;
|
|
750
753
|
}
|
|
751
754
|
case "rect":
|
|
@@ -930,6 +933,7 @@
|
|
|
930
933
|
const target = () => {
|
|
931
934
|
const f = stack[stack.length - 1];
|
|
932
935
|
if (!f) return ctx2;
|
|
936
|
+
if (f.kind === "fx") return f.ctx;
|
|
933
937
|
return f.phase === "content" && f.contentCtx ? f.contentCtx : f.matteCtx;
|
|
934
938
|
};
|
|
935
939
|
const newCtx = () => {
|
|
@@ -939,29 +943,54 @@
|
|
|
939
943
|
return c.getContext("2d");
|
|
940
944
|
};
|
|
941
945
|
const composite = (f) => {
|
|
942
|
-
if (
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
946
|
+
if (f.kind === "matte") {
|
|
947
|
+
if (!f.contentCtx) return;
|
|
948
|
+
if (f.mode === "luma") lumaToAlpha(f.matteCtx);
|
|
949
|
+
f.contentCtx.save();
|
|
950
|
+
f.contentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
951
|
+
f.contentCtx.globalCompositeOperation = "destination-in";
|
|
952
|
+
f.contentCtx.drawImage(f.matteCtx.canvas, 0, 0);
|
|
953
|
+
f.contentCtx.restore();
|
|
954
|
+
f.parent.save();
|
|
955
|
+
f.parent.setTransform(1, 0, 0, 1, 0, 0);
|
|
956
|
+
f.parent.globalAlpha = 1;
|
|
957
|
+
f.parent.globalCompositeOperation = "source-over";
|
|
958
|
+
f.parent.filter = "none";
|
|
959
|
+
f.parent.drawImage(f.contentCtx.canvas, 0, 0);
|
|
960
|
+
f.parent.restore();
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
949
963
|
f.parent.save();
|
|
950
964
|
f.parent.setTransform(1, 0, 0, 1, 0, 0);
|
|
951
965
|
f.parent.globalAlpha = 1;
|
|
952
|
-
f.parent.globalCompositeOperation = "source-over";
|
|
953
|
-
f.parent.filter = "none";
|
|
954
|
-
|
|
966
|
+
f.parent.globalCompositeOperation = f.blend ? mapBlend(f.blend) : "source-over";
|
|
967
|
+
f.parent.filter = f.blur ? `blur(${f.blur}px)` : "none";
|
|
968
|
+
if (f.shadowColor) {
|
|
969
|
+
f.parent.shadowColor = f.shadowColor;
|
|
970
|
+
f.parent.shadowBlur = f.shadowBlur ?? 0;
|
|
971
|
+
f.parent.shadowOffsetX = f.shadowX ?? 0;
|
|
972
|
+
f.parent.shadowOffsetY = f.shadowY ?? 0;
|
|
973
|
+
}
|
|
974
|
+
f.parent.drawImage(f.ctx.canvas, 0, 0);
|
|
955
975
|
f.parent.restore();
|
|
956
976
|
};
|
|
957
977
|
for (const op of ops) {
|
|
978
|
+
if (op.type === "group-fx-push") {
|
|
979
|
+
stack.push({ kind: "fx", parent: target(), ctx: newCtx(), blur: op.blur, shadowColor: op.shadowColor, shadowBlur: op.shadowBlur, shadowX: op.shadowX, shadowY: op.shadowY, blend: op.blend });
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (op.type === "group-fx-pop") {
|
|
983
|
+
const f = stack.pop();
|
|
984
|
+
if (f) composite(f);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
958
987
|
if (op.type === "matte-push") {
|
|
959
|
-
stack.push({ mode: op.mode, parent: target(), matteCtx: newCtx(), phase: "matte" });
|
|
988
|
+
stack.push({ kind: "matte", mode: op.mode, parent: target(), matteCtx: newCtx(), phase: "matte" });
|
|
960
989
|
continue;
|
|
961
990
|
}
|
|
962
991
|
if (op.type === "matte-sep") {
|
|
963
992
|
const f = stack[stack.length - 1];
|
|
964
|
-
if (f) {
|
|
993
|
+
if (f && f.kind === "matte") {
|
|
965
994
|
f.contentCtx = newCtx();
|
|
966
995
|
f.phase = "content";
|
|
967
996
|
}
|
package/dist/index.js
CHANGED
|
@@ -3177,15 +3177,18 @@ function evaluate(compiled, t) {
|
|
|
3177
3177
|
switch (node.type) {
|
|
3178
3178
|
case "group": {
|
|
3179
3179
|
const childClips = node.props.clip ? [...clips, { transform: matrix, shape: node.props.clip }] : clips;
|
|
3180
|
+
const hasFx = fx.blur !== void 0 || fx.shadowColor !== void 0 || fx.blend !== void 0;
|
|
3181
|
+
if (hasFx) ops.push({ type: "group-fx-push", id, transform: matrix, opacity, ...fx, ...clipSpread });
|
|
3180
3182
|
if (node.props.matte && node.children.length >= 2) {
|
|
3181
3183
|
ops.push({ type: "matte-push", id, transform: matrix, opacity, mode: node.props.matte, ...clipSpread });
|
|
3182
3184
|
walk(node.children[0], matrix, opacity, childClips);
|
|
3183
3185
|
ops.push({ type: "matte-sep", id, transform: matrix, opacity });
|
|
3184
3186
|
for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips);
|
|
3185
3187
|
ops.push({ type: "matte-pop", id, transform: matrix, opacity });
|
|
3186
|
-
|
|
3188
|
+
} else {
|
|
3189
|
+
for (const child of node.children) walk(child, matrix, opacity, childClips);
|
|
3187
3190
|
}
|
|
3188
|
-
|
|
3191
|
+
if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
|
|
3189
3192
|
return;
|
|
3190
3193
|
}
|
|
3191
3194
|
case "rect":
|
package/dist/renderer-canvas.js
CHANGED
|
@@ -40,6 +40,7 @@ function drawDisplayList(ctx, ops, images, videos) {
|
|
|
40
40
|
const target = () => {
|
|
41
41
|
const f = stack[stack.length - 1];
|
|
42
42
|
if (!f) return ctx;
|
|
43
|
+
if (f.kind === "fx") return f.ctx;
|
|
43
44
|
return f.phase === "content" && f.contentCtx ? f.contentCtx : f.matteCtx;
|
|
44
45
|
};
|
|
45
46
|
const newCtx = () => {
|
|
@@ -49,29 +50,54 @@ function drawDisplayList(ctx, ops, images, videos) {
|
|
|
49
50
|
return c.getContext("2d");
|
|
50
51
|
};
|
|
51
52
|
const composite = (f) => {
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
if (f.kind === "matte") {
|
|
54
|
+
if (!f.contentCtx) return;
|
|
55
|
+
if (f.mode === "luma") lumaToAlpha(f.matteCtx);
|
|
56
|
+
f.contentCtx.save();
|
|
57
|
+
f.contentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
58
|
+
f.contentCtx.globalCompositeOperation = "destination-in";
|
|
59
|
+
f.contentCtx.drawImage(f.matteCtx.canvas, 0, 0);
|
|
60
|
+
f.contentCtx.restore();
|
|
61
|
+
f.parent.save();
|
|
62
|
+
f.parent.setTransform(1, 0, 0, 1, 0, 0);
|
|
63
|
+
f.parent.globalAlpha = 1;
|
|
64
|
+
f.parent.globalCompositeOperation = "source-over";
|
|
65
|
+
f.parent.filter = "none";
|
|
66
|
+
f.parent.drawImage(f.contentCtx.canvas, 0, 0);
|
|
67
|
+
f.parent.restore();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
59
70
|
f.parent.save();
|
|
60
71
|
f.parent.setTransform(1, 0, 0, 1, 0, 0);
|
|
61
72
|
f.parent.globalAlpha = 1;
|
|
62
|
-
f.parent.globalCompositeOperation = "source-over";
|
|
63
|
-
f.parent.filter = "none";
|
|
64
|
-
|
|
73
|
+
f.parent.globalCompositeOperation = f.blend ? mapBlend(f.blend) : "source-over";
|
|
74
|
+
f.parent.filter = f.blur ? `blur(${f.blur}px)` : "none";
|
|
75
|
+
if (f.shadowColor) {
|
|
76
|
+
f.parent.shadowColor = f.shadowColor;
|
|
77
|
+
f.parent.shadowBlur = f.shadowBlur ?? 0;
|
|
78
|
+
f.parent.shadowOffsetX = f.shadowX ?? 0;
|
|
79
|
+
f.parent.shadowOffsetY = f.shadowY ?? 0;
|
|
80
|
+
}
|
|
81
|
+
f.parent.drawImage(f.ctx.canvas, 0, 0);
|
|
65
82
|
f.parent.restore();
|
|
66
83
|
};
|
|
67
84
|
for (const op of ops) {
|
|
85
|
+
if (op.type === "group-fx-push") {
|
|
86
|
+
stack.push({ kind: "fx", parent: target(), ctx: newCtx(), blur: op.blur, shadowColor: op.shadowColor, shadowBlur: op.shadowBlur, shadowX: op.shadowX, shadowY: op.shadowY, blend: op.blend });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (op.type === "group-fx-pop") {
|
|
90
|
+
const f = stack.pop();
|
|
91
|
+
if (f) composite(f);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
68
94
|
if (op.type === "matte-push") {
|
|
69
|
-
stack.push({ mode: op.mode, parent: target(), matteCtx: newCtx(), phase: "matte" });
|
|
95
|
+
stack.push({ kind: "matte", mode: op.mode, parent: target(), matteCtx: newCtx(), phase: "matte" });
|
|
70
96
|
continue;
|
|
71
97
|
}
|
|
72
98
|
if (op.type === "matte-sep") {
|
|
73
99
|
const f = stack[stack.length - 1];
|
|
74
|
-
if (f) {
|
|
100
|
+
if (f && f.kind === "matte") {
|
|
75
101
|
f.contentCtx = newCtx();
|
|
76
102
|
f.phase = "content";
|
|
77
103
|
}
|
package/dist/types/evaluate.d.ts
CHANGED
|
@@ -110,6 +110,10 @@ export type DisplayOp = (OpBase & {
|
|
|
110
110
|
type: "matte-sep";
|
|
111
111
|
}) | (OpBase & {
|
|
112
112
|
type: "matte-pop";
|
|
113
|
+
}) | (OpBase & {
|
|
114
|
+
type: "group-fx-push";
|
|
115
|
+
}) | (OpBase & {
|
|
116
|
+
type: "group-fx-pop";
|
|
113
117
|
});
|
|
114
118
|
export type DisplayList = DisplayOp[];
|
|
115
119
|
/**
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -40,7 +40,8 @@ export interface BaseProps {
|
|
|
40
40
|
* Paint effects (animatable scalars, in screen pixels — not transformed by the
|
|
41
41
|
* node's rotation/scale or the camera, so a shadow keeps a consistent light
|
|
42
42
|
* direction). `shadowColor` enables a drop shadow / outer glow (`glow`/`dropShadow`
|
|
43
|
-
* helpers).
|
|
43
|
+
* helpers). On a `group` these apply to the WHOLE subtree as one composite (the
|
|
44
|
+
* renderer renders it offscreen, then draws it back with the effect once).
|
|
44
45
|
*/
|
|
45
46
|
blur?: number;
|
|
46
47
|
shadowColor?: string;
|
|
@@ -48,7 +49,8 @@ export interface BaseProps {
|
|
|
48
49
|
shadowX?: number;
|
|
49
50
|
shadowY?: number;
|
|
50
51
|
/** How this node composites with what's already drawn (default "normal"). `screen`/
|
|
51
|
-
* `add` brighten (additive light/glow), `multiply` tints/deepens.
|
|
52
|
+
* `add` brighten (additive light/glow), `multiply` tints/deepens. On a `group` the
|
|
53
|
+
* whole subtree composites as one layer (offscreen) — true group blend. */
|
|
52
54
|
blend?: BlendMode;
|
|
53
55
|
}
|
|
54
56
|
/** Compositing modes (Canvas `globalCompositeOperation`; `add` maps to `lighter`). */
|
package/guides/edsl-guide.md
CHANGED
|
@@ -193,8 +193,8 @@ rect({ id: "card", /* … */, blur: 18 }); tween("card", { blur: 0 }, { duration
|
|
|
193
193
|
something to animate from).
|
|
194
194
|
- Sugar: `glow(color, blur)` (offset 0) and `dropShadow(color, blur, x, y)` return
|
|
195
195
|
a partial you spread into props (`...glow("#FFD24B", 28)`); still animatable.
|
|
196
|
-
-
|
|
197
|
-
`examples/scenes/shadow-demo.ts`.
|
|
196
|
+
- On a `group` these apply to the whole subtree as one composite (focus pull / one
|
|
197
|
+
silhouette shadow) — see "Group effects" below. `examples/scenes/shadow-demo.ts`.
|
|
198
198
|
|
|
199
199
|
### Blend modes (compositing)
|
|
200
200
|
|
|
@@ -210,8 +210,8 @@ rect({ id: "neon", fill: linearGradient([...]), shadowColor: "#7A4DFF", blend: "
|
|
|
210
210
|
- Modes: `normal` (default), `multiply`, `screen`, `overlay`, `lighten`, `darken`,
|
|
211
211
|
`add` (additive light), `color-dodge`, `soft-light`, `hard-light`, `difference`.
|
|
212
212
|
- **Discrete**, not interpolated — set per node (a static string). Default `normal`.
|
|
213
|
-
- Per-shape
|
|
214
|
-
|
|
213
|
+
- Per-shape, or on a `group` to blend the whole composited subtree against the bg as one
|
|
214
|
+
layer (see "Group effects" below). See `examples/scenes/blend-demo.ts`.
|
|
215
215
|
|
|
216
216
|
## Character rig (skeleton, poses, IK)
|
|
217
217
|
|
|
@@ -370,6 +370,28 @@ group({ id: "reveal", x: W/2, y: H/2, anchor: "center", matte: "alpha" }, [
|
|
|
370
370
|
combined via `destination-in`). Deterministic same-machine. See
|
|
371
371
|
`examples/scenes/matte-demo.ts`.
|
|
372
372
|
|
|
373
|
+
## Group effects (blur / shadow / blend on a whole group)
|
|
374
|
+
|
|
375
|
+
The paint effects (`blur`, `shadowColor`/`shadowBlur`/`shadowX`/`shadowY`, `blend`) also
|
|
376
|
+
work on a **group** — there they apply to the whole subtree as ONE composite layer, not per
|
|
377
|
+
child. The classic uses: a depth-of-field **focus pull** on a multi-node lockup, a single
|
|
378
|
+
**silhouette drop shadow** under a multi-shape mark, and a group that **blends against the
|
|
379
|
+
background** as one layer (so its own overlaps composite together).
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// the whole lockup sharpens as one image (animate the GROUP's blur)
|
|
383
|
+
group({ id: "lockup", x: 0, y: 0, blur: 20 }, [ card, dot, label ])
|
|
384
|
+
// timeline: tween("lockup", { blur: 0 }, { duration: 1.1, ease: "easeInOutCubic" })
|
|
385
|
+
|
|
386
|
+
group({ id: "mark", x: 0, y: 0, ...dropShadow("#000", 40, 0, 26) }, [ shapeA, shapeB, dot ]) // one shadow
|
|
387
|
+
group({ id: "burst", x, y, blend: "screen" }, [ disc1, disc2, disc3 ]) // one screen layer
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
- Group `blur` is **animatable** (`tween(group, { blur })`); shadow scalars too.
|
|
391
|
+
- Same **offscreen compositing** as mattes (the subtree renders to a buffer, drawn back once
|
|
392
|
+
with the effect). It wraps a matte group and nests. The effects are screen-pixel space.
|
|
393
|
+
See `examples/scenes/group-fx-demo.ts`.
|
|
394
|
+
|
|
373
395
|
## Cursor (UI demos)
|
|
374
396
|
|
|
375
397
|
A vector mouse pointer that glides across the scene and clicks things — for app
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"motion-graphics",
|
package/preview/src/main.ts
CHANGED