reframe-video 0.6.5 → 0.6.6
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 +175 -69
- package/dist/browserEntry.js +82 -35
- package/dist/cli.js +151 -53
- package/dist/index.js +46 -6
- package/dist/labels.js +1 -0
- package/dist/renderer-canvas.js +31 -25
- package/dist/trace-cli.js +1 -0
- package/dist/types/assets.d.ts +3 -1
- package/dist/types/dsl.d.ts +4 -1
- package/dist/types/evaluate.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/ir.d.ts +21 -0
- package/guides/edsl-guide.md +21 -0
- package/package.json +1 -1
- package/preview/src/main.ts +2 -1
package/dist/index.js
CHANGED
|
@@ -357,6 +357,7 @@ var PROPS_BY_TYPE = {
|
|
|
357
357
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
358
358
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
359
359
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
360
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
|
|
360
361
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
361
362
|
group: COMMON_PROPS
|
|
362
363
|
};
|
|
@@ -654,6 +655,10 @@ function image(props) {
|
|
|
654
655
|
const { id, ...rest } = props;
|
|
655
656
|
return { type: "image", id, props: rest };
|
|
656
657
|
}
|
|
658
|
+
function video(props) {
|
|
659
|
+
const { id, ...rest } = props;
|
|
660
|
+
return { type: "video", id, props: rest };
|
|
661
|
+
}
|
|
657
662
|
function path(props) {
|
|
658
663
|
const { id, ...rest } = props;
|
|
659
664
|
return { type: "path", id, props: rest };
|
|
@@ -3187,6 +3192,33 @@ function evaluate(compiled, t) {
|
|
|
3187
3192
|
});
|
|
3188
3193
|
return;
|
|
3189
3194
|
}
|
|
3195
|
+
case "video": {
|
|
3196
|
+
const width = num(id, "width", node.props.width);
|
|
3197
|
+
const height = num(id, "height", node.props.height);
|
|
3198
|
+
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
3199
|
+
const fps = compiled.ir.fps ?? 30;
|
|
3200
|
+
const start = node.props.start ?? 0;
|
|
3201
|
+
const rate = node.props.rate ?? 1;
|
|
3202
|
+
const clipStart = node.props.clipStart ?? 0;
|
|
3203
|
+
const srcT = clipStart + Math.max(0, t - start) * rate;
|
|
3204
|
+
const frame = Math.max(0, Math.round(srcT * fps));
|
|
3205
|
+
ops.push({
|
|
3206
|
+
type: "video",
|
|
3207
|
+
id,
|
|
3208
|
+
transform: matrix,
|
|
3209
|
+
opacity,
|
|
3210
|
+
src: str(id, "src", node.props.src),
|
|
3211
|
+
width,
|
|
3212
|
+
height,
|
|
3213
|
+
offsetX: -width * ax,
|
|
3214
|
+
offsetY: -height * ay,
|
|
3215
|
+
frame,
|
|
3216
|
+
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
3217
|
+
...fx,
|
|
3218
|
+
...clipSpread
|
|
3219
|
+
});
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3190
3222
|
case "path": {
|
|
3191
3223
|
const ox = num(id, "originX", node.props.originX ?? 0);
|
|
3192
3224
|
const oy = num(id, "originY", node.props.originY ?? 0);
|
|
@@ -3255,13 +3287,13 @@ function evaluate(compiled, t) {
|
|
|
3255
3287
|
}
|
|
3256
3288
|
|
|
3257
3289
|
// ../core/src/assets.ts
|
|
3258
|
-
function
|
|
3290
|
+
function collectSrcs(ir, type) {
|
|
3259
3291
|
const srcs = /* @__PURE__ */ new Set();
|
|
3260
|
-
const
|
|
3292
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3261
3293
|
const walkNodes = (nodes) => {
|
|
3262
3294
|
for (const node of nodes) {
|
|
3263
|
-
if (node.type ===
|
|
3264
|
-
|
|
3295
|
+
if (node.type === type) {
|
|
3296
|
+
ids.add(node.id);
|
|
3265
3297
|
srcs.add(node.props.src);
|
|
3266
3298
|
}
|
|
3267
3299
|
if (node.type === "group") walkNodes(node.children);
|
|
@@ -3270,14 +3302,14 @@ function collectImageSrcs(ir) {
|
|
|
3270
3302
|
walkNodes(ir.nodes);
|
|
3271
3303
|
for (const overrides of Object.values(ir.states ?? {})) {
|
|
3272
3304
|
for (const [nodeId, props] of Object.entries(overrides)) {
|
|
3273
|
-
if (
|
|
3305
|
+
if (ids.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
|
|
3274
3306
|
}
|
|
3275
3307
|
}
|
|
3276
3308
|
const walkTimeline = (step) => {
|
|
3277
3309
|
if (!step) return;
|
|
3278
3310
|
if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
|
|
3279
3311
|
for (const child of step.children) walkTimeline(child);
|
|
3280
|
-
} else if (step.kind === "tween" &&
|
|
3312
|
+
} else if (step.kind === "tween" && ids.has(step.target)) {
|
|
3281
3313
|
const src = step.props.src;
|
|
3282
3314
|
if (typeof src === "string") srcs.add(src);
|
|
3283
3315
|
}
|
|
@@ -3285,6 +3317,12 @@ function collectImageSrcs(ir) {
|
|
|
3285
3317
|
walkTimeline(ir.timeline);
|
|
3286
3318
|
return [...srcs];
|
|
3287
3319
|
}
|
|
3320
|
+
function collectImageSrcs(ir) {
|
|
3321
|
+
return collectSrcs(ir, "image");
|
|
3322
|
+
}
|
|
3323
|
+
function collectVideoSrcs(ir) {
|
|
3324
|
+
return collectSrcs(ir, "video");
|
|
3325
|
+
}
|
|
3288
3326
|
|
|
3289
3327
|
// ../core/src/motion.ts
|
|
3290
3328
|
var EASE_BY_CLASS = {
|
|
@@ -3351,6 +3389,7 @@ export {
|
|
|
3351
3389
|
cameraTo,
|
|
3352
3390
|
characterPreset,
|
|
3353
3391
|
collectImageSrcs,
|
|
3392
|
+
collectVideoSrcs,
|
|
3354
3393
|
compileComposition,
|
|
3355
3394
|
compileScene,
|
|
3356
3395
|
composeScene,
|
|
@@ -3417,6 +3456,7 @@ export {
|
|
|
3417
3456
|
tween,
|
|
3418
3457
|
validateComposition,
|
|
3419
3458
|
validateScene,
|
|
3459
|
+
video,
|
|
3420
3460
|
wait,
|
|
3421
3461
|
wiggle
|
|
3422
3462
|
};
|
package/dist/labels.js
CHANGED
|
@@ -341,6 +341,7 @@ var PROPS_BY_TYPE = {
|
|
|
341
341
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
342
342
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
343
343
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
344
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
|
|
344
345
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
345
346
|
group: COMMON_PROPS
|
|
346
347
|
};
|
package/dist/renderer-canvas.js
CHANGED
|
@@ -25,7 +25,7 @@ function resolvePaint(ctx, paint, box) {
|
|
|
25
25
|
for (const s of paint.stops) g.addColorStop(Math.max(0, Math.min(1, s.offset)), s.color);
|
|
26
26
|
return g;
|
|
27
27
|
}
|
|
28
|
-
function renderFrame(ctx, compiled, t, images) {
|
|
28
|
+
function renderFrame(ctx, compiled, t, images, videos) {
|
|
29
29
|
const { size, background } = compiled.ir;
|
|
30
30
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
31
31
|
ctx.clearRect(0, 0, size.width, size.height);
|
|
@@ -33,9 +33,9 @@ function renderFrame(ctx, compiled, t, images) {
|
|
|
33
33
|
ctx.fillStyle = background;
|
|
34
34
|
ctx.fillRect(0, 0, size.width, size.height);
|
|
35
35
|
}
|
|
36
|
-
drawDisplayList(ctx, evaluate(compiled, t), images);
|
|
36
|
+
drawDisplayList(ctx, evaluate(compiled, t), images, videos);
|
|
37
37
|
}
|
|
38
|
-
function drawDisplayList(ctx, ops, images) {
|
|
38
|
+
function drawDisplayList(ctx, ops, images, videos) {
|
|
39
39
|
for (const op of ops) {
|
|
40
40
|
ctx.save();
|
|
41
41
|
if (op.clips) {
|
|
@@ -117,28 +117,11 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
117
117
|
break;
|
|
118
118
|
}
|
|
119
119
|
case "image": {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
ctx.drawImage(img, sx, sy, sw, sh, op.offsetX, op.offsetY, op.width, op.height);
|
|
126
|
-
} else {
|
|
127
|
-
ctx.drawImage(img, op.offsetX, op.offsetY, op.width, op.height);
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
ctx.fillStyle = "#2A2A30";
|
|
131
|
-
ctx.fillRect(op.offsetX, op.offsetY, op.width, op.height);
|
|
132
|
-
ctx.strokeStyle = "#FF00FF";
|
|
133
|
-
ctx.lineWidth = 2;
|
|
134
|
-
ctx.strokeRect(op.offsetX, op.offsetY, op.width, op.height);
|
|
135
|
-
ctx.beginPath();
|
|
136
|
-
ctx.moveTo(op.offsetX, op.offsetY);
|
|
137
|
-
ctx.lineTo(op.offsetX + op.width, op.offsetY + op.height);
|
|
138
|
-
ctx.moveTo(op.offsetX + op.width, op.offsetY);
|
|
139
|
-
ctx.lineTo(op.offsetX, op.offsetY + op.height);
|
|
140
|
-
ctx.stroke();
|
|
141
|
-
}
|
|
120
|
+
drawRaster(ctx, images?.get(op.src), op);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "video": {
|
|
124
|
+
drawRaster(ctx, videos?.frame(op.src, op.frame), op);
|
|
142
125
|
break;
|
|
143
126
|
}
|
|
144
127
|
case "path": {
|
|
@@ -193,6 +176,29 @@ function intrinsicSize(img) {
|
|
|
193
176
|
const a = img;
|
|
194
177
|
return [a.naturalWidth || a.width || 0, a.naturalHeight || a.height || 0];
|
|
195
178
|
}
|
|
179
|
+
function drawRaster(ctx, img, op) {
|
|
180
|
+
if (img) {
|
|
181
|
+
if (op.fit === "cover") {
|
|
182
|
+
const [iw, ih] = intrinsicSize(img);
|
|
183
|
+
const { sx, sy, sw, sh } = coverRect(iw, ih, op.width, op.height);
|
|
184
|
+
ctx.drawImage(img, sx, sy, sw, sh, op.offsetX, op.offsetY, op.width, op.height);
|
|
185
|
+
} else {
|
|
186
|
+
ctx.drawImage(img, op.offsetX, op.offsetY, op.width, op.height);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
ctx.fillStyle = "#2A2A30";
|
|
191
|
+
ctx.fillRect(op.offsetX, op.offsetY, op.width, op.height);
|
|
192
|
+
ctx.strokeStyle = "#FF00FF";
|
|
193
|
+
ctx.lineWidth = 2;
|
|
194
|
+
ctx.strokeRect(op.offsetX, op.offsetY, op.width, op.height);
|
|
195
|
+
ctx.beginPath();
|
|
196
|
+
ctx.moveTo(op.offsetX, op.offsetY);
|
|
197
|
+
ctx.lineTo(op.offsetX + op.width, op.offsetY + op.height);
|
|
198
|
+
ctx.moveTo(op.offsetX + op.width, op.offsetY);
|
|
199
|
+
ctx.lineTo(op.offsetX, op.offsetY + op.height);
|
|
200
|
+
ctx.stroke();
|
|
201
|
+
}
|
|
196
202
|
function quoteFamily(family) {
|
|
197
203
|
return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
|
|
198
204
|
}
|
package/dist/trace-cli.js
CHANGED
|
@@ -14,6 +14,7 @@ var PROPS_BY_TYPE = {
|
|
|
14
14
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
15
15
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
16
16
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
17
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
|
|
17
18
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
18
19
|
group: COMMON_PROPS
|
|
19
20
|
};
|
package/dist/types/assets.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Asset discovery shared by every consumer that must preload
|
|
2
|
+
* Asset discovery shared by every consumer that must preload media before
|
|
3
3
|
* rendering (the capture page and the preview). One walker means the two
|
|
4
4
|
* sides can never disagree about which srcs a scene uses — including srcs
|
|
5
5
|
* introduced only mid-scene by a state override or a tween.
|
|
@@ -7,3 +7,5 @@
|
|
|
7
7
|
import type { SceneIR } from "./ir.js";
|
|
8
8
|
/** All image srcs a scene can ever display, deduped, in discovery order. */
|
|
9
9
|
export declare function collectImageSrcs(ir: SceneIR): string[];
|
|
10
|
+
/** All video srcs a scene can ever display, deduped, in discovery order. */
|
|
11
|
+
export declare function collectVideoSrcs(ir: SceneIR): string[];
|
package/dist/types/dsl.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The eDSL surface: thin factories that build plain IR objects.
|
|
3
3
|
* `scene()` validates and returns the IR — the return value is the document.
|
|
4
4
|
*/
|
|
5
|
-
import type { AudioIR, BehaviorIR, CameraIR, CompositionIR, CompositionSceneEntry, Ease, EllipseProps, GroupProps, ImageProps, LineProps, NodeIR, PathProps, PropValue, RectProps, SceneIR, Size, StateOverride, TextProps, TimelineIR } from "./ir.js";
|
|
5
|
+
import type { AudioIR, BehaviorIR, CameraIR, CompositionIR, CompositionSceneEntry, Ease, EllipseProps, GroupProps, ImageProps, LineProps, NodeIR, PathProps, PropValue, RectProps, SceneIR, Size, StateOverride, TextProps, TimelineIR, VideoProps } from "./ir.js";
|
|
6
6
|
export interface SceneInput {
|
|
7
7
|
id: string;
|
|
8
8
|
size: Size;
|
|
@@ -42,6 +42,9 @@ export declare function text(props: {
|
|
|
42
42
|
export declare function image(props: {
|
|
43
43
|
id: string;
|
|
44
44
|
} & ImageProps): NodeIR;
|
|
45
|
+
export declare function video(props: {
|
|
46
|
+
id: string;
|
|
47
|
+
} & VideoProps): NodeIR;
|
|
45
48
|
export declare function path(props: {
|
|
46
49
|
id: string;
|
|
47
50
|
} & PathProps): NodeIR;
|
package/dist/types/evaluate.d.ts
CHANGED
|
@@ -80,6 +80,18 @@ export type DisplayOp = (OpBase & {
|
|
|
80
80
|
offsetY: number;
|
|
81
81
|
/** Box-fit; present only when authored and not "fill". */
|
|
82
82
|
fit?: ImageFit;
|
|
83
|
+
}) | (OpBase & {
|
|
84
|
+
type: "video";
|
|
85
|
+
/** Raw src string as authored in the IR — consumers resolve it. */
|
|
86
|
+
src: string;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
offsetX: number;
|
|
90
|
+
offsetY: number;
|
|
91
|
+
/** Source frame index at scene-time t (renderer clamps to the extracted count). */
|
|
92
|
+
frame: number;
|
|
93
|
+
/** Box-fit; present only when authored and not "fill". */
|
|
94
|
+
fit?: ImageFit;
|
|
83
95
|
}) | (OpBase & {
|
|
84
96
|
type: "path";
|
|
85
97
|
/** SVG path data, drawn via Path2D. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -21,5 +21,5 @@ export { resolveAudioPlan, resolveCompositionAudioPlan, SFX_DURATION, type Audio
|
|
|
21
21
|
export { evaluate, sampleProp, nodeParentMatrix, type DisplayList, type DisplayOp, type Mat2D, type ClipRegion, type TextAlign, type TextBaseline, } from "./evaluate.js";
|
|
22
22
|
export { resolveEase, lerpValue, isColor, EASE_NAMES } from "./interpolate.js";
|
|
23
23
|
export { sampleBehavior } from "./behaviors.js";
|
|
24
|
-
export { collectImageSrcs } from "./assets.js";
|
|
24
|
+
export { collectImageSrcs, collectVideoSrcs } from "./assets.js";
|
|
25
25
|
export { sketchToTimeline, type MotionSketch, type MotionEvent, type MotionEventKind, type MotionRegion, } from "./motion.js";
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -192,6 +192,23 @@ export interface ImageProps extends BaseProps {
|
|
|
192
192
|
}
|
|
193
193
|
/** Image box-fit mode. `cover` = crop-to-fill at the image's aspect (centered). */
|
|
194
194
|
export type ImageFit = "fill" | "cover";
|
|
195
|
+
export interface VideoProps extends BaseProps {
|
|
196
|
+
/** Video file path (absolute, or relative to the scene file). */
|
|
197
|
+
src: string;
|
|
198
|
+
width: number;
|
|
199
|
+
height: number;
|
|
200
|
+
/** Box-fit into width×height, like the image node. `"fill"` (default) | `"cover"`. */
|
|
201
|
+
fit?: ImageFit;
|
|
202
|
+
/**
|
|
203
|
+
* Scene-time (seconds) at which playback begins. Before it, frame 0 (clipStart) shows;
|
|
204
|
+
* the node's visibility is still controlled by opacity/timeline. Default 0.
|
|
205
|
+
*/
|
|
206
|
+
start?: number;
|
|
207
|
+
/** Playback speed multiplier (2 = double speed). Default 1. */
|
|
208
|
+
rate?: number;
|
|
209
|
+
/** Source in-point (seconds) shown at `start`. Default 0. */
|
|
210
|
+
clipStart?: number;
|
|
211
|
+
}
|
|
195
212
|
export type NodeIR = {
|
|
196
213
|
type: "rect";
|
|
197
214
|
id: string;
|
|
@@ -212,6 +229,10 @@ export type NodeIR = {
|
|
|
212
229
|
type: "image";
|
|
213
230
|
id: string;
|
|
214
231
|
props: ImageProps;
|
|
232
|
+
} | {
|
|
233
|
+
type: "video";
|
|
234
|
+
id: string;
|
|
235
|
+
props: VideoProps;
|
|
215
236
|
} | {
|
|
216
237
|
type: "path";
|
|
217
238
|
id: string;
|
package/guides/edsl-guide.md
CHANGED
|
@@ -317,6 +317,27 @@ scene({ size, nodes: [...m.nodes, ...titles], timeline: par(m.timeline, titleTra
|
|
|
317
317
|
- Seeded + pure (same `(images, opts)` → identical IR). Note: image-node sources do
|
|
318
318
|
not render in `reframe player` / artifacts — montage ships as mp4.
|
|
319
319
|
|
|
320
|
+
## Video clips (`video`)
|
|
321
|
+
|
|
322
|
+
Draw a video clip as a layer. It plays on the scene clock — at scene-time `t` it
|
|
323
|
+
shows the source frame at `clipStart + max(0, t - start) * rate`.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
video({ id: "clip", src: "shot.mp4", x: 960, y: 540, width: 1920, height: 1080,
|
|
327
|
+
anchor: "center", fit: "cover", start: 0, rate: 1, clipStart: 0 })
|
|
328
|
+
tween("clip", { scale: 1.08 }, { duration: 5 }) // transform composes with playback (Ken Burns)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- Props: `src` (mp4 / mov / webm / m4v / mkv, absolute or scene-relative), `width`/`height`,
|
|
332
|
+
`fit` (`"cover"` like the image node), `start` (scene-time playback begins), `rate`
|
|
333
|
+
(speed), `clipStart` (source in-point s). Transform/opacity/effects compose as usual.
|
|
334
|
+
- **Deterministic by frame extraction**: render-cli runs `ffmpeg -vf fps=<sceneFps>` to pull
|
|
335
|
+
the clip's frames, and the renderer draws frame `round(t·fps)` — no live `<video>` seek, so
|
|
336
|
+
it stays byte-identical (same machine).
|
|
337
|
+
- **v1 limitations**: visual-only (the clip's own audio is not muxed — use `scene.audio`);
|
|
338
|
+
all frames are pre-decoded so keep clips short (≤~5s); like images, not rendered in
|
|
339
|
+
`reframe player` / artifacts (mp4 only). See `examples/scenes/video-demo.ts`.
|
|
340
|
+
|
|
320
341
|
## Cursor (UI demos)
|
|
321
342
|
|
|
322
343
|
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.6",
|
|
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
|
@@ -551,7 +551,8 @@ function opCorners(op: DisplayOp): [number, number][] {
|
|
|
551
551
|
switch (op.type) {
|
|
552
552
|
case "rect":
|
|
553
553
|
case "ellipse":
|
|
554
|
-
case "image":
|
|
554
|
+
case "image":
|
|
555
|
+
case "video": {
|
|
555
556
|
const { offsetX: x, offsetY: y, width: w, height: h } = op;
|
|
556
557
|
return [[x, y], [x + w, y], [x + w, y + h], [x, y + h]].map(([px, py]) =>
|
|
557
558
|
applyMat(op.transform, px!, py!),
|