Back to Remotion

Effects

packages/skills/skills/remotion-markup/effects.md

4.0.5098.5 KB
Original Source

Use this rule only when the top-level skill lists an effect that matches the requested look, or when the user asks to create a reusable custom effect.

Docs: https://www.remotion.dev/docs/effects Custom effect docs: https://www.remotion.dev/docs/create-effect

Usage

Install the package that provides the chosen effect:

bash
npx remotion add @remotion/effects

Effects are functions passed to the effects prop of canvas-based components such as <Video> from @remotion/media, <Solid>, <CanvasImage>, and <HtmlInCanvas>.

tsx
import {Video} from '@remotion/media';
import {blur} from '@remotion/effects/blur';

<Video
  src="https://remotion.media/video.mp4"
  effects={[blur({radius: 8})]}
/>;

Use the effect docs for exact props and imports. Most @remotion/effects imports use @remotion/effects/<effect-slug>; uvTranslate() and xyTranslate() use @remotion/effects/translate.

These effects use WebGL2. During renders, enable WebGL with:

ts
import {Config} from '@remotion/cli/config';

Config.setChromiumOpenGlRenderer('angle');

Available effects

brightness(), contrast(), colorKey(), duotone(), grayscale(), hue(), invert(), saturation(), tint(), linearGradient(), linearGradientTint(), thermalVision(), blur(), linearProgressiveBlur(), radialProgressiveBlur(), zoomBlur(), dropShadow(), glow(), lightTrail(), evolve(), venetianBlinds(), mirror(), scale(), uvTranslate(), xyTranslate(), barrelDistortion(), chromaticAberration(), fisheye(), cornerPin(), wave(), burlap(), emboss(), dotGrid(), halftone(), noise(), noiseDisplacement(), paper(), roughenEdges(), pattern(), pixelate(), pixelDissolve(), scanlines(), speckle(), shine(), shrinkwrap(), vignette(), contourLines(), checkerboard(), halftoneLinearGradient(), gridlines(), whiteNoise(), tvSignalOff(), lines(), rings(), waves(), zigzag(), lightLeak(), starburst().

Example:

tsx
import {brightness} from "@remotion/effects";

<Video
  src="https://remotion.media/video.mp4"
  effects={[brightness({})]}
/>;

Custom effects

Use createEffect() from remotion when the user wants a reusable effect factory that works in the same effects array as @remotion/effects.

Prefer a custom effect over <HtmlInCanvas onPaint> when the transformation should be reusable, parameterized, editable in Studio, or stackable with other effects.

For quick project-specific effects, keep the effect next to the composition, for example src/effects/palette-map.ts. For library effects intended for @remotion/effects, follow the repository's add-effect skill instead.

createEffect() expects:

  • type: stable reverse-DNS identifier, for example com.example.paletteMap.
  • label: Studio label, commonly paletteMap().
  • documentationLink: URL or null.
  • backend: "2d", "webgl2" or "webgpu".
  • calculateKey(params): stable string containing every resolved parameter that changes output.
  • setup(target): create reusable backend state, or return null.
  • apply({source, target, width, height, params, state, flipSourceY}): draw the transformed result into target.
  • cleanup(state): free resources created by setup().
  • schema: an InteractivitySchema for Studio controls. disabled is added automatically.
  • validateParams(params): throw on missing or invalid values.

Use backend: "2d" for simple pixel, filter, drawImage, or image-data effects. Use WebGL2 only when shader math or GPU performance is needed; during renders, enable WebGL as shown above.

ts
import {createEffect, type InteractivitySchema} from 'remotion';

type MyEffectParams = {
  readonly amount?: number;
};

const myEffectSchema = {
  amount: {
    type: 'number',
    min: 0,
    max: 1,
    step: 0.01,
    default: 1,
    description: 'Amount',
  },
} as const satisfies InteractivitySchema;

const resolve = (params: MyEffectParams) => ({
  amount: params.amount ?? 1,
});

