colorx reference

An OpenFX re-creation of Nuke's Color > Math > Expression node — per-channel math, parsed and evaluated at run time.
Hosts: Flame 2021+ · Resolve · Nuke · Natron GPU: Metal · CUDA · CPU everywhere 20 built-in presets

# Overview

A faithful clone of Nuke's Expression node: four per-channel text fields (r= g= b= a=) hold math that is compiled once per render and evaluated per pixel. It runs as a filter over its source clip and loads in Autodesk Flame (2021+), DaVinci Resolve / Fusion, Nuke, Natron, and any other OpenFX host. The node appears under FORGE / color / colorx (labelled colorx).

The panel has four pages: Channels (the r/g/b/a expressions), Variables (a block of name = formula statements), Constants (the k1..k4 knobs + Reference Colour), and Output (Mix, Clamp, and the Preset pulldown).

# Install

Grab a prebuilt bundle from the latest release (macOS + Linux), or build from source.

  1. Unzip → colorx.ofx.bundle.
  2. Copy it to the OFX plugin path (root-owned — use sudo): /Library/OFX/Plugins/ on macOS, /usr/OFX/Plugins/ on Linux.
  3. macOS only — clear the download quarantine, or the host silently skips it:
    sudo xattr -dr com.apple.quarantine "/Library/OFX/Plugins/colorx.ofx.bundle"
  4. Restart the host — OFX plugins are scanned at launch.
Linux: the release ships two builds — a RHEL / Flame one (needs only glibc 2.29, loads on Flame's RHEL boxes) and a modern one (Ubuntu, newer glibc). On a RHEL-based Flame workstation, take the RHEL build.
Upgrading from the old “Expression” node? This release renames the node to colorx (menu label + grouping only) — the plugin identifier is unchanged, so it's a drop-in update: existing comps keep working, no re-linking. Just replace the old Expression.ofx.bundle with colorx.ofx.bundle (don't leave both installed — they share one identifier).

# The pixel model

An expression sees only the current pixel — its r g b a and its coordinates. That makes it perfect for generators and per-pixel grades, but it cannot blur, sharpen, or sample neighbouring pixels.

  • x y are pixel coordinates, origin bottom-left (as in Nuke).
  • cx cy are centred and aspect-preserved: 0,0 at the centre, ±1 at the left/right edges — use these for resolution-independent patterns.
  • Channels are normalised 0..1. For byte/short clips they are scaled on the way in and written back at the clip's bit depth (clamped); float clips pass through unclamped (HDR-safe).
  • An empty channel evaluates to 0; the defaults r g b a are a pass-through.

# Predefined variables

Available to every expression, alongside any names you define.

NameMeaning
r g b aInput channels, normalised 0..1.
x yPixel coordinates, origin bottom-left.
cx cyCentred, aspect-preserved coords: (0,0) centre, ±1 at L/R edges.
width heightImage size (region of definition).
frame · tCurrent frame / render time (continuous — can be fractional). t is the Nuke-parity alias.
pi eMath constants (also callable as pi() e()).
k1 k2 k3 k4The Constants-page knobs (k1 defaults to 1, rest 0). Animatable.
ref.r ref.g ref.bThe Reference Colour swatch (RGB), default 0.
your aliasesA name you give a knob (e.g. call k1 "gamma" → gamma resolves to it).
your variablesAny name = formula you define in the Variables block.
Variables vs constants. A variable is computed (a name = formula in the Variables block, derived from the image and evaluated in order before the channels). A constant is dialled (a named knob + slider). Both are usable anywhere in the channels. Use # to start a comment; separate statements with ; or a newline (Flame collapses newlines, so prefer ; there).

# Knobs & output

ControlEffect
k1 … k4Animatable scalar knobs. Reference by kN or by the alias you name them. Range is unbounded; display range −1…1.
Reference ColourAn RGB swatch, read as ref.r ref.g ref.b.
MixBlends original image (0) ↔ expression result (1).
Clamp OutputClamps the result to 0..1 (applied before Mix).
PresetA pulldown that loads a whole effect (channels + variables + knob values). See the gallery.
Expression syntaxA button that opens this reference page in your web browser.

# Operators

GroupOperators
Arithmetic+ - * / %   ^ (power, right-associative)
Comparison< <= > >= == !=
Logical&& || !
Conditionalcond ? a : b (ternary)

Precedence follows C, with one deliberate choice: ^ binds tighter than unary minus, so -2^2 == -4.

# Function library

Matches Nuke's Expression function set. Type to filter.

