Image Subtract
The node that finally lets you subtract two images without murdering the negative values
- image_a
- image_b
- signed_image
Here's the situation that makes you want this node: you've blurred a copy of your image, and you want the difference - the high-frequency detail that the blur removed. Subtract image B from image A in plain 0..1 land and every pixel where B is brighter than A goes negative, and the moment that negative value reaches a normal IMAGE socket it gets clipped to black. Half your detail silently vanishes. That's the problem this pack exists to solve, and Image Subtract is the node that makes it concrete.
What it is. Image Subtract takes two standard IMAGE tensors and returns their difference as a SIGNED_IMAGE - the pack's zero-centered, nominally -1..1 convention where zero means "no change". The math is boring and that's the point: image_a - image_b. Because the difference of two 0..1 images naturally lands in -1..1, the node folds the "to signed" conversion in for you. You don't need a separate To Signed Image step here, which is one less node to wire and one less thing to get backwards.
This is the classic unsharp-mask setup, and the pack ships it as a sample workflow. The KB's post-processing doc says it plainly: sharpening is blur-and-difference, and the "detail" in an unsharp mask is the subtracted high-pass map. Shrink, enlarge, subtract - that's Upscale Image By feeding a blurred copy into one input while the original feeds the other, and Image Subtract hands you the signed detail map you then re-add scaled by an amount. It's also how you'd build a high-pass pass for compositing or sharpening-in-graph without a filter node.
What actually matters about the inputs. Two required inputs, both IMAGE: image_a and image_b. It subtracts image_b from image_a, so the order decides the sign of everything in the output - swap them and your edges flip from bright-on-dark to dark-on-bright. The one genuine gotcha is sizes. If the two images have different H/W, both get zero-padded, centered, up to the larger of each dimension - a 500x500 and a 400x600 become 500x600 before the subtraction, and that centering matters if you're comparing a crop against its parent. But batch size and channel count have to match exactly, and there's no sensible way to pad those, so mismatches raise a ValueError with both shapes in the message. That error is the node telling you your branches diverged, not a bug.
Where the output goes. The single output is signed_image (SIGNED_IMAGE). Wire it into Preview Signed Image in abs mode to actually see the edge detail, or into To Unsigned Image if you're piping the detail back into standard nodes - say, adding it back to the original for a real unsharp mask.
Install. Standard for this pack, and it's a lightweight one:
cd ComfyUI/custom_nodes
git clone https://github.com/NobutakaKuroki/comfyui-signed-image
Restart ComfyUI. There are no extra Python dependencies - the pyproject.toml declares an empty dependency list and the README confirms it. Or use ComfyUI Manager and search "comfyui-signed-image".
Two traps worth naming. First, if you preview the raw SIGNED_IMAGE with the default clamp mode, a subtle detail map looks like flat gray - that's not the node being broken, that's mid-gray being zero. Reach for abs when you want to see edges. Second, remember the SIGNED_IMAGE convention is exactly that - a convention. Nothing validates that a tensor on that socket is really in -1..1, so what you get out of the subtraction is only as meaningful as what you put in.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image_a | IMAGE | — | |
| image_b | IMAGE | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| signed_image | SIGNED_IMAGE | — |