@remotion/effects 4.0.521 → 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.
- package/dist/effect-internals.d.ts +8 -0
- package/dist/entrypoints/blur.d.ts +6 -0
- package/dist/esm/page-turn.mjs +521 -0
- package/dist/esm/tear.mjs +392 -0
- package/dist/page-turn.d.ts +91 -0
- package/dist/tear.d.ts +13 -0
- package/package.json +11 -3
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
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 };
|
|
@@ -0,0 +1,521 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// src/tear.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/tear.ts
|
|
110
|
+
var { createEffect, createWebGL2ContextError } = Internals;
|
|
111
|
+
var DEFAULT_PROGRESS = 0.5;
|
|
112
|
+
var DEFAULT_ANGLE = 0;
|
|
113
|
+
var DEFAULT_ROTATION = 20;
|
|
114
|
+
var DEFAULT_JAGGEDNESS = 20;
|
|
115
|
+
var MESH_ROWS = 192;
|
|
116
|
+
var tearSchema = {
|
|
117
|
+
progress: {
|
|
118
|
+
type: "number",
|
|
119
|
+
min: 0,
|
|
120
|
+
step: 0.01,
|
|
121
|
+
default: DEFAULT_PROGRESS,
|
|
122
|
+
description: "Progress",
|
|
123
|
+
hiddenFromList: false
|
|
124
|
+
},
|
|
125
|
+
angle: {
|
|
126
|
+
type: "number",
|
|
127
|
+
step: 1,
|
|
128
|
+
default: DEFAULT_ANGLE,
|
|
129
|
+
description: "Angle",
|
|
130
|
+
hiddenFromList: false
|
|
131
|
+
},
|
|
132
|
+
rotation: {
|
|
133
|
+
type: "number",
|
|
134
|
+
min: 0,
|
|
135
|
+
max: 90,
|
|
136
|
+
step: 1,
|
|
137
|
+
default: DEFAULT_ROTATION,
|
|
138
|
+
description: "Rotation",
|
|
139
|
+
hiddenFromList: false
|
|
140
|
+
},
|
|
141
|
+
jaggedness: {
|
|
142
|
+
type: "number",
|
|
143
|
+
min: 0,
|
|
144
|
+
max: 100,
|
|
145
|
+
step: 1,
|
|
146
|
+
default: DEFAULT_JAGGEDNESS,
|
|
147
|
+
description: "Jaggedness",
|
|
148
|
+
hiddenFromList: false
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var resolve = (p) => ({
|
|
152
|
+
progress: p.progress ?? DEFAULT_PROGRESS,
|
|
153
|
+
angle: p.angle ?? DEFAULT_ANGLE,
|
|
154
|
+
rotation: p.rotation ?? DEFAULT_ROTATION,
|
|
155
|
+
jaggedness: p.jaggedness ?? DEFAULT_JAGGEDNESS
|
|
156
|
+
});
|
|
157
|
+
var validateTearParams = (params) => {
|
|
158
|
+
assertEffectParamsObject(params, "Tear");
|
|
159
|
+
assertOptionalFiniteNumber(params.progress, "progress");
|
|
160
|
+
assertOptionalFiniteNumber(params.angle, "angle");
|
|
161
|
+
assertOptionalFiniteNumber(params.rotation, "rotation");
|
|
162
|
+
assertOptionalFiniteNumber(params.jaggedness, "jaggedness");
|
|
163
|
+
const r = resolve(params);
|
|
164
|
+
if (r.progress < 0) {
|
|
165
|
+
throw new Error('"progress" must be >= 0');
|
|
166
|
+
}
|
|
167
|
+
if (r.rotation < 0 || r.rotation > 90) {
|
|
168
|
+
throw new Error('"rotation" must be between 0 and 90');
|
|
169
|
+
}
|
|
170
|
+
if (r.jaggedness < 0) {
|
|
171
|
+
throw new Error('"jaggedness" must be >= 0');
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var VERTEX_SHADER = `#version 300 es
|
|
175
|
+
in vec2 aPos;
|
|
176
|
+
out vec2 vUv;
|
|
177
|
+
uniform vec2 uResolution;
|
|
178
|
+
uniform float uAngle;
|
|
179
|
+
uniform float uProgress;
|
|
180
|
+
uniform float uRotation;
|
|
181
|
+
uniform float uJaggedness;
|
|
182
|
+
uniform float uSide;
|
|
183
|
+
|
|
184
|
+
float seam(float y, float width) {
|
|
185
|
+
// Six zigzags, with matching edges on both pieces. Limit the amplitude
|
|
186
|
+
// to keep the tear inside the source, even on very narrow canvases.
|
|
187
|
+
float zigzag = 1.0 - 4.0 * abs(fract(y * 6.0 + 0.25) - 0.5);
|
|
188
|
+
return width * 0.5 + zigzag * min(uJaggedness, width * 0.45);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
void main() {
|
|
192
|
+
// Work in a rectangle aligned with the rip, large enough to cover the
|
|
193
|
+
// original canvas at every angle. Rotate UVs back to the source so the
|
|
194
|
+
// image itself keeps its orientation.
|
|
195
|
+
float direction = radians(uAngle);
|
|
196
|
+
float dc = cos(direction);
|
|
197
|
+
float ds = sin(direction);
|
|
198
|
+
mat2 toCanvas = mat2(dc, -ds, ds, dc);
|
|
199
|
+
vec2 extent = vec2(
|
|
200
|
+
abs(dc) * uResolution.x + abs(ds) * uResolution.y,
|
|
201
|
+
abs(ds) * uResolution.x + abs(dc) * uResolution.y
|
|
202
|
+
);
|
|
203
|
+
float outerEdge = uSide < 0.0 ? 0.0 : extent.x;
|
|
204
|
+
vec2 source = vec2(mix(outerEdge, seam(aPos.y, extent.x), aPos.x), aPos.y * extent.y);
|
|
205
|
+
vec2 sourceCanvas = toCanvas * (source - extent * 0.5) + uResolution * 0.5;
|
|
206
|
+
vUv = vec2(sourceCanvas.x / uResolution.x, 1.0 - sourceCanvas.y / uResolution.y);
|
|
207
|
+
|
|
208
|
+
float tearProgress = min(uProgress, 1.0);
|
|
209
|
+
float tipY = tearProgress * extent.y;
|
|
210
|
+
// Keep the motion independent of the jagged edge. Following the seam
|
|
211
|
+
// with the pivot would rock the entire image at every zigzag.
|
|
212
|
+
vec2 pivot = vec2(extent.x * 0.5, tipY);
|
|
213
|
+
// The short bend near the tip joins the rotating portion continuously
|
|
214
|
+
// to the untouched area below it. Work in pixels to preserve angles
|
|
215
|
+
// at different aspect ratios.
|
|
216
|
+
float fullAngle = radians(uRotation) * tearProgress;
|
|
217
|
+
// Widen the bend for larger rotations / wider sheets so neighboring
|
|
218
|
+
// rows cannot fold over each other at the outside edges.
|
|
219
|
+
float bendHeight = max(extent.y * 0.15, fullAngle * extent.x * 1.5);
|
|
220
|
+
float bend = smoothstep(0.0, bendHeight, tipY - source.y);
|
|
221
|
+
float angle = uSide * fullAngle * bend;
|
|
222
|
+
float c = cos(angle);
|
|
223
|
+
float s = sin(angle);
|
|
224
|
+
vec2 delta = source - pivot;
|
|
225
|
+
vec2 position = pivot + vec2(c * delta.x - s * delta.y, s * delta.x + c * delta.y);
|
|
226
|
+
// After the tear reaches the bottom, keep the rotation stable and
|
|
227
|
+
// move each piece outward by half a canvas width per unit of progress.
|
|
228
|
+
position.x += uSide * max(uProgress - 1.0, 0.0) * extent.x * 0.5;
|
|
229
|
+
position = toCanvas * (position - extent * 0.5) + uResolution * 0.5;
|
|
230
|
+
gl_Position = vec4(position.x / uResolution.x * 2.0 - 1.0, 1.0 - position.y / uResolution.y * 2.0, 0.0, 1.0);
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
var FRAGMENT_SHADER = `#version 300 es
|
|
234
|
+
precision highp float;
|
|
235
|
+
in vec2 vUv;
|
|
236
|
+
out vec4 fragColor;
|
|
237
|
+
uniform sampler2D uSource;
|
|
238
|
+
|
|
239
|
+
void main() {
|
|
240
|
+
// The rotated mesh extends beyond the original image. Those parts
|
|
241
|
+
// must remain transparent instead of smearing clamped border pixels.
|
|
242
|
+
if (any(lessThan(vUv, vec2(0.0))) || any(greaterThan(vUv, vec2(1.0)))) {
|
|
243
|
+
discard;
|
|
244
|
+
}
|
|
245
|
+
fragColor = texture(uSource, vUv);
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
var compileShader = (gl, type, source) => {
|
|
249
|
+
const shader = gl.createShader(type);
|
|
250
|
+
if (!shader) {
|
|
251
|
+
throw new Error("Failed to create WebGL shader");
|
|
252
|
+
}
|
|
253
|
+
gl.shaderSource(shader, source);
|
|
254
|
+
gl.compileShader(shader);
|
|
255
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
256
|
+
const log = gl.getShaderInfoLog(shader);
|
|
257
|
+
gl.deleteShader(shader);
|
|
258
|
+
throw new Error(`Tear shader compile failed: ${log ?? "(no log)"}`);
|
|
259
|
+
}
|
|
260
|
+
return shader;
|
|
261
|
+
};
|
|
262
|
+
var linkProgram = (gl, vs, fs) => {
|
|
263
|
+
const program = gl.createProgram();
|
|
264
|
+
if (!program) {
|
|
265
|
+
throw new Error("Failed to create WebGL program");
|
|
266
|
+
}
|
|
267
|
+
gl.attachShader(program, vs);
|
|
268
|
+
gl.attachShader(program, fs);
|
|
269
|
+
gl.linkProgram(program);
|
|
270
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
271
|
+
const log = gl.getProgramInfoLog(program);
|
|
272
|
+
gl.deleteProgram(program);
|
|
273
|
+
throw new Error(`Tear program link failed: ${log ?? "(no log)"}`);
|
|
274
|
+
}
|
|
275
|
+
return program;
|
|
276
|
+
};
|
|
277
|
+
var createTearState = (gl) => {
|
|
278
|
+
const vs = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
279
|
+
const fs = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
|
|
280
|
+
const program = linkProgram(gl, vs, fs);
|
|
281
|
+
gl.deleteShader(vs);
|
|
282
|
+
gl.deleteShader(fs);
|
|
283
|
+
const vao = gl.createVertexArray();
|
|
284
|
+
if (!vao) {
|
|
285
|
+
throw new Error("Failed to create WebGL vertex array");
|
|
286
|
+
}
|
|
287
|
+
gl.bindVertexArray(vao);
|
|
288
|
+
const data = new Float32Array((MESH_ROWS + 1) * 4);
|
|
289
|
+
for (let row = 0;row <= MESH_ROWS; row++) {
|
|
290
|
+
data.set([0, row / MESH_ROWS, 1, row / MESH_ROWS], row * 4);
|
|
291
|
+
}
|
|
292
|
+
const vbo = gl.createBuffer();
|
|
293
|
+
if (!vbo) {
|
|
294
|
+
throw new Error("Failed to create WebGL buffer");
|
|
295
|
+
}
|
|
296
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
297
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
298
|
+
const aPos = gl.getAttribLocation(program, "aPos");
|
|
299
|
+
gl.enableVertexAttribArray(aPos);
|
|
300
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 8, 0);
|
|
301
|
+
gl.bindVertexArray(null);
|
|
302
|
+
const texture = gl.createTexture();
|
|
303
|
+
if (!texture) {
|
|
304
|
+
throw new Error("Failed to create WebGL texture");
|
|
305
|
+
}
|
|
306
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
307
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
308
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
309
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
310
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
311
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
312
|
+
return {
|
|
313
|
+
gl,
|
|
314
|
+
program,
|
|
315
|
+
vao,
|
|
316
|
+
vbo,
|
|
317
|
+
texture,
|
|
318
|
+
uSource: gl.getUniformLocation(program, "uSource"),
|
|
319
|
+
uResolution: gl.getUniformLocation(program, "uResolution"),
|
|
320
|
+
uAngle: gl.getUniformLocation(program, "uAngle"),
|
|
321
|
+
uProgress: gl.getUniformLocation(program, "uProgress"),
|
|
322
|
+
uRotation: gl.getUniformLocation(program, "uRotation"),
|
|
323
|
+
uJaggedness: gl.getUniformLocation(program, "uJaggedness"),
|
|
324
|
+
uSide: gl.getUniformLocation(program, "uSide")
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
var tear = createEffect({
|
|
328
|
+
type: "dev.remotion.effects.tear",
|
|
329
|
+
label: "tear()",
|
|
330
|
+
documentationLink: "https://www.remotion.dev/docs/effects/tear",
|
|
331
|
+
backend: "webgl2",
|
|
332
|
+
calculateKey: (params) => {
|
|
333
|
+
const r = resolve(params);
|
|
334
|
+
return `tear-${r.progress}-${r.rotation}-${r.jaggedness}-${r.angle}`;
|
|
335
|
+
},
|
|
336
|
+
setup: (target) => {
|
|
337
|
+
const gl = target.getContext("webgl2", {
|
|
338
|
+
premultipliedAlpha: true,
|
|
339
|
+
alpha: true,
|
|
340
|
+
preserveDrawingBuffer: true
|
|
341
|
+
});
|
|
342
|
+
if (!gl) {
|
|
343
|
+
throw createWebGL2ContextError("tear effect");
|
|
344
|
+
}
|
|
345
|
+
return createTearState(gl);
|
|
346
|
+
},
|
|
347
|
+
apply: ({ source, width, height, params, state, flipSourceY }) => {
|
|
348
|
+
const r = resolve(params);
|
|
349
|
+
state.gl.viewport(0, 0, width, height);
|
|
350
|
+
state.gl.bindFramebuffer(state.gl.FRAMEBUFFER, null);
|
|
351
|
+
state.gl.clearColor(0, 0, 0, 0);
|
|
352
|
+
state.gl.clear(state.gl.COLOR_BUFFER_BIT);
|
|
353
|
+
state.gl.useProgram(state.program);
|
|
354
|
+
state.gl.bindVertexArray(state.vao);
|
|
355
|
+
state.gl.activeTexture(state.gl.TEXTURE0);
|
|
356
|
+
state.gl.bindTexture(state.gl.TEXTURE_2D, state.texture);
|
|
357
|
+
state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
|
|
358
|
+
state.gl.pixelStorei(state.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
359
|
+
state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, state.gl.RGBA, state.gl.UNSIGNED_BYTE, source);
|
|
360
|
+
if (state.uSource)
|
|
361
|
+
state.gl.uniform1i(state.uSource, 0);
|
|
362
|
+
if (state.uAngle)
|
|
363
|
+
state.gl.uniform1f(state.uAngle, r.angle % 360);
|
|
364
|
+
if (state.uProgress)
|
|
365
|
+
state.gl.uniform1f(state.uProgress, r.progress);
|
|
366
|
+
if (state.uRotation)
|
|
367
|
+
state.gl.uniform1f(state.uRotation, r.rotation);
|
|
368
|
+
if (state.uJaggedness)
|
|
369
|
+
state.gl.uniform1f(state.uJaggedness, r.jaggedness);
|
|
370
|
+
if (state.uResolution)
|
|
371
|
+
state.gl.uniform2f(state.uResolution, width, height);
|
|
372
|
+
for (const side of [-1, 1]) {
|
|
373
|
+
if (state.uSide)
|
|
374
|
+
state.gl.uniform1f(state.uSide, side);
|
|
375
|
+
state.gl.drawArrays(state.gl.TRIANGLE_STRIP, 0, (MESH_ROWS + 1) * 2);
|
|
376
|
+
}
|
|
377
|
+
state.gl.bindVertexArray(null);
|
|
378
|
+
state.gl.bindTexture(state.gl.TEXTURE_2D, null);
|
|
379
|
+
state.gl.useProgram(null);
|
|
380
|
+
},
|
|
381
|
+
cleanup: ({ gl, program, vao, vbo, texture }) => {
|
|
382
|
+
gl.deleteTexture(texture);
|
|
383
|
+
gl.deleteBuffer(vbo);
|
|
384
|
+
gl.deleteProgram(program);
|
|
385
|
+
gl.deleteVertexArray(vao);
|
|
386
|
+
},
|
|
387
|
+
schema: tearSchema,
|
|
388
|
+
validateParams: validateTearParams
|
|
389
|
+
});
|
|
390
|
+
export {
|
|
391
|
+
tear
|
|
392
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
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>;
|
package/dist/tear.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type TearParams = {
|
|
2
|
+
/** Tear progress: 0 is intact, 1 reaches the opposite edge, and values above 1 move the pieces farther apart. Defaults to 0.5. */
|
|
3
|
+
readonly progress?: number;
|
|
4
|
+
/** Rip direction in degrees: 0 top-to-bottom, 90 left-to-right. Defaults to 0. */
|
|
5
|
+
readonly angle?: number;
|
|
6
|
+
/** Maximum outward rotation in degrees, between 0 and 90. Defaults to 20. */
|
|
7
|
+
readonly rotation?: number;
|
|
8
|
+
/** Zigzag amplitude in pixels. Use 0 for a straight seam. Defaults to 20. */
|
|
9
|
+
readonly jaggedness?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const tear: (params?: (TearParams & {
|
|
12
|
+
readonly disabled?: boolean | undefined;
|
|
13
|
+
}) | undefined) => import("remotion").EffectDescriptor<unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/effects",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.523",
|
|
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.
|
|
29
|
+
"remotion": "4.0.523"
|
|
30
30
|
},
|
|
31
31
|
"exports": {
|
|
32
32
|
".": {
|
|
@@ -319,6 +319,11 @@
|
|
|
319
319
|
"module": "./dist/esm/starburst.mjs",
|
|
320
320
|
"import": "./dist/esm/starburst.mjs"
|
|
321
321
|
},
|
|
322
|
+
"./tear": {
|
|
323
|
+
"types": "./dist/tear.d.ts",
|
|
324
|
+
"module": "./dist/esm/tear.mjs",
|
|
325
|
+
"import": "./dist/esm/tear.mjs"
|
|
326
|
+
},
|
|
322
327
|
"./thermal-vision": {
|
|
323
328
|
"types": "./dist/thermal-vision.d.ts",
|
|
324
329
|
"module": "./dist/esm/thermal-vision.mjs",
|
|
@@ -564,6 +569,9 @@
|
|
|
564
569
|
"starburst": [
|
|
565
570
|
"dist/starburst.d.ts"
|
|
566
571
|
],
|
|
572
|
+
"tear": [
|
|
573
|
+
"dist/tear.d.ts"
|
|
574
|
+
],
|
|
567
575
|
"thermal-vision": [
|
|
568
576
|
"dist/thermal-vision.d.ts"
|
|
569
577
|
],
|
|
@@ -610,7 +618,7 @@
|
|
|
610
618
|
},
|
|
611
619
|
"homepage": "https://www.remotion.dev/docs/effects/api",
|
|
612
620
|
"devDependencies": {
|
|
613
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
621
|
+
"@remotion/eslint-config-internal": "4.0.523",
|
|
614
622
|
"@vitest/browser-playwright": "4.0.9",
|
|
615
623
|
"eslint": "9.19.0",
|
|
616
624
|
"vitest": "4.0.9",
|