SignatureGroupDescription
sin(x) cos(x) tan(x)trigSine, cosine, tangent (radians).
asin(x) acos(x) atan(x)trigInverse trig (radians).
atan2(y, x) / atan(y,x)trigTwo-argument arctangent — angle of the vector (x,y), full −π…π range.
sinh(x) cosh(x) tanh(x)trigHyperbolic sine, cosine, tangent.
exp(x)exp/loge raised to x.
log(x) log10(x) log2(x)exp/logNatural, base-10, base-2 logarithms.
pow(x, y)exp/logx raised to y (same as x^y).
pow2(x)exp/logx*x.
sqrt(x)exp/logSquare root.
ldexp(x, e)exp/logx · 2^e.
exponent(x) mantissa(x)exp/logSplit a float into its base-2 exponent and mantissa.
abs(x) / fabs(x)roundAbsolute value.
floor(x) ceil(x)roundRound down / up to an integer.
trunc(x) / int(x)roundTruncate toward zero.
round(x) rint(x)roundRound to nearest integer.
sign(x)round−1, 0, +1 by sign of x.
fmod(x, y)roundFloating-point remainder (C-style — sign of the dividend).
hypot(x, y)geomEuclidean length √(x²+y²) — distance from origin.
radians(x) degrees(x)geomConvert between degrees and radians.
min(…) max(…)rangeSmallest / largest — variadic (max(r,g,b) works).
clamp(x)rangeClamp to [0,1] (1-arg form).
clamp(x, a, b)rangeClamp to [a,b].
lerp(a, b, t) / mixinterpLinear blend: a at t=0, b at t=1.
step(edge, x)interp0 if x<edge, else 1.
smoothstep(a, b, x)interpSmooth Hermite ramp from 0 to 1 across [a,b].
noise(x[, y[, z]])noiseClassic Perlin gradient noise, signed ~[−1,1]. 1–3 dimensions.
random(x[, y[, z]])noiseDeterministic per-cell hash in [0,1] — feed pixel coords for per-pixel values.
fBm(x, y, z, oct, lac, gain)noiseFractal Brownian motion — summed noise octaves (fractal detail).
turbulence(x, y, z, oct, lac, gain)noiseLike fBm but on |noise| — billowy / veined look.
from_sRGB(x) to_sRGB(x)colourConvert between linear and sRGB.
from_rec709(x) to_rec709(x)colourConvert between linear and Rec.709.
from_byte(x) to_byte(x)colourConvert between 0..1 and 0..255.
Noise parity. noise() is tableless Perlin (Gustavson/Ashima), computed in float so it is identical across the CPU, CUDA and Metal back-ends. It is not a bit-exact match to Nuke's own permutation. For an integer-frame seed that doesn't shimmer within a frame, wrap time as floor(t).

# Preset gallery

Every preset below is built into the Preset pulldown — picking one fills the channels, the Variables block, and the suggested k1..k4. They're also copy-paste ready. k1..k4 are the live sliders; ref is the Reference Colour.

Technical

UV (ST) pass

Red = horizontal, green = vertical coordinate pass. +0.5 samples each pixel's centre.

r = (x+0.5)/width
g = (y+0.5)/height
b = 0
a = 1
Technical

Radial gradient

White at centre → black. k1 = radius (try 1.0).

vars: d = hypot(cx,cy)
r = clamp(1 - d/k1)
g = clamp(1 - d/k1)
b = clamp(1 - d/k1)
a = 1
Technical

Angle sweep

A 0→1 ramp swept around the centre (conical gradient).

vars: ang = (atan2(cy,cx) + pi) / (2*pi)
r = ang
g = ang
b = ang
a = 1
Gradient

Rainbow (cosine palette)

A smooth horizontal rainbow.

vars: u = x/width
r = 0.5 + 0.5*cos(6.2831853*(u + 0.00))
g = 0.5 + 0.5*cos(6.2831853*(u + 0.33))
b = 0.5 + 0.5*cos(6.2831853*(u + 0.67))
a = 1
Gradient

Reference → white

Left = Reference Colour, right = white. Pick a colour to drive it.

vars: u = x/width
r = lerp(ref.r, 1, u)
g = lerp(ref.g, 1, u)
b = lerp(ref.b, 1, u)
a = 1
Pattern

Checkerboard

k1 = cell size in pixels (try 32).

vars: c = fmod(floor(x/k1) + floor(y/k1), 2)
r = c
g = c
b = c
a = 1
Pattern

Stripes

Vertical bars. k1 = period in pixels (try 32).

