Eses Image Offset
The 'move this and don't desync the mask' node — also a free seam checker
- image
- mask
- IMAGE
- MASK
- offset_x
- offset_y
- info
Eses Image Offset does one thing and does it quietly: it shifts an image, and any mask you connect, by offset_x/offset_y pixels. No models, no API, no VRAM hit. It's the kind of node you don't think about until you need it, and then you're annoyed you didn't install it sooner.
The case that actually sells it is seamless textures. Generate a tile, run it through with wrap_around on and an offset of half the tile's width and height, and the seam jumps dead center where you can see it. That's the classic "offset + inpaint the seam" move for tiling backgrounds. Second case: outpainting and compositing, where you want to slide a subject before blending it in. Third - the reason to pick this over moving things in Photoshop - is that the image and its mask move in lockstep. If you've got a segmentation or inpaint mask tied to an image and you shift the image, this shifts the mask with it, so they never desync.
How it works
Mechanically it's straightforward PIL math, and the code is honest about it. With wrap_around set to On, it uses ImageChops.offset, so content that exits one edge wraps back in from the opposite side - pure tiling. With it Off, it builds a new canvas filled with your fill_color and pastes the image at the offset, so newly exposed edges show the fill. The mask gets the same treatment.
fill_color is a string like 0,0,0 (RGB) or 255,255,255,128 (RGBA), or a 6/8-digit hex. Default is black. One thing to know: if you connect a mask, the masked-out regions get composited over fill_color - the output image is flattened to RGB, so don't expect transparency out of this node.
Inputs and outputs that matter
The four you'll actually touch:
- offset_x / offset_y - how far to shift, from −4096 to 4096 pixels.
- wrap_around - On wraps for tiling, Off fills the exposed edges.
- fill_color - what the exposed areas become when wrap is Off.
- invert_mask_output - flips the mask's polarity on the way out.
Outputs are IMAGE, MASK, the applied offset_x/offset_y as ints, and an info string along the lines of Offset: (0, 0), Size: 1024x1024, Wrapped: Off. That info output is handy for dropping into a text display to confirm what actually got applied, or for reusing the offset downstream. The output mask follows ComfyUI's white-is-masked convention - if it ever looks flipped, flip the invert toggle before blaming the node.
Install
ComfyUI Manager (search "ComfyUI-EsesImageOffset") or the manual route:
cd ComfyUI/custom_nodes
git clone https://github.com/quasiblob/ComfyUI-EsesImageOffset.git
Then restart ComfyUI - it appears under Eses Nodes/Image. There's no requirements.txt and no model download; it only needs torch, Pillow and numpy, all of which ship with ComfyUI already. About as dependency-light as custom nodes get.
Gotchas
Wire in neither image nor mask and it doesn't error - it returns a dummy 64×64 output with the info string "No Image or Mask Input". So if something downstream suddenly looks tiny, check you actually connected an image. The code path exists to keep the node from crashing, not as a feature.
The license is a custom "My ComfyUI Nodes License": free to use in your workflows (personal or company), but you can't redistribute modified copies. Fine for running it; read the LICENSE if you're thinking of forking. And the author's own README warns they may not have time to act on bug reports - treat this as "works as-is" rather than an actively evolving project.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| offset_x | INT | 0-4096–4096 | — |
| offset_y | INT | 0-4096–4096 | — |
| wrap_around | COMBO | 2 options: Off, On | |
| fill_color | STRING | 0,0,0 | — |
| invert_mask_output | COMBO | 2 options: No, Yes | |
| imageopt | IMAGE | — | |
| maskopt | MASK | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |
| MASK | MASK | — |
| offset_x | INT | — |
| offset_y | INT | — |
| info | STRING | — |