@remotion/motion-blur 4.0.527 → 4.0.529

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.
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { type HtmlInCanvasProps } from 'remotion';
3
+ export type HtmlInCanvasMotionBlurProps = Pick<HtmlInCanvasProps, 'from' | 'durationInFrames' | 'trimBefore' | 'playbackRate'> & {
4
+ readonly children: React.ReactNode;
5
+ readonly width: number;
6
+ readonly height: number;
7
+ readonly shutterAngle?: number;
8
+ readonly samples?: number;
9
+ };
10
+ /**
11
+ * Experimental motion blur that averages separately captured HTML-in-canvas
12
+ * snapshots of the children at fractional frames. Preview requires Chrome with
13
+ * the experimental HTML-in-canvas flag enabled. Nested HtmlInCanvas components
14
+ * are currently unsupported.
15
+ */
16
+ export declare const HtmlInCanvasMotionBlur: React.FC<HtmlInCanvasMotionBlurProps>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HtmlInCanvasMotionBlur = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const remotion_1 = require("remotion");
7
+ const MotionBlurSample = ({ children, width, height, index, count, shutterInFrames, firstFrame, lastFrame, }) => {
8
+ const frame = (0, remotion_1.useCurrentFrame)();
9
+ const sampleFrame = Math.max(firstFrame, Math.min(lastFrame, frame + ((index + 0.5) / count - 0.5) * shutterInFrames));
10
+ return (jsx_runtime_1.jsx("div", { "aria-hidden": index !== Math.floor(count / 2), drawable: '', style: {
11
+ position: 'absolute',
12
+ inset: 0,
13
+ width,
14
+ height,
15
+ isolation: 'isolate',
16
+ pointerEvents: index === Math.floor(count / 2) ? 'auto' : 'none',
17
+ }, children: jsx_runtime_1.jsx(remotion_1.Freeze, { frame: sampleFrame, children: children }) }));
18
+ };
19
+ /**
20
+ * Experimental motion blur that averages separately captured HTML-in-canvas
21
+ * snapshots of the children at fractional frames. Preview requires Chrome with
22
+ * the experimental HTML-in-canvas flag enabled. Nested HtmlInCanvas components
23
+ * are currently unsupported.
24
+ */
25
+ const HtmlInCanvasMotionBlur = ({ children, width, height, shutterAngle = 180, samples = 8, from, durationInFrames, trimBefore, playbackRate, }) => {
26
+ const { durationInFrames: compositionDurationInFrames } = (0, remotion_1.useVideoConfig)();
27
+ if (!Number.isInteger(samples) || samples < 1 || samples > 64) {
28
+ throw new TypeError(`"samples" must be an integer between 1 and 64, but got ${String(samples)}`);
29
+ }
30
+ if (typeof shutterAngle !== 'number' ||
31
+ !Number.isFinite(shutterAngle) ||
32
+ shutterAngle < 0 ||
33
+ shutterAngle > 360) {
34
+ throw new TypeError(`"shutterAngle" must be between 0 and 360, but got ${String(shutterAngle)}`);
35
+ }
36
+ const actualSamples = shutterAngle === 0 ? 1 : samples;
37
+ const shutterInFrames = (shutterAngle / 360) * (playbackRate !== null && playbackRate !== void 0 ? playbackRate : 1);
38
+ const firstFrame = trimBefore !== null && trimBefore !== void 0 ? trimBefore : 0;
39
+ const visibleDuration = Math.max(0, Math.min(durationInFrames !== null && durationInFrames !== void 0 ? durationInFrames : Infinity, compositionDurationInFrames - (from !== null && from !== void 0 ? from : 0)));
40
+ const lastFrame = firstFrame + Math.max(0, visibleDuration - 1) * (playbackRate !== null && playbackRate !== void 0 ? playbackRate : 1);
41
+ const onPaint = (0, react_1.useCallback)(({ canvas, element, elementImage }) => {
42
+ const context = canvas.getContext('2d');
43
+ if (!context) {
44
+ throw new Error('Failed to acquire 2D context for motion blur');
45
+ }
46
+ const layoutCanvas = element.parentElement;
47
+ if (!(layoutCanvas instanceof HTMLCanvasElement)) {
48
+ throw new Error('Failed to find the HTML-in-canvas layout canvas');
49
+ }
50
+ const captureElements = Array.from(layoutCanvas.children);
51
+ if (captureElements.length !== actualSamples ||
52
+ captureElements[0] !== element) {
53
+ throw new Error('Motion blur samples are not ready to paint');
54
+ }
55
+ context.reset();
56
+ context.globalCompositeOperation = 'lighter';
57
+ context.globalAlpha = 1 / actualSamples;
58
+ for (const [index, sampleElement] of captureElements.entries()) {
59
+ const image = index === 0
60
+ ? elementImage
61
+ : layoutCanvas.captureElementImage(sampleElement);
62
+ try {
63
+ const sampleTransform = context.drawElementImage(image, 0, 0);
64
+ if (index === Math.floor(actualSamples / 2)) {
65
+ sampleElement.style.transform =
66
+ sampleTransform.toString();
67
+ }
68
+ }
69
+ finally {
70
+ if (index !== 0) {
71
+ image.close();
72
+ }
73
+ }
74
+ }
75
+ }, [actualSamples]);
76
+ const sampleElements = Array.from({ length: actualSamples }, (_, index) => {
77
+ return (jsx_runtime_1.jsx(MotionBlurSample, { width: width, height: height, index: index, count: actualSamples, shutterInFrames: shutterInFrames, firstFrame: firstFrame, lastFrame: lastFrame, children: children }, index));
78
+ });
79
+ // The root wrapper holds the first sample. The rest must be canvas children.
80
+ return (jsx_runtime_1.jsx(remotion_1.HtmlInCanvas, { width: width, height: height, from: from, durationInFrames: durationInFrames, trimBefore: trimBefore, playbackRate: playbackRate, name: "<HtmlInCanvasMotionBlur>", onPaint: onPaint, _remotionInternalCanvasSiblings: sampleElements.slice(1), children: sampleElements[0] }));
81
+ };
82
+ exports.HtmlInCanvasMotionBlur = HtmlInCanvasMotionBlur;
@@ -1,2 +1,3 @@
1
1
  export { CameraMotionBlur, CameraMotionBlurProps } from './CameraMotionBlur';