vars: f = x/k1 - floor(x/k1)
r = step(0.5, f)
g = step(0.5, f)
b = step(0.5, f)
a = 1
Pattern

Concentric rings

k1 = ring frequency (try 6).

vars: d = hypot(cx,cy)
r = 0.5 + 0.5*sin(d*k1*6.2831853)
g = 0.5 + 0.5*sin(d*k1*6.2831853)
b = 0.5 + 0.5*sin(d*k1*6.2831853)
a = 1
Pattern

Flower / rose

A petalled mask. k2 = number of petals (try 5).

vars: ang = atan2(cy,cx); rad = hypot(cx,cy);
      m = step(rad, 0.4 + 0.3*cos(ang*k2))
r = m
g = m
b = m
a = 1
Pattern · animated

Plasma

Retro demoscene plasma — animates on t. k1 = scale (try 3).

vars: v = sin(cx*k1*3) + sin(cy*k1*3 + t*0.1)
      + sin((cx+cy)*k1*2)
      + sin(hypot(cx,cy)*k1*4 - t*0.13)
r = 0.5 + 0.5*sin(v)
g = 0.5 + 0.5*sin(v + 2.0944)
b = 0.5 + 0.5*sin(v + 4.1888)
a = 1
Noise · animated

Clouds (fBm)

Soft drifting clouds. k1 = scale (try 3).

vars: n = fBm(cx*k1, cy*k1, t*0.05, 5, 2, 0.5);
      c = clamp(n*0.5 + 0.5)
r = c
g = c
b = c
a = 1
Noise

Marble

Veined marble from turbulence. k1 = scale (3), k2 = vein freq (2).

vars: tu = turbulence(cx*k1, cy*k1, t*0.05, 5, 2, 0.5);
      m = 0.5 + 0.5*sin((cx + tu*2)*6.2831853*k2)
r = m
g = m
b = m
a = 1
Noise

Colorized noise

Perlin pushed through the cosine palette. k1 = scale (try 3).

vars: n = fBm(cx*k1, cy*k1, t*0.05, 5, 2, 0.5)*0.5 + 0.5
r = 0.5 + 0.5*cos(6.2831853*(n + 0.00))
g = 0.5 + 0.5*cos(6.2831853*(n + 0.33))
b = 0.5 + 0.5*cos(6.2831853*(n + 0.67))
a = 1
Noise · on input

Film grain

Per-pixel grain on the image. k1 = amount (try 0.1). Offsets decorrelate the channels.

r = r + (random(x,    y,    t) - 0.5)*k1
g = g + (random(x+11, y,    t) - 0.5)*k1
b = b + (random(x,    y+7,  t) - 0.5)*k1
a = a
Grade · on input

Gamma

k1 = gamma (try 2.2).

r = pow(r, 1/k1)
g = pow(g, 1/k1)
b = pow(b, 1/k1)
a = a
Grade · on input

Saturation

k1: 0 = greyscale, 1 = unchanged, >1 = boosted.

vars: lum = 0.2126*r + 0.7152*g + 0.0722*b
r = lum + (r - lum)*k1
g = lum + (g - lum)*k1
b = lum + (b - lum)*k1
a = a
Grade · on input

Duotone

Shadows → Reference Colour, highlights → white, by luminance.

vars: lum = 0.2126*r + 0.7152*g + 0.0722*b
r = lerp(ref.r, 1, lum)
g = lerp(ref.g, 1, lum)
b = lerp(ref.b, 1, lum)
a = a
Grade · on input

Posterize

Quantise to bands. k1 = number of levels (try 6).

r = floor(r*k1)/k1
g = floor(g*k1)/k1
b = floor(b*k1)/k1
a = a
Grade · on input

Vignette

Darken the edges. k1 = strength (try 0.7).

vars: d = hypot(cx,cy); v = clamp(1 - d*k1)
r = r*v
g = g*v
b = b*v
a = a
Grade · on input

Scanlines (CRT)

k1 = line frequency (try 1.5).

vars: s = 0.6 + 0.4*sin(y*k1)
r = r*s
g = g*s
b = b*s
a = a

# Hosts & GPU

The same expression runs on whichever back-end the host supports; results match across all of them.

DaVinci Resolve Metal

GPU path on macOS — pixel-verified. CUDA on Linux/NVIDIA.

Autodesk Flame CPU

Multithreaded CPU path. Metal is gated off — Flame's macOS OFX host crashes on a Metal-advertising node.

Nuke · Natron · Fusion CPU

Multithreaded CPU evaluation; each expression is compiled once per render.