export const myEffect = createEffect<MyEffectParams, null>({
  type: 'com.example.myEffect',
  label: 'myEffect()',
  documentationLink: null,
  backend: '2d',
  calculateKey: (params) => {
    const {amount} = resolve(params);
    return `my-effect-${amount}`;
  },
  setup: () => null,
  apply: ({source, target, width, height, params}) => {
    const ctx = target.getContext('2d');
    if (!ctx) {
      throw new Error('Could not get a 2D context for myEffect().');
    }

    const {amount} = resolve(params);

    ctx.clearRect(0, 0, width, height);
    ctx.filter = `opacity(${amount * 100}%)`;
    ctx.drawImage(source, 0, 0, width, height);
    ctx.filter = 'none';
  },
  cleanup: () => undefined,
  schema: myEffectSchema,
  validateParams: ({amount = 1}) => {
    if (typeof amount !== 'number' || !Number.isFinite(amount) || amount < 0 || amount > 1) {
      throw new TypeError('amount must be a number between 0 and 1');
    }
  },
});

For a WebGL2 effect, compile/link shaders in setup(), keep the program, fullscreen quad, texture, and uniform locations in state, upload source in apply(), and free GPU resources in cleanup(). Minimal shape:

ts
import {createEffect, type InteractivitySchema} from 'remotion';

type RgbShiftParams = {
  readonly amount?: number;
};

type RgbShiftState = {
  readonly gl: WebGL2RenderingContext;
  readonly program: WebGLProgram;
  readonly vao: WebGLVertexArrayObject;
  readonly vbo: WebGLBuffer;
  readonly texture: WebGLTexture;
  readonly uSource: WebGLUniformLocation | null;
  readonly uOffset: WebGLUniformLocation | null;
};

const rgbShiftSchema = {
  amount: {
    type: 'number',
    min: 0,
    max: 80,
    step: 1,
    default: 12,
    description: 'Amount',
  },
} as const satisfies InteractivitySchema;

export const rgbShift = createEffect<RgbShiftParams, RgbShiftState>({
  type: 'com.example.rgbShift',
  label: 'rgbShift()',
  documentationLink: null,
  backend: 'webgl2',
  calculateKey: ({amount = 12}) => `rgb-shift-${amount}`,
  setup: (target) => {
    const gl = target.getContext('webgl2', {
      premultipliedAlpha: true,
      alpha: true,
      preserveDrawingBuffer: true,
    });
    if (!gl) {
      throw new Error('Could not get a WebGL2 context for rgbShift().');
    }

    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

    // Compile/link shaders, create a fullscreen quad VAO/VBO, create a
    // CLAMP_TO_EDGE RGBA texture, and get uSource/uOffset uniform locations.
    return createRgbShiftState(gl);
  },
  apply: ({source, width, height, params, state, flipSourceY}) => {
    const amount = params.amount ?? 12;
    const {gl} = state;

    gl.viewport(0, 0, width, height);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, state.texture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      source as TexImageSource,
    );

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.useProgram(state.program);
    if (state.uSource) gl.uniform1i(state.uSource, 0);
    if (state.uOffset) gl.uniform2f(state.uOffset, amount / width, 0);
    gl.bindVertexArray(state.vao);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  },
  cleanup: ({gl, program, vao, vbo, texture}) => {
    gl.deleteTexture(texture);
    gl.deleteBuffer(vbo);
    gl.deleteProgram(program);
    gl.deleteVertexArray(vao);
  },
  schema: rgbShiftSchema,
  validateParams: ({amount = 12}) => {
    if (typeof amount !== 'number' || !Number.isFinite(amount) || amount < 0 || amount > 80) {
      throw new TypeError('amount must be a number between 0 and 80');
    }
  },
});

For a complete 2D and WebGL2 pair, see packages/example/src/EffectsTestbed/sample-posterize-2d.ts and packages/example/src/EffectsTestbed/sample-rgb-shift-webgl.ts.

Use the returned factory in an effects array:

tsx
import {CanvasImage, staticFile} from 'remotion';
import {myEffect} from './effects/my-effect';

export const MyComp: React.FC = () => {
  return (
    <CanvasImage
      src={staticFile('image.png')}
      effects={[myEffect({amount: 0.8})]}
    />
  );
};

When generating a custom effect, also:

  • Include disabled?: boolean only through the returned factory; do not add it to the custom params type or schema.
  • Validate required parameters at factory-call time with validateParams.
  • Include all defaults in both schema and the resolve() helper.
  • Reset mutable 2D context state such as filter, globalAlpha, transforms, and compositing after drawing.
  • Preserve alpha unless the requested effect intentionally changes transparency.