〽️ Image Ops Transform
Pan, rotate, scale, flip — your whole frame batch in one GPU pass
- fill_color
- image
- mask
- image
- mask
You'd think "move and rotate an image" would be covered by core ComfyUI. It sort of is, in the most awkward way possible - geometry nodes that are fiddly to wire, resize nodes that can't rotate, nothing that treats a batch of video frames as one thing. ImageOps Transform is the version you actually want: translate, rotate, scale, and flip in a single GPU pass, applied to the whole frame batch at once, with a mask so you can move a subject without touching the background.
It's the geometric workhorse of the MajoorWaldi ComfyUI-Majoor-ImageOps pack, the one you reach for when you need a punch-in, a camera pan on generated footage, a mirror flip, or just to deskew a tilted scan before it feeds the sampler. The batch-first design is the point - the same pack philosophy ("stop treating frames like mysterious ghosts in the graph") means one node call warps every frame identically, which is exactly what you want from a camera move.
How it works
The implementation is refreshingly clean: it builds a batch of inverse affine matrices and does a single affine_grid + grid_sample pass over the whole tensor. No intermediate canvases, no image-sized temporary buffers - translate, rotate, and scale all resolve in one sampling operation, so quality stays high even when you're doing a heavy punch-in (scale 2.0 + a pan at the same time only ever resamples once).
The mask handling is where a compositor's instincts show up in the code. If you supply a mask, the node premultiplies the RGB by the mask before warping and unpremultiplies after. That sounds like implementation trivia, but it's the difference between a moving subject with a smeared colored halo at its edges and a clean one. Cheap implementations skip it; this one doesn't.
Inputs that matter
translate_x/translate_y- pixel offsets, ±4096. Positivexmoves right, positiveymoves down (screen space).rotate_deg- degrees, clockwise positive, ±180.scale- 0.01 to 8.0; 1.0 is identity. Values above 1 magnify (crop in), below 1 shrink (reveal edges).flip- none / horizontal / vertical. Cheaper than encoding a flip as a 180° rotation of the canvas.fill_mode- what happens to the holes a rotate or pan leaves behind:transparent(the default - you get an RGBA image with clear corners),mirror,stretch,expand(edge border), orcolor(solidfill_color).filter- nearest / bilinear / bicubic resampling. Bicubic for final-quality work, nearest if you're downscaling pixel art.expand- here's the trap: it's a compatibility option that does nothing. The README and the code agree - the GPU affine path always keeps the output size fixed. Don't tick it expecting the canvas to grow; it won't.
Outputs
image and mask, both warped consistently. The mask tracks the transform, so a rotating subject's alpha stays glued to it - which is how you composite the transformed result back over an untouched background.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/MajoorWaldi/ComfyUI-Majoor-ImageOps
Or search "ComfyUI-Majoor-ImageOps" in ComfyUI Manager, restart, and hard-refresh the browser (Ctrl+F5 / Cmd+Shift+R on macOS). No extra pip packages, no model downloads - just torch's affine_grid/grid_sample, which every ComfyUI already has. ComfyUI ≥ 0.13.0.
Where people get burned
Two recurring ones. First: rotate 45° with the default transparent fill and you get an RGBA image with see-through corners - and if your downstream node expects RGB, that alpha channel travels with it. Set fill_mode to mirror or color unless you actually want the transparency. Second: because the output size never grows (no expand), a 45° rotation crops the corners rather than letterboxing them - the corners are genuinely gone, so plan the composition or pad first with ImageOps PadOut.
Inputs (13)
| Name | Type | Default | Description |
|---|---|---|---|
| bypass | BOOLEAN | false | — |
| translate_x | INT | 0-4096–4096 | — |
| translate_y | INT | 0-4096–4096 | — |
| rotate_deg | FLOAT | 0.0-180–180 | — |
| scale | FLOAT | 1.000.01–8 | — |
| flip | COMBO | none | 4 options: none, horizontal, vertical, both |
| filter | COMBO | 3 options: nearest, bilinear, bicubic | |
| expand | BOOLEAN | false | Reserved. Currently inactive — the GPU affine path uses a fixed-size canvas. Kept for workflow compatibility. |
| fill_mode | COMBO | transparent | How to fill uncovered areas when scale, rotate, or translate leaves holes. |
| fill_color | COLOR | #000000 | — |
| invert_mask | BOOLEAN | false | — |
| imageopt | IMAGE,VIDEO | Images/Video input. Accepts IMAGE batches and VIDEO frame sources. | |
| maskopt | MASK | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |