API reference

Every public class, method, and event in @codexo/exojs. Generated from source.

C

classLutFilter

@codexo/exojs / rendering / stable

A Filter that maps every pixel of the input through a Look-Up Table texture. Two storage modes: - **RGB 1D LUT** (`N×1`, default `N=256`): three independent per-channel curves - `R' = lut(src.r).r`, `G' = lut(src.g).g`, `B' = lut(src.b).b`, alpha untouched. Used for levels/curves-style grading, colour ramps, posterisation and animated recolouring. - **3D LUT** (`N²×N` unwrapped cube): indexed by the full source RGB with trilinear interpolation between slices. Used for cinematic colour grading, tone mapping, film stock emulation, accessibility filters (color-blindness simulation), and similar standard colour-pipeline tasks. `N=17` matches DaVinci/OBS export defaults. A 1D LUT cannot express cross-channel mixing (that is what the 3D mode is for), and it is not an indexed-colour palette lookup: each channel only ever sees its own curve. ## Quick start ```ts // Color-graded look from a 17³ LUT exported by your DCC/grading tool: const lut = LutFilter.fromImage(myLutImage); // 289×17 PNG const filter = new LutFilter({ mode: '3d', size: 17 }).setLut(lut); sprite.filters = [filter]; // Animated per-channel curves - shift the ramp every frame: const ramp = LutFilter.identityLut1D(); const filter = new LutFilter({ mode: 'rgb1d' }).setLut(ramp); // Replace `ramp.source` per frame with a shifted copy. ``` Runs on a ShaderFilter carrying both a GLSL and a WGSL source, so it works on either backend without the caller choosing one.

4
props
8
methods
0
events
Import
import { LutFilter } from '@codexo/exojs'

A Filter that maps every pixel of the input through a Look-Up Table texture.

Two storage modes: - **RGB 1D LUT** (`N×1`, default `N=256`): three independent per-channel curves - `R' = lut(src.r).r`, `G' = lut(src.g).g`, `B' = lut(src.b).b`, alpha untouched. Used for levels/curves-style grading, colour ramps, posterisation and animated recolouring. - **3D LUT** (`N²×N` unwrapped cube): indexed by the full source RGB with trilinear interpolation between slices. Used for cinematic colour grading, tone mapping, film stock emulation, accessibility filters (color-blindness simulation), and similar standard colour-pipeline tasks. `N=17` matches DaVinci/OBS export defaults.

A 1D LUT cannot express cross-channel mixing (that is what the 3D mode is for), and it is not an indexed-colour palette lookup: each channel only ever sees its own curve.

## Quick start

```ts // Color-graded look from a 17³ LUT exported by your DCC/grading tool: const lut = LutFilter.fromImage(myLutImage); // 289×17 PNG const filter = new LutFilter({ mode: '3d', size: 17 }).setLut(lut); sprite.filters = [filter];

// Animated per-channel curves - shift the ramp every frame: const ramp = LutFilter.identityLut1D(); const filter = new LutFilter({ mode: 'rgb1d' }).setLut(ramp); // Replace `ramp.source` per frame with a shifted copy. ```

Runs on a ShaderFilter carrying both a GLSL and a WGSL source, so it works on either backend without the caller choosing one.

Constructors1
Methods8
Execute one filter pass: sample from input, write the result to output. Both textures are bounds × resolution texels - NOT the drawable's logical bounding box. Any parameter a subclass expresses in pixels (a blur radius, a displacement amount) is in LOGICAL units by convention and must be multiplied by resolution before it is used as a texel offset; otherwise the effect shrinks by 1/resolution on a HiDPI display. Parameters expressed as a fraction of the target (or as pure colour maths) need no adjustment. The engine always passes resolution. It is optional for the hand-rolled case - a post-processing chain that creates its own RenderTextures and calls apply directly - where the textures are whatever size the caller made them and 1 is the honest answer.
destroy(): void
Release any GPU-side resources held by this filter (uniform buffers, pipelines, intermediate textures). The base drops the attachment list; subclasses with GPU state (BlurFilter, ColorMatrixFilter) override and call super.destroy().
The logical bounds this effect can produce from the logical bounds it is given - the contract that lets an effect change a drawable's visual extent instead of being clipped by the geometry it was captured from. Both rectangles are in the capture domain's LOGICAL units, the same ones RenderNode.getBounds reports. They are not device pixels: the target a pass runs against is separately allocated at bounds × resolution texels, so an expansion of 8 stays 8 logical units at every pixel ratio. The default is the identity - an effect that only recolours what it is given (a colour matrix, a LUT) needs no override. An effect that reaches outside its input (a blur, a glow) must declare that reach, and one that reaches asymmetrically (a drop shadow) may move the edges independently: ts public override getOutputBounds(input: ReadonlyRectangle, output: Rectangle): void { output.set(input.x - this.radius, input.y - this.radius, input.width + this.radius * 2, input.height + this.radius * 2); } In a CHAIN each filter is asked in turn, with the previous filter's output as its input, and the barrier's capture domain is the union of the source bounds and every stage's answer. A bounds-REDUCING effect is therefore represented - the domain simply keeps the room its predecessors needed, so no pass is ever clipped by a target smaller than what it declared. input and output are never the same object, so an implementation may read input freely while writing output. Called once per frame for every filtered node, so it must not allocate.
invalidate(): void
Tell every node this filter is attached to that its rendered output is out of date. Call it after mutating anything that changes what the filter draws or how far it reaches - the stock filters do this from their own setters. Without it a cached or retained representation of the owning node keeps replaying the result the filter produced before the change.
Wrap an image element as a LUT texture with the right sampler defaults (linear filtering, clamp-to-edge, no mipmaps). Accepts the standard LUT image conventions exported by Photoshop, DaVinci Resolve, OBS, and similar tools - typically a 289×17 or 1024×32 strip for 3D LUTs, or a 256×1 strip for 1D.
Build a 1D identity LUT (N×1 texture with a smooth grayscale gradient). Because all three channels carry the same ramp, applying this LUT in 'rgb1d' mode is an exact no-op for ANY colour. Mutate texture.source to derive curves, posterization, contrast pushes, per-channel ramps, etc.
Build a 3D identity LUT (N²×N unwrapped cube texture). Applying this LUT is a no-op for any RGB input. Use as a starting point for procedural grading or as a fallback when a real LUT image hasn't loaded yet.
Properties4
resolution: TargetResolution
Resolution this filter's render targets are rasterized at, in device pixels per logical unit. 'inherit' (the default) matches the surface the result is composited into, so a filtered subtree stays as sharp as its surroundings on a HiDPI display. Lower it for a filter whose output is low-frequency anyway - a heavy blur at 0.5 costs a quarter of the fragments and is hard to tell apart. A filter CHAIN shares one target size, so the whole chain runs at the lowest resolution any of its filters asks for. ts const blur = new BlurFilter({ strength: 4 }); blur.resolution = 0.5; // half-resolution blur, quarter the fill cost
The LUT mode this filter was constructed with.
size: number
The cube edge size (3D only). For 1D this returns the constructor-time size hint.
Source