2
+ export { HtmlInCanvasMotionBlur, HtmlInCanvasMotionBlurProps, } from './HtmlInCanvasMotionBlur';
2
3
  export { Trail, TrailProps } from './Trail';
package/dist/cjs/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Trail = exports.CameraMotionBlur = void 0;
3
+ exports.Trail = exports.HtmlInCanvasMotionBlur = exports.CameraMotionBlur = void 0;
4
4
  const CameraMotionBlur_1 = require("./CameraMotionBlur");
5
5
  Object.defineProperty(exports, "CameraMotionBlur", { enumerable: true, get: function () { return CameraMotionBlur_1.CameraMotionBlur; } });
6
+ const HtmlInCanvasMotionBlur_1 = require("./HtmlInCanvasMotionBlur");
7
+ Object.defineProperty(exports, "HtmlInCanvasMotionBlur", { enumerable: true, get: function () { return HtmlInCanvasMotionBlur_1.HtmlInCanvasMotionBlur; } });
6
8
  const Trail_1 = require("./Trail");
7
9
  Object.defineProperty(exports, "Trail", { enumerable: true, get: function () { return Trail_1.Trail; } });
@@ -56,16 +56,132 @@ var CameraMotionBlur = ({
56
56
  })
57
57
  });
58
58
  };
