Skip to main content

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 { MX } from '@motion-script/core';

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

// Boost the blue channel only
filters={MX.curves([[0, 0.1], [0.5, 0.6], [1, 1]], '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)

MX
.curves([[0, 0], [0.5, 0.55], [1, 1]], 'r') // warm reds
.curves([[0, 0.05], [0.5, 0.5], [1, 0.95]], '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',
}],
});
});