White balance

화이트 밸런스

Shifts a photo's overall color temperature warmer or cooler so a surface that should look white actually reads as white.

Also known as: 색온도 보정Color temperature correction
···
html
<div class="wrap">
  <div class="stage"><canvas id="c" width="300" height="180"></canvas></div>
  <div class="hud"><b id="tp">0</b><span>왼쪽 원본 / 오른쪽 보정</span></div>
</div>
css
.wrap{position:absolute;inset:0;padding:clamp(8px,3vmin,20px);display:flex;flex-direction:column;gap:6px}
.stage{position:relative;flex:1;min-height:0}
canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;border-radius:10px;border:1px solid var(--line);background:#0a0a0a}
.hud{display:flex;justify-content:space-between;gap:8px;font:700 clamp(9px,2.2vmin,11px)/1 ui-monospace,monospace;color:var(--muted)}
.hud b{color:var(--fg)}
js
var W = 300, H = 180;
var c = document.getElementById('c'), ctx = c.getContext('2d');

function paintPhoto(cx, w, h) {
  var g = cx.createLinearGradient(0, 0, 0, h * 0.62);
  g.addColorStop(0, '#5b86c9'); g.addColorStop(0.55, '#bcd6ea'); g.addColorStop(1, '#f5e0ad');
  cx.fillStyle = g; cx.fillRect(0, 0, w, h);
  var sx = w * 0.74, sy = h * 0.22, sr = h * 0.11;
  var sg = cx.createRadialGradient(sx, sy, 0, sx, sy, sr * 3.2);
  sg.addColorStop(0, 'rgba(255,247,214,0.9)'); sg.addColorStop(1, 'rgba(255,247,214,0)');
  cx.fillStyle = sg; cx.fillRect(0, 0, w, h * 0.62);
  cx.fillStyle = '#fff8df'; cx.beginPath(); cx.arc(sx, sy, sr, 0, 7); cx.fill();
  cx.fillStyle = '#6f9152';
  cx.beginPath(); cx.moveTo(0, h * 0.64);
  for (var x = 0; x <= w; x += 14) cx.lineTo(x, h * 0.64 - Math.sin(x * 0.014) * h * 0.05 - Math.sin(x * 0.032 + 1.2) * h * 0.02);
  cx.lineTo(w, h); cx.lineTo(0, h); cx.closePath(); cx.fill();
  cx.fillStyle = '#405f38';
  cx.beginPath(); cx.moveTo(0, h * 0.79);
  for (var x2 = 0; x2 <= w; x2 += 14) cx.lineTo(x2, h * 0.79 - Math.sin(x2 * 0.022 + 0.6) * h * 0.035);
  cx.lineTo(w, h); cx.lineTo(0, h); cx.closePath(); cx.fill();
  var tx = w * 0.2, ty = h * 0.8;
  cx.strokeStyle = '#3a2a1c'; cx.lineWidth = Math.max(1.5, w * 0.008);
  cx.beginPath(); cx.moveTo(tx, ty); cx.lineTo(tx - w * 0.01, ty - h * 0.13); cx.stroke();
  cx.fillStyle = '#2e4a28';
  for (var i = 0; i < 5; i++) { cx.beginPath(); cx.arc(tx + (i - 2) * w * 0.013, ty - h * 0.15 - (i % 2) * h * 0.018, w * 0.028, 0, 7); cx.fill(); }
  cx.fillStyle = '#1e1e1c';
  cx.beginPath(); cx.arc(w * 0.56, h * 0.745, h * 0.018, 0, 7); cx.fill();
  cx.fillRect(w * 0.555, h * 0.76, w * 0.01, h * 0.06);
  var img = cx.getImageData(0, 0, w, h), d = img.data;
  for (var p = 0; p < d.length; p += 4) { var n = (Math.random() - 0.5) * 10; d[p] += n; d[p + 1] += n; d[p + 2] += n; }
  cx.putImageData(img, 0, 0);
}
paintPhoto(ctx, W, H);
var orig = ctx.getImageData(0, 0, W, H);
var t = 0;
function frame() {
  t += 0.018;
  var amt = Math.sin(t) * 34;
  var out = ctx.createImageData(W, H);
  var od = out.data, id = orig.data;
  for (var i = 0; i < id.length; i += 4) {
    od[i] = Math.min(255, Math.max(0, id[i] + amt));
    od[i + 1] = id[i + 1];
    od[i + 2] = Math.min(255, Math.max(0, id[i + 2] - amt));
    od[i + 3] = 255;
  }
  ctx.putImageData(out, 0, 0);
  var frac = (Math.sin(t * 0.6) + 1) / 2 * 0.7 + 0.15;
  var sw = Math.round(W * frac);
  if (sw > 0) ctx.putImageData(orig, 0, 0, 0, 0, sw, H);
  ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(sw, 0); ctx.lineTo(sw, H); ctx.stroke();
  document.getElementById('tp').textContent = (amt >= 0 ? '따뜻하게 +' : '차갑게 ') + Math.round(Math.abs(amt));
  requestAnimationFrame(frame);
}
frame();

A camera sensor records whatever color the light actually is — yellow under tungsten, blue in open shade. White balance cancels that cast by pushing the R and B channels in opposite directions while leaving G alone, since eyes are most sensitive to a green shift. This demo adds that R/B offset to the original pixels every frame as the temperature value swings.

Reach for it when a whole shot skews green under fluorescent light, or skin looks pallid and blue in open shade. The simplest way to judge it is to find something in frame that should be neutral — a wall, a gray card — and adjust until that spot actually reads neutral.

Push it too far and skin turns orange or shadows turn implausibly blue. It's also easy to forget that a scene lit by two different color temperatures at once — daylight through a window plus warm interior lamps — can't be fixed with one global white balance.

When to use

Use it when the whole frame skews one color. Anchor the adjustment to something that should be neutral. A scene with two mixed light sources needs local masking, not one global fix.