AM Reformat
The resize node that thinks like a compositor
- image
- mask
- video
- image
- mask
- width
- height
- info
- video
Stock ComfyUI has an ImageScale node. AM Reformat is not that. It's the resize node for people who've reformatted a shot in Nuke or Flame: fit vs. fill vs. crop vs. distort, named broadcast presets, five real pixel filters, and alpha that stays honest through the whole thing. If you're building video pipelines where frames have to land at exactly 1920×1080 or 2048×1556, you'll use this constantly.
It's part of comfyui-am-vfx-tools ("AM VFX Tools" category), Adrian Meyer's 13-node VFX toolkit. The reformat core (_core/reformat.py) is on the hot path of every I/O node in the pack - AM Read Image, AM Write Image, AM Read/Write Video all carry a reformat step - so this is the same engine, exposed standalone.
How it works
OpenCV-backed, four-channel-aware. Three modes:
off- passthrough.scale- uniform scale by a factor (0.01–16), output isround(input × scale).to_box- hit a target W×H from a preset ortarget_width/target_height.
In to_box you also pick how the input maps into the box: width/height (scale to that edge), fit (letterbox), fill (crop the overflow), distort (stretch, changes aspect), or none (no scale, center it). Presets run from HD 1920×1080 through 2K DCI 2048×1080, 4K DCI, and squares.
The detail that shows the VFX thinking: cropped-away and padded regions come out transparent, not black. A 3-channel source gets promoted to RGBA with alpha 0 in the dead areas, so downstream compositing stays clean instead of you discovering black bars in your alpha later.
The filters matter more than you think
Five filters, and the tooltips are worth reading before you pick:
impulse- nearest-neighbor. Exact pixels, for masks.linear- bilinear, cheap and smooth.cubic- bicubic, the safe default.Lanczos4- sharpest, for final stills.area- mean pooling, the right choice for downscaling.
The one people miss: cubic and Lanczos overshoot. Their convolution kernels have negative lobes, so a bright pixel next to a dark edge produces values below the source minimum - sometimes negative. The pack clamps that ringing (only the overshoot, not your legit scene-linear highlights), and it does it because a negative pixel fed into a video model's VAE encode decodes as splotchy firefly artifacts. That's a real, specific failure mode this node prevents by construction.
Inputs and outputs
image, optional mask (reformatted in lockstep with the image, auto-resized to match if dimensions differ), and optional video (lazy per-frame resize). Outputs: image, mask, width, height, a one-line info summary, and video. There's also an output_dtype (fp32/fp16) - fp16 is the default and saves half the memory, but if a downstream node assumes fp32, flip it back.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/am-pipeline-prod/comfyui-am-vfx-tools.git
cd comfyui-am-vfx-tools
pip install -r requirements.txt
Restart ComfyUI, or search comfyui-am-vfx-tools in ComfyUI Manager. The pip line is what pulls in OpenCV headless (the reformat engine), OpenImageIO, OpenColorIO, and PyAV.
Where people get burned
Choosing distort when you meant fill - it stretches the image and changes the aspect, which is almost never what you want. And remember the transparent padding: if you chain a node that assumes RGB after a fit or none reformat, a 4-channel tensor might trip it up. That's the pack's convention doing its job, not a bug.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| reformat_mode | COMBO | off | Reformat mode. off = bypass, output matches input. scale = uniform scale by `scale` (other widgets ignored). to_box = resize/crop to a target W×H from `preset` or `target_width`/`target_height`. |
| scale | FLOAT | 1.000.01–16 | Uniform scale factor. Used when `reformat_mode=scale`; ignored otherwise. Output dimensions are round(input × scale). |
| preset | COMBO | Width/Height | Named output format. Used when `reformat_mode=to_box`. `Width/Height` = use the `target_width` / `target_height` widgets below. Any other entry overrides those widgets with the preset's resolution. |
| target_width | INT | 19201–16384 | Target output width in pixels. Used when `reformat_mode=to_box` AND `preset=Width/Height`; ignored when a named preset is selected. |
| target_height | INT | 10801–16384 | Target output height in pixels. Used when `reformat_mode=to_box` AND `preset=Width/Height`; ignored when a named preset is selected. |
| resize_type | COMBO | fit | How input maps into the target box. Used when `reformat_mode=to_box`. width/height = scale uniformly to match that edge. fit = scale to fit inside the box (letterbox; black where the box exceeds the scaled image). fill = scale to cover the box (crops the overflow). distort = scale W and H independently to exactly match the box (changes aspect). none = no scale; place input centered in the box (crop if larger, pad if smaller). Cropped-away/padded regions are TRANSPARENT — RGB sources are promoted to RGBA with alpha=0 in the padded area so downstream compositing is clean. |
| filter | COMBO | cubic | Pixel filter for resampling. impulse = nearest-neighbor (mask passes, exact pixel preservation). linear = bilinear (cheap, smooth). cubic = bicubic (default; the safe Nuke-equivalent). Lanczos4 = sharpest; for high-quality stills / final delivery. area = best for downscaling — anti-aliased mean pooling, softer but artifact-free. |
| output_dtype | COMBO | fp16 | Output tensor dtype. fp32 = ComfyUI default (4 bytes/sample). fp16 = half memory + half VRAM (2 bytes/sample). EXR-native precision; fits the [0,1] LDR + scene-linear range with headroom up to ~65504. Some downstream nodes assume fp32 — flip back to fp32 if you hit dtype errors. |
| show_preview | BOOLEAN | true | Show a thumbnail of the reformatted result on the node. |
| imageopt | IMAGE | Image batch to reformat (N×H×W×C float). | |
| maskopt | MASK | Optional MASK input (N×H×W float in [0,1]). MASK CONVENTION (stock ComfyUI): mask = 1 - alpha white (1.0) = 'area to inpaint' (source was transparent) black (0.0) = 'keep' (source was opaque) empty mask = all zeros (source has no alpha = fully visible) This is the SD-inpainting convention every stock ComfyUI mask-using node expects (LoadImage, MaskComposite, SetLatentNoiseMask, ImpactPack mask pipeline, etc.). Drop-in compatible with all of them. If you want NUKE-STYLE natural alpha (mask = alpha, where 1.0 = opaque), wire a MaskInvert node between this socket and your downstream consumer. When wired, the same geometric reformat is applied to the mask in lockstep with the image. Mismatched mask dimensions are auto-resized (cubic) to the image's H,W before reformat. | |
| videoopt | VIDEO | Optional VIDEO input. When wired, returns a lazy `ReformatVideo` wrapper applying the resize per-frame on consumption — no IMAGE materialisation here. Alpha (when present) is resized alongside the image. `image` and `mask` are ignored when `video` is wired. See invariant 28. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | Reformatted image batch (N×H×W×3, dtype per `output_dtype`). |
| mask | MASK | Reformatted MASK (N×H×W float in [0,1]). MASK CONVENTION (stock ComfyUI): mask = 1 - alpha white (1.0) = 'area to inpaint' (source was transparent) black (0.0) = 'keep' (source was opaque) empty mask = all zeros (source has no alpha = fully visible) This is the SD-inpainting convention every stock ComfyUI mask-using node expects (LoadImage, MaskComposite, SetLatentNoiseMask, ImpactPack mask pipeline, etc.). Drop-in compatible with all of them. If you want NUKE-STYLE natural alpha (mask = alpha, where 1.0 = opaque), wire a MaskInvert node between this socket and your downstream consumer. Sources, in priority order: 1. The wired `mask` input (post-reformat geometry). 2. Extracted from a 4-channel IMAGE input's alpha (mask = 1 - alpha). 3. Empty mask (zeros) — IMAGE was 3-channel with no MASK wired. |
| width | INT | Output width in pixels (post-reformat). |
| height | INT | Output height in pixels (post-reformat). |
| info | STRING | One-line summary of the reformat applied. |
| video | VIDEO | Lazy VIDEO output — emits a `ReformatVideo` wrapper when `video` is wired, else a zero-copy `VideoFromComponents` around the IMAGE batch. None when no input is wired. |