ImageMask_AlignRestore
Cut the padding back off without resampling a thing
- image
- mask
- align_data
- image
- mask
This is the second half of the pair, and it barely does anything - which is exactly why it's worth having. ImageMask_Align padded your 1000x1400 up to 1024x1408 so a model would accept it. ImageMask_AlignRestore takes the result and cuts it back to 1000x1400. By slicing, not scaling. Nothing is resampled, and the pixels that survive are the same pixels that went in.
If you're the type who pads, generates, then reaches for ImageScale to squash the output back down - stop. Scaling 1024x1408 down to 1000x1400 is a lossy filter pass over the whole image to recover a border you deliberately added. This node takes the crop.
How it works
It reads the original width and height out of the align_data the align node produced, computes centred crop offsets from the tensor it receives - (current_width - original_width) // 2 for the left edge, same vertically - clamps them at zero, and slices. It's the inverse of the padding maths, which is why it lands exactly on the original.
Note what it doesn't depend on: the stored target size. Offsets come from the incoming tensor, so it doesn't matter which multiple you aligned to, or whether the dimensions changed again upstream, as long as the original is still centred in the frame.
Inputs and outputs
- image - the padded/generated result. The only input marked required.
- mask (optional) - if you padded a mask and want it cropped back too, wire it here.
- align_data (optional) - the wire from ImageMask_Align. The UI calls it optional. The code does not.
That's the trap in this node, and it's worth being blunt: the function reads align_data first and indexes straight into it, so leaving it unconnected doesn't degrade gracefully. You get TypeError: 'NoneType' object is not subscriptable and a red node. Treat it as required, every time.
Outputs are image and mask. The image goes wherever your result belongs - SaveImage, PreviewImage, an upscale group, or the ComfyPanel panel's output node. The mask follows one rule copied from the align side: a mask that's all zeros, or that arrives as a 64x64 tensor, is the "there is no mask" placeholder and you get back a 1x64x64 zero tensor. That's a sentinel, not a mask - don't wire it into a composite and expect anything sensible.
Why bother with the round trip
"Close enough" compounds. Crop-and-stitch replaced plain masked inpainting as the standard approach precisely because any step that re-touches your pixels - a resample, a VAE decode - degrades them, and you notice it most across a chain of edits. Same logic applies to sizing: pad, run, crop back, and the untouched region is byte-for-byte what you started with.
The pack leans on this pattern hard. The author ships a whole family of process-and-restore pairs (CropByMask/CropByMaskRestore, ImageConcat/ImageConcatRestore, ImageMask_Scale/ImageMask_ScaleRestore), and this is the geometry member of that family.
Installing it
Search ComfyPanel in ComfyUI Manager and install it, or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/Ginolazy/ComfyPanel
cd ComfyPanel && pip install -r requirements.txt
then restart. Both nodes live in the pack's image.py, which the loader skips wholesale if opencv-python, kornia or scipy aren't importable - so if they don't appear, run the pip command rather than reinstalling. ComfyPanel is mostly a Photoshop UXP plugin and the README is mostly about that; the node library is MIT backend code in /modules/ and needs no Photoshop at all.
Common issues
TypeError: 'NoneType' object is not subscriptable on run. align_data isn't connected. That's the number one way people break this node.
Your "restored" output still has black bars. You saved or previewed before the restore node, or the restore sits on a branch nothing consumes. Neither node in the pair is an output node, so if nothing downstream needs the result, ComfyUI skips it and the padded tensor sails straight into SaveImage.
The crop is off-centre, or smaller than expected, with no error. Offsets are clamped at zero instead of complaining, so if whatever produced your image resized rather than padded - Kontext's internal stretching is the usual culprit - the centre crop lands on the wrong region. The pack has a dedicated answer for that case (FluxKontextImageCompensate / FluxKontextImageRestore), because Kontext's stretch isn't the same problem as divisibility. Downscaling the padded image below the original size gets you a silently smaller crop for the same reason.
One align_data per aligned image. In a batch of separate pad/generate/crop operations, swapping wires gives you a centre crop computed from the wrong original dimensions. It won't look like an error. It'll just be subtly wrong, which is worse.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| maskopt | MASK | — | |
| align_dataopt | ALIGN_DATA | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |