Setup
Import everything from the framefields npm package. If the project doesn't have framefields set up yet, follow the framefields skill first (install, project scaffold, first render).
Overview
framefields features a native WebGPU shader execution pipeline for 2D VFX, tonal grading, cinematic lens simulation, spatial relighting, and real-time neural vision conditioning.
Effects in framefields are strongly typed Effect subclasses whose uniform properties can be driven by static values, time-varying keyframes, reactive Signal instances, or audio extraction pipelines.
See references/effects-catalog.md for the generated prop-level catalog, defaults, and type bounds.
Attaching Effects
There are 4 main attachment modes in the engine:
1. Layer-Level Effect Pipeline
Attach one or more effects to an individual media, box, or text layer:
import { Layer, Curves, Vignette, Blur } from "framefields";
const videoLayer = Layer.video("assets/footage.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: 0.4, radius: 0.85, softness: 0.6 }))
.withEffect(new Blur({ strength: 8, blurType: "Gaussian" }));
2. Whole-Composition Master Emulsion
Apply an overarching post-processing grade or film emulsion across all rendered layers:
import { Composition, FilmGrain, ColorBalance } from "framefields";
const comp = new Composition({ width: 1920, height: 1080, fps: 30 });
// Shared organic 35mm grain applied across titles, background video, and vector graphics
comp.apply(new FilmGrain({
strength: 0.06,
size: 1.5,
monochrome: true,
animated: true,
speed: 1.0,
}));
comp.apply(new ColorBalance({
shadows: { cyanRed: 4, yellowBlue: -6 },
highlights: { cyanRed: 2, yellowBlue: -4 },
preserveLuminosity: true,
}));
3. Spatial & Tracked Sections (comp.section / Layer.section)
Isolate a sub-region of a plate or canvas to apply localized effects:
// Box blur on a tracked object or explicit bounding rectangle
const blurredPlate = comp.section({
source: "assets/interview.mp4",
x: 400,
y: 200,
width: 300,
height: 300,
borderRadius: 150,
effects: [new Blur({ strength: 25, blurType: "Gaussian" })],
});
4. Fluid Fluent Media Chains (Media)
Process clips outside a layout tree before mounting or exporting:
import { Media, ApplyLUT, Crop } from "framefields";
const clip = Media.video("assets/raw_log.mp4")
.apply(new ApplyLUT({ lutUrl: "assets/luts/cinematic.cube", intensity: 1.0 }))
.apply(new Crop({ cropType: "rectangle", topPercentage: 10, heightPercentage: 80 }));
const pngBuffer = await clip.renderFrame({ atMs: 1500 });
Effect Categories
1. Color Grading & Tonal Dynamics
Curves: Spline-interpolated tone curves for master, red, green, blue, plus hue/saturation curves (hueVsHue,hueVsSat,lumVsSat,satVsSat).ColorBalance: Shadows, midtones, and highlights split color adjustments withpreserveLuminosity.Levels: Master and per-channel input/output black/white points and gamma.SelectiveColor: CMYK-style photographic selective adjustments targeting reds, yellows, greens, cyans, blues, magentas, whites, neutrals, and blacks.GradientMap: Multi-stop gradient remapping (stops: [{ position: 0, color: "#000" }, { position: 1, color: "#FFF" }]).ShadowsHighlights: Separate shadow lifting and highlight recovery with radius and tonal width controls.ApplyLUT: 1D and 3D.cubecolor lookup table applicator.Modulate: Real-time HSL/contrast controls:hue,brightness,contrast,exposure,saturation,sepia.
2. Optical, Texture & Cinematic Emulsions
FilmGrain: GPU-synthesized film grain with grain sizing, shadow/highlight masking, and temporal animation speed.Vignette: Elliptical or circular lens falloff with customizable center, roundness, and softness.Blur: High-performance multi-mode blur:"Gaussian","Box","Median","Motion","Bilateral","Edge-preserving","Radial","Zoom". Supports partial blur regions and tracked object bounding boxes.UnsharpMask: High-frequency edge sharpener with threshold suppression.HighPass: Edge and frequency isolation with contrast boost.HalftoneScreen: Procedural print screening with"Circle","Diamond","Line", or"Square"dots in"Monochrome"or"CMYK"angles.TileOffset: Seamless UV wrapping with"wrap","clamp","mirror", or"transparent"boundaries.MotionBlur: Directional shutter simulation with custom shutter angles and velocity clamps.TemporalDeflicker: Multi-frame optical flow blending to eliminate flickering in generative AI clips or high-speed footage.
3. 3D Spatial Lighting & Materials
Relight3D: Screen-space normal map relighting with point, spot, and directional lights, specular highlights, roughness, and metallic uniforms.SSAO: Screen-space ambient occlusion generating contact shadows from depth buffers.PBRGlass: Physically-based transmission, index of refraction (IOR), optical dispersion, and Fresnel reflections.DepthOfField: Camera lens simulation with aperture size, focal length, focus plane distance, and circle-of-confusion (CoC) blur.
4. Matte, Keying & Edge Operations
ColorKey: Studio greenscreen/bluescreen keyer with spill suppression and smoothness controls.Crop: Rectangular, circular, or arbitrary polygon path cropping with corner rounding.
On-Device Vision
framefields runs vision models on the rendered frame and exposes the results as reactive signals. Models download lazily on first use and are cached in $FRAMEFIELDS_MODELS_DIR (default ~/.cache/framefields/models).
| Option | Model | Gives you |
|---|---|---|
enableDetection (default) |
RTMDet-Ins | COCO-80 boxes, tracked over time → vision.objects |
enableSegmentation |
RTMDet-Ins (same pass) | Soft per-instance masks → vision.masks, vision.segmentation |
enablePose |
RTMO | 17 COCO keypoints per person → vision.poseLandmarks, track.pose |
enableMatte |
Selfie Segmenter | Fast person alpha → vision.segmentation.matte |
variant: "t" | "s" | "m" trades speed for accuracy (default "s"). confidence (default 0.3) and classes: ["person", ...] filter detections.
// Whole composition: returns the reactive bundle
const vision = comp.withVision({ enableSegmentation: true, enablePose: true });
// One layer: renders through a node mode
Layer.video("assets/dancer.mp4").withVision({ mode: "matte", enableSegmentation: true });
Node modes: passthrough, mask, matte, crop, skeleton, boxes, tracking. For mask / matte / crop, matteSource: "instance" (default; any COCO class, overlapping parts such as a dress merged into the subject) or "selfie" (people only, fastest).
Choosing settings
Measured per 1280–2048 px frame on CPU (onnxruntime-node):
| Task | t |
s (default) |
m |
|---|---|---|---|
| detect / segment (one shared pass) | 150–300 ms | 200–340 ms | 420–700 ms |
| pose | ~50 ms | ~120 ms | ~280 ms |
| matte (Selfie Segmenter) | ~20 ms | ~20 ms | ~20 ms |
- Start with
"s". Use"m"for hero shots with fast motion blur or busy backgrounds;"t"mainly saves time on pose. - Enabling both
enableDetectionandenableSegmentationcosts one inference, not two. - Selfie matte only for close framing. It is tuned for a person filling much of the frame: it misses distant figures and can report a "person" on close-ups with nobody in them. Keep
matteSource: "instance"for anything else. - Raise
confidence(e.g.0.5) on abstract or stylized footage — at the default0.3the model will put loose labels ("teddy bear", "donut") on smoke, eyes and planets. - Restrict
classeswhen you only care about one thing; it also stops the subject from switching to another object when the person leaves frame.
How it behaves at render time
- One-frame delay. Vision reads the layer's previous rendered frame, so the very first frame has no results (a cutout renders transparent) and masks trail the plate by one frame. This is invisible at normal playback speed.
- Render vision frames in order.
renderVideodoes this for you. For stills, render at least two consecutive frames with the same renderer and keep the last one. Jumping straight to a later frame (or using a frame grid) cuts out pixels from whatever frame was rendered before it, which shows up as a ghosted double of the subject. - Subject choice.
mask/matte/cropuse the largest person; with no person, the most confident instance. Instances overlapping the subject and no more than twice its size are merged in (a dress, a held instrument) — large containers around it (a tunnel, a window frame) are not. - Each vision layer tracks objects independently; track ids from two layers are unrelated.
Troubleshooting
| Symptom | Fix |
|---|---|
| Part of a fast-moving garment drops out of the cutout | variant: "m", or keyBackground: true to grow the subject into connected foreground |
| Cutout is the wrong object | Set classes: ["person"] (or the class you want) |
| Faint halo around the cutout on dark backgrounds | Lower featherRadius, or raise maskThreshold (e.g. 0.6) |
| Renders offline / in CI | Pre-download with runner.preload([...]) into $FRAMEFIELDS_MODELS_DIR, or point FRAMEFIELDS_MODELS_BASE_URL at a mirror |
| Check the models themselves | await runner.preload([...]) on a fresh models directory; it downloads each model and verifies its SHA-256 (about 380 MB for all of them) |
1. Subject Sandwich ("Text Behind Subject")
Cuts out the foreground subject from footage and sandwiches typography or graphics directly behind them:
comp.addSubjectSandwich({
source: "assets/dancer.mp4",
behind: [
Layer.text("HEADLINE BEHIND", {
fontSize: 120,
fill: "#FF5A1F",
fontWeight: 900
})
],
feather: 4,
fit: "cover"
});
2. Smart Re-Framing (16:9 to 9:16 Auto-Crop)
Smoothly reframes landscape video into vertical shorts by following a tracked subject with virtual camera damping:
const vision = comp.withVision({ enableDetection: true });
comp.addSmartFraming({
source: "assets/action.mp4",
target: vision.objects.primary,
targetAspect: 9 / 16,
damping: 0.15,
leadHeadroom: 0.1
});
3. Subject Outline Glow & Neon Pulse
Strokes the segmented subject boundary with an audio-reactive contour glow:
const vision = comp.withVision({ enableSegmentation: true });
comp.addSubjectOutline(vision.segmentation.subject, {
source: "assets/character.mp4",
color: "#FF5A1F",
width: 6,
blur: 12
});
4. Tracked Region Blur
Blurs faces, license plates, or any detected class by following a live track:
const vision = comp.withVision({ classes: ["person"] });
layer.blurRegion(vision.objects.byCategory("person"), { strength: 30 });
5. Analyze Before Authoring
One-shot, ffmpeg-free report of what is in a clip (tracks, classes, mask coverage):
const report = await comp.analyzeVisionSequence("assets/street.mp4", {
tasks: ["detect", "pose"],
categories: ["person"]
});
report.tracks; // [{ trackId, category, frames: [start, end], centerPath, ... }]
Reactive Signal Modulation
Uniforms on any Effect accept reactive Signal instances instead of static values. When the signal changes, the GPU uniform buffer updates automatically without rebuilding the pipeline:
import { Layer, Blur, sineSignal, computed } from "framefields";
// Oscillating breath blur
const blurSignal = sineSignal({
frequencyHz: 0.5,
min: 0,
max: 20
});
const layer = Layer.image("assets/bg.jpg")
.withEffect(new Blur({
strength: blurSignal,
blurType: "Gaussian"
}));
Best Practices:
- Emulsion Restraint: Keep
FilmGrain(0.04–0.08) andVignette(0.2–0.45) subtle. Over-filtering looks amateurish. - Order Matters: Place tonal grading (
Curves,ColorBalance) before lens optics (Blur,DepthOfField), and apply grain (FilmGrain) as the outermost layer. - No Redundant Shaders: For static image exports, set
animated: falseonFilmGrainto preserve deterministic static renders.