@remotion/effects 4.0.524 → 4.0.526

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,501 @@
1
+ // src/lut/index.ts
2
+ import { Internals as Internals2 } from "remotion";
3
+
4
+ // src/lut/lut-runtime.ts
5
+ import { Internals } from "remotion";
6
+
7
+ // src/lut/lut-shaders.ts
8
+ var LUT_VERTEX_SHADER = `#version 300 es
9
+ layout(location = 0) in vec2 aPos;
10
+ layout(location = 1) in vec2 aUv;
11
+ out vec2 vUv;
12
+
13
+ void main() {
14
+ vUv = aUv;
15
+ gl_Position = vec4(aPos, 0.0, 1.0);
16
+ }
17
+ `;
18
+ var LUT_FRAGMENT_SHADER = `#version 300 es
19
+ precision highp float;
20
+ precision highp sampler3D;
21
+
22
+ in vec2 vUv;
23
+ out vec4 fragColor;
24
+
25
+ uniform sampler2D uSource;
26
+ uniform sampler3D uLut;
27
+ uniform int uLutSize;
28
+ uniform vec3 uDomainMin;
29
+ uniform vec3 uDomainMax;
30
+
31
+ vec3 sampleLut(vec3 color) {
32
+ vec3 normalizedColor = clamp(
33
+ (color - uDomainMin) / (uDomainMax - uDomainMin),
34
+ vec3(0.0),
35
+ vec3(1.0)
36
+ );
37
+ vec3 position = normalizedColor * float(uLutSize - 1);
38
+ ivec3 lower = ivec3(floor(position));
39
+ ivec3 upper = min(lower + ivec3(1), ivec3(uLutSize - 1));
40
+ vec3 fraction = fract(position);
41
+
42
+ vec3 c000 = texelFetch(uLut, ivec3(lower.x, lower.y, lower.z), 0).rgb;
43
+ vec3 c100 = texelFetch(uLut, ivec3(upper.x, lower.y, lower.z), 0).rgb;
44
+ vec3 c010 = texelFetch(uLut, ivec3(lower.x, upper.y, lower.z), 0).rgb;
45
+ vec3 c110 = texelFetch(uLut, ivec3(upper.x, upper.y, lower.z), 0).rgb;
46
+ vec3 c001 = texelFetch(uLut, ivec3(lower.x, lower.y, upper.z), 0).rgb;
47
+ vec3 c101 = texelFetch(uLut, ivec3(upper.x, lower.y, upper.z), 0).rgb;
48
+ vec3 c011 = texelFetch(uLut, ivec3(lower.x, upper.y, upper.z), 0).rgb;
49
+ vec3 c111 = texelFetch(uLut, ivec3(upper.x, upper.y, upper.z), 0).rgb;
50
+
51
+ vec3 c00 = mix(c000, c100, fraction.x);
52
+ vec3 c10 = mix(c010, c110, fraction.x);
53
+ vec3 c01 = mix(c001, c101, fraction.x);
54
+ vec3 c11 = mix(c011, c111, fraction.x);
55
+ vec3 c0 = mix(c00, c10, fraction.y);
56
+ vec3 c1 = mix(c01, c11, fraction.y);
57
+ return mix(c0, c1, fraction.z);
58
+ }
59
+
60
+ void main() {
61
+ vec4 sourceColor = texture(uSource, vUv);
62
+ float alpha = sourceColor.a;
63
+
64
+ if (alpha <= 0.0) {
65
+ fragColor = vec4(0.0);
66
+ return;
67
+ }
68
+
69
+ vec3 straightColor = sourceColor.rgb / alpha;
70
+ vec3 transformedColor = sampleLut(straightColor);
71
+ fragColor = vec4(transformedColor * alpha, alpha);
72
+ }
73
+ `;
74
+
75
+ // src/lut/lut-runtime.ts
76
+ var { createWebGL2ContextError } = Internals;
77
+ var compileShader = (gl, type, source) => {
78
+ const shader = gl.createShader(type);
79
+ if (!shader) {
80
+ throw new Error("Failed to create LUT shader");
81
+ }
82
+ gl.shaderSource(shader, source);
83
+ gl.compileShader(shader);
84
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
85
+ const log = gl.getShaderInfoLog(shader);
86
+ gl.deleteShader(shader);
87
+ throw new Error(`LUT shader compile failed: ${log ?? "(no log)"}`);
88
+ }
89
+ return shader;
90
+ };
91
+ var createProgram = (gl) => {
92
+ const vertexShader = compileShader(gl, gl.VERTEX_SHADER, LUT_VERTEX_SHADER);
93
+ const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, LUT_FRAGMENT_SHADER);
94
+ const program = gl.createProgram();
95
+ if (!program) {
96
+ throw new Error("Failed to create LUT shader program");
97
+ }
98
+ gl.attachShader(program, vertexShader);
99
+ gl.attachShader(program, fragmentShader);
100
+ gl.linkProgram(program);
101
+ gl.deleteShader(vertexShader);
102
+ gl.deleteShader(fragmentShader);
103
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
104
+ const log = gl.getProgramInfoLog(program);
105
+ gl.deleteProgram(program);
106
+ throw new Error(`LUT shader link failed: ${log ?? "(no log)"}`);
107
+ }
108
+ return program;
109
+ };
110
+ var createSourceTexture = (gl) => {
111
+ const texture = gl.createTexture();
112
+ if (!texture) {
113
+ throw new Error("Failed to create LUT source texture");
114
+ }
115
+ gl.bindTexture(gl.TEXTURE_2D, texture);
116
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
117
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
118
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
119
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
120
+ gl.bindTexture(gl.TEXTURE_2D, null);
121
+ return texture;
122
+ };
123
+ var createLutTexture = (gl) => {
124
+ const texture = gl.createTexture();
125
+ if (!texture) {
126
+ throw new Error("Failed to create LUT texture");
127
+ }
128
+ gl.bindTexture(gl.TEXTURE_3D, texture);
129
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
130
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
131
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
132
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
133
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
134
+ gl.bindTexture(gl.TEXTURE_3D, null);
135
+ return texture;
136
+ };
137
+ var setupLut = (target) => {
138
+ const gl = target.getContext("webgl2", {
139
+ premultipliedAlpha: true,
140
+ alpha: true,
141
+ preserveDrawingBuffer: true
142
+ });
143
+ if (!gl) {
144
+ throw createWebGL2ContextError("LUT effect");
145
+ }
146
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
147
+ const program = createProgram(gl);
148
+ const vao = gl.createVertexArray();
149
+ if (!vao) {
150
+ throw new Error("Failed to create LUT vertex array");
151
+ }
152
+ const vbo = gl.createBuffer();
153
+ if (!vbo) {
154
+ throw new Error("Failed to create LUT vertex buffer");
155
+ }
156
+ gl.bindVertexArray(vao);
157
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
158
+ 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);
159
+ gl.enableVertexAttribArray(0);
160
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
161
+ gl.enableVertexAttribArray(1);
162
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);
163
+ gl.bindVertexArray(null);
164
+ return {
165
+ gl,
166
+ program,
167
+ vao,
168
+ vbo,
169
+ sourceTexture: createSourceTexture(gl),
170
+ lutTexture: createLutTexture(gl),
171
+ uniforms: {
172
+ uSource: gl.getUniformLocation(program, "uSource"),
173
+ uLut: gl.getUniformLocation(program, "uLut"),
174
+ uLutSize: gl.getUniformLocation(program, "uLutSize"),
175
+ uDomainMin: gl.getUniformLocation(program, "uDomainMin"),
176
+ uDomainMax: gl.getUniformLocation(program, "uDomainMax")
177
+ },
178
+ cachedContent: null
179
+ };
180
+ };
181
+ var applyLut = ({
182
+ state,
183
+ source,
184
+ width,
185
+ height,
186
+ content,
187
+ parsed,
188
+ flipSourceY
189
+ }) => {
190
+ const { gl, program, sourceTexture, lutTexture, uniforms, vao } = state;
191
+ if (state.cachedContent !== content) {
192
+ const maxSize = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE);
193
+ if (parsed.size > maxSize) {
194
+ throw new Error(`LUT_3D_SIZE ${parsed.size} exceeds this browser's maximum 3D texture size of ${maxSize}`);
195
+ }
196
+ gl.activeTexture(gl.TEXTURE1);
197
+ gl.bindTexture(gl.TEXTURE_3D, lutTexture);
198
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
199
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
200
+ gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA16F, parsed.size, parsed.size, parsed.size, 0, gl.RGBA, gl.FLOAT, parsed.data);
201
+ const uploadError = gl.getError();
202
+ if (uploadError !== gl.NO_ERROR) {
203
+ throw new Error(`Failed to upload the 3D LUT texture (WebGL error ${uploadError})`);
204
+ }
205
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
206
+ state.cachedContent = content;
207
+ }
208
+ gl.viewport(0, 0, width, height);
209
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
210
+ gl.clearColor(0, 0, 0, 0);
211
+ gl.clear(gl.COLOR_BUFFER_BIT);
212
+ gl.activeTexture(gl.TEXTURE0);
213
+ gl.bindTexture(gl.TEXTURE_2D, sourceTexture);
214
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
215
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
216
+ gl.activeTexture(gl.TEXTURE1);
217
+ gl.bindTexture(gl.TEXTURE_3D, lutTexture);
218
+ gl.useProgram(program);
219
+ if (uniforms.uSource)
220
+ gl.uniform1i(uniforms.uSource, 0);
221
+ if (uniforms.uLut)
222
+ gl.uniform1i(uniforms.uLut, 1);
223
+ if (uniforms.uLutSize)
224
+ gl.uniform1i(uniforms.uLutSize, parsed.size);
225
+ if (uniforms.uDomainMin) {
226
+ gl.uniform3f(uniforms.uDomainMin, ...parsed.domainMin);
227
+ }
228
+ if (uniforms.uDomainMax) {
229
+ gl.uniform3f(uniforms.uDomainMax, ...parsed.domainMax);
230
+ }
231
+ gl.bindVertexArray(vao);
232
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
233
+ gl.bindVertexArray(null);
234
+ gl.bindTexture(gl.TEXTURE_3D, null);
235
+ gl.activeTexture(gl.TEXTURE0);
236
+ gl.bindTexture(gl.TEXTURE_2D, null);
237
+ gl.useProgram(null);
238
+ };
239
+ var cleanupLut = ({
240
+ gl,
241
+ program,
242
+ vao,
243
+ vbo,
244
+ sourceTexture,
245
+ lutTexture
246
+ }) => {
247
+ gl.deleteTexture(sourceTexture);
248
+ gl.deleteTexture(lutTexture);
249
+ gl.deleteBuffer(vbo);
250
+ gl.deleteProgram(program);
251
+ gl.deleteVertexArray(vao);
252
+ };
253
+
254
+ // src/lut/parse-cube-lut.ts
255
+ var stripComment = (line) => {
256
+ let insideQuotes = false;
257
+ for (let i = 0;i < line.length; i++) {
258
+ const character = line[i];
259
+ if (character === '"') {
260
+ insideQuotes = !insideQuotes;
261
+ } else if (character === "#" && !insideQuotes) {
262
+ return line.slice(0, i);
263
+ }
264
+ }
265
+ return line;
266
+ };
267
+ var parseTriplet = ({
268
+ values,
269
+ lineNumber,
270
+ label
271
+ }) => {
272
+ if (values.length !== 3) {
273
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: ${label} must contain exactly 3 numbers`);
274
+ }
275
+ const parsed = values.map((value) => Number(value));
276
+ if (parsed.some((value) => !Number.isFinite(value))) {
277
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: ${label} must contain only finite numbers`);
278
+ }
279
+ return [parsed[0], parsed[1], parsed[2]];
280
+ };
281
+ var HEADER_DIRECTIVES = new Set([
282
+ "TITLE",
283
+ "LUT_3D_SIZE",
284
+ "DOMAIN_MIN",
285
+ "DOMAIN_MAX"
286
+ ]);
287
+ var parseCubeLut = (content) => {
288
+ const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
289
+ let size = null;
290
+ let domainMin = [0, 0, 0];
291
+ let domainMax = [1, 1, 1];
292
+ let hasDomainMin = false;
293
+ let hasDomainMax = false;
294
+ let hasTitle = false;
295
+ let dataRows = 0;
296
+ let dataStarted = false;
297
+ for (let index = 0;index < lines.length; index++) {
298
+ const lineNumber = index + 1;
299
+ const line = stripComment(lines[index]).trim();
300
+ if (line.length === 0) {
301
+ continue;
302
+ }
303
+ const [keyword, ...values] = line.split(/\s+/);
304
+ if (keyword === "LUT_1D_SIZE") {
305
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: 1D LUTs are not supported`);
306
+ }
307
+ if (HEADER_DIRECTIVES.has(keyword)) {
308
+ if (dataStarted) {
309
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: ${keyword} must appear before the color data`);
310
+ }
311
+ if (keyword === "TITLE") {
312
+ if (hasTitle) {
313
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: TITLE may only be declared once`);
314
+ }
315
+ hasTitle = true;
316
+ continue;
317
+ }
318
+ if (keyword === "LUT_3D_SIZE") {
319
+ if (size !== null) {
320
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: LUT_3D_SIZE may only be declared once`);
321
+ }
322
+ if (values.length !== 1) {
323
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: LUT_3D_SIZE must contain exactly one integer`);
324
+ }
325
+ const parsedSize = Number(values[0]);
326
+ if (!Number.isInteger(parsedSize) || parsedSize < 2 || parsedSize > 256) {
327
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: LUT_3D_SIZE must be an integer from 2 to 256`);
328
+ }
329
+ size = parsedSize;
330
+ continue;
331
+ }
332
+ if (keyword === "DOMAIN_MIN") {
333
+ if (hasDomainMin) {
334
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: DOMAIN_MIN may only be declared once`);
335
+ }
336
+ domainMin = parseTriplet({
337
+ values,
338
+ lineNumber,
339
+ label: "DOMAIN_MIN"
340
+ });
341
+ hasDomainMin = true;
342
+ continue;
343
+ }
344
+ if (hasDomainMax) {
345
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: DOMAIN_MAX may only be declared once`);
346
+ }
347
+ domainMax = parseTriplet({
348
+ values,
349
+ lineNumber,
350
+ label: "DOMAIN_MAX"
351
+ });
352
+ hasDomainMax = true;
353
+ continue;
354
+ }
355
+ if (/^[A-Z_]/i.test(keyword)) {
356
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: unsupported directive "${keyword}"`);
357
+ }
358
+ if (size === null) {
359
+ throw new TypeError(`Invalid LUT content on line ${lineNumber}: LUT_3D_SIZE must be declared before the color data`);
360
+ }
361
+ parseTriplet({
362
+ values: [keyword, ...values],
363
+ lineNumber,
364
+ label: "color row"
365
+ });
366
+ dataStarted = true;
367
+ dataRows++;
368
+ const maxDataRows = size ** 3;
369
+ if (dataRows > maxDataRows) {
370
+ throw new TypeError(`Invalid LUT content: expected ${maxDataRows} color rows for LUT_3D_SIZE ${size}, but got more`);
371
+ }
372
+ }
373
+ if (size === null) {
374
+ throw new TypeError("Invalid LUT content: LUT_3D_SIZE is missing");
375
+ }
376
+ for (let channel = 0;channel < 3; channel++) {
377
+ if (domainMin[channel] >= domainMax[channel]) {
378
+ throw new TypeError(`Invalid LUT content: DOMAIN_MIN must be smaller than DOMAIN_MAX for every channel`);
379
+ }
380
+ }
381
+ const expectedRows = size ** 3;
382
+ if (dataRows !== expectedRows) {
383
+ throw new TypeError(`Invalid LUT content: expected ${expectedRows} color rows for LUT_3D_SIZE ${size}, but got ${dataRows}`);
384
+ }
385
+ const data = new Float32Array(expectedRows * 4);
386
+ let dataIndex = 0;
387
+ for (let index = 0;index < lines.length; index++) {
388
+ const line = stripComment(lines[index]).trim();
389
+ if (line.length === 0) {
390
+ continue;
391
+ }
392
+ const [keyword, ...values] = line.split(/\s+/);
393
+ if (HEADER_DIRECTIVES.has(keyword)) {
394
+ continue;
395
+ }
396
+ const [red, green, blue] = parseTriplet({
397
+ values: [keyword, ...values],
398
+ lineNumber: index + 1,
399
+ label: "color row"
400
+ });
401
+ const offset = dataIndex * 4;
402
+ data[offset] = red;
403
+ data[offset + 1] = green;
404
+ data[offset + 2] = blue;
405
+ data[offset + 3] = 1;
406
+ dataIndex++;
407
+ }
408
+ return {
409
+ size,
410
+ domainMin,
411
+ domainMax,
412
+ data
413
+ };
414
+ };
415
+
416
+ // src/lut/index.ts
417
+ var { createEffect } = Internals2;
418
+ var DEFAULT_LUT_CONTENT = `TITLE "Identity"
419
+ LUT_3D_SIZE 2
420
+
421
+ 0 0 0
422
+ 1 0 0
423
+ 0 1 0
424
+ 1 1 0
425
+ 0 0 1
426
+ 1 0 1
427
+ 0 1 1
428
+ 1 1 1`;
429
+ var lutSchema = {
430
+ content: {
431
+ type: "text-content",
432
+ default: DEFAULT_LUT_CONTENT,
433
+ description: "Cube LUT content",
434
+ keyframable: false
435
+ }
436
+ };
437
+ var MAX_CACHED_LUTS = 4;
438
+ var parsedLuts = new Map;
439
+ var getCachedLut = (content) => {
440
+ const cached = parsedLuts.get(content);
441
+ if (cached) {
442
+ parsedLuts.delete(content);
443
+ parsedLuts.set(content, cached);
444
+ return cached;
445
+ }
446
+ let firstHash = 2166136261;
447
+ let secondHash = 5381;
448
+ for (let i = 0;i < content.length; i++) {
449
+ const character = content.charCodeAt(i);
450
+ firstHash ^= character;
451
+ firstHash = Math.imul(firstHash, 16777619);
452
+ secondHash = Math.imul(secondHash, 33) ^ character;
453
+ }
454
+ const created = {
455
+ parsed: parseCubeLut(content),
456
+ effectKey: `lut-${content.length}-${firstHash >>> 0}-${secondHash >>> 0}`
457
+ };
458
+ parsedLuts.set(content, created);
459
+ if (parsedLuts.size > MAX_CACHED_LUTS) {
460
+ const oldestContent = parsedLuts.keys().next().value;
461
+ if (oldestContent !== undefined) {
462
+ parsedLuts.delete(oldestContent);
463
+ }
464
+ }
465
+ return created;
466
+ };
467
+ var validateLutParams = (params) => {
468
+ if (params === null || typeof params !== "object") {
469
+ throw new TypeError(`LUT effect requires a parameters object, but got ${JSON.stringify(params)}`);
470
+ }
471
+ if (typeof params.content !== "string" || params.content.trim().length === 0) {
472
+ throw new TypeError(`"content" must be a non-empty string, but got ${JSON.stringify(params.content)}`);
473
+ }
474
+ getCachedLut(params.content);
475
+ };
476
+ var lut = createEffect({
477
+ type: "remotion/lut",
478
+ label: "lut()",
479
+ documentationLink: "https://www.remotion.dev/docs/effects/lut",
480
+ backend: "webgl2",
481
+ calculateKey: (params) => getCachedLut(params.content).effectKey,
482
+ setup: setupLut,
483
+ apply: ({ source, width, height, params, state, flipSourceY }) => {
484
+ applyLut({
485
+ state,
486
+ source,
487
+ width,
488
+ height,
489
+ content: params.content,
490
+ parsed: getCachedLut(params.content).parsed,
491
+ flipSourceY
492
+ });
493
+ },
494
+ cleanup: cleanupLut,
495
+ schema: lutSchema,
496
+ validateParams: validateLutParams
497
+ });
498
+ export {
499
+ lutSchema,
500
+ lut
501
+ };
@@ -0,0 +1,15 @@
1
+ export declare const lutSchema: {
2
+ readonly content: {
3
+ readonly type: "text-content";
4
+ readonly default: "TITLE \"Identity\"\nLUT_3D_SIZE 2\n\n0 0 0\n1 0 0\n0 1 0\n1 1 0\n0 0 1\n1 0 1\n0 1 1\n1 1 1";
5
+ readonly description: "Cube LUT content";
6
+ readonly keyframable: false;
7
+ };
8
+ };
9
+ export type LutParams = {
10
+ /** The inline contents of a 3D `.cube` LUT file. */
11
+ readonly content: string;
12
+ };
13
+ export declare const lut: (params: LutParams & {
14
+ readonly disabled?: boolean | undefined;
15
+ }) => import("remotion").EffectDescriptor<unknown>;
@@ -0,0 +1,28 @@
1
+ import type { ParsedCubeLut } from './parse-cube-lut.js';
2
+ export type LutState = {
3
+ readonly gl: WebGL2RenderingContext;
4
+ readonly program: WebGLProgram;
5
+ readonly vao: WebGLVertexArrayObject;
6
+ readonly vbo: WebGLBuffer;
7
+ readonly sourceTexture: WebGLTexture;
8
+ readonly lutTexture: WebGLTexture;
9
+ readonly uniforms: {
10
+ readonly uSource: WebGLUniformLocation | null;
11
+ readonly uLut: WebGLUniformLocation | null;
12
+ readonly uLutSize: WebGLUniformLocation | null;
13
+ readonly uDomainMin: WebGLUniformLocation | null;
14
+ readonly uDomainMax: WebGLUniformLocation | null;
15
+ };
16
+ cachedContent: string | null;
17
+ };
18
+ export declare const setupLut: (target: HTMLCanvasElement) => LutState;
19
+ export declare const applyLut: ({ state, source, width, height, content, parsed, flipSourceY, }: {
20
+ readonly state: LutState;
21
+ readonly source: CanvasImageSource;
22
+ readonly width: number;
23
+ readonly height: number;
24
+ readonly content: string;
25
+ readonly parsed: ParsedCubeLut;
26
+ readonly flipSourceY: boolean;
27
+ }) => void;
28
+ export declare const cleanupLut: ({ gl, program, vao, vbo, sourceTexture, lutTexture, }: LutState) => void;
@@ -0,0 +1,2 @@
1
+ export declare const LUT_VERTEX_SHADER = "#version 300 es\nlayout(location = 0) in vec2 aPos;\nlayout(location = 1) in vec2 aUv;\nout vec2 vUv;\n\nvoid main() {\n\tvUv = aUv;\n\tgl_Position = vec4(aPos, 0.0, 1.0);\n}\n";
2
+ export declare const LUT_FRAGMENT_SHADER = "#version 300 es\nprecision highp float;\nprecision highp sampler3D;\n\nin vec2 vUv;\nout vec4 fragColor;\n\nuniform sampler2D uSource;\nuniform sampler3D uLut;\nuniform int uLutSize;\nuniform vec3 uDomainMin;\nuniform vec3 uDomainMax;\n\nvec3 sampleLut(vec3 color) {\n\tvec3 normalizedColor = clamp(\n\t\t(color - uDomainMin) / (uDomainMax - uDomainMin),\n\t\tvec3(0.0),\n\t\tvec3(1.0)\n\t);\n\tvec3 position = normalizedColor * float(uLutSize - 1);\n\tivec3 lower = ivec3(floor(position));\n\tivec3 upper = min(lower + ivec3(1), ivec3(uLutSize - 1));\n\tvec3 fraction = fract(position);\n\n\tvec3 c000 = texelFetch(uLut, ivec3(lower.x, lower.y, lower.z), 0).rgb;\n\tvec3 c100 = texelFetch(uLut, ivec3(upper.x, lower.y, lower.z), 0).rgb;\n\tvec3 c010 = texelFetch(uLut, ivec3(lower.x, upper.y, lower.z), 0).rgb;\n\tvec3 c110 = texelFetch(uLut, ivec3(upper.x, upper.y, lower.z), 0).rgb;\n\tvec3 c001 = texelFetch(uLut, ivec3(lower.x, lower.y, upper.z), 0).rgb;\n\tvec3 c101 = texelFetch(uLut, ivec3(upper.x, lower.y, upper.z), 0).rgb;\n\tvec3 c011 = texelFetch(uLut, ivec3(lower.x, upper.y, upper.z), 0).rgb;\n\tvec3 c111 = texelFetch(uLut, ivec3(upper.x, upper.y, upper.z), 0).rgb;\n\n\tvec3 c00 = mix(c000, c100, fraction.x);\n\tvec3 c10 = mix(c010, c110, fraction.x);\n\tvec3 c01 = mix(c001, c101, fraction.x);\n\tvec3 c11 = mix(c011, c111, fraction.x);\n\tvec3 c0 = mix(c00, c10, fraction.y);\n\tvec3 c1 = mix(c01, c11, fraction.y);\n\treturn mix(c0, c1, fraction.z);\n}\n\nvoid main() {\n\tvec4 sourceColor = texture(uSource, vUv);\n\tfloat alpha = sourceColor.a;\n\n\tif (alpha <= 0.0) {\n\t\tfragColor = vec4(0.0);\n\t\treturn;\n\t}\n\n\tvec3 straightColor = sourceColor.rgb / alpha;\n\tvec3 transformedColor = sampleLut(straightColor);\n\tfragColor = vec4(transformedColor * alpha, alpha);\n}\n";
@@ -0,0 +1,7 @@
1
+ export type ParsedCubeLut = {
2
+ readonly size: number;
3
+ readonly domainMin: readonly [number, number, number];
4
+ readonly domainMax: readonly [number, number, number];
5
+ readonly data: Float32Array;
6
+ };
7
+ export declare const parseCubeLut: (content: string) => ParsedCubeLut;
package/dist/lut.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { lut, lutSchema, type LutParams } from './lut/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/effects",
3
- "version": "4.0.524",
3
+ "version": "4.0.526",
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.524"
29
+ "remotion": "4.0.526"
30
30
  },
31
31
  "exports": {
32
32
  ".": {
@@ -204,6 +204,11 @@
204
204
  "module": "./dist/esm/levels.mjs",
205
205
  "import": "./dist/esm/levels.mjs"
206
206
  },
207
+ "./lut": {
208
+ "types": "./dist/lut.d.ts",
209
+ "module": "./dist/esm/lut.mjs",
210
+ "import": "./dist/esm/lut.mjs"
211
+ },
207
212
  "./light-leak": {
208
213
  "types": "./dist/light-leak.d.ts",
209
214
  "module": "./dist/esm/light-leak.mjs",
@@ -500,6 +505,9 @@
500
505
  "levels": [
501
506
  "dist/levels.d.ts"
502
507
  ],
508
+ "lut": [
509
+ "dist/lut.d.ts"
510
+ ],
503
511
  "light-leak": [
504
512
  "dist/light-leak.d.ts"
505
513
  ],
@@ -618,7 +626,7 @@
618
626
  },
619
627
  "homepage": "https://www.remotion.dev/docs/effects/api",
620
628
  "devDependencies": {
621
- "@remotion/eslint-config-internal": "4.0.524",
629
+ "@remotion/eslint-config-internal": "4.0.526",
622
630
  "@vitest/browser-playwright": "4.0.9",
623
631
  "eslint": "9.19.0",
624
632
  "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>;