Curves Filter

Remaps pixel values along a smooth tone curve defined by control points. The renderer interpolates a curve through the points and applies it to the selected channel.

Usage

import { ImageFilters } from '@motion-script/core';

// S-curve for contrast on RGB composite
<Image src="./photo.jpg" fit="fill" width={600} height={400}
  filters={ImageFilters.curves(
    [[0, 0], [0.25, 0.15], [0.75, 0.85], [1, 1]],
    'rgb'
  )}
/>

// Boost the blue channel only
filters={ImageFilters.curves({ points: [[0, 0.1], [0.5, 0.6], [1, 1]], channel: 'b' })}

// Raw object
filters={[{
  type: 'curves',
  points: [[0, 0], [0.5, 0.7], [1, 1]],
  channel: 'rgb',
}]}

Props

PropTypeDefaultDescription
type'curves'Filter identifier
points[number, number][]Control points as [input, output] pairs in [0, 1]
channel'rgb' | 'r' | 'g' | 'b' | 'a''rgb'Channel(s) to apply the curve to

Reading the curve

Each control point is an [input, output] pair:

  • input: the original pixel value (0 = black, 1 = white)
  • output: the remapped value after the curve

A straight line from [0,0] to [1,1] is a no-op identity. Pulling points above the diagonal brightens; below darkens.

Common recipes

S-curve (contrast)

[[0, 0], [0.25, 0.15], [0.75, 0.85], [1, 1]]

Lift shadows

[[0, 0.08], [0.5, 0.5], [1, 1]]

Crush blacks

[[0, 0], [0.15, 0], [0.5, 0.5], [1, 1]]

Orange-teal look (combine channels)

ImageFilters
  .curves({ points: [[0, 0], [0.5, 0.55], [1, 1]], channel: 'r' })   // warm reds
  .curves({ points: [[0, 0.05], [0.5, 0.5], [1, 0.95]], channel: 'b' }) // cool darks

Animating

Control points interpolate linearly when tweened. Both curve sets must have the same number of points:

yield* tween(1.5, (t) => {
  img.set({
    filters: [{
      type: 'curves',
      points: [
        [0, lerpNumber(0, 0.1, t)],
        [0.5, 0.5],
        [1, lerpNumber(1, 0.9, t)],
      ],
      channel: 'rgb',
    }],
  });
});