Loading...
Loading...
Add a new effect to @remotion/effects, including implementation, package exports, docs, demos, preview images, tests, formatting, and builds.
npx skill4agent add remotion-dev/remotion add-effect@remotion/effects@remotion/effectspackages/effects/src/<effect-name>.tspackages/effects/src/<effect-name>/chromatic-aberrationchromaticAberrationChromaticAberrationParamsremotion/<kebab-case-name>SequenceSchemaInternalsremotionconst {createEffect, createWebGL2ContextError} = Internals;constsatisfies SequenceSchemaresolve()packages/effects/src/validate-effect-param.tspackages/effects/src/color-utils.tscreateWebGL2ContextError('<effect name> effect')documentationLinkhttps://www.remotion.dev/docs/effects/<slug>calculateKey()import type {SequenceSchema} from 'remotion';
import {Internals} from 'remotion';
import {assertOptionalFiniteNumber, validateUnitInterval} from './color-utils.js';
import {assertEffectParamsObject} from './validate-effect-param.js';
const {createEffect, createWebGL2ContextError} = Internals;
const DEFAULT_AMOUNT = 1 as const;
const myEffectSchema = {
amount: {
type: 'number',
min: 0,
max: 1,
step: 0.01,
default: DEFAULT_AMOUNT,
description: 'Amount',
},
} as const satisfies SequenceSchema;
export type MyEffectParams = {
readonly amount?: number;
};
type MyEffectResolved = {
amount: number;
};
const resolve = (p: MyEffectParams): MyEffectResolved => ({
amount: p.amount ?? DEFAULT_AMOUNT,
});
const validateMyEffectParams = (params: MyEffectParams): void => {
assertEffectParamsObject(params, 'My effect');
assertOptionalFiniteNumber(params.amount, 'amount');
validateUnitInterval(params.amount ?? DEFAULT_AMOUNT, 'amount');
};
type MyEffectState = {
readonly gl: WebGL2RenderingContext;
readonly program: WebGLProgram;
readonly vao: WebGLVertexArrayObject;
readonly vbo: WebGLBuffer;
readonly texture: WebGLTexture;
readonly uSource: WebGLUniformLocation | null;
readonly uAmount: WebGLUniformLocation | null;
};
const VERTEX_SHADER = /* glsl */ `#version 300 es
in vec2 aPos;
in vec2 aUv;
out vec2 vUv;
void main() {
vUv = aUv;
gl_Position = vec4(aPos, 0.0, 1.0);
}
`;
const FRAGMENT_SHADER = /* glsl */ `#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D uSource;
uniform float uAmount;
void main() {
vec4 color = texture(uSource, vUv);
fragColor = vec4(color.rgb * uAmount, color.a);
}
`;
// Follow existing helpers in halftone.ts or a runtime file for shader
// compilation, program linking, fullscreen-quad setup, and texture setup.
export const myEffect = createEffect<MyEffectParams, MyEffectState>({
type: 'remotion/my-effect',
label: 'My Effect',
documentationLink: 'https://www.remotion.dev/docs/effects/my-effect',
backend: 'webgl2',
calculateKey: (params) => {
const r = resolve(params);
return `my-effect-${r.amount}`;
},
setup: (target) => {
const gl = target.getContext('webgl2', {
premultipliedAlpha: true,
alpha: true,
preserveDrawingBuffer: true,
});
if (!gl) {
throw createWebGL2ContextError('my effect effect');
}
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
return createMyEffectState(gl, VERTEX_SHADER, FRAGMENT_SHADER);
},
apply: ({source, width, height, params, state, flipSourceY}) => {
const r = resolve(params);
state.gl.viewport(0, 0, width, height);
state.gl.bindFramebuffer(state.gl.FRAMEBUFFER, null);
state.gl.activeTexture(state.gl.TEXTURE0);
state.gl.bindTexture(state.gl.TEXTURE_2D, state.texture);
state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
state.gl.texImage2D(
state.gl.TEXTURE_2D,
0,
state.gl.RGBA,
state.gl.RGBA,
state.gl.UNSIGNED_BYTE,
source as TexImageSource,
);
state.gl.useProgram(state.program);
if (state.uSource) state.gl.uniform1i(state.uSource, 0);
if (state.uAmount) state.gl.uniform1f(state.uAmount, r.amount);
state.gl.bindVertexArray(state.vao);
state.gl.drawArrays(state.gl.TRIANGLE_STRIP, 0, 4);
},
cleanup: ({gl, program, vao, vbo, texture}) => {
gl.deleteTexture(texture);
gl.deleteBuffer(vbo);
gl.deleteProgram(program);
gl.deleteVertexArray(vao);
},
schema: myEffectSchema,
validateParams: validateMyEffectParams,
});halftone.tsblur/blur-runtime.tschromatic-aberration/chromatic-aberration-runtime.tswave/wave-runtime.tscreateMyEffectState()packages/effects/bundle.tssrc/<effect-name>.tspackages/effects/package.jsonexports["./<effect-name>"]typesVersionsexport {myEffect, type MyEffectParams} from './my-effect/index.js';packages/effects/src/test/effect-params.test.tseffectKeycd packages/effects
bun test src/test
bunx turbo make --filter="@remotion/effects"packages/docs/docs/effects/<effect-name>.mdxslugtitlesidebar_labelcrumb: '@remotion/effects'image:bun render-cards.ts# effectName()<AvailableFrom v="..." />_Part of the [@remotion/effects](/docs/effects/api) package._<Demo type="effects-<effect-name>" />title="MyComp.tsx"###?disabled?packages/docs/sidebars.ts'effects/<effect-name>'packages/docs/docs/effects/table-of-contents.tsxpackages/docs/src/data/articles.tswriting-docspackages/docs/components/effects/effects-<effect-name>-preview.tsximport {myEffect} from '@remotion/effects/my-effect';
import React from 'react';
import {CanvasImage} from 'remotion';
import {EFFECTS_PREVIEW_IMAGE_SRC} from './effects-preview-image';
export const EffectsMyEffectPreview: React.FC<{
readonly amount: number;
}> = ({amount}) => {
return (
<CanvasImage
src={EFFECTS_PREVIEW_IMAGE_SRC}
width={1280}
height={720}
fit="cover"
effects={[myEffect({amount})]}
/>
);
};fit="cover"packages/docs/components/demos/types.tseffectsMyEffectDemoid: 'effects-<effect-name>'packages/docs/components/demos/index.tsxdemosdocs-demoimport React from 'react';
import {Composition, registerRoot} from 'remotion';
import {EffectsMyEffectPreview} from '../../components/effects/effects-my-effect-preview';
const Root: React.FC = () => {
return (
<Composition
id="effects-my-effect-preview"
component={EffectsMyEffectPreview}
width={1080}
height={720}
fps={30}
durationInFrames={1}
defaultProps={{
amount: 1,
}}
/>
);
};
registerRoot(Root);packages/docsnpx --no-install --package @remotion/cli remotion still src/remotion/effects-preview-entry.tsx effects-my-effect-preview static/img/effects-my-effect-preview.jpg --overwrite --image-format=jpegpackages/docs/static/img/table-of-contents.tsxcd packages/docs
bun render-cards.tspackages/docs/static/generated/articles-docs-effects-<effect-name>.pngimage:render-cards.tscd packages/effects
bunx oxfmt src --write
cd ../..
bun run build
bun run formattingbun run formattingpackages/docs/srcgit diff --check
git status --shortpackage.jsonexportstypesVersions@remotion/effects/my-effectbundle.tspackages/docs/src/remotion