59
+ // src/HtmlInCanvasMotionBlur.tsx
60
+ import { useCallback } from "react";
61
+ import {
62
+ Freeze as Freeze2,
63
+ HtmlInCanvas,
64
+ useCurrentFrame as useCurrentFrame2,
65
+ useVideoConfig
66
+ } from "remotion";
67
+ import { jsx as jsx2 } from "react/jsx-runtime";
68
+ var MotionBlurSample = ({
69
+ children,
70
+ width,
71
+ height,
72
+ index,
73
+ count,
74
+ shutterInFrames,
75
+ firstFrame,
76
+ lastFrame
77
+ }) => {
78
+ const frame = useCurrentFrame2();
79
+ const sampleFrame = Math.max(firstFrame, Math.min(lastFrame, frame + ((index + 0.5) / count - 0.5) * shutterInFrames));
80
+ return /* @__PURE__ */ jsx2("div", {
81
+ "aria-hidden": index !== Math.floor(count / 2),
82
+ ...{ drawable: "" },
83
+ style: {
84
+ position: "absolute",
85
+ inset: 0,
86
+ width,
87
+ height,
88
+ isolation: "isolate",
89
+ pointerEvents: index === Math.floor(count / 2) ? "auto" : "none"
90
+ },
91
+ children: /* @__PURE__ */ jsx2(Freeze2, {
92
+ frame: sampleFrame,
93
+ children
94
+ })
95
+ });
96
+ };
97
+ var HtmlInCanvasMotionBlur = ({
98
+ children,
99
+ width,
100
+ height,
101
+ shutterAngle = 180,
102
+ samples = 8,
103
+ from,
104
+ durationInFrames,
105
+ trimBefore,
106
+ playbackRate
107
+ }) => {
108
+ const { durationInFrames: compositionDurationInFrames } = useVideoConfig();
109
+ if (!Number.isInteger(samples) || samples < 1 || samples > 64) {
110
+ throw new TypeError(`"samples" must be an integer between 1 and 64, but got ${String(samples)}`);
111
+ }
112
+ if (typeof shutterAngle !== "number" || !Number.isFinite(shutterAngle) || shutterAngle < 0 || shutterAngle > 360) {
113
+ throw new TypeError(`"shutterAngle" must be between 0 and 360, but got ${String(shutterAngle)}`);
114
+ }
115
+ const actualSamples = shutterAngle === 0 ? 1 : samples;
116
+ const shutterInFrames = shutterAngle / 360 * (playbackRate ?? 1);
117
+ const firstFrame = trimBefore ?? 0;
118
+ const visibleDuration = Math.max(0, Math.min(durationInFrames ?? Infinity, compositionDurationInFrames - (from ?? 0)));
119
+ const lastFrame = firstFrame + Math.max(0, visibleDuration - 1) * (playbackRate ?? 1);
120
+ const onPaint = useCallback(({ canvas, element, elementImage }) => {
121
+ const context = canvas.getContext("2d");
122
+ if (!context) {
123
+ throw new Error("Failed to acquire 2D context for motion blur");
124
+ }
125
+ const layoutCanvas = element.parentElement;
126
+ if (!(layoutCanvas instanceof HTMLCanvasElement)) {
127
+ throw new Error("Failed to find the HTML-in-canvas layout canvas");
128
+ }
129
+ const captureElements = Array.from(layoutCanvas.children);
130
+ if (captureElements.length !== actualSamples || captureElements[0] !== element) {
131
+ throw new Error("Motion blur samples are not ready to paint");
132
+ }
133
+ context.reset();
134
+ context.globalCompositeOperation = "lighter";
135
+ context.globalAlpha = 1 / actualSamples;
136
+ for (const [index, sampleElement] of captureElements.entries()) {
137
+ const image = index === 0 ? elementImage : layoutCanvas.captureElementImage(sampleElement);
138
+ try {
139
+ const sampleTransform = context.drawElementImage(image, 0, 0);
140
+ if (index === Math.floor(actualSamples / 2)) {
141
+ sampleElement.style.transform = sampleTransform.toString();
142
+ }
143
+ } finally {
144
+ if (index !== 0) {
145
+ image.close();
146
+ }
147
+ }
148
+ }
149
+ }, [actualSamples]);
150
+ const sampleElements = Array.from({ length: actualSamples }, (_, index) => {
151
+ return /* @__PURE__ */ jsx2(MotionBlurSample, {
152
+ width,
153
+ height,
154
+ index,
155
+ count: actualSamples,
156
+ shutterInFrames,
157
+ firstFrame,
158
+ lastFrame,
159
+ children
160
+ }, index);
161
+ });
162
+ return /* @__PURE__ */ jsx2(HtmlInCanvas, {
163
+ width,
164
+ height,
165
+ from,
166
+ durationInFrames,
167
+ trimBefore,
168
+ playbackRate,
169
+ name: "<HtmlInCanvasMotionBlur>",
170
+ onPaint,
171
+ _remotionInternalCanvasSiblings: sampleElements.slice(1),
172
+ children: sampleElements[0]
173
+ });
174
+ };
59
175
  // src/Trail.tsx
