ꘇ top.transform
Fractions, Not Pixels (and That's the Whole Trap)
- image
- image
Move, scale, rotate, mirror. Every image tool does it, most of them make you leave the graph to do it, and the ones that don't are usually pixel-based - you type 128 and hope you remembered the resolution.
ꘇ top.transform works in fractions of the image instead. translate_x 0.1 moves the picture ten percent of the frame width, whatever that width happens to be. Scale is centered, rotation is in degrees, and everything outside the original frame comes back transparent. Output resolution is unchanged from input.
That resolution-preserving bit is the design decision to internalize. This is a destination-space transform of a finished texture, not a resize. If you want to actually change the pixel dimensions, ꘇ top.cleanup takes explicit width and height with resize quality stages. If you want to crop a region in source space and stretch it to full size, that's ꘇ top.crop. If you want a projective four-point mapping - texture onto an angled surface - it's ꘇ top.cornerpin. top.transform is the plain one, and plain is what you reach for ninety percent of the time.
What's on the node
- translate_x / translate_y - −4 to 4, fractions of the image, default 0. Positive x moves right, positive y moves down. Range goes to ±4 frames, so "way off screen" is a legal, silent state.
- scale - 0.001 to 8, default 1, applied about the center. Below 1 shrinks.
- rotate - −360 to 360 degrees, clockwise.
- flip_x / flip_y - booleans. Mirror horizontally or vertically.
Six controls, one IMAGE in, one image out. Wire it before a preview, into ꘇ top.composite as either input, or into the front of a feedback setup.
How it works
The mechanism is inverse sampling: for every output pixel, the node computes which source coordinate it should read from, then samples. Sampling is done on premultiplied RGBA and un-premultiplied afterward, so transparent regions interpolate as nothing rather than as black - which is exactly why a rotated cutout doesn't grow a dark fringe along its edges. Outside the source is transparent, not clamped or smeared.
There's also an identity shortcut worth knowing about. If translate is zero, scale is 1 and rotate is a multiple of 360, it skips resampling entirely and flips pixels by index instead. That means a pure flip is lossless and leaves low-alpha pixels exactly as they were - no resample, no softening, no alpha drift on a 0.02-alpha edge. The moment you add a fractional rotation, you're resampling everything, and that's the fair trade.
Live mode runs the same transform as a browser GPU shader; Queue runs the torch version. Both need a recent ComfyUI; Live needs a WebGPU-capable browser.
What people actually use it for
Repositioning, mostly. A plate that's slightly off-center in a larger canvas, a logo you're compositing into a corner, a source you want on the left third so a feedback smear has room to re-enter on the right. Fractions save you arithmetic every time the resolution changes.
Symmetry, too - flip_x on one branch and composite the two halves, and you've built a butterfly pattern without touching a model. Scaling down and back up inside a feedback chain is the classic way to get zoom-like drift.
It's also the cheap way to fake movement for a still: animate scale slowly and a photograph breathes.
Installing it
One folder, one clone:
cd ComfyUI/custom_nodes
git clone https://github.com/languel/genereti.git genereti
./.venv/bin/python -m pip install -r genereti/requirements.txt # ComfyUI's Python
Windows PowerShell: .\.venv\Scripts\python.exe. Windows Portable: python_embeded\python.exe - never global pip. The requirements file is effectively a frontend floor; torch, NumPy and Pillow come from Comfy, and there are no model downloads. Manager install by searching "Genereti" works when the registry listing is live; git is the version that always works. Restart, then hard-refresh the browser page.
If you previously ran Genereti as the old split packs (genereti_comfy_texture and friends), move those folders out of custom_nodes first - the pack detects the duplicate and refuses to load rather than half-registering.
Where people get burned
- Typing pixels.
translate_x: 100doesn't move it 100 pixels, it moves it a hundred frames. This is the single most common mistake with this node. - A transparent hole where the image used to be. Expected - outside is transparent. Composite over a background (
ꘇ top.compositeresizes B to A for you) or scale up so the frame is covered. - Nothing visible at all. With a big enough translate, the image is off-screen and the node doesn't complain. Reset to 0.
- Rotating softened the edges. Any rotation that isn't a multiple of 360 resamples, and resampling touches your alpha. Fine for most work, visible on hard-edged cutouts.
- Live looks slightly crunchier than Queue. Live intermediates are 8-bit; Queue stays in float. Subtle at small angles, less subtle if you stack several transforms.
- The pack's own warning. Super alpha, classroom release, changing interfaces. Back up workflows before updating, and don't build a client deliverable on this node's parameter names yet.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| translate_x | FLOAT | 0.00-4–4 | — |
| translate_y | FLOAT | 0.00-4–4 | — |
| scale | FLOAT | 1.000.001–8 | — |
| rotate | FLOAT | 0.00-360–360 | — |
| flip_x | BOOLEAN | false | — |
| flip_y | BOOLEAN | false | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |