@remotion/effects 4.0.506 → 4.0.507

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,554 @@
1
+ // src/region-blur/index.ts
2
+ import { Internals as Internals2 } from "remotion";
3
+
4
+ // src/validate-effect-param.ts
5
+ var assertEffectParamsObject = (params, effectLabel) => {
6
+ if (params === null || typeof params !== "object") {
7
+ throw new TypeError(`${effectLabel} effect requires a parameters object, but got ${JSON.stringify(params)}`);
8
+ }
9
+ };
10
+ var assertRequiredFiniteNumber = (value, name) => {
11
+ if (typeof value !== "number" || !Number.isFinite(value)) {
12
+ throw new TypeError(`"${name}" must be a finite number, but got ${JSON.stringify(value)}`);
13
+ }
14
+ };
15
+ var assertRequiredColor = (value, name) => {
16
+ if (typeof value !== "string" || value.length === 0) {
17
+ throw new TypeError(`"${name}" must be a non-empty string, but got ${JSON.stringify(value)}`);
18
+ }
19
+ };
20
+ var assertOptionalColor = (value, name) => {
21
+ if (value === undefined) {
22
+ return;
23
+ }
24
+ assertRequiredColor(value, name);
25
+ };
26
+ var assertOptionalBoolean = (value, name) => {
27
+ if (value === undefined) {
28
+ return;
29
+ }
30
+ if (typeof value !== "boolean") {
31
+ throw new TypeError(`"${name}" must be a boolean, but got ${JSON.stringify(value)}`);
32
+ }
33
+ };
34
+
35
+ // src/color-utils.ts
36
+ var DEFAULT_AMOUNT = 1;
37
+ var DEFAULT_BRIGHTNESS_AMOUNT = 0;
38
+ var DEFAULT_HUE_DEGREES = 0;
39
+ var colorAmountSchema = {
40
+ type: "number",
41
+ min: 0,
42
+ max: 1,
43
+ step: 0.01,
44
+ default: DEFAULT_AMOUNT,
45
+ description: "Amount",
46
+ hiddenFromList: false
47
+ };
48
+ var colorMultiplierSchema = {
49
+ type: "number",
50
+ min: 0,
51
+ step: 0.01,
52
+ default: DEFAULT_AMOUNT,
53
+ description: "Amount",
54
+ hiddenFromList: false
55
+ };
56
+ var brightnessAmountSchema = {
57
+ type: "number",
58
+ min: -1,
59
+ max: 1,
60
+ step: 0.01,
61
+ default: DEFAULT_BRIGHTNESS_AMOUNT,
62
+ description: "Amount",
63
+ hiddenFromList: false
64
+ };
65
+ var hueDegreesSchema = {
66
+ type: "rotation-degrees",
67
+ step: 1,
68
+ default: DEFAULT_HUE_DEGREES,
69
+ description: "Degrees"
70
+ };
71
+ var assertOptionalFiniteNumber = (value, name) => {
72
+ if (value === undefined) {
73
+ return;
74
+ }
75
+ assertRequiredFiniteNumber(value, name);
76
+ };
77
+ var validateUnitInterval = (value, name) => {
78
+ if (value < 0) {
79
+ throw new TypeError(`"${name}" must be >= 0, but got ${JSON.stringify(value)}`);
80
+ }
81
+ if (value > 1) {
82
+ throw new TypeError(`"${name}" must be <= 1, but got ${JSON.stringify(value)}`);
83
+ }
84
+ };
85
+ var validateNonNegative = (value, name) => {
86
+ if (value < 0) {
87
+ throw new TypeError(`"${name}" must be >= 0, but got ${JSON.stringify(value)}`);
88
+ }
89
+ };
90
+ var validateSignedUnitInterval = (value, name) => {
91
+ if (value < -1) {
92
+ throw new TypeError(`"${name}" must be >= -1, but got ${JSON.stringify(value)}`);
93
+ }
94
+ if (value > 1) {
95
+ throw new TypeError(`"${name}" must be <= 1, but got ${JSON.stringify(value)}`);
96
+ }
97
+ };
98
+ var clampColorChannel = (value) => {
99
+ return Math.max(0, Math.min(255, value));
100
+ };
101
+ var parseColorRgba = (ctx, color) => {
102
+ ctx.clearRect(0, 0, 1, 1);
103
+ ctx.fillStyle = color;
104
+ ctx.fillRect(0, 0, 1, 1);
105
+ const { data } = ctx.getImageData(0, 0, 1, 1);
106
+ return [data[0], data[1], data[2], data[3]];
107
+ };
108
+
109
+ // src/region-blur/region-blur-runtime.ts
110
+ import { Internals } from "remotion";
111
+
112
+ // src/gaussian-blur-shader.ts
113
+ var buildGaussianBlurFs = (direction) => {
114
+ const dirVec = direction === "horizontal" ? "vec2(1.0, 0.0)" : "vec2(0.0, 1.0)";
115
+ return `#version 300 es
116
+ precision highp float;
117
+
118
+ in vec2 vUv;
119
+ out vec4 fragColor;
120
+
121
+ uniform sampler2D uSource;
122
+ uniform float uRadius;
123
+ uniform vec2 uTexelSize;
124
+
125
+ const int MIN_KERNEL_HALF = 4;
126
+ const int MAX_KERNEL_HALF = 32;
127
+ const float TARGET_SAMPLE_DISTANCE_PX = 2.0;
128
+ const vec2 DIRECTION = ${dirVec};
129
+
130
+ void main() {
131
+ if (uRadius <= 0.0) {
132
+ fragColor = texture(uSource, vUv);
133
+ return;
134
+ }
135
+
136
+ int kernelHalf = int(
137
+ min(
138
+ float(MAX_KERNEL_HALF),
139
+ max(float(MIN_KERNEL_HALF), ceil(uRadius / TARGET_SAMPLE_DISTANCE_PX))
140
+ )
141
+ );
142
+ float pixelStride = uRadius / float(kernelHalf);
143
+ float sigma = uRadius / 3.0;
144
+ float twoSigmaSq = 2.0 * sigma * sigma;
145
+
146
+ vec4 sum = vec4(0.0);
147
+ float weightSum = 0.0;
148
+
149
+ for (int i = -MAX_KERNEL_HALF; i <= MAX_KERNEL_HALF; ++i) {
150
+ if (abs(i) > kernelHalf) {
151
+ continue;
152
+ }
153
+
154
+ float offsetPx = float(i) * pixelStride;
155
+ float w = exp(-(offsetPx * offsetPx) / twoSigmaSq);
156
+ vec2 uv = vUv + DIRECTION * uTexelSize * offsetPx;
157
+ sum += texture(uSource, uv) * w;
158
+ weightSum += w;
159
+ }
160
+
161
+ fragColor = sum / weightSum;
162
+ }
163
+ `;
164
+ };
165
+
166
+ // src/blur/blur-shaders.ts
167
+ var BLUR_VS = `#version 300 es
168
+ layout(location = 0) in vec2 aPos;
169
+ layout(location = 1) in vec2 aUv;
170
+ out vec2 vUv;
171
+ void main() {
172
+ vUv = aUv;
173
+ gl_Position = vec4(aPos, 0.0, 1.0);
174
+ }
175
+ `;
176
+ var BLUR_FS_HORIZONTAL = buildGaussianBlurFs("horizontal");
177
+ var BLUR_FS_VERTICAL = buildGaussianBlurFs("vertical");
178
+
179
+ // src/uv-coordinate.ts
180
+ var publicUvToShaderUv = (uv) => {
181
+ return [uv[0], 1 - uv[1]];
182
+ };
183
+
184
+ // src/region-blur/region-blur-runtime.ts
185
+ var { createWebGL2ContextError } = Internals;
186
+ var COMPOSITE_FRAGMENT_SHADER = `#version 300 es
187
+ precision highp float;
188
+
189
+ in vec2 vUv;
190
+ out vec4 fragColor;
191
+
192
+ uniform sampler2D uSource;
193
+ uniform sampler2D uBlurred;
194
+ uniform vec2 uResolution;
195
+ uniform vec2 uTopLeft;
196
+ uniform vec2 uBottomRight;
197
+ uniform float uFeather;
198
+ uniform float uRoundness;
199
+
200
+ float roundedBoxDistance(vec2 point, vec2 center, vec2 halfSize, float radius) {
201
+ vec2 offset = abs(point - center) - halfSize + vec2(radius);
202
+ return length(max(offset, vec2(0.0))) + min(max(offset.x, offset.y), 0.0) - radius;
203
+ }
204
+
205
+ void main() {
206
+ vec2 minimum = vec2(uTopLeft.x, uBottomRight.y) * uResolution;
207
+ vec2 maximum = vec2(uBottomRight.x, uTopLeft.y) * uResolution;
208
+ vec2 halfSize = (maximum - minimum) * 0.5;
209
+ vec2 center = (minimum + maximum) * 0.5;
210
+ float cornerRadius = min(halfSize.x, halfSize.y) * uRoundness;
211
+ float distanceToRegion = roundedBoxDistance(
212
+ vUv * uResolution,
213
+ center,
214
+ halfSize,
215
+ cornerRadius
216
+ );
217
+ float mask = uFeather <= 0.0
218
+ ? (distanceToRegion <= 0.0 ? 1.0 : 0.0)
219
+ : 1.0 - smoothstep(-uFeather * 0.5, uFeather * 0.5, distanceToRegion);
220
+
221
+ fragColor = mix(texture(uSource, vUv), texture(uBlurred, vUv), mask);
222
+ }
223
+ `;
224
+ var compileShader = (gl, type, source) => {
225
+ const shader = gl.createShader(type);
226
+ if (!shader) {
227
+ throw new Error("Failed to create WebGL shader");
228
+ }
229
+ gl.shaderSource(shader, source);
230
+ gl.compileShader(shader);
231
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
232
+ const log = gl.getShaderInfoLog(shader);
233
+ gl.deleteShader(shader);
234
+ throw new Error(`Shader compile failed: ${log ?? "(no log)"}`);
235
+ }
236
+ return shader;
237
+ };
238
+ var createProgram = (gl, vertexSource, fragmentSource) => {
239
+ const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
240
+ const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
241
+ const program = gl.createProgram();
242
+ if (!program) {
243
+ throw new Error("Failed to create WebGL program");
244
+ }
245
+ gl.attachShader(program, vertexShader);
246
+ gl.attachShader(program, fragmentShader);
247
+ gl.linkProgram(program);
248
+ gl.deleteShader(vertexShader);
249
+ gl.deleteShader(fragmentShader);
250
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
251
+ const log = gl.getProgramInfoLog(program);
252
+ gl.deleteProgram(program);
253
+ throw new Error(`Program link failed: ${log ?? "(no log)"}`);
254
+ }
255
+ return program;
256
+ };
257
+ var createTexture = (gl) => {
258
+ const texture = gl.createTexture();
259
+ if (!texture) {
260
+ throw new Error("Failed to create WebGL texture");
261
+ }
262
+ gl.bindTexture(gl.TEXTURE_2D, texture);
263
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
264
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
265
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
266
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
267
+ gl.bindTexture(gl.TEXTURE_2D, null);
268
+ return texture;
269
+ };
270
+ var getBlurUniforms = (gl, program) => ({
271
+ source: gl.getUniformLocation(program, "uSource"),
272
+ radius: gl.getUniformLocation(program, "uRadius"),
273
+ texelSize: gl.getUniformLocation(program, "uTexelSize")
274
+ });
275
+ var setupRegionBlur = (target) => {
276
+ const gl = target.getContext("webgl2", {
277
+ premultipliedAlpha: true,
278
+ alpha: true,
279
+ preserveDrawingBuffer: true
280
+ });
281
+ if (!gl) {
282
+ throw createWebGL2ContextError("region blur effect");
283
+ }
284
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
285
+ const horizontalProgram = createProgram(gl, BLUR_VS, BLUR_FS_HORIZONTAL);
286
+ const verticalProgram = createProgram(gl, BLUR_VS, BLUR_FS_VERTICAL);
287
+ const compositeProgram = createProgram(gl, BLUR_VS, COMPOSITE_FRAGMENT_SHADER);
288
+ const vao = gl.createVertexArray();
289
+ const vbo = gl.createBuffer();
290
+ const framebuffer = gl.createFramebuffer();
291
+ if (!vao || !vbo || !framebuffer) {
292
+ throw new Error("Failed to create region blur WebGL resources");
293
+ }
294
+ gl.bindVertexArray(vao);
295
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
296
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]), gl.STATIC_DRAW);
297
+ gl.enableVertexAttribArray(0);
298
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
299
+ gl.enableVertexAttribArray(1);
300
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);
301
+ gl.bindVertexArray(null);
302
+ return {
303
+ gl,
304
+ horizontalProgram,
305
+ verticalProgram,
306
+ compositeProgram,
307
+ vao,
308
+ vbo,
309
+ sourceTexture: createTexture(gl),
310
+ horizontalTexture: createTexture(gl),
311
+ blurredTexture: createTexture(gl),
312
+ framebuffer,
313
+ horizontalUniforms: getBlurUniforms(gl, horizontalProgram),
314
+ verticalUniforms: getBlurUniforms(gl, verticalProgram),
315
+ compositeUniforms: {
316
+ source: gl.getUniformLocation(compositeProgram, "uSource"),
317
+ blurred: gl.getUniformLocation(compositeProgram, "uBlurred"),
318
+ resolution: gl.getUniformLocation(compositeProgram, "uResolution"),
319
+ topLeft: gl.getUniformLocation(compositeProgram, "uTopLeft"),
320
+ bottomRight: gl.getUniformLocation(compositeProgram, "uBottomRight"),
321
+ feather: gl.getUniformLocation(compositeProgram, "uFeather"),
322
+ roundness: gl.getUniformLocation(compositeProgram, "uRoundness")
323
+ }
324
+ };
325
+ };
326
+ var draw = (state) => {
327
+ state.gl.bindVertexArray(state.vao);
328
+ state.gl.drawArrays(state.gl.TRIANGLE_STRIP, 0, 4);
329
+ state.gl.bindVertexArray(null);
330
+ };
331
+ var allocateTexture = (gl, texture, width, height) => {
332
+ gl.bindTexture(gl.TEXTURE_2D, texture);
333
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
334
+ };
335
+ var renderBlurPass = ({
336
+ state,
337
+ program,
338
+ uniforms,
339
+ input,
340
+ output,
341
+ width,
342
+ height,
343
+ radius
344
+ }) => {
345
+ const { gl } = state;
346
+ gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
347
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, output, 0);
348
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
349
+ throw new Error("Region blur framebuffer is incomplete");
350
+ }
351
+ gl.clear(gl.COLOR_BUFFER_BIT);
352
+ gl.activeTexture(gl.TEXTURE0);
353
+ gl.bindTexture(gl.TEXTURE_2D, input);
354
+ gl.useProgram(program);
355
+ if (uniforms.source)
356
+ gl.uniform1i(uniforms.source, 0);
357
+ if (uniforms.radius)
358
+ gl.uniform1f(uniforms.radius, radius);
359
+ if (uniforms.texelSize) {
360
+ gl.uniform2f(uniforms.texelSize, 1 / width, 1 / height);
361
+ }
362
+ draw(state);
363
+ };
364
+ var applyRegionBlur = ({
365
+ state,
366
+ source,
367
+ width,
368
+ height,
369
+ flipSourceY,
370
+ topLeft,
371
+ bottomRight,
372
+ blurRadius,
373
+ feather,
374
+ roundness
375
+ }) => {
376
+ const { gl } = state;
377
+ gl.viewport(0, 0, width, height);
378
+ gl.clearColor(0, 0, 0, 0);
379
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
380
+ gl.bindTexture(gl.TEXTURE_2D, state.sourceTexture);
381
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
382
+ allocateTexture(gl, state.horizontalTexture, width, height);
383
+ allocateTexture(gl, state.blurredTexture, width, height);
384
+ renderBlurPass({
385
+ state,
386
+ program: state.horizontalProgram,
387
+ uniforms: state.horizontalUniforms,
388
+ input: state.sourceTexture,
389
+ output: state.horizontalTexture,
390
+ width,
391
+ height,
392
+ radius: blurRadius
393
+ });
394
+ renderBlurPass({
395
+ state,
396
+ program: state.verticalProgram,
397
+ uniforms: state.verticalUniforms,
398
+ input: state.horizontalTexture,
399
+ output: state.blurredTexture,
400
+ width,
401
+ height,
402
+ radius: blurRadius
403
+ });
404
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
405
+ gl.clear(gl.COLOR_BUFFER_BIT);
406
+ gl.useProgram(state.compositeProgram);
407
+ gl.activeTexture(gl.TEXTURE0);
408
+ gl.bindTexture(gl.TEXTURE_2D, state.sourceTexture);
409
+ gl.activeTexture(gl.TEXTURE1);
410
+ gl.bindTexture(gl.TEXTURE_2D, state.blurredTexture);
411
+ const topLeftShader = publicUvToShaderUv(topLeft);
412
+ const bottomRightShader = publicUvToShaderUv(bottomRight);
413
+ const uniforms = state.compositeUniforms;
414
+ if (uniforms.source)
415
+ gl.uniform1i(uniforms.source, 0);
416
+ if (uniforms.blurred)
417
+ gl.uniform1i(uniforms.blurred, 1);
418
+ if (uniforms.resolution)
419
+ gl.uniform2f(uniforms.resolution, width, height);
420
+ if (uniforms.topLeft) {
421
+ gl.uniform2f(uniforms.topLeft, topLeftShader[0], topLeftShader[1]);
422
+ }
423
+ if (uniforms.bottomRight) {
424
+ gl.uniform2f(uniforms.bottomRight, bottomRightShader[0], bottomRightShader[1]);
425
+ }
426
+ if (uniforms.feather)
427
+ gl.uniform1f(uniforms.feather, feather);
428
+ if (uniforms.roundness)
429
+ gl.uniform1f(uniforms.roundness, roundness);
430
+ draw(state);
431
+ gl.activeTexture(gl.TEXTURE1);
432
+ gl.bindTexture(gl.TEXTURE_2D, null);
433
+ gl.activeTexture(gl.TEXTURE0);
434
+ gl.bindTexture(gl.TEXTURE_2D, null);
435
+ gl.useProgram(null);
436
+ };
437
+ var cleanupRegionBlur = (state) => {
438
+ const { gl } = state;
439
+ gl.deleteFramebuffer(state.framebuffer);
440
+ gl.deleteTexture(state.sourceTexture);
441
+ gl.deleteTexture(state.horizontalTexture);
442
+ gl.deleteTexture(state.blurredTexture);
443
+ gl.deleteBuffer(state.vbo);
444
+ gl.deleteVertexArray(state.vao);
445
+ gl.deleteProgram(state.horizontalProgram);
446
+ gl.deleteProgram(state.verticalProgram);
447
+ gl.deleteProgram(state.compositeProgram);
448
+ };
449
+
450
+ // src/region-blur/index.ts
451
+ var { createEffect } = Internals2;
452
+ var DEFAULT_BLUR_RADIUS = 40;
453
+ var DEFAULT_FEATHER = 0;
454
+ var DEFAULT_ROUNDNESS = 0;
455
+ var regionBlurSchema = {
456
+ topLeft: {
457
+ type: "uv-coordinate",
458
+ min: -1,
459
+ max: 2,
460
+ step: 0.01,
461
+ default: undefined,
462
+ description: "Top left"
463
+ },
464
+ bottomRight: {
465
+ type: "uv-coordinate",
466
+ min: -1,
467
+ max: 2,
468
+ step: 0.01,
469
+ default: undefined,
470
+ description: "Bottom right"
471
+ },
472
+ blurRadius: {
473
+ type: "number",
474
+ min: 0,
475
+ max: 200,
476
+ step: 1,
477
+ default: DEFAULT_BLUR_RADIUS,
478
+ description: "Blur radius",
479
+ hiddenFromList: false
480
+ },
481
+ feather: {
482
+ type: "number",
483
+ min: 0,
484
+ max: 200,
485
+ step: 1,
486
+ default: DEFAULT_FEATHER,
487
+ description: "Feather",
488
+ hiddenFromList: false
489
+ },
490
+ roundness: {
491
+ type: "number",
492
+ min: 0,
493
+ max: 1,
494
+ step: 0.01,
495
+ default: DEFAULT_ROUNDNESS,
496
+ description: "Roundness",
497
+ hiddenFromList: false
498
+ }
499
+ };
500
+ var resolve = (params) => ({
501
+ topLeft: [...params.topLeft],
502
+ bottomRight: [...params.bottomRight],
503
+ blurRadius: params.blurRadius ?? DEFAULT_BLUR_RADIUS,
504
+ feather: params.feather ?? DEFAULT_FEATHER,
505
+ roundness: params.roundness ?? DEFAULT_ROUNDNESS
506
+ });
507
+ var assertRequiredUvCoordinate = (value, name) => {
508
+ if (!Array.isArray(value) || value.length !== 2 || value.some((item) => typeof item !== "number" || !Number.isFinite(item))) {
509
+ throw new TypeError(`"${name}" must be a [number, number] tuple`);
510
+ }
511
+ };
512
+ var validateRegionBlurParams = (params) => {
513
+ assertEffectParamsObject(params, "Region blur");
514
+ assertRequiredUvCoordinate(params.topLeft, "topLeft");
515
+ assertRequiredUvCoordinate(params.bottomRight, "bottomRight");
516
+ assertOptionalFiniteNumber(params.blurRadius, "blurRadius");
517
+ assertOptionalFiniteNumber(params.feather, "feather");
518
+ assertOptionalFiniteNumber(params.roundness, "roundness");
519
+ const resolved = resolve(params);
520
+ validateNonNegative(resolved.blurRadius, "blurRadius");
521
+ validateNonNegative(resolved.feather, "feather");
522
+ validateUnitInterval(resolved.roundness, "roundness");
523
+ if (resolved.topLeft[0] >= resolved.bottomRight[0] || resolved.topLeft[1] >= resolved.bottomRight[1]) {
524
+ throw new TypeError('"topLeft" must be above and to the left of "bottomRight"');
525
+ }
526
+ };
527
+ var regionBlur = createEffect({
528
+ type: "remotion/region-blur",
529
+ label: "regionBlur()",
530
+ documentationLink: "https://www.remotion.dev/docs/effects/region-blur",
531
+ backend: "webgl2",
532
+ calculateKey: (params) => {
533
+ const resolved = resolve(params);
534
+ return `region-blur-${resolved.topLeft.join(":")}-${resolved.bottomRight.join(":")}-${resolved.blurRadius}-${resolved.feather}-${resolved.roundness}`;
535
+ },
536
+ setup: (target) => setupRegionBlur(target),
537
+ apply: ({ source, width, height, params, state, flipSourceY }) => {
538
+ const resolved = resolve(params);
539
+ applyRegionBlur({
540
+ state,
541
+ source,
542
+ width,
543
+ height,
544
+ flipSourceY,
545
+ ...resolved
546
+ });
547
+ },
548
+ cleanup: (state) => cleanupRegionBlur(state),
549
+ schema: regionBlurSchema,
550
+ validateParams: validateRegionBlurParams
551
+ });
552
+ export {
553
+ regionBlur
554
+ };
@@ -0,0 +1,61 @@
1
+ export type RegionBlurUvCoordinate = readonly [number, number];
2
+ export type RegionBlurParams = {
3
+ /** Top-left corner of the region in UV coordinates. */
4
+ readonly topLeft: RegionBlurUvCoordinate;
5
+ /** Bottom-right corner of the region in UV coordinates. */
6
+ readonly bottomRight: RegionBlurUvCoordinate;
7
+ /** Gaussian blur radius in pixels. Defaults to `40`. */
8
+ readonly blurRadius?: number;
9
+ /** Width of the softened region edge in pixels. Defaults to `0`. */
10
+ readonly feather?: number;
11
+ /** Corner roundness from rectangular (`0`) to fully rounded (`1`). Defaults to `0`. */
12
+ readonly roundness?: number;
13
+ };
14
+ export declare const regionBlurSchema: {
15
+ readonly topLeft: {
16
+ readonly type: "uv-coordinate";
17
+ readonly min: -1;
18
+ readonly max: 2;
19
+ readonly step: 0.01;
20
+ readonly default: undefined;
21
+ readonly description: "Top left";
22
+ };
23
+ readonly bottomRight: {
24
+ readonly type: "uv-coordinate";
25
+ readonly min: -1;
26
+ readonly max: 2;
27
+ readonly step: 0.01;
28
+ readonly default: undefined;
29
+ readonly description: "Bottom right";
30
+ };
31
+ readonly blurRadius: {
32
+ readonly type: "number";
33
+ readonly min: 0;
34
+ readonly max: 200;
35
+ readonly step: 1;
36
+ readonly default: 40;
37
+ readonly description: "Blur radius";
38
+ readonly hiddenFromList: false;
39
+ };
40
+ readonly feather: {
41
+ readonly type: "number";
42
+ readonly min: 0;
43
+ readonly max: 200;
44
+ readonly step: 1;
45
+ readonly default: 0;
46
+ readonly description: "Feather";
47
+ readonly hiddenFromList: false;
48
+ };
49
+ readonly roundness: {
50
+ readonly type: "number";
51
+ readonly min: 0;
52
+ readonly max: 1;
53
+ readonly step: 0.01;
54
+ readonly default: 0;
55
+ readonly description: "Roundness";
56
+ readonly hiddenFromList: false;
57
+ };
58
+ };
59
+ export declare const regionBlur: (params: RegionBlurParams & {
60
+ readonly disabled?: boolean | undefined;
61
+ }) => import("remotion").EffectDescriptor<unknown>;
@@ -0,0 +1,44 @@
1
+ import type { RegionBlurUvCoordinate } from './index.js';
2
+ type BlurUniforms = {
3
+ readonly source: WebGLUniformLocation | null;
4
+ readonly radius: WebGLUniformLocation | null;
5
+ readonly texelSize: WebGLUniformLocation | null;
6
+ };
7
+ export type RegionBlurState = {
8
+ readonly gl: WebGL2RenderingContext;
9
+ readonly horizontalProgram: WebGLProgram;
10
+ readonly verticalProgram: WebGLProgram;
11
+ readonly compositeProgram: WebGLProgram;
12
+ readonly vao: WebGLVertexArrayObject;
13
+ readonly vbo: WebGLBuffer;
14
+ readonly sourceTexture: WebGLTexture;
15
+ readonly horizontalTexture: WebGLTexture;
16
+ readonly blurredTexture: WebGLTexture;
17
+ readonly framebuffer: WebGLFramebuffer;
18
+ readonly horizontalUniforms: BlurUniforms;
19
+ readonly verticalUniforms: BlurUniforms;
20
+ readonly compositeUniforms: {
21
+ readonly source: WebGLUniformLocation | null;
22
+ readonly blurred: WebGLUniformLocation | null;
23
+ readonly resolution: WebGLUniformLocation | null;
24
+ readonly topLeft: WebGLUniformLocation | null;
25
+ readonly bottomRight: WebGLUniformLocation | null;
26
+ readonly feather: WebGLUniformLocation | null;
27
+ readonly roundness: WebGLUniformLocation | null;
28
+ };
29
+ };
30
+ export declare const setupRegionBlur: (target: HTMLCanvasElement) => RegionBlurState;
31
+ export declare const applyRegionBlur: ({ state, source, width, height, flipSourceY, topLeft, bottomRight, blurRadius, feather, roundness, }: {
32
+ readonly state: RegionBlurState;
33
+ readonly source: CanvasImageSource;
34
+ readonly width: number;
35
+ readonly height: number;
36
+ readonly flipSourceY: boolean;
37
+ readonly topLeft: RegionBlurUvCoordinate;
38
+ readonly bottomRight: RegionBlurUvCoordinate;
39
+ readonly blurRadius: number;
40
+ readonly feather: number;
41
+ readonly roundness: number;
42
+ }) => void;
43
+ export declare const cleanupRegionBlur: (state: RegionBlurState) => void;
44
+ export {};
@@ -0,0 +1 @@
1
+ export { regionBlur, type RegionBlurParams, type RegionBlurUvCoordinate, } from './region-blur/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/effects",
3
- "version": "4.0.506",
3
+ "version": "4.0.507",
4
4
  "description": "Effects that can be applied to Remotion-based canvas components",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "url": "https://github.com/remotion-dev/remotion/issues"
27
27
  },
28
28
  "dependencies": {
29
- "remotion": "4.0.506"
29
+ "remotion": "4.0.507"
30
30
  },
31
31
  "exports": {
32
32
  ".": {
@@ -244,6 +244,11 @@
244
244
  "module": "./dist/esm/radial-progressive-pixelate.mjs",
245
245
  "import": "./dist/esm/radial-progressive-pixelate.mjs"
246
246
  },
247
+ "./region-blur": {
248
+ "types": "./dist/region-blur.d.ts",
249
+ "module": "./dist/esm/region-blur.mjs",
250
+ "import": "./dist/esm/region-blur.mjs"
251
+ },
247
252
  "./rings": {
248
253
  "types": "./dist/rings.d.ts",
249
254
  "module": "./dist/esm/rings.mjs",
@@ -474,6 +479,9 @@
474
479
  "radial-progressive-pixelate": [
475
480
  "dist/radial-progressive-pixelate.d.ts"
476
481
  ],
482
+ "region-blur": [
483
+ "dist/region-blur.d.ts"
484
+ ],
477
485
  "rings": [
478
486
  "dist/rings.d.ts"
479
487
  ],
@@ -538,7 +546,7 @@
538
546
  },
539
547
  "homepage": "https://www.remotion.dev/docs/effects/api",
540
548
  "devDependencies": {
541
- "@remotion/eslint-config-internal": "4.0.506",
549
+ "@remotion/eslint-config-internal": "4.0.507",
542
550
  "@vitest/browser-playwright": "4.0.9",
543
551
  "eslint": "9.19.0",
544
552
  "vitest": "4.0.9",
@@ -1,8 +0,0 @@
1
- export declare const EffectInternals: {
2
- readonly halftone: (params?: (import("./halftone.js").HalftoneParams & {
3
- readonly disabled?: boolean | undefined;
4
- }) | undefined) => import("remotion").EffectDescriptor<unknown>;
5
- readonly tint: (params: import("./tint.js").TintParams & {
6
- readonly disabled?: boolean | undefined;
7
- }) => import("remotion").EffectDescriptor<unknown>;
8
- };
@@ -1,6 +0,0 @@
1
- import * as blurExports from '../blur/index.js';
2
- export type { BlurParams } from '../blur/index.js';
3
- declare const blur: (params: blurExports.BlurParams & {
4
- readonly disabled?: boolean | undefined;
5
- }) => import("remotion").EffectDescriptor<unknown>;
6
- export { blur };
@@ -1,521 +0,0 @@
1
- // src/page-turn.ts
2
- import { Internals } from "remotion";
3
-
4
- // src/validate-effect-param.ts
5
- var assertEffectParamsObject = (params, effectLabel) => {
6
- if (params === null || typeof params !== "object") {
7
- throw new TypeError(`${effectLabel} effect requires a parameters object, but got ${JSON.stringify(params)}`);
8
- }
9
- };
10
- var assertRequiredFiniteNumber = (value, name) => {
11
- if (typeof value !== "number" || !Number.isFinite(value)) {
12
- throw new TypeError(`"${name}" must be a finite number, but got ${JSON.stringify(value)}`);
13
- }
14
- };
15
- var assertRequiredColor = (value, name) => {
16
- if (typeof value !== "string" || value.length === 0) {
17
- throw new TypeError(`"${name}" must be a non-empty string, but got ${JSON.stringify(value)}`);
18
- }
19
- };
20
- var assertOptionalColor = (value, name) => {
21
- if (value === undefined) {
22
- return;
23
- }
24
- assertRequiredColor(value, name);
25
- };
26
- var assertOptionalBoolean = (value, name) => {
27
- if (value === undefined) {
28
- return;
29
- }
30
- if (typeof value !== "boolean") {
31
- throw new TypeError(`"${name}" must be a boolean, but got ${JSON.stringify(value)}`);
32
- }
33
- };
34
-
35
- // src/color-utils.ts
36
- var DEFAULT_AMOUNT = 1;
37
- var DEFAULT_BRIGHTNESS_AMOUNT = 0;
38
- var DEFAULT_HUE_DEGREES = 0;
39
- var colorAmountSchema = {
40
- type: "number",
41
- min: 0,
42
- max: 1,
43
- step: 0.01,
44
- default: DEFAULT_AMOUNT,
45
- description: "Amount",
46
- hiddenFromList: false
47
- };
48
- var colorMultiplierSchema = {
49
- type: "number",
50
- min: 0,
51
- step: 0.01,
52
- default: DEFAULT_AMOUNT,
53
- description: "Amount",
54
- hiddenFromList: false
55
- };
56
- var brightnessAmountSchema = {
57
- type: "number",
58
- min: -1,
59
- max: 1,
60
- step: 0.01,
61
- default: DEFAULT_BRIGHTNESS_AMOUNT,
62
- description: "Amount",
63
- hiddenFromList: false
64
- };
65
- var hueDegreesSchema = {
66
- type: "rotation-degrees",
67
- step: 1,
68
- default: DEFAULT_HUE_DEGREES,
69
- description: "Degrees"
70
- };
71
- var assertOptionalFiniteNumber = (value, name) => {
72
- if (value === undefined) {
73
- return;
74
- }
75
- assertRequiredFiniteNumber(value, name);
76
- };
77
- var validateUnitInterval = (value, name) => {
78
- if (value < 0) {
79
- throw new TypeError(`"${name}" must be >= 0, but got ${JSON.stringify(value)}`);
80
- }
81
- if (value > 1) {
82
- throw new TypeError(`"${name}" must be <= 1, but got ${JSON.stringify(value)}`);
83
- }
84
- };
85
- var validateNonNegative = (value, name) => {
86
- if (value < 0) {
87
- throw new TypeError(`"${name}" must be >= 0, but got ${JSON.stringify(value)}`);
88
- }
89
- };
90
- var validateSignedUnitInterval = (value, name) => {
91
- if (value < -1) {
92
- throw new TypeError(`"${name}" must be >= -1, but got ${JSON.stringify(value)}`);
93
- }
94
- if (value > 1) {
95
- throw new TypeError(`"${name}" must be <= 1, but got ${JSON.stringify(value)}`);
96
- }
97
- };
98
- var clampColorChannel = (value) => {
99
- return Math.max(0, Math.min(255, value));
100
- };
101
- var parseColorRgba = (ctx, color) => {
102
- ctx.clearRect(0, 0, 1, 1);
103
- ctx.fillStyle = color;
104
- ctx.fillRect(0, 0, 1, 1);
105
- const { data } = ctx.getImageData(0, 0, 1, 1);
106
- return [data[0], data[1], data[2], data[3]];
107
- };
108
-
109
- // src/uv-coordinate.ts
110
- var publicUvToShaderUv = (uv) => {
111
- return [uv[0], 1 - uv[1]];
112
- };
113
-
114
- // src/page-turn.ts
115
- var { createEffect, createWebGL2ContextError } = Internals;
116
- var DEFAULT_PROGRESS = 0.5;
117
- var DEFAULT_FOLD_POSITION = [1, 1];
118
- var DEFAULT_ANGLE = 225;
119
- var DEFAULT_FOLD_RADIUS = 0.18;
120
- var DEFAULT_LIGHT_DIRECTION = 60;
121
- var DEFAULT_SHADOW = 0.45;
122
- var DEFAULT_BACK_OPACITY = 0.85;
123
- var DEFAULT_PAPER_COLOR = "#fff8dc";
124
- var pageTurnSchema = {
125
- progress: {
126
- type: "number",
127
- min: 0,
128
- max: 1,
129
- step: 0.01,
130
- default: DEFAULT_PROGRESS,
131
- description: "Progress",
132
- hiddenFromList: false
133
- },
134
- foldPosition: {
135
- type: "uv-coordinate",
136
- step: 0.01,
137
- default: DEFAULT_FOLD_POSITION,
138
- description: "Fold position"
139
- },
140
- angle: {
141
- type: "rotation-degrees",
142
- step: 1,
143
- default: DEFAULT_ANGLE,
144
- description: "Angle"
145
- },
146
- foldRadius: {
147
- type: "number",
148
- min: 0.02,
149
- max: 0.5,
150
- step: 0.01,
151
- default: DEFAULT_FOLD_RADIUS,
152
- description: "Fold radius",
153
- hiddenFromList: false
154
- },
155
- lightDirection: {
156
- type: "rotation-degrees",
157
- step: 1,
158
- default: DEFAULT_LIGHT_DIRECTION,
159
- description: "Light direction"
160
- },
161
- shadow: {
162
- type: "number",
163
- min: 0,
164
- max: 1,
165
- step: 0.01,
166
- default: DEFAULT_SHADOW,
167
- description: "Shadow",
168
- hiddenFromList: false
169
- },
170
- backOpacity: {
171
- type: "number",
172
- min: 0,
173
- max: 1,
174
- step: 0.01,
175
- default: DEFAULT_BACK_OPACITY,
176
- description: "Back opacity",
177
- hiddenFromList: false
178
- },
179
- paperColor: {
180
- type: "color",
181
- default: DEFAULT_PAPER_COLOR,
182
- description: "Paper color"
183
- }
184
- };
185
- var resolve = (p) => ({
186
- progress: p.progress ?? DEFAULT_PROGRESS,
187
- foldPosition: [
188
- ...p.foldPosition ?? DEFAULT_FOLD_POSITION
189
- ],
190
- angle: p.angle ?? DEFAULT_ANGLE,
191
- foldRadius: p.foldRadius ?? DEFAULT_FOLD_RADIUS,
192
- lightDirection: p.lightDirection ?? DEFAULT_LIGHT_DIRECTION,
193
- shadow: p.shadow ?? DEFAULT_SHADOW,
194
- backOpacity: p.backOpacity ?? DEFAULT_BACK_OPACITY,
195
- paperColor: p.paperColor ?? DEFAULT_PAPER_COLOR
196
- });
197
- var assertOptionalUvCoordinate = (value, name) => {
198
- if (value === undefined) {
199
- return;
200
- }
201
- if (!Array.isArray(value) || value.length !== 2 || value.some((item) => typeof item !== "number" || !Number.isFinite(item))) {
202
- throw new TypeError(`"${name}" must be a [number, number] tuple`);
203
- }
204
- };
205
- var validatePageTurnParams = (params) => {
206
- assertEffectParamsObject(params, "Page turn");
207
- assertOptionalFiniteNumber(params.progress, "progress");
208
- assertOptionalUvCoordinate(params.foldPosition, "foldPosition");
209
- assertOptionalFiniteNumber(params.angle, "angle");
210
- assertOptionalFiniteNumber(params.foldRadius, "foldRadius");
211
- assertOptionalFiniteNumber(params.lightDirection, "lightDirection");
212
- assertOptionalFiniteNumber(params.shadow, "shadow");
213
- assertOptionalFiniteNumber(params.backOpacity, "backOpacity");
214
- assertOptionalColor(params.paperColor, "paperColor");
215
- validateUnitInterval(params.progress ?? DEFAULT_PROGRESS, "progress");
216
- validateUnitInterval(params.shadow ?? DEFAULT_SHADOW, "shadow");
217
- validateUnitInterval(params.backOpacity ?? DEFAULT_BACK_OPACITY, "backOpacity");
218
- const foldRadius = params.foldRadius ?? DEFAULT_FOLD_RADIUS;
219
- if (foldRadius < 0.02) {
220
- throw new TypeError(`"foldRadius" must be >= 0.02, but got ${JSON.stringify(foldRadius)}`);
221
- }
222
- if (foldRadius > 0.5) {
223
- throw new TypeError(`"foldRadius" must be <= 0.5, but got ${JSON.stringify(foldRadius)}`);
224
- }
225
- };
226
- var PAGE_TURN_VS = `#version 300 es
227
- in vec2 aPos;
228
- in vec2 aUv;
229
- out vec2 vUv;
230
-
231
- void main() {
232
- vUv = aUv;
233
- gl_Position = vec4(aPos, 0.0, 1.0);
234
- }
235
- `;
236
- var PAGE_TURN_FS = `#version 300 es
237
- precision highp float;
238
-
239
- in vec2 vUv;
240
- out vec4 fragColor;
241
-
242
- uniform sampler2D uSource;
243
- uniform float uProgress;
244
- uniform vec2 uFoldPosition;
245
- uniform vec2 uDirectionVector;
246
- uniform vec2 uLightVector;
247
- uniform float uFoldRadius;
248
- uniform float uShadow;
249
- uniform float uBackOpacity;
250
- uniform vec4 uPaperColor;
251
-
252
- vec2 rangeFromPosition(vec2 origin, vec2 v) {
253
- float a0 = dot(vec2(0.0, 0.0) - origin, v);
254
- float a1 = dot(vec2(1.0, 0.0) - origin, v);
255
- float a2 = dot(vec2(0.0, 1.0) - origin, v);
256
- float a3 = dot(vec2(1.0, 1.0) - origin, v);
257
- return vec2(
258
- min(min(a0, a1), min(a2, a3)),
259
- max(max(a0, a1), max(a2, a3))
260
- );
261
- }
262
-
263
- float toTurnAxis(vec2 uv) {
264
- vec2 dir = normalize(uDirectionVector);
265
- vec2 range = rangeFromPosition(uFoldPosition, dir);
266
- float rawAxis = dot(uv - uFoldPosition, dir);
267
- return rawAxis / max(range.y, 0.0001);
268
- }
269
-
270
- vec2 fromTurnAxis(float axis, float cross, vec2 dir, float axisSpan) {
271
- vec2 perpendicular = vec2(-dir.y, dir.x);
272
- return uFoldPosition + dir * (axis * axisSpan) + perpendicular * cross;
273
- }
274
-
275
- float toCrossAxis(vec2 uv, vec2 dir) {
276
- vec2 perpendicular = vec2(-dir.y, dir.x);
277
- return dot(uv - uFoldPosition, perpendicular);
278
- }
279
-
280
- void main() {
281
- vec2 dir = normalize(uDirectionVector);
282
- vec2 perpendicular = vec2(-dir.y, dir.x);
283
- vec2 axisRange = rangeFromPosition(uFoldPosition, dir);
284
- vec2 crossRange = rangeFromPosition(uFoldPosition, perpendicular);
285
- float axisSpan = max(axisRange.y, 0.0001);
286
- float axis = toTurnAxis(vUv);
287
- float cross = toCrossAxis(vUv, dir);
288
- float progress = clamp(uProgress, 0.0, 1.0);
289
- float radius = clamp(uFoldRadius, 0.02, 0.5);
290
- float start = progress - radius;
291
- float end = progress;
292
-
293
- if (axis >= end) {
294
- fragColor = texture(uSource, vUv);
295
- return;
296
- }
297
-
298
- if (axis <= start) {
299
- fragColor = vec4(0.0);
300
- return;
301
- }
302
-
303
- float t = clamp((axis - start) / max(radius, 0.0001), 0.0, 1.0);
304
- float crossNormalized = (cross - crossRange.x) / max(crossRange.y - crossRange.x, 0.0001);
305
- float sourceAxis = mix(max(start, 0.0), end, t);
306
- vec2 sampleUv = fromTurnAxis(sourceAxis, cross, dir, axisSpan);
307
-
308
- if (
309
- sampleUv.x < 0.0 || sampleUv.x > 1.0 ||
310
- sampleUv.y < 0.0 || sampleUv.y > 1.0
311
- ) {
312
- fragColor = vec4(0.0);
313
- return;
314
- }
315
-
316
- vec4 color = texture(uSource, sampleUv);
317
- float crease = smoothstep(0.72, 1.0, t);
318
- float freeEdge = 1.0 - smoothstep(0.0, 0.28, t);
319
- float backMix = 1.0 - smoothstep(0.22, 0.95, t);
320
- float lightAgainstFold = dot(normalize(uLightVector), normalize(dir + perpendicular * 0.24));
321
- float diffuse = 0.88 + 0.12 * lightAgainstFold;
322
- float shade = diffuse - uShadow * (0.36 * crease + 0.16 * freeEdge);
323
- float sheen = pow(max(0.0, 1.0 - abs(t - 0.44) / 0.13), 2.0);
324
- float glossyStreak = pow(max(0.0, 1.0 - abs(crossNormalized - (0.36 + t * 0.16)) / 0.09), 2.0);
325
- float edgeFade = smoothstep(0.0, 0.08, t);
326
-
327
- vec3 rgb = color.a > 0.001 ? color.rgb / color.a : vec3(0.0);
328
- vec3 paperRgb = uPaperColor.rgb;
329
- vec3 backRgb = mix(paperRgb, rgb, 0.08 + 0.06 * t);
330
- vec3 shaded = mix(rgb, backRgb, backMix) * shade;
331
- shaded += vec3(1.0) * sheen * glossyStreak * (0.18 + 0.18 * backMix);
332
- shaded += paperRgb * crease * 0.08;
333
- float alpha = color.a * mix(1.0, uBackOpacity, backMix) * edgeFade;
334
-
335
- fragColor = vec4(shaded * alpha, alpha);
336
- }
337
- `;
338
- var compileShader = (gl, type, source) => {
339
- const shader = gl.createShader(type);
340
- if (!shader) {
341
- throw new Error("Failed to create WebGL shader");
342
- }
343
- gl.shaderSource(shader, source);
344
- gl.compileShader(shader);
345
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
346
- const log = gl.getShaderInfoLog(shader);
347
- gl.deleteShader(shader);
348
- throw new Error(`Page turn shader compile failed: ${log ?? "(no log)"}`);
349
- }
350
- return shader;
351
- };
352
- var linkProgram = (gl, vs, fs) => {
353
- const program = gl.createProgram();
354
- if (!program) {
355
- throw new Error("Failed to create WebGL program");
356
- }
357
- gl.attachShader(program, vs);
358
- gl.attachShader(program, fs);
359
- gl.linkProgram(program);
360
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
361
- const log = gl.getProgramInfoLog(program);
362
- gl.deleteProgram(program);
363
- throw new Error(`Page turn program link failed: ${log ?? "(no log)"}`);
364
- }
365
- return program;
366
- };
367
- var pageTurn = createEffect({
368
- type: "remotion/page-turn",
369
- label: "pageTurn()",
370
- documentationLink: "https://www.remotion.dev/docs/effects/page-turn",
371
- backend: "webgl2",
372
- calculateKey: (params) => {
373
- const r = resolve(params);
374
- return `page-turn-${r.progress}-${r.foldPosition.join(":")}-${r.angle}-${r.foldRadius}-${r.lightDirection}-${r.shadow}-${r.backOpacity}-${r.paperColor}`;
375
- },
376
- setup: (target) => {
377
- const gl = target.getContext("webgl2", {
378
- premultipliedAlpha: true,
379
- alpha: true,
380
- preserveDrawingBuffer: true
381
- });
382
- if (!gl) {
383
- throw createWebGL2ContextError("page turn effect");
384
- }
385
- gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
386
- const vs = compileShader(gl, gl.VERTEX_SHADER, PAGE_TURN_VS);
387
- const fs = compileShader(gl, gl.FRAGMENT_SHADER, PAGE_TURN_FS);
388
- const program = linkProgram(gl, vs, fs);
389
- gl.deleteShader(vs);
390
- gl.deleteShader(fs);
391
- const vao = gl.createVertexArray();
392
- if (!vao) {
393
- throw new Error("Failed to create WebGL vertex array");
394
- }
395
- gl.bindVertexArray(vao);
396
- const data = new Float32Array([
397
- -1,
398
- -1,
399
- 0,
400
- 0,
401
- 1,
402
- -1,
403
- 1,
404
- 0,
405
- -1,
406
- 1,
407
- 0,
408
- 1,
409
- 1,
410
- 1,
411
- 1,
412
- 1
413
- ]);
414
- const vbo = gl.createBuffer();
415
- if (!vbo) {
416
- throw new Error("Failed to create WebGL buffer");
417
- }
418
- gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
419
- gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
420
- const aPos = gl.getAttribLocation(program, "aPos");
421
- const aUv = gl.getAttribLocation(program, "aUv");
422
- gl.enableVertexAttribArray(aPos);
423
- gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
424
- gl.enableVertexAttribArray(aUv);
425
- gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 16, 8);
426
- gl.bindVertexArray(null);
427
- const texture = gl.createTexture();
428
- if (!texture) {
429
- throw new Error("Failed to create WebGL texture");
430
- }
431
- gl.bindTexture(gl.TEXTURE_2D, texture);
432
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
433
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
434
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
435
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
436
- gl.bindTexture(gl.TEXTURE_2D, null);
437
- const colorCanvas = document.createElement("canvas");
438
- colorCanvas.width = 1;
439
- colorCanvas.height = 1;
440
- const colorCtx = colorCanvas.getContext("2d", { willReadFrequently: true });
441
- if (!colorCtx) {
442
- throw new Error("Failed to acquire 2D context for color parsing");
443
- }
444
- return {
445
- gl,
446
- program,
447
- vao,
448
- vbo,
449
- texture,
450
- uSource: gl.getUniformLocation(program, "uSource"),
451
- uProgress: gl.getUniformLocation(program, "uProgress"),
452
- uFoldPosition: gl.getUniformLocation(program, "uFoldPosition"),
453
- uDirectionVector: gl.getUniformLocation(program, "uDirectionVector"),
454
- uLightVector: gl.getUniformLocation(program, "uLightVector"),
455
- uFoldRadius: gl.getUniformLocation(program, "uFoldRadius"),
456
- uShadow: gl.getUniformLocation(program, "uShadow"),
457
- uBackOpacity: gl.getUniformLocation(program, "uBackOpacity"),
458
- uPaperColor: gl.getUniformLocation(program, "uPaperColor"),
459
- colorCtx,
460
- cachedPaperColor: "",
461
- cachedPaperColorRgba: [255, 248, 220, 255]
462
- };
463
- },
464
- apply: ({ source, width, height, params, state, flipSourceY }) => {
465
- const r = resolve(params);
466
- const { gl, program, texture, vao } = state;
467
- if (state.cachedPaperColor !== r.paperColor) {
468
- state.cachedPaperColor = r.paperColor;
469
- state.cachedPaperColorRgba = parseColorRgba(state.colorCtx, r.paperColor);
470
- }
471
- const [paperR, paperG, paperB, paperA] = state.cachedPaperColorRgba;
472
- const [foldX, foldY] = publicUvToShaderUv(r.foldPosition);
473
- gl.viewport(0, 0, width, height);
474
- gl.clearColor(0, 0, 0, 0);
475
- gl.clear(gl.COLOR_BUFFER_BIT);
476
- gl.useProgram(program);
477
- gl.bindVertexArray(vao);
478
- gl.activeTexture(gl.TEXTURE0);
479
- gl.bindTexture(gl.TEXTURE_2D, texture);
480
- gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
481
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
482
- if (state.uSource)
483
- gl.uniform1i(state.uSource, 0);
484
- if (state.uProgress)
485
- gl.uniform1f(state.uProgress, r.progress);
486
- if (state.uFoldPosition)
487
- gl.uniform2f(state.uFoldPosition, foldX, foldY);
488
- if (state.uDirectionVector) {
489
- const radians = r.angle * Math.PI / 180;
490
- gl.uniform2f(state.uDirectionVector, Math.cos(radians), -Math.sin(radians));
491
- }
492
- if (state.uLightVector) {
493
- const radians = r.lightDirection * Math.PI / 180;
494
- gl.uniform2f(state.uLightVector, Math.cos(radians), -Math.sin(radians));
495
- }
496
- if (state.uFoldRadius)
497
- gl.uniform1f(state.uFoldRadius, r.foldRadius);
498
- if (state.uShadow)
499
- gl.uniform1f(state.uShadow, r.shadow);
500
- if (state.uBackOpacity)
501
- gl.uniform1f(state.uBackOpacity, r.backOpacity);
502
- if (state.uPaperColor)
503
- gl.uniform4f(state.uPaperColor, paperR / 255, paperG / 255, paperB / 255, paperA / 255);
504
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
505
- gl.bindVertexArray(null);
506
- gl.bindTexture(gl.TEXTURE_2D, null);
507
- gl.useProgram(null);
508
- },
509
- cleanup: ({ gl, program, vao, vbo, texture }) => {
510
- gl.deleteBuffer(vbo);
511
- gl.deleteProgram(program);
512
- gl.deleteVertexArray(vao);
513
- gl.deleteTexture(texture);
514
- },
515
- schema: pageTurnSchema,
516
- validateParams: validatePageTurnParams
517
- });
518
- export {
519
- pageTurnSchema,
520
- pageTurn
521
- };
@@ -1,91 +0,0 @@
1
- export declare const pageTurnSchema: {
2
- readonly progress: {
3
- readonly type: "number";
4
- readonly min: 0;
5
- readonly max: 1;
6
- readonly step: 0.01;
7
- readonly default: 0.5;
8
- readonly description: "Progress";
9
- readonly hiddenFromList: false;
10
- };
11
- readonly foldPosition: {
12
- readonly type: "uv-coordinate";
13
- readonly step: 0.01;
14
- readonly default: readonly [1, 1];
15
- readonly description: "Fold position";
16
- };
17
- readonly angle: {
18
- readonly type: "rotation-degrees";
19
- readonly step: 1;
20
- readonly default: 225;
21
- readonly description: "Angle";
22
- };
23
- readonly foldRadius: {
24
- readonly type: "number";
25
- readonly min: 0.02;
26
- readonly max: 0.5;
27
- readonly step: 0.01;
28
- readonly default: 0.18;
29
- readonly description: "Fold radius";
30
- readonly hiddenFromList: false;
31
- };
32
- readonly lightDirection: {
33
- readonly type: "rotation-degrees";
34
- readonly step: 1;
35
- readonly default: 60;
36
- readonly description: "Light direction";
37
- };
38
- readonly shadow: {
39
- readonly type: "number";
40
- readonly min: 0;
41
- readonly max: 1;
42
- readonly step: 0.01;
43
- readonly default: 0.45;
44
- readonly description: "Shadow";
45
- readonly hiddenFromList: false;
46
- };
47
- readonly backOpacity: {
48
- readonly type: "number";
49
- readonly min: 0;
50
- readonly max: 1;
51
- readonly step: 0.01;
52
- readonly default: 0.85;
53
- readonly description: "Back opacity";
54
- readonly hiddenFromList: false;
55
- };
56
- readonly paperColor: {
57
- readonly type: "color";
58
- readonly default: "#fff8dc";
59
- readonly description: "Paper color";
60
- };
61
- };
62
- export type PageTurnFoldPosition = readonly [number, number];
63
- export type PageTurnParams = {
64
- /**
65
- * Turn amount from 0 (flat source) to 1 (fully turned away).
66
- * Defaults to `0.5`.
67
- */
68
- readonly progress?: number;
69
- /**
70
- * Point where the page starts folding, in UV coordinates.
71
- * Defaults to `[1, 1]`.
72
- */
73
- readonly foldPosition?: PageTurnFoldPosition;
74
- /** Direction of the turn in degrees. Defaults to `225`. */
75
- readonly angle?: number;
76
- /**
77
- * Width of the curled band as a fraction of the canvas. Defaults to `0.18`.
78
- */
79
- readonly foldRadius?: number;
80
- /** Direction of the light in degrees. Defaults to `60`. */
81
- readonly lightDirection?: number;
82
- /** Strength of the crease and back-face shading. Defaults to `0.45`. */
83
- readonly shadow?: number;
84
- /** Opacity of the dimmed back face of the page. Defaults to `0.85`. */
85
- readonly backOpacity?: number;
86
- /** Color of the paper back side. Defaults to `#fff8dc`. */
87
- readonly paperColor?: string;
88
- };
89
- export declare const pageTurn: (params?: (PageTurnParams & {
90
- readonly disabled?: boolean | undefined;
91
- }) | undefined) => import("remotion").EffectDescriptor<unknown>;