60
- import { AbsoluteFill as AbsoluteFill2, Freeze as Freeze2, useCurrentFrame as useCurrentFrame2 } from "remotion";
61
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
176
+ import { AbsoluteFill as AbsoluteFill2, Freeze as Freeze3, useCurrentFrame as useCurrentFrame3 } from "remotion";
177
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
62
178
  var Trail = ({
63
179
  children,
64
180
  layers,
65
181
  lagInFrames,
66
182
  trailOpacity
67
183
  }) => {
68
- const frame = useCurrentFrame2();
184
+ const frame = useCurrentFrame3();
69
185
  if (typeof layers !== "number" || Number.isNaN(layers) || !Number.isFinite(layers)) {
70
186
  throw new TypeError(`"layers" must be a number, but got ${JSON.stringify(layers)}`);
71
187
  }
@@ -84,11 +200,11 @@ var Trail = ({
84
200
  return /* @__PURE__ */ jsxs(AbsoluteFill2, {
85
201
  children: [
86
202
  new Array(layers).fill(true).map((_, i) => {
87
- return /* @__PURE__ */ jsx2(AbsoluteFill2, {
203
+ return /* @__PURE__ */ jsx3(AbsoluteFill2, {
88
204
  style: {
89
205
  opacity: trailOpacity - (layers - i) / layers * trailOpacity
90
206
  },
91
- children: /* @__PURE__ */ jsx2(Freeze2, {
207
+ children: /* @__PURE__ */ jsx3(Freeze3, {
92
208
  frame: frame - lagInFrames * (layers - i),
93
209
  children
94
210
  })
@@ -100,5 +216,6 @@ var Trail = ({
100
216
  };
101
217
  export {
102
218
  Trail,
219
+ HtmlInCanvasMotionBlur,
103
220
  CameraMotionBlur
104
221
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/motion-blur"
4
4
  },
5
5
  "name": "@remotion/motion-blur",
6
- "version": "4.0.527",
6
+ "version": "4.0.529",
7
7
  "description": "Motion blur effect for Remotion",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "url": "https://github.com/remotion-dev/remotion/issues"
22
22
  },
23
23
  "dependencies": {
24
- "remotion": "4.0.527"
24
+ "remotion": "4.0.529"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": ">=16.8.0",
@@ -39,8 +39,8 @@
39
39
  "devDependencies": {
40
40
  "react": "19.2.3",
41
41
  "react-dom": "19.2.3",
42
- "remotion": "4.0.527",
43
- "@remotion/eslint-config-internal": "4.0.527",
42
+ "remotion": "4.0.529",
43
+ "@remotion/eslint-config-internal": "4.0.529",
44
44
  "eslint": "9.19.0",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
46
46
  },