Nodes/BrainDead Nodes/BD Crop To Mask
ComfyUI Node

BD Crop To Mask

Crop to the lips, square it, and frame every viseme identically

By BizaNator·Created 8 months ago·Updated 3 days ago· 15
BD Crop To Mask
  • image
  • mask
  • cropped
  • crop_box
  • status
channelR
threshold0.05
padding_pct0.15
fixed_size_px0
squaretrue
output_size512
resize_modefit
fallbackfull_image

A viseme atlas - the frames of a character's mouth shapes - only looks right in-engine if every cell frames the mouth the same way: same scale, same center, same crop region. If each viseme gets cropped to its own bounding box, one cell has the lips huge and another has them tiny, and the atlas is garbage. BD CropToMask exists for exactly this: it crops an image to the bounding box of its active region, adds proportional padding, optionally enforces a square, resizes to a target resolution - and, critically, lets you force the exact same crop size across all visemes so every frame covers the same mouth region at the same scale.

It's built for the pack's lip-viseme pipeline, which is why the channel trick exists: channel='R' centers and sizes the crop on the lips only - not the teeth or tongue - so the R channel of your packed mouth render drives the framing while the other channels ride along.

The inputs that matter

  • image - the source.
  • mask - optional; the bbox comes from the active region of this, else from the image's channel.
  • channel - which channel drives bbox detection: all (union of R/G/B - lips+teeth+tongue), R (lips only, recommended for consistent atlas framing), G (teeth), B (tongue), or A (alpha/depth).
  • padding_pct - padding on each side as a fraction of the larger bbox dimension (15% per side by default). Used when fixed_size_px is 0.
  • fixed_size_px - the consistency knob. When > 0, forces the crop to exactly this square size (in source pixels), centered on the detected channel's centroid. Use the same value across all visemes and every cell frames identically. The node description's tip: run once with it at 0, read the crop sizes from the status output, then set it to the largest bbox+padding size you saw.
  • square - enforce a square crop (ignored when fixed_size_px is set, since that's already square).
  • output_size - the output resolution, default 512.
  • resize_mode - fit (default) scales to fill output_size preserving aspect, then pads with black - lips keep their shape, horizontal visemes get top/bottom bars and vertical ones get side bars. stretch squashes to exact size and can distort the lips.
  • threshold - pixel value above which a pixel counts as active.

Outputs: cropped (IMAGE), crop_box (STRING - the actual crop rectangle), and status (STRING). The crop_box and status outputs are your calibration instruments: run once, read where the crops landed, then set fixed_size_px.

How you'll chain it

[BD MP Mouth Parts → packed RGBA mouth render] → [BD CropToMask]
        channel: R  fixed_size_px: <largest>  output_size: 512
        → cropped → [atlas/pack] → viseme sheet

Each of the seven visemes goes through the same settings, so every cell of the atlas covers the same mouth region at the same scale. It's the framing step that makes the atlas usable, and it's the piece most people rebuild with three generic crop/resize nodes before finding this.

Installing it

Part of BizaNator/ComfyUI-BrainDead (BrainDeadGuild). ComfyUI Manager → search "BrainDead" → install, or:

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

Restart; it's under 🧠BrainDead/Segmentation.

Gotchas

The trap is thinking padding_pct gives you consistency - it doesn't. Proportional padding scales with each viseme's bbox, so different mouth sizes still produce different crop windows. That's why fixed_size_px exists; if your atlas is misaligned, that's the knob, not the padding. Also be careful with resize_mode if you're feeding the results into anything that assumes square pixels on a specific grid - stretch distorts lip shape, and fit pads with black, so pick by what your engine tolerates. And if a viseme has no active region in the chosen channel (say, tongue only appears in one frame), check the fallback behavior - it's there so a frame with nothing to crop doesn't wreck the batch.

Category🧠BrainDead/Segmentation

Inputs (10)

NameTypeDefaultDescription
imageIMAGE
maskoptMASK
channeloptCOMBORWhich image channel drives the bounding box detection. all — union of R/G/B (lips+teeth+tongue) R — lips only (recommended for consistent atlas framing) G — teeth only B — tongue only A — alpha/depth channel
thresholdoptFLOAT0.050–1Pixel value above which a pixel counts as active.
padding_pctoptFLOAT0.150–0.5Padding on each side as a fraction of the larger bbox dimension. 0.15 = 15%% per side. Used when fixed_size_px=0.
fixed_size_pxoptINT00–4096When >0, force the crop to exactly this square size (in source pixels) centered on the detected channel centroid. Use the SAME value across all 7 visemes for a consistent atlas — every cell covers the same mouth region at the same scale. Tip: run once with fixed_size_px=0 to see crop sizes in the status output, then set fixed_size_px to the largest bbox+padding size.
squareoptBOOLEANtrueEnforce a square crop. Ignored when fixed_size_px>0 (already square).
output_sizeoptINT51264–4096Output resolution (square).
resize_modeoptCOMBOfitfit — scale to fill output_size preserving aspect ratio, then pad edges with black. Lips keep their natural shape; horizontal visemes get top/bottom bars, vertical visemes get left/right bars. stretch — squash to exact output_size (may distort lip shape).
fallbackoptCOMBOfull_image2 options: full_image, center_crop

Outputs (3)

NameTypeDescription
croppedIMAGE
crop_boxSTRING
statusSTRING