ꘇ top.displace
Warp an image with a noise map (and why your map needs to hover near 0.5)
- image
- displacement
- image
Displacement is the operation that turns a static image into something that looks alive: every pixel is fetched from somewhere else, with the "somewhere else" supplied by a second image. top.displace reads the red and green channels of a displacement map, subtracts a centre value, and shifts each source pixel by that amount. Feed it noise and you get organic warp; feed it another frame's optical flow and you get real motion-driven distortion; feed it its own output through feedback and you get the classic smeared, painterly trail loop the pack ships a demo for.
How it works
The map's red channel moves pixels horizontally and green moves them vertically. Both are offset by center (default 0.5), so a mid-grey map means "don't move anything" - that's the key intuition. amount_x and amount_y are fractions of the source dimensions, so 0.02 shifts by 2% of the width, and the ±1 range lets you throw pixels clean off the canvas. Anything sampled outside the frame comes back transparent, and sampling is premultiplied, so displaced cut-outs don't grow dark halos at the edges.
The author's own example chain is a nice one: "Bloomed feedback can drive its own displacement" - blur a feedback output to soften it, then use it as the map for the next displacement pass, and the warp smears in a direction that follows what's already in the frame.
The inputs that matter
image- the thing being warped. Its resolution is the output resolution, and the map is matched to it.displacement- the map. R and G, aroundcenter. A third channel is ignored; alpha is ignored.amount_x/amount_y- kick in small increments. 0.02 is the default and it's already a visible warp at 512px.center- set this to whatever your map's neutral value is. For a noise field mapped to 0..1, 0.5 is right. For a gradient or an edge image, it isn't.
Output: one image. Chain it into top.composite (to screen the warp over the original), top.feedback (the trail loop), or top.convolve for a blur pass that feeds back in.
Install
ComfyUI Manager → search ꘇ Genereti → install, or manually:
git clone https://github.com/languel/genereti.git custom_nodes/genereti
./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
Restart and refresh. One frontend dependency, no models, no services. The node shows as ꘇ top.displace in Genereti / TOP; search genereti, because the leading glyph sorts to the bottom of the node list.
Traps
Your map probably isn't centred. This is the number one confusion. top.noise outputs 0..1 with an average around 0.5, so center 0.5 is correct there. But an edge image, a top.channels output routed from a constant, or a luminance ramp will have a very different mean, and instead of warping you'll get a uniform shove in one direction. If the whole image slides sideways instead of warping, center is wrong.
Match the map's resolution. The map is resampled to the source, so a 64px noise field stretched to 1024px becomes a smooth, low-frequency smear. Generate the noise at the size you're displacing.
A displacement map is not a normal map. You're feeding offsets, not direction vectors in tangent space. If you wire up something that looks like a normal map you'll get something that looks like a colour cast.
Live vs Queue. Live keeps the whole chain on the browser GPU (WebGPU browser required); Queue runs the PyTorch version. Live textures are 8-bit, Queue floats.
The pack is labelled super-alpha, early classroom testing - back up your workflows before updating.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| displacement | IMAGE | — | |
| amount_x | FLOAT | 0.02-1–1 | — |
| amount_y | FLOAT | 0.02-1–1 | — |
| center | FLOAT | 0.500–1 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |