reframe-video 0.6.2 → 0.6.3
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/bin.js +16 -2
- package/dist/browserEntry.js +6 -1
- package/dist/cli.js +15 -1
- package/dist/index.js +16 -1
- package/dist/labels.js +15 -1
- package/dist/renderer-canvas.js +4 -0
- package/dist/trace-cli.js +1 -1
- package/dist/types/evaluate.d.ts +3 -1
- package/dist/types/ir.d.ts +5 -0
- package/guides/edsl-guide.md +17 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -379,6 +379,7 @@ function validateScene(ir) {
|
|
|
379
379
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
380
380
|
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
381
381
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
382
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
382
383
|
if (node.type === "group") {
|
|
383
384
|
const clip = node.props.clip;
|
|
384
385
|
if (clip) {
|
|
@@ -593,11 +594,24 @@ function validateComposition(comp) {
|
|
|
593
594
|
}
|
|
594
595
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
595
596
|
}
|
|
596
|
-
var FX_PROPS, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
597
|
+
var FX_PROPS, BLEND_MODES, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
597
598
|
var init_validate = __esm({
|
|
598
599
|
"../core/src/validate.ts"() {
|
|
599
600
|
"use strict";
|
|
600
|
-
FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
601
|
+
FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
602
|
+
BLEND_MODES = /* @__PURE__ */ new Set([
|
|
603
|
+
"normal",
|
|
604
|
+
"multiply",
|
|
605
|
+
"screen",
|
|
606
|
+
"overlay",
|
|
607
|
+
"lighten",
|
|
608
|
+
"darken",
|
|
609
|
+
"add",
|
|
610
|
+
"color-dodge",
|
|
611
|
+
"soft-light",
|
|
612
|
+
"hard-light",
|
|
613
|
+
"difference"
|
|
614
|
+
]);
|
|
601
615
|
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
602
616
|
CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
603
617
|
PROPS_BY_TYPE = {
|
package/dist/browserEntry.js
CHANGED
|
@@ -334,7 +334,7 @@
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
// ../core/src/validate.ts
|
|
337
|
-
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
337
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
338
338
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
339
339
|
var PROPS_BY_TYPE = {
|
|
340
340
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
@@ -689,6 +689,7 @@
|
|
|
689
689
|
fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
|
|
690
690
|
fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
|
|
691
691
|
}
|
|
692
|
+
if (p.blend !== void 0 && p.blend !== "normal") fx.blend = p.blend;
|
|
692
693
|
return fx;
|
|
693
694
|
};
|
|
694
695
|
const walk = (node, parent, parentOpacity, clips) => {
|
|
@@ -914,6 +915,7 @@
|
|
|
914
915
|
ctx2.shadowOffsetX = op.shadowX ?? 0;
|
|
915
916
|
ctx2.shadowOffsetY = op.shadowY ?? 0;
|
|
916
917
|
}
|
|
918
|
+
if (op.blend) ctx2.globalCompositeOperation = mapBlend(op.blend);
|
|
917
919
|
switch (op.type) {
|
|
918
920
|
case "rect": {
|
|
919
921
|
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
@@ -1024,6 +1026,9 @@
|
|
|
1024
1026
|
ctx2.restore();
|
|
1025
1027
|
}
|
|
1026
1028
|
}
|
|
1029
|
+
function mapBlend(blend) {
|
|
1030
|
+
return blend === "add" ? "lighter" : blend;
|
|
1031
|
+
}
|
|
1027
1032
|
function quoteFamily(family) {
|
|
1028
1033
|
return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
|
|
1029
1034
|
}
|
package/dist/cli.js
CHANGED
|
@@ -324,7 +324,20 @@ function compileScene(ir) {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
// ../core/src/validate.ts
|
|
327
|
-
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
327
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
328
|
+
var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
329
|
+
"normal",
|
|
330
|
+
"multiply",
|
|
331
|
+
"screen",
|
|
332
|
+
"overlay",
|
|
333
|
+
"lighten",
|
|
334
|
+
"darken",
|
|
335
|
+
"add",
|
|
336
|
+
"color-dodge",
|
|
337
|
+
"soft-light",
|
|
338
|
+
"hard-light",
|
|
339
|
+
"difference"
|
|
340
|
+
]);
|
|
328
341
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
329
342
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
330
343
|
var PROPS_BY_TYPE = {
|
|
@@ -378,6 +391,7 @@ function validateScene(ir) {
|
|
|
378
391
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
379
392
|
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
380
393
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
394
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
381
395
|
if (node.type === "group") {
|
|
382
396
|
const clip = node.props.clip;
|
|
383
397
|
if (clip) {
|
package/dist/index.js
CHANGED
|
@@ -334,7 +334,20 @@ function compileScene(ir) {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
// ../core/src/validate.ts
|
|
337
|
-
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
337
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
338
|
+
var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
339
|
+
"normal",
|
|
340
|
+
"multiply",
|
|
341
|
+
"screen",
|
|
342
|
+
"overlay",
|
|
343
|
+
"lighten",
|
|
344
|
+
"darken",
|
|
345
|
+
"add",
|
|
346
|
+
"color-dodge",
|
|
347
|
+
"soft-light",
|
|
348
|
+
"hard-light",
|
|
349
|
+
"difference"
|
|
350
|
+
]);
|
|
338
351
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
339
352
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
340
353
|
var PROPS_BY_TYPE = {
|
|
@@ -388,6 +401,7 @@ function validateScene(ir) {
|
|
|
388
401
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
389
402
|
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
390
403
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
404
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
391
405
|
if (node.type === "group") {
|
|
392
406
|
const clip = node.props.clip;
|
|
393
407
|
if (clip) {
|
|
@@ -2958,6 +2972,7 @@ function evaluate(compiled, t) {
|
|
|
2958
2972
|
fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
|
|
2959
2973
|
fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
|
|
2960
2974
|
}
|
|
2975
|
+
if (p.blend !== void 0 && p.blend !== "normal") fx.blend = p.blend;
|
|
2961
2976
|
return fx;
|
|
2962
2977
|
};
|
|
2963
2978
|
const walk = (node, parent, parentOpacity, clips) => {
|
package/dist/labels.js
CHANGED
|
@@ -318,7 +318,20 @@ function compileScene(ir) {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
// ../core/src/validate.ts
|
|
321
|
-
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
321
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
322
|
+
var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
323
|
+
"normal",
|
|
324
|
+
"multiply",
|
|
325
|
+
"screen",
|
|
326
|
+
"overlay",
|
|
327
|
+
"lighten",
|
|
328
|
+
"darken",
|
|
329
|
+
"add",
|
|
330
|
+
"color-dodge",
|
|
331
|
+
"soft-light",
|
|
332
|
+
"hard-light",
|
|
333
|
+
"difference"
|
|
334
|
+
]);
|
|
322
335
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
323
336
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
324
337
|
var PROPS_BY_TYPE = {
|
|
@@ -372,6 +385,7 @@ function validateScene(ir) {
|
|
|
372
385
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
373
386
|
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
374
387
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
388
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
375
389
|
if (node.type === "group") {
|
|
376
390
|
const clip = node.props.clip;
|
|
377
391
|
if (clip) {
|
package/dist/renderer-canvas.js
CHANGED
|
@@ -62,6 +62,7 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
62
62
|
ctx.shadowOffsetX = op.shadowX ?? 0;
|
|
63
63
|
ctx.shadowOffsetY = op.shadowY ?? 0;
|
|
64
64
|
}
|
|
65
|
+
if (op.blend) ctx.globalCompositeOperation = mapBlend(op.blend);
|
|
65
66
|
switch (op.type) {
|
|
66
67
|
case "rect": {
|
|
67
68
|
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
@@ -172,6 +173,9 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
172
173
|
ctx.restore();
|
|
173
174
|
}
|
|
174
175
|
}
|
|
176
|
+
function mapBlend(blend) {
|
|
177
|
+
return blend === "add" ? "lighter" : blend;
|
|
178
|
+
}
|
|
175
179
|
function quoteFamily(family) {
|
|
176
180
|
return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
|
|
177
181
|
}
|
package/dist/trace-cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolve as resolve2 } from "node:path";
|
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
// ../core/src/validate.ts
|
|
9
|
-
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
9
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
10
10
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
11
11
|
var PROPS_BY_TYPE = {
|
|
12
12
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
package/dist/types/evaluate.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* always. Renderers only draw; they never compute animation.
|
|
5
5
|
*/
|
|
6
6
|
import type { CompiledScene } from "./compile.js";
|
|
7
|
-
import type { ClipShape, Paint, PropValue } from "./ir.js";
|
|
7
|
+
import type { BlendMode, ClipShape, Paint, PropValue } from "./ir.js";
|
|
8
8
|
/** Canvas-style 2D affine matrix [a, b, c, d, e, f]. */
|
|
9
9
|
export type Mat2D = [number, number, number, number, number, number];
|
|
10
10
|
/** A clip from an ancestor group: its shape in the group's coordinate space,
|
|
@@ -30,6 +30,8 @@ interface OpBase {
|
|
|
30
30
|
shadowBlur?: number;
|
|
31
31
|
shadowX?: number;
|
|
32
32
|
shadowY?: number;
|
|
33
|
+
/** Compositing mode (discrete; present only when authored and not "normal"). */
|
|
34
|
+
blend?: BlendMode;
|
|
33
35
|
}
|
|
34
36
|
export type DisplayOp = (OpBase & {
|
|
35
37
|
type: "rect";
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -47,7 +47,12 @@ export interface BaseProps {
|
|
|
47
47
|
shadowBlur?: number;
|
|
48
48
|
shadowX?: number;
|
|
49
49
|
shadowY?: number;
|
|
50
|
+
/** How this node composites with what's already drawn (default "normal"). `screen`/
|
|
51
|
+
* `add` brighten (additive light/glow), `multiply` tints/deepens. No-op on a group. */
|
|
52
|
+
blend?: BlendMode;
|
|
50
53
|
}
|
|
54
|
+
/** Compositing modes (Canvas `globalCompositeOperation`; `add` maps to `lighter`). */
|
|
55
|
+
export type BlendMode = "normal" | "multiply" | "screen" | "overlay" | "lighten" | "darken" | "add" | "color-dodge" | "soft-light" | "hard-light" | "difference";
|
|
51
56
|
/**
|
|
52
57
|
* A paint is a solid color string OR a gradient. Coordinates are normalized to the
|
|
53
58
|
* node's bounding box (0..1, SVG `objectBoundingBox` style) so a gradient is just an
|
package/guides/edsl-guide.md
CHANGED
|
@@ -194,6 +194,23 @@ rect({ id: "card", /* … */, blur: 18 }); tween("card", { blur: 0 }, { duration
|
|
|
194
194
|
- No-op on a `group` (apply to a child; group/composite blur is a later add). See
|
|
195
195
|
`examples/scenes/shadow-demo.ts`.
|
|
196
196
|
|
|
197
|
+
### Blend modes (compositing)
|
|
198
|
+
|
|
199
|
+
`blend` selects how a shape composites with what's already drawn beneath it — the
|
|
200
|
+
primitive that makes light read.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
ellipse({ id: "glow", fill: radialGradient(["#FF2D6A", "#FF2D6A00"]), blend: "screen" }) // additive light: brightens where blobs overlap
|
|
204
|
+
rect({ id: "tint", fill: "#1E5BFF", blend: "multiply" }) // tint/deepen the layer beneath
|
|
205
|
+
rect({ id: "neon", fill: linearGradient([...]), shadowColor: "#7A4DFF", blend: "screen" }) // compose with glow
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- Modes: `normal` (default), `multiply`, `screen`, `overlay`, `lighten`, `darken`,
|
|
209
|
+
`add` (additive light), `color-dodge`, `soft-light`, `hard-light`, `difference`.
|
|
210
|
+
- **Discrete**, not interpolated — set per node (a static string). Default `normal`.
|
|
211
|
+
- Per-shape. A whole-group blend (composite the subtree, then blend) is a later add;
|
|
212
|
+
on a `group` the prop is a no-op. See `examples/scenes/blend-demo.ts`.
|
|
213
|
+
|
|
197
214
|
## Character rig (skeleton, poses, IK)
|
|
198
215
|
|
|
199
216
|
A first-class, declarative character rig that **compiles to plain IR** (nested
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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",
|