@remotion/effects 4.0.535 → 4.0.537

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,376 @@
1
+ // src/sphere/index.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/webgpu-projection-runtime.ts
110
+ var cleanupWebGpuProjection = (state) => {
111
+ state.resources?.texture.destroy();
112
+ state.resources?.uniforms.destroy();
113
+ state.resources = null;
114
+ state.context.unconfigure();
115
+ };
116
+ var setupWebGpuProjection = (target, label) => {
117
+ const context = target.getContext("webgpu");
118
+ if (!context) {
119
+ throw new Error(`WebGPU is not available for the ${label} effect`);
120
+ }
121
+ return { context, resources: null };
122
+ };
123
+ var applyWebGpuProjection = ({
124
+ state,
125
+ source,
126
+ width,
127
+ height,
128
+ device,
129
+ shader,
130
+ uniformData
131
+ }) => {
132
+ if (state.resources && (state.resources.device !== device || state.resources.width !== width || state.resources.height !== height)) {
133
+ cleanupWebGpuProjection(state);
134
+ }
135
+ if (!state.resources) {
136
+ const format = navigator.gpu.getPreferredCanvasFormat();
137
+ state.context.configure({ device, format, alphaMode: "premultiplied" });
138
+ const module = device.createShaderModule({ code: shader });
139
+ const pipeline = device.createRenderPipeline({
140
+ layout: "auto",
141
+ vertex: { module, entryPoint: "vertex" },
142
+ fragment: { module, entryPoint: "fragment", targets: [{ format }] },
143
+ primitive: { topology: "triangle-list" }
144
+ });
145
+ const uniforms = device.createBuffer({
146
+ size: uniformData.byteLength,
147
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
148
+ });
149
+ const sampler = device.createSampler({
150
+ addressModeU: "repeat",
151
+ magFilter: "linear",
152
+ minFilter: "linear"
153
+ });
154
+ const texture = device.createTexture({
155
+ size: [width, height],
156
+ format: "rgba8unorm",
157
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
158
+ });
159
+ const bindGroup = device.createBindGroup({
160
+ layout: pipeline.getBindGroupLayout(0),
161
+ entries: [
162
+ { binding: 0, resource: texture.createView() },
163
+ { binding: 1, resource: sampler },
164
+ { binding: 2, resource: { buffer: uniforms } }
165
+ ]
166
+ });
167
+ state.resources = {
168
+ device,
169
+ pipeline,
170
+ uniforms,
171
+ sampler,
172
+ texture,
173
+ bindGroup,
174
+ width,
175
+ height
176
+ };
177
+ }
178
+ const { resources } = state;
179
+ device.queue.copyExternalImageToTexture({ source }, { texture: resources.texture, premultipliedAlpha: true }, [width, height]);
180
+ device.queue.writeBuffer(resources.uniforms, 0, uniformData);
181
+ const encoder = device.createCommandEncoder();
182
+ const pass = encoder.beginRenderPass({
183
+ colorAttachments: [
184
+ {
185
+ view: state.context.getCurrentTexture().createView(),
186
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
187
+ loadOp: "clear",
188
+ storeOp: "store"
189
+ }
190
+ ]
191
+ });
192
+ pass.setPipeline(resources.pipeline);
193
+ pass.setBindGroup(0, resources.bindGroup);
194
+ pass.draw(3);
195
+ pass.end();
196
+ device.queue.submit([encoder.finish()]);
197
+ };
198
+
199
+ // src/sphere/shader.ts
200
+ var SPHERE_SHADER = `
201
+ struct Params {
202
+ resolution: vec2f,
203
+ center: vec2f,
204
+ radius: f32,
205
+ padding: f32,
206
+ shading: f32,
207
+ faces: f32,
208
+ rotation: vec4f,
209
+ }
210
+ @group(0) @binding(0) var source: texture_2d<f32>;
211
+ @group(0) @binding(1) var sourceSampler: sampler;
212
+ @group(0) @binding(2) var<uniform> params: Params;
213
+
214
+ @vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
215
+ let positions = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
216
+ return vec4f(positions[index], 0, 1);
217
+ }
218
+
219
+ fn sampleFace(normal: vec3f, inverse: mat3x3f) -> vec4f {
220
+ let local = inverse * normal;
221
+ // Longitude wraps at the seam; latitude spans the complete image height.
222
+ let uv = vec2f(fract(atan2(local.x, local.z) / 6.28318530718 + 0.5), asin(clamp(local.y, -1.0, 1.0)) / 3.14159265359 + 0.5);
223
+ let color = textureSampleLevel(source, sourceSampler, uv, 0);
224
+ let shade = mix(1.0, abs(normal.z), params.shading);
225
+ return vec4f(color.rgb * shade, color.a);
226
+ }
227
+
228
+ @fragment fn fragment(@builtin(position) position: vec4f) -> @location(0) vec4f {
229
+ let p = (position.xy - params.center) / params.radius;
230
+ let distanceSquared = dot(p, p);
231
+ let coverage = 1.0 - smoothstep(1.0 - fwidth(distanceSquared), 1.0, distanceSquared);
232
+ if (distanceSquared >= 1.0) { return vec4f(0); }
233
+ let z = sqrt(1.0 - distanceSquared);
234
+ let s = sin(params.rotation.xyz);
235
+ let c = cos(params.rotation.xyz);
236
+ let rx = mat3x3f(vec3f(1, 0, 0), vec3f(0, c.x, s.x), vec3f(0, -s.x, c.x));
237
+ let ry = mat3x3f(vec3f(c.y, 0, -s.y), vec3f(0, 1, 0), vec3f(s.y, 0, c.y));
238
+ let rz = mat3x3f(vec3f(c.z, s.z, 0), vec3f(-s.z, c.z, 0), vec3f(0, 0, 1));
239
+ let inverse = transpose(rz * ry * rx);
240
+ let front = sampleFace(vec3f(p, z), inverse);
241
+ let back = sampleFace(vec3f(p, -z), inverse);
242
+ if (params.faces == 1) { return front * coverage; }
243
+ if (params.faces == 2) { return back * coverage; }
244
+ return (front + back * (1.0 - front.a)) * coverage;
245
+ }
246
+ `;
247
+
248
+ // src/sphere/index.ts
249
+ var DEFAULT_RADIUS = 250;
250
+ var DEFAULT_ROTATION = 0;
251
+ var DEFAULT_POSITION = [0.5, 0.5];
252
+ var DEFAULT_RENDER = "both";
253
+ var DEFAULT_SHADING = 0.35;
254
+ var sphereSchema = {
255
+ radius: {
256
+ type: "number",
257
+ min: 1,
258
+ max: 1000,
259
+ step: 1,
260
+ default: DEFAULT_RADIUS,
261
+ description: "Radius",
262
+ hiddenFromList: false
263
+ },
264
+ rotationX: {
265
+ type: "rotation-degrees",
266
+ default: DEFAULT_ROTATION,
267
+ description: "X rotation"
268
+ },
269
+ rotationY: {
270
+ type: "rotation-degrees",
271
+ default: DEFAULT_ROTATION,
272
+ description: "Y rotation"
273
+ },
274
+ rotationZ: {
275
+ type: "rotation-degrees",
276
+ default: DEFAULT_ROTATION,
277
+ description: "Z rotation"
278
+ },
279
+ position: {
280
+ type: "uv-coordinate",
281
+ default: DEFAULT_POSITION,
282
+ description: "Position"
283
+ },
284
+ render: {
285
+ type: "enum",
286
+ variants: { both: {}, front: {}, back: {} },
287
+ default: DEFAULT_RENDER,
288
+ description: "Faces"
289
+ },
290
+ shading: {
291
+ type: "number",
292
+ min: 0,
293
+ max: 1,
294
+ step: 0.01,
295
+ default: DEFAULT_SHADING,
296
+ description: "Shading",
297
+ hiddenFromList: false
298
+ }
299
+ };
300
+ var resolve = (params) => ({
301
+ radius: params.radius ?? DEFAULT_RADIUS,
302
+ rotationX: params.rotationX ?? DEFAULT_ROTATION,
303
+ rotationY: params.rotationY ?? DEFAULT_ROTATION,
304
+ rotationZ: params.rotationZ ?? DEFAULT_ROTATION,
305
+ position: params.position ?? DEFAULT_POSITION,
306
+ render: params.render ?? DEFAULT_RENDER,
307
+ shading: params.shading ?? DEFAULT_SHADING
308
+ });
309
+ var sphere = Internals.createEffect({
310
+ type: "remotion/sphere",
311
+ label: "sphere()",
312
+ documentationLink: "https://www.remotion.dev/docs/effects/sphere",
313
+ backend: "webgpu",
314
+ schema: sphereSchema,
315
+ validateParams: (params) => {
316
+ assertEffectParamsObject(params, "Sphere");
317
+ for (const key of [
318
+ "radius",
319
+ "rotationX",
320
+ "rotationY",
321
+ "rotationZ",
322
+ "shading"
323
+ ]) {
324
+ assertOptionalFiniteNumber(params[key], key);
325
+ }
326
+ const r = resolve(params);
327
+ if (r.radius <= 0) {
328
+ throw new TypeError('"radius" must be greater than 0');
329
+ }
330
+ validateUnitInterval(r.shading, "shading");
331
+ if (params.position !== undefined && (!Array.isArray(params.position) || params.position.length !== 2 || !Number.isFinite(params.position[0]) || !Number.isFinite(params.position[1]))) {
332
+ throw new TypeError('"position" must contain two finite numbers');
333
+ }
334
+ if (params.render !== undefined && params.render !== "both" && params.render !== "front" && params.render !== "back") {
335
+ throw new TypeError('"render" must be "both", "front", or "back"');
336
+ }
337
+ },
338
+ calculateKey: (params) => {
339
+ const r = resolve(params);
340
+ return `sphere-${r.radius}-${r.rotationX}-${r.rotationY}-${r.rotationZ}-${r.position.join(",")}-${r.render}-${r.shading}`;
341
+ },
342
+ setup: (target) => setupWebGpuProjection(target, "sphere"),
343
+ apply: ({ source, width, height, params, state, gpuDevice }) => {
344
+ if (!gpuDevice) {
345
+ throw new Error("A WebGPU device is required for the sphere effect");
346
+ }
347
+ const r = resolve(params);
348
+ const radians = Math.PI / 180;
349
+ applyWebGpuProjection({
350
+ source,
351
+ width,
352
+ height,
353
+ state,
354
+ device: gpuDevice,
355
+ shader: SPHERE_SHADER,
356
+ uniformData: new Float32Array([
357
+ width,
358
+ height,
359
+ r.position[0] * width,
360
+ r.position[1] * height,
361
+ r.radius,
362
+ 0,
363
+ r.shading,
364
+ r.render === "front" ? 1 : r.render === "back" ? 2 : 0,
365
+ r.rotationX % 360 * radians,
366
+ r.rotationY % 360 * radians,
367
+ r.rotationZ % 360 * radians,
368
+ 0
369
+ ])
370
+ });
371
+ },
372
+ cleanup: cleanupWebGpuProjection
373
+ });
374
+ export {
375
+ sphere
376
+ };