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 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 = {
@@ -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) {
@@ -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"],
@@ -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";
@@ -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
@@ -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.2",
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",