@remotion/transitions 4.0.522 → 4.0.523

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,282 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blurSlide = exports.blurSlideShader = void 0;
4
+ const html_in_canvas_presentation_1 = require("../html-in-canvas-presentation");
5
+ const upload_element_image_1 = require("./upload-element-image");
6
+ const DEFAULT_DIRECTION = 'from-left';
7
+ const DEFAULT_BLUR = 0.5;
8
+ const VALID_DIRECTIONS = [
9
+ 'from-left',
10
+ 'from-right',
11
+ 'from-top',
12
+ 'from-bottom',
13
+ ];
14
+ const VERTEX_SHADER = `#version 300 es
15
+ in vec2 a_pos;
16
+ out vec2 v_uv;
17
+ void main() {
18
+ v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);
19
+ gl_Position = vec4(a_pos, 0.0, 1.0);
20
+ }`;
21
+ // The blur is a box kernel spanning the full blur length, built from two
22
+ // passes: pass 1 places SAMPLES coarse taps across the whole length, pass 2
23
+ // places SAMPLES fine taps across exactly one coarse tap spacing. Together they
24
+ // form SAMPLES² evenly spaced taps, which avoids the moiré a single pass with
25
+ // widely spaced taps produces on detailed content.
26
+ const SAMPLES = 32;
27
+ // Pass 1: slide both scenes along u_direction, wrapping around the edges
28
+ // (REPEAT), crossfade them around the midpoint and apply the coarse blur.
29
+ const SLIDE_FRAGMENT_SHADER = `#version 300 es
30
+ precision highp float;
31
+
32
+ uniform sampler2D u_prev;
33
+ uniform sampler2D u_next;
34
+ uniform float u_progress;
35
+ uniform vec2 u_direction;
36
+ uniform float u_blur_length;
37
+
38
+ in vec2 v_uv;
39
+ out vec4 outColor;
40
+
41
+ const int SAMPLES = ${SAMPLES};
42
+
43
+ void main() {
44
+ // Both scenes travel together; the exiting scene wraps around the edges
45
+ // while the entering scene takes over through a crossfade in the middle.
46
+ vec2 slidUv = v_uv - u_direction * u_progress;
47
+ float mixFactor = smoothstep(0.3, 0.7, u_progress);
48
+
49
+ vec4 color = vec4(0.0);
50
+ for (int i = 0; i < SAMPLES; i++) {
51
+ float t = (float(i) / float(SAMPLES - 1) - 0.5) * u_blur_length;
52
+ vec2 uv = slidUv + u_direction * t;
53
+ color += mix(texture(u_prev, uv), texture(u_next, uv), mixFactor);
54
+ }
55
+
56
+ outColor = color / float(SAMPLES);
57
+ }`;
58
+ // Pass 2: fill in the gaps between the coarse taps of pass 1.
59
+ // u_blur_length is one coarse tap spacing here.
60
+ const BLUR_FRAGMENT_SHADER = `#version 300 es
61
+ precision highp float;
62
+
63
+ uniform sampler2D u_source;
64
+ uniform vec2 u_direction;
65
+ uniform float u_blur_length;
66
+
67
+ in vec2 v_uv;
68
+ out vec4 outColor;
69
+
70
+ const int SAMPLES = ${SAMPLES};
71
+
72
+ void main() {
73
+ vec4 color = vec4(0.0);
74
+ for (int i = 0; i < SAMPLES; i++) {
75
+ float t = ((float(i) + 0.5) / float(SAMPLES) - 0.5) * u_blur_length;
76
+ vec2 uv = v_uv + u_direction * t;
77
+ // The framebuffer texture is stored bottom-up, so flip Y when sampling it.
78
+ color += texture(u_source, vec2(uv.x, 1.0 - uv.y));
79
+ }
80
+
81
+ outColor = color / float(SAMPLES);
82
+ }`;
83
+ const compileShader = (gl, source, type) => {
84
+ const shader = gl.createShader(type);
85
+ if (!shader) {
86
+ throw new Error('Failed to create shader');
87
+ }
88
+ gl.shaderSource(shader, source);
89
+ gl.compileShader(shader);
90
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
91
+ const log = gl.getShaderInfoLog(shader);
92
+ gl.deleteShader(shader);
93
+ throw new Error(`Failed to compile shader: ${log}`);
94
+ }
95
+ return shader;
96
+ };
97
+ const createProgram = (gl, fragmentShader) => {
98
+ const program = gl.createProgram();
99
+ if (!program) {
100
+ throw new Error('Failed to create WebGL program');
101
+ }
102
+ const vs = compileShader(gl, VERTEX_SHADER, gl.VERTEX_SHADER);
103
+ const fs = compileShader(gl, fragmentShader, gl.FRAGMENT_SHADER);
104
+ gl.attachShader(program, vs);
105
+ gl.attachShader(program, fs);
106
+ gl.linkProgram(program);
107
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
108
+ const log = gl.getProgramInfoLog(program);
109
+ gl.deleteProgram(program);
110
+ throw new Error(`Failed to link program: ${log}`);
111
+ }
112
+ return program;
113
+ };
114
+ const createTexture = (gl) => {
115
+ const tex = gl.createTexture();
116
+ if (!tex) {
117
+ throw new Error('Failed to create texture');
118
+ }
119
+ gl.bindTexture(gl.TEXTURE_2D, tex);
120
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
121
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
122
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
123
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
124
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));
125
+ return tex;
126
+ };
127
+ const getDirectionVector = (direction) => {
128
+ // v_uv has its origin in the top-left corner, so +y points down.
129
+ switch (direction) {
130
+ case 'from-left':
131
+ return [1, 0];
132
+ case 'from-right':
133
+ return [-1, 0];
134
+ case 'from-top':
135
+ return [0, 1];
136
+ case 'from-bottom':
137
+ return [0, -1];
138
+ default:
139
+ throw new Error(`Invalid direction: ${direction}`);
140
+ }
141
+ };
142
+ const validateProps = (props) => {
143
+ var _a, _b;
144
+ const direction = (_a = props.direction) !== null && _a !== void 0 ? _a : DEFAULT_DIRECTION;
145
+ const blur = (_b = props.blur) !== null && _b !== void 0 ? _b : DEFAULT_BLUR;
146
+ if (!VALID_DIRECTIONS.includes(direction)) {
147
+ throw new TypeError(`direction passed to blurSlide() must be one of ${VALID_DIRECTIONS.map((d) => `"${d}"`).join(', ')}, received ${JSON.stringify(direction)}`);
148
+ }
149
+ if (typeof blur !== 'number' || !Number.isFinite(blur)) {
150
+ throw new TypeError(`blur passed to blurSlide() must be a finite number, received ${blur}`);
151
+ }
152
+ if (blur < 0) {
153
+ throw new TypeError(`blur passed to blurSlide() must be greater than or equal to 0, received ${blur}`);
154
+ }
155
+ };
156
+ const blurSlideShader = (canvas) => {
157
+ const gl = canvas.getContext('webgl2', { premultipliedAlpha: true });
158
+ if (!gl) {
159
+ throw new Error('Failed to create WebGL2 context');
160
+ }
161
+ const slideProgram = createProgram(gl, SLIDE_FRAGMENT_SHADER);
162
+ const blurProgram = createProgram(gl, BLUR_FRAGMENT_SHADER);
163
+ const prevTex = createTexture(gl);
164
+ const nextTex = createTexture(gl);
165
+ const intermediateTex = createTexture(gl);
166
+ const framebuffer = gl.createFramebuffer();
167
+ if (!framebuffer) {
168
+ throw new Error('Failed to create framebuffer');
169
+ }
170
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
171
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, intermediateTex, 0);
172
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
173
+ let intermediateWidth = 1;
174
+ let intermediateHeight = 1;
175
+ const vao = gl.createVertexArray();
176
+ gl.bindVertexArray(vao);
177
+ const buffer = gl.createBuffer();
178
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
179
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
180
+ // Both programs share the same vertex shader, so the attribute location matches.
181
+ const aPos = gl.getAttribLocation(slideProgram, 'a_pos');
182
+ gl.enableVertexAttribArray(aPos);
183
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
184
+ const uPrev = gl.getUniformLocation(slideProgram, 'u_prev');
185
+ const uNext = gl.getUniformLocation(slideProgram, 'u_next');
186
+ const uProgress = gl.getUniformLocation(slideProgram, 'u_progress');
187
+ const uSlideDirection = gl.getUniformLocation(slideProgram, 'u_direction');
188
+ const uSlideBlurLength = gl.getUniformLocation(slideProgram, 'u_blur_length');
189
+ const uSource = gl.getUniformLocation(blurProgram, 'u_source');
190
+ const uBlurDirection = gl.getUniformLocation(blurProgram, 'u_direction');
191
+ const uBlurBlurLength = gl.getUniformLocation(blurProgram, 'u_blur_length');
192
+ const cleanup = () => {
193
+ gl.deleteProgram(slideProgram);
194
+ gl.deleteProgram(blurProgram);
195
+ gl.deleteTexture(prevTex);
196
+ gl.deleteTexture(nextTex);
197
+ gl.deleteTexture(intermediateTex);
198
+ gl.deleteFramebuffer(framebuffer);
199
+ gl.deleteBuffer(buffer);
200
+ gl.deleteVertexArray(vao);
201
+ };
202
+ const clear = () => {
203
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
204
+ gl.clearColor(0, 0, 0, 0);
205
+ gl.clear(gl.COLOR_BUFFER_BIT);
206
+ };
207
+ const draw = ({ prevImage, nextImage, width, height, time, passedProps, }) => {
208
+ const { direction = DEFAULT_DIRECTION, blur = DEFAULT_BLUR } = passedProps;
209
+ if (!prevImage && !nextImage) {
210
+ return;
211
+ }
212
+ if (prevImage && (prevImage.width === 0 || prevImage.height === 0)) {
213
+ return;
214
+ }
215
+ if (nextImage && (nextImage.width === 0 || nextImage.height === 0)) {
216
+ return;
217
+ }
218
+ // At time=0 the shader outputs nextImage. At time=1 the shader outputs prevImage.
219
+ const effectiveTime = !prevImage ? 0 : !nextImage ? 1 : time;
220
+ const p = 1 - effectiveTime;
221
+ // Ease the slide with a quintic curve so that it starts and ends at rest.
222
+ // The blur follows the distance traveled until the midpoint, then the
223
+ // distance remaining, normalized to peak at 1 halfway through.
224
+ const progress = p * p * p * (p * (p * 6 - 15) + 10);
225
+ const blurLength = blur * 2 * Math.min(progress, 1 - progress);
226
+ const coarseTapSpacing = blurLength / (SAMPLES - 1);
227
+ const [dirX, dirY] = getDirectionVector(direction);
228
+ gl.bindVertexArray(vao);
229
+ if (intermediateWidth !== width || intermediateHeight !== height) {
230
+ gl.bindTexture(gl.TEXTURE_2D, intermediateTex);
231
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
232
+ intermediateWidth = width;
233
+ intermediateHeight = height;
234
+ }
235
+ // Pass 1: slide + crossfade + first blur into the intermediate texture.
236
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
237
+ gl.viewport(0, 0, width, height);
238
+ gl.clearColor(0, 0, 0, 0);
239
+ gl.clear(gl.COLOR_BUFFER_BIT);
240
+ gl.useProgram(slideProgram);
241
+ gl.activeTexture(gl.TEXTURE0);
242
+ gl.bindTexture(gl.TEXTURE_2D, prevTex);
243
+ if (prevImage) {
244
+ (0, upload_element_image_1.uploadElementImage)(gl, prevImage);
245
+ }
246
+ gl.uniform1i(uPrev, 0);
247
+ gl.activeTexture(gl.TEXTURE1);
248
+ gl.bindTexture(gl.TEXTURE_2D, nextTex);
249
+ if (nextImage) {
250
+ (0, upload_element_image_1.uploadElementImage)(gl, nextImage);
251
+ }
252
+ gl.uniform1i(uNext, 1);
253
+ gl.uniform1f(uProgress, progress);
254
+ gl.uniform2f(uSlideDirection, dirX, dirY);
255
+ gl.uniform1f(uSlideBlurLength, blurLength);
256
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
257
+ // Pass 2: second blur into the output canvas.
258
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
259
+ gl.viewport(0, 0, width, height);
260
+ gl.clearColor(0, 0, 0, 0);
261
+ gl.clear(gl.COLOR_BUFFER_BIT);
262
+ gl.useProgram(blurProgram);
263
+ gl.activeTexture(gl.TEXTURE0);
264
+ gl.bindTexture(gl.TEXTURE_2D, intermediateTex);
265
+ gl.uniform1i(uSource, 0);
266
+ gl.uniform2f(uBlurDirection, dirX, dirY);
267
+ gl.uniform1f(uBlurBlurLength, coarseTapSpacing);
268
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
269
+ };
270
+ return {
271
+ clear,
272
+ cleanup,
273
+ draw,
274
+ };
275
+ };
276
+ exports.blurSlideShader = blurSlideShader;
277
+ const makeBlurSlide = (0, html_in_canvas_presentation_1.makeHtmlInCanvasPresentation)(exports.blurSlideShader);
278
+ const blurSlide = (props = {}) => {
279
+ validateProps(props);
280
+ return makeBlurSlide(props);
281
+ };
282
+ exports.blurSlide = blurSlide;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/transitions"
4
4
  },
