cv2.add
Cv2.add saturates — that's the entire point
- src1
- src2
- mask
- result
200 + 100 in numpy on a uint8 array is 44. In cv2.add it's 255. That difference - saturating arithmetic versus wraparound - is why this node exists and why you should reach for it instead of doing pixel math anywhere else in the graph.
Add a brightness offset, add a bias field, add two exposures, add a constant to a latent. All of that is cv2.add, and all of it is wrong the moment something wraps around.
How it works
src1 + src2, element by element, with saturating cast on the output. Two array inputs plus an optional mask, plus an optional dtype for the output depth.
Here's the part that trips everybody: when you link a ComfyUI IMAGE, it becomes 8-bit BGR, 0–255. So "add a little brightness" means adding something like 10 or 20, not 0.05. Float thinking and 8-bit thinking do not mix, and this node works in the 8-bit domain.
The escape hatch is dtype. Set it to CV_32F and the sum is computed in float, which means nothing clips at 255 - you keep the over-range values and normalize on the way back out. That's the right way to add a large offset without destroying highlights, and it's why the widget is there.
LATENT inputs are a different story and a good one: a latent goes through as float32 [H,W,C] with values untouched, and add is on the short list of ops the author lets take a whole [B,C,H,W] latent batch in one call. So adding a scalar to a latent batch is exact - no uint8 quantization at all. The pack's own latent playground does exactly this ("Latent + scalar"), and it's a legitimate way to bias a latent before the sampler.
Inputs and outputs
src1(COMFY_MATCHTYPE_V3) - first array or scalar; decides the output format.src2(NPARRAY,IMAGE,MASK,LATENT) - second array or scalar. A constant goes in via CV Scalar.mask(optional) - restricts which output elements the operation touches, per OpenCV's definition. Since this node exposes nodstinput, don't build workflows that depend on what sits in the unmasked region - add, then composite explicitly.dtype(optional) - output depth:same as input,CV_8U…CV_64F.- result - echoes
src1: IMAGE in → IMAGE out, MASK in → MASK out, NPARRAY stays raw.
What you actually use it for
- Brightness / exposure offset. Add a constant. Saturating, so highlights go white rather than wrapping to black speckle.
- Two-exposure blend setup. Add the pair, then scale - though if you're blending,
cv2_addWeightedis the node you actually want. - Bias-field correction. Add a measured bias map back to a corrected image; the pack's photometric-align node fits that map for you.
- Latent bias. Add a constant, or two latents together, in float - no quantization.
- Raise a black floor. Add a small constant to lift pure black before a downscale or a compression step.
If you want a masked composite - "this region from A, that region from B" - cv2_addWeighted with a premultiplied alpha, cv2_blendLinear, or the pack's curated paste/crop nodes are the right tools. cv2_add is pure arithmetic.
Install
ComfyUI Manager → comfyui_cv (ComfyUI CV), or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart ComfyUI. Python ≥ 3.12 and a recent V3-API ComfyUI; behaviour is curated against OpenCV 5.0.0.93. Use the contrib wheel - installing a non-contrib opencv-python over it empties the contrib submodules without warning.
Where people get burned
Adding 0.1 instead of 25. The 0–1 float convention from the rest of ComfyUI doesn't survive the trip into OpenCV. Small increments in this node are invisible.
Mixing depths. An IMAGE (uint8) plus an NPARRAY that came out of a float pipeline raises on the depth mismatch. CV Cast Array first.
Size mismatch. Different resolutions raise, same as every other binary op here.
Wraparound inside your own math. If you precompute the offset somewhere else with numpy and hand it in already negative, you've moved the problem, not solved it. Let cv2_add do the arithmetic.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| src1 | COMFY_MATCHTYPE_V3 | first input array or a scalar. The image output(s) echo this input's format. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| src2 | NPARRAY,IMAGE,MASK,LATENT | second input array or a scalar. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| maskopt | NPARRAY,IMAGE,MASK,LATENT | optional operation mask - CV_8U, CV_8S or CV_Bool single channel array, that specifies elements of the output array to be changed. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| dtypeopt | COMBO | same as input | optional depth of the output array (see the discussion below). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src1' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |