# Effects

> Attach GPU effects to layers, whole compositions, regions and media chains; order them; drive their props with signals.

Effects are typed classes. Construct one with props, attach it, and the GPU runs it each frame. The full list with every prop is in the [Effects reference](/docs/reference/effects/).

```ts
import { Blur, Curves, FilmGrain, Vignette } from "framefields";          // or "framefields/effects"
```

## Where to attach

```ts
// 1. One layer (chain as many as you like, in order)
Layer.video("assets/footage/plate.mp4", { fit: "cover" })
  .withEffect(new Curves({ master: [{ x: 0, y: 0.05 }, { x: 0.5, y: 0.52 }, { x: 1, y: 0.95 }] }))
  .withEffect(new Vignette({ strength: 40, radius: 0.85, softness: 0.6 }));

// 2. The whole frame
comp.apply(new FilmGrain({ strength: 12, size: 1.5, animated: true }));

// 3. A region of a plate (rectangle, rounded, or a tracked object)
comp.section({
  source: "assets/footage/interview.mp4",
  x: 400, y: 200, width: 300, height: 300, borderRadius: 150,
  effects: [new Blur({ strength: 25, blurType: "Gaussian" })],
});

// 4. A clip outside the layer tree
import { ApplyLUT, Crop, Media } from "framefields";
const clip = Media.video("assets/footage/raw_log.mp4")
  .apply(new ApplyLUT({ lutUrl: "assets/luts/cinematic.cube", intensity: 1 }))
  .apply(new Crop({ cropType: "rectangle", topPercentage: 10, heightPercentage: 80 }));
await clip.renderFrame({ atMs: 1500 });
```

`.withEffects([a, b])` attaches several at once. Lowercase factories work too: `Effect.blur({ strength: 9 })`.

## Value ranges

Ranges come from each effect's schema; out-of-range values warn (set `FRAMEFIELDS_STRICT_PROPS=1` to throw). Many "strength" props are **0–100**, not 0–1:

| Effect | Prop | Range | Default | Tasteful |
| --- | --- | --- | --- | --- |
| `Vignette` | `strength` | 0–100 | 50 | 20–45 |
| `FilmGrain` | `strength` | 0–100 | 15 | 8–20 |
| `FilmGrain` | `size` | 0.5–4 | 1.5 | 1.2–1.8 |
| `Blur` | `strength` | 0–100 | 5 | — |
| `ColorBalance` | `cyanRed` / `magentaGreen` / `yellowBlue` | −100–100 | 0 | ±4–12 |
| `ColorKey` | `similarity`, `smoothness` | 0–1 | 0.4, 0.1 | — |
| `Modulate` | `brightness`, `contrast`, `saturation` | multiplier | 1 | 0.9–1.2 |

## Order

1. Tonal grading first: `Curves`, `Levels`, `ColorBalance`, `SelectiveColor`, `ApplyLUT`.
2. Then optics: `Blur`, `DepthOfField`, `Vignette`.
3. Grain last, as the outermost pass: `FilmGrain`.

For a still export, set `animated: false` on `FilmGrain` so the frame is deterministic across renders of different lengths.

## Drive props with signals

Any numeric effect prop accepts a signal instead of a number. The uniform updates per frame without rebuilding the pipeline.

```ts
import { Blur, Layer, Signal } from "framefields";

const breathe = Signal.sine({ frequency: 0.5, min: 0, max: 20 }); // Hz, sampled at the composition frame

Layer.image("assets/images/bg.jpg").withEffect(new Blur({ strength: breathe, blurType: "Gaussian" }));
```

Vision signals plug in the same way, e.g. `new Vignette({ strength: vision.segmentation.subject.coverage })`. See [Vision](/docs/guides/vision/).

## Families

| Family | Effects |
| --- | --- |
| Grading | `Curves`, `Levels`, `ColorBalance`, `SelectiveColor`, `GradientMap`, `ShadowsHighlights`, `ApplyLUT`, `ExtractLUT`, `Modulate`, `ChannelSplitter`, `ChannelMerger` |
| Texture & lens | `FilmGrain`, `Vignette`, `Blur`, `MotionBlur`, `UnsharpMask`, `HighPass`, `HalftoneScreen`, `TileOffset`, `TemporalDeflicker` |
| Distortion | `DisplacementMap`, `Liquify`, `MeshWarp`, `CornerPin`, `Rotate`, `Flip`, `KenBurns`, `ResizerScaler` |
| 3D & light | `Relight3D`, `SSAO`, `PBRGlass`, `DepthOfField`, `RefractionCaustics3D`, `CameraParallax3D` |
| Mattes & keying | `ColorKey`, `Crop`, `RefineEdge`, `MaskMath`, `PatchHeal` |
| Generators & style | `NoiseGenerator`, `ProceduralVFX`, `Paint`, `LayerStyle` |
| Audio | `AudioFade`, `AudioCompressor`, `AudioParametricEq`, `AudioReverb`, `AudioDelay`, `AudioNoiseGate`, `StereoPanning`, `AudioSignalExtractor` |
| Vision | `Vision` (prefer `withVision`, see [Vision](/docs/guides/vision/)) |

## Restraint

Grain and vignette should be felt, not seen. If a viewer notices the effect before the content, halve it.
