Image Filters Overview
An image filter is a pixel operation applied to one media layer's own pixels — an
Image node, or an image/video fill — via the filters prop. A
node effect applies to a node and everything drawn inside
it; a filter stops at the layer it is attached to.
That difference is the whole point. To make a card's background image look like an oil painting, an effect would have to go on the card — and would paint the card's text and icons as brushwork too, so the background has to be split into its own node behind the content. As a filter it stays on the fill:
<Rect
width="fill" height="fill" cornerRadius={32}
fill={Fills.image('./bg.jpg', { fit: 'fill', filters: ImageFilters.oilPaint(4) })}
>
<Text text="still sharp" fontSize={64} />
</Rect>
A filter is also confined to the shape carrying it: it is clipped to the fill's own outline, corner radius and all.
Where filters apply
// On an Image node
<Image src="./photo.jpg" fit="fill" width={600} height={400} filters={ImageFilters.grayscale(1)} />
// On an image fill
<Rect fill={Fills.image('./photo.jpg', { filters: ImageFilters.blur(5) })} />
// On a video fill — same filters, plus the video-only temporal ones
<Rect fill={Fills.video('./clip.mp4', { filters: VideoFilters.posterizeTime(6).halftone(8) })} />
Using the ImageFilters builder
ImageFilters is an empty, immutable chain: each method returns a new chain with
the filter appended, so chains are safe to share and branch.
import { ImageFilters } from 'motion-script';
<Image src="./photo.jpg" fit="fill" width={600} height={400}
filters={ImageFilters.grayscale(1).blur(3).colorAdjustment({ brightness: 0.1 })}
/>
Filters interpolate, so a chain can be tweened like any other fill property:
yield* card().to(
{ fill: Fills.image('./bg.jpg', { filters: ImageFilters.halftone(24) }) },
2, easeInOut('quad'),
);
Available filters
Nearly every scene effect is also a filter — same name, same
options, same implementation — so ImageFilters.dither(…) and Effects.dither(…) do
the same thing to different things. See each effect's page for its options.
| Group | Filters |
|---|---|
| Blur & light | blur, directionalBlur, radialBlur, progressiveBlur, bloom, streak, godRays, sharpen |
| Colour | grayscale, exposure, alpha, invert, vintage, duotone, posterize, threshold, bitCrush, curves, colorAdjustment, colorMatrix |
| Texture & pattern | dither, halftone, grain, scanlines, pixelate, oilPaint, ascii, edges, vignette, texture |
| Displacement & lens | chromaticAberration, rgbShift, scatter, blockDisplace, bulge, twirl, wave, kaleidoscope, displace, sksl |
Video only (VideoFilters) | posterizeTime, echo |
Four effects are deliberately not filters, because a fill layer has nothing to give
them: magnify samples the backdrop beneath the node, motionBlur and trails are
derived from the node's own velocity, and outline draws outside the silhouette a fill
is clipped to. Use them as node effects.
A filter takes the matching effect's options minus mode — a filter is on the fill's
own pixels by definition, so there is no backdrop to point it at.
Chaining and order
Filters apply in the order they are chained:
const look = ImageFilters
.grayscale(0.6)
.colorAdjustment({ contrast: 1.2, highlights: -0.1 });
<Image src="./photo.jpg" fit="fill" width={600} height={400} filters={look} />
One ordering caveat. Filters take one of two render paths: colour transforms compose
into a single image filter, while the ones that resample pixel positions (oilPaint,
dither, halftone, twirl, …) wrap the fill's shader. A resampler always runs before
the composed colour pass, because a shader's output is what the image filter is applied
to. So oilPaint(4).grayscale(1) is honoured exactly, while grayscale(1).oilPaint(4)
also paints brushwork over grey — the difference being whether the brush windows saw
colour. Put resamplers first when the distinction matters.
Raw objects
A chain is sugar. Any filter can be written as a plain object, and arrays mix freely:
filters={[
{ type: 'grayscale', amount: 0.6 },
{ type: 'oilPaint', radius: 4 },
]}