ꘇ top.expression
Write a tiny shader in a text box and get an image out
- image
- controls
- image
This is the one node in the pack that feels like a different category of tool. top.expression evaluates a math expression per pixel, per channel, so you can build a procedural texture, drive a value from the clock, or do arithmetic on an input image without ever leaving the graph. Two lines of formula gets you a plasma field, a radial gradient, a scanline wipe, a ripple - the sort of thing you'd otherwise open After Effects for. The KB's read on this layer is that people leave ComfyUI for a compositor to do exactly this kind of trivial math; here's the node that keeps them in.
How it works
You write an expression in the multiline text box. It's evaluated for each of R, G, B in turn, with these variables in scope:
tseconds,x/ynormalized 0..1 across the frame,ithe pixel index,cthe channel index (0,1,2)vanda- the input value for that pixel and channel (identical;bis a constant zero, kept for symmetry)w,h- dimensions;zfor 3D patterns;piandtau__time,__beat,__bar,__bpm,__ticks,__phase,__playing,__rate,__root,__tuning- read from theperformanceJSON, so a musical transport can drive the pattern.
The default expression is 0.5 + 0.5*sin(t + x*12)*cos(y*12) - a plasma grate that animates. Functions available: sin cos tan abs sqrt floor ceil exp log min max minimum maximum pow fract clamp mix step, plus noise, perlin, simplex, value for coherent noise. ^ is accepted as a power operator. Results are clamped to 0..1 and non-finite values become zero rather than blowing up the graph.
It's a restricted evaluator, not Python: arithmetic, named variables and whitelisted functions only, expressions capped at 2048 characters and 512 syntax nodes. That's a safety boundary, and it also means no conditionals and no loops - you get one line of math.
If you connect an IMAGE, the resolution comes from the image and v/a are that pixel's actual value, so the node is a per-pixel filter. Alpha is copied from the source. If you leave the image socket empty, the node generates a texture at your width/height with alpha 1.
The other input worth knowing is controls. Drop a // @param comment into the expression and the node grows a wireable widget for it:
// @param radius = 0.35 (0.0..1.0)
// @param speed = 1.0 (0.0..4.0)
step(radius, x) * (0.5 + 0.5*sin(y*6 - t*speed))
There's no if and no comparison operators in the whitelist - which is exactly why step and clamp are there. Params become autogrow inputs named value0, value1… in declaration order, so you can wire anything numeric - a chop signal, another node's output - straight into the formula. parameters is a socketless JSON string holding their current values, mainly useful when a workflow is being driven programmatically.
Install
ComfyUI Manager → search ꘇ Genereti → install, or:
git clone https://github.com/languel/genereti.git custom_nodes/genereti
./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
Restart Comfy, refresh the browser. Requirements are a single frontend pin; no models to download. Look for ꘇ top.expression under Genereti / TOP, and search genereti - the leading mark is a glyph and it sorts oddly in the node list.
Traps
In Queue it's NumPy on the CPU. Live mode evaluates on the browser GPU; a Queue run walks the pixels in Python. At 512×512 that's fine, at 4K on a batch it is not. Generate small and scale up with top.cleanup.
Live t and Queue t are different clocks. In Live, t is the time widget plus the running graph clock, so the pattern animates while you watch. In Queue, t is just the time widget - freeze or animate it deliberately.
Errors are silent-ish. Out-of-range variables raise "Unknown variable"; non-finite results are quietly clamped to zero. If a region of your output is black, check your division.
The pack is labelled super-alpha / early classroom testing; back up workflows before updating.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| expression | STRING | 0.5 + 0.5*sin(t + x*12)*cos(y*12) | — |
| width | INT | 5121–4096 | — |
| height | INT | 5121–4096 | — |
| time | FLOAT | 0.00-100000–100000 | — |
| imageopt | IMAGE | — | |
| performanceopt | STRING | {} | — |
| offset_xopt | FLOAT | 0.00-100000–100000 | Domain offset x; zero leaves the domain unchanged |
| offset_yopt | FLOAT | 0.00-100000–100000 | Domain offset y; zero leaves the domain unchanged |
| offset_zopt | FLOAT | 0.00-100000–100000 | Domain offset z; zero leaves the domain unchanged |
| offset_topt | FLOAT | 0.00-100000–100000 | Domain offset t; zero leaves the domain unchanged |
| parametersopt | STRING | {} | — |
| controlsopt | COMFY_AUTOGROW_V3 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |