Nodes/ComfyUI CV/cv2.floodFill
ComfyUI Node

cv2.floodFill

The magic wand Photoshop has and ComfyUI mostly doesn't

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.floodFill
  • image
  • seedPoint
  • mask
  • retval
  • filledImage
  • mask
  • rect
◄newVal(0, 0, 0, 0)►
◄loDiff►
◄upDiff►
◄flags4-connectivity (mask filled with 1)►

Every masking pipeline in ComfyUI eventually wants a manual click: "select this region, the one I'm pointing at". Usually that means reaching for SAM or a segmentation model with a text prompt. cv2.floodFill is the ancient, model-free version of that click - the paint-bucket tool - and it's genuinely useful when the region is flat-ish and bounded, because it's deterministic, instant, and doesn't need a checkpoint to decide where the sky ends.

It's a raw wrapper in ComfyUI CV (bmad4ever), the pack that exposes OpenCV 5.0 as ComfyUI nodes. One caveat before you build a masking pipeline on it: the README is unusually upfront that the pack was built with heavy LLM assistance and isn't meant for production without your own review. There's essentially no community discussion of it to lean on either - the author has other ComfyUI utility packs, but no thread about this one - so the tooltips and the source are your documentation.

How the fill works

Start at seedPoint. Look at the neighbours. Each neighbouring pixel joins the region if its value is within loDiff below and upDiff above the pixel it's growing from - or, with FLOODFILL_FIXED_RANGE set, if it's within that range of the seed pixel instead. Growth continues until nothing new qualifies. That last distinction is the difference between a fill that crawls along a soft gradient and one that stops where the seed's colour stops.

