Nodes/PortraitUtils/Auto White-Balance + Color Match
ComfyUI Node

Auto White-Balance + Color Match

White-balance and color-match one photo to another, in one node

By heyburns·Created 11 months ago·Updated 4 months ago· 1
Auto White-Balance + Color Match
  • image
  • reference
  • IMAGE
methodwb_highlight+reinhard
percentile95.0
strength1.00
clip_gamuttrue
force_sizefalse
target_width1440
target_height1080

The classic photo-editing pain: you shot the same scene under different lights, or scanned the same photo twice years apart, and now every frame in a sequence has a different white balance and color cast. Models don't care - they'll happily bake each frame's tint into the result, and suddenly your output looks like it was shot on three different cameras. AutoWBColorMatch is the fix: give it an image and a reference, and it pulls the image's white balance toward neutral and its overall color toward the reference frame. It's the tool for keeping batch color consistent before anything generative touches the frames.

Mechanically it's a well-trodden two-stage recipe, all in torch on your tensors. First it converts sRGB to linear light and then to Lab, because working in Lab keeps lightness and color separable. White balance comes first, using whichever method you pick:

  • wb_grayworld - assumes the average scene color is gray and neutralizes the global RGB balance. Fast, decent for general casts.
  • wb_highlight - takes the high-percentile luminance pixels (percentile, default 95) as the white point and shifts to it. Better when a scan has a clean bright area but no trustworthy average.
  • reinhard_lab - the Reinhard color transfer: matches the mean and standard deviation of each Lab channel to the reference. This is where the match part actually happens.
  • lab_l_only - Reinhard transfer on lightness only. Color stays put, tonal range matches. Good when the reference is a different subject but the lighting should feel the same.
  • wb_highlight+reinhard (default) - highlight white-balance first, then full Lab Reinhard. The "do everything" preset, and the reason it's the default.

strength (0–1) blends between the input and the fully-processed result, so you can back off a too-aggressive match. clip_gamut (on by default) clamps values back into sRGB range after the Lab round-trip - leave it on unless you're deliberately going for out-of-gamut looks downstream. force_size with target_width/target_height (defaults 1440×1080) resizes both inputs before analysis, which stabilizes the statistics when your image and reference are wildly different resolutions; for normal use you can leave it off and it'll just analyze at native size.

The single IMAGE output drops straight into your processing chain. It does internal resizing for the analysis pass regardless, so it's reasonably cheap on big batches.

Install

Manager → search "PortraitUtils", or:

cd ComfyUI/custom_nodes
git clone https://github.com/heyburns/PortraitUtils

Restart after. Torch and numpy only - no extra downloads.

Common issues

  • The match overdoes it and everything looks gray - that's Reinhard being literal about matching stats. Dial strength down to ~0.5–0.7, or switch to lab_l_only if the color transfer is the problem and the lightness isn't.
  • Still yellowed after wb_highlight - the percentile is looking for your white point too high up. Drop percentile from 95 toward 90 so it lands on the neutral highlight instead of a specular blowout.
  • Reference and image are different crops of different scenes - color match only transfers statistics; it can't align content. Keep the reference in the same lighting family and don't expect magic.

If you're assembling a consistent look across a folder of scans before restoration, this is the node that stops your batch from looking like it was color-graded by four different people.

CategoryPortraitUtils/Analysis

Inputs (9)

NameTypeDefaultDescription
imageIMAGE
referenceIMAGE
methodCOMBOwb_highlight+reinhard5 options: wb_grayworld, wb_highlight, reinhard_lab, lab_l_only, wb_highlight+reinhard
percentileFLOAT95.080–99.9
strengthFLOAT1.000–1
clip_gamutBOOLEANtrue
force_sizeBOOLEANfalse
target_widthINT144016–8192
target_heightINT108016–8192

Outputs (1)

NameTypeDescription
IMAGEIMAGE