5
5
  "name": "@remotion/transitions",
6
- "version": "4.0.522",
6
+ "version": "4.0.523",
7
7
  "description": "Library for creating transitions in Remotion",
8
8
  "main": "dist/esm/index.mjs",
9
9
  "module": "dist/esm/index.js",
@@ -22,18 +22,18 @@
22
22
  "url": "https://github.com/remotion-dev/remotion/issues"
23
23
  },
24
24
  "dependencies": {
25
- "remotion": "4.0.522",
26
- "@remotion/shapes": "4.0.522",
27
- "@remotion/paths": "4.0.522"
25
+ "remotion": "4.0.523",
26
+ "@remotion/shapes": "4.0.523",
27
+ "@remotion/paths": "4.0.523"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@happy-dom/global-registrator": "14.5.1",
31
- "remotion": "4.0.522",
31
+ "remotion": "4.0.523",
32
32
  "react": "19.2.3",
33
33
  "react-dom": "19.2.3",
34
- "@remotion/test-utils": "4.0.522",
35
- "@remotion/player": "4.0.522",
36
- "@remotion/eslint-config-internal": "4.0.522",
34
+ "@remotion/test-utils": "4.0.523",
35
+ "@remotion/player": "4.0.523",
36
+ "@remotion/eslint-config-internal": "4.0.523",
37
37
  "eslint": "9.19.0",
38
38
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
39
39
  },
@@ -169,6 +169,12 @@
169
169
  "import": "./dist/esm/push-cut.mjs",
170
170
  "require": "./dist/presentations/push-cut.js"
171
171
  },
172
+ "./blur-slide": {
173
+ "types": "./dist/presentations/blur-slide.d.ts",
174
+ "module": "./dist/esm/blur-slide.mjs",
175
+ "import": "./dist/esm/blur-slide.mjs",
176
+ "require": "./dist/presentations/blur-slide.js"
177
+ },
172
178
  "./package.json": "./package.json"
173
179
  },
174
180
  "typesVersions": {
@@ -229,6 +235,9 @@
229
235
  ],
230
236
  "push-cut": [
231
237
  "dist/presentations/push-cut.d.ts"
238
+ ],
239
+ "blur-slide": [
240
+ "dist/presentations/blur-slide.d.ts"
232
241
  ]
233
242
  }
234
243
  },