The operation mask is how you make it smart. Non-zero mask cells block the fill, so an edge map from Canny (or the pack's CV Detect Lines (Hough), or any contour mask) turns the flood into a region that respects boundaries instead of leaking across them. Set FLOODFILL_MASK_ONLY and the image comes back untouched while the fill is written into the mask output - that's click-to-mask in one node, with no model involved.

The sockets

  • image - the frame. Match-type: IMAGE in, IMAGE out on filledImage.
  • seedPoint - a CV_TUPLE with two components (x, y). It travels as one value, so it can't arrive half-connected; wire it from CV Tuple or type the two numbers.
  • newVal - the replacement colour as a cv2 Scalar literal, default (0, 0, 0, 0). Note components are BGR here, so (0, 255, 0) is green, and a bare number broadcasts to every component ("255" means white).
  • mask - optional, and here's the trap: cv2's mask must be 2 pixels wider and 2 pixels taller than the image, single-channel uint8. Leave it unconnected and cv2 allocates a temporary one for you (the wrapper passes None positionally, because keyword-passing it collides with seedPoint). Connect a mask straight from Mask → CV Array and you'll be handing over an h × w array where cv2 wants (h+2) × (w+2).
  • loDiff / upDiff - the tolerance band as Scalar literals, blank for the OpenCV default of zero (which means only exactly-equal pixels join - usually tighter than you want).
  • flags - a pipe-joined string the frontend renders as a base dropdown plus one toggle per flag: connectivity (4 or 8 neighbours), what value the mask is filled with (1 or 255), and FLOODFILL_FIXED_RANGE / FLOODFILL_MASK_ONLY. The default "4-connectivity (mask filled with 1)" is a sane starting point.

Four outputs: retval (INT - the number of pixels filled), filledImage (echoes your input's format), mask (an NPARRAY, the (h+2, w+2) mask - not an image socket, so it needs CV Array → Image or CV Array → Mask before you can look at it), and rect, a BOUNDING_BOX of the filled domain. The tooltip names its consumers: Crop By Bounding Boxes, Draw BBoxes, or CV Split Tuple for the raw x/y/w/h.

Leak detection, and the rest

Read retval. It's the pixel count of the fill. A fill that escaped into a gradient fills hundreds of thousands of pixels and returns a number that size; a fill that worked returns something sensible for a region you can see. Tightening loDiff/upDiff or switching on FIXED_RANGE is how you stop the leak.

Install

ComfyUI Manager → ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install -r comfyui_cv/requirements.txt

Python ≥3.12, ComfyUI on the V3 node API, restart. Package dependency is opencv-contrib-python-headless~=5.0.0.93; floodFill is core imgproc. There's no shipped example workflow using this node - the pack's playgrounds cover watershed, inpainting and contours, which are the nearest neighbours in spirit.

Common issues

Overload resolution failed on the call. That's the mask/seed positional collision the wrapper works around - if you're feeding a mask, check its shape first, because cv2 rejects a wrong-sized one at the same call site.

The fill covers everything. The tolerance band is too wide for the texture, or you're in non-fixed mode on a gradient. Tighten loDiff/upDiff, or set FLOODFILL_FIXED_RANGE.

The seed point is out of bounds. The wrapper re-raises cv2's error with every argument it passed - including the point - so you can see instantly whether you swapped x and y. Points here are (x, y); anything you took from an IMAGE-shaped crop is usually (row, column).

Categoryimage/CV/low-level/cv2 F

Inputs (7)

NameTypeDefaultDescription
imageCOMFY_MATCHTYPE_V3Input/output 1- or 3-channel, 8-bit, or floating-point image. It is modified by the function unless the #FLOODFILL_MASK_ONLY flag is set in the second variant of the function. See the details below. The image output(s) echo this input's format. 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.
seedPointCV_TUPLE0,0Starting point. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
newValSTRING(0, 0, 0, 0)New value of the repainted domain pixels. cv2 Scalar as a literal, e.g. "(0, 255, 0)" (BGR) or "(0, 255, 0, 64)" (BGRA). A bare number broadcasts to every component, so "255" means (255, 255, 255, 255). Components past the target's channel count are ignored by OpenCV.
maskoptNPARRAY,IMAGE,MASKOperation mask that should be a single-channel 8-bit image, 2 pixels wider and 2 pixels taller than image. If an empty Mat is passed it will be created automatically. Since this is both an input and output parameter, you must take responsibility of initializing it. Flood-filling cannot go across non-zero pixels in the input mask. For example, an edge detector output can be used as a mask to stop filling at edges. On output, pixels in the mask corresponding to filled pixels in the image are set to 1 or to the specified value in flags as described below. Additionally, the function fills the border of the mask with ones to simplify internal processing. It is therefore possible to use the same mask in multiple calls to the function to make sure the filled areas do not overlap. Optional - leave unconnected for the OpenCV default (None). 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.
loDiffoptSTRINGMaximal lower brightness/color difference between the currently observed pixel and one of its neighbors belonging to the component, or a seed pixel being added to the component. cv2 Scalar as a literal, e.g. "(0, 255, 0)" (BGR) or "(0, 255, 0, 64)" (BGRA). A bare number broadcasts to every component, so "255" means (255, 255, 255, 255). Components past the target's channel count are ignored by OpenCV. Leave blank for the OpenCV default.
upDiffoptSTRINGMaximal upper brightness/color difference between the currently observed pixel and one of its neighbors belonging to the component, or a seed pixel being added to the component. cv2 Scalar as a literal, e.g. "(0, 255, 0)" (BGR) or "(0, 255, 0, 64)" (BGRA). A bare number broadcasts to every component, so "255" means (255, 255, 255, 255). Components past the target's channel count are ignored by OpenCV. Leave blank for the OpenCV default.
flagsoptSTRING4-connectivity (mask filled with 1)Operation flags. The first 8 bits contain a connectivity value. The default value of 4 means that only the four nearest neighbor pixels (those that share an edge) are considered. A connectivity value of 8 means that the eight nearest neighbor pixels (those that share a corner) will be considered. The next 8 bits (8-16) contain a value between 1 and 255 with which to fill the mask (the default value is 1). For example, 4 | ( 255 << 8 ) will consider 4 nearest neighbours and fill the mask with a value of 255. The following additional options occupy higher bits and therefore may be further combined with the connectivity and mask fill values using bit-wise or (|), see #FloodFillFlags. cv2.floodFill flags: one of 4-connectivity (mask filled with 1), 8-connectivity (mask filled with 1), 4-connectivity, mask filled with 255, 8-connectivity, mask filled with 255 plus any of FLOODFILL_FIXED_RANGE, FLOODFILL_MASK_ONLY, pipe-joined (e.g. "4-connectivity (mask filled with 1) | FLOODFILL_FIXED_RANGE"). In the UI this renders as a dropdown with one toggle per flag.

Outputs (4)

NameTypeDescription
retvalINT—
filledImageCOMFY_MATCHTYPE_V3Echoes the 'image' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.
maskNPARRAY—
rectBOUNDING_BOXOperation mask that should be a single-channel 8-bit image, 2 pixels wider and 2 pixels taller than image. If an empty Mat is passed it will be created automatically. Since this is both an input and output parameter, you must take responsibility of initializing it. Flood-filling cannot go across non-zero pixels in the input mask. For example, an edge detector output can be used as a mask to stop filling at edges. On output, pixels in the mask corresponding to filled pixels in the image are set to 1 or to the specified value in flags as described below. Additionally, the function fills the border of the mask with ones to simplify internal processing. It is therefore possible to use the same mask in multiple calls to the function to make sure the filled areas do not overlap. A cv2 Rect as core BOUNDING_BOX data ({x, y, width, height}, nested one group per frame) - feed 'Crop By Bounding Boxes', 'Draw BBoxes', or 'CV Split Tuple' for x/y/w/h.