Vibrance vs. saturation

바이브런스 vs 채도

Saturation boosts every color's purity by the same amount; vibrance boosts only the muted colors and leaves already-saturated ones alone.

Also known as: Vibrance vs Saturation자연채도와 채도
···
html
<div class="wrap">
  <div class="stage">
    <div class="pane"><canvas id="lc" width="300" height="180"></canvas><span class="lbl">채도</span></div>
    <div class="pane"><canvas id="rc" width="300" height="180"></canvas><span class="lbl">바이브런스</span></div>
  </div>
  <div class="hud"><span>같은 세기를 동시에 적용</span><b id="amt">0%</b></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;display:flex;gap:clamp(6px,2vmin,12px)}
.pane{position:relative;flex:1;min-height:0}
.lbl{position:absolute;left:8px;bottom:8px;z-index:2;font:800 clamp(9px,2.2vmin,11px)/1 ui-monospace,monospace;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.6)}
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 lc = document.getElementById('lc'), lx = lc.getContext('2d');
var rc = document.getElementById('rc'), rx = rc.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(lx, W, H);
var orig = lx.getImageData(0, 0, W, H);
function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h = 0, s = 0, l = (max + min) / 2;
  var d2 = max - min;
  if (d2 !== 0) {
    s = l > 0.5 ? d2 / (2 - max - min) : d2 / (max + min);
    if (max === r) h = ((g - b) / d2 + (g < b ? 6 : 0));
    else if (max === g) h = (b - r) / d2 + 2;
    else h = (r - g) / d2 + 4;
    h /= 6;
  }
  return [h, s, l];
}
function hue2rgb(p, q, t) {
  if (t < 0) t += 1; if (t > 1) t -= 1;
  if (t < 1 / 6) return p + (q - p) * 6 * t;
  if (t < 1 / 2) return q;
  if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  return p;
}
function hslToRgb(h, s, l) {
  if (s === 0) { var v = l * 255; return [v, v, v]; }
  var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  var p = 2 * l - q;
  return [hue2rgb(p, q, h + 1 / 3) * 255, hue2rgb(p, q, h) * 255, hue2rgb(p, q, h - 1 / 3) * 255];
}
var t = 0;
function frame() {
  t += 0.02;
  var amt = (Math.sin(t) + 1) / 2;
  var id = orig.data;
  var satOut = lx.createImageData(W, H), vibOut = rx.createImageData(W, H);
  var sd = satOut.data, vd = vibOut.data;
  var satFactor = 1 + amt * 1.6;
  for (var i = 0; i < id.length; i += 4) {
    var hsl = rgbToHsl(id[i], id[i + 1], id[i + 2]);
    var s1 = Math.min(1, hsl[1] * satFactor);
    var c1 = hslToRgb(hsl[0], s1, hsl[2]);
    sd[i] = c1[0]; sd[i + 1] = c1[1]; sd[i + 2] = c1[2]; sd[i + 3] = 255;
    var vibFactor = 1 + amt * 1.6 * (1 - hsl[1]);
    var s2 = Math.min(1, hsl[1] * vibFactor);
    var c2 = hslToRgb(hsl[0], s2, hsl[2]);
    vd[i] = c2[0]; vd[i + 1] = c2[1]; vd[i + 2] = c2[2]; vd[i + 3] = 255;
  }
  lx.putImageData(satOut, 0, 0);
  rx.putImageData(vibOut, 0, 0);
  document.getElementById('amt').textContent = Math.round(amt * 100) + '%';
  requestAnimationFrame(frame);
}
frame();

Saturation is a plain operation — multiply every pixel's HSL saturation by the same factor. Vibrance uses a different factor per pixel: pixels that started out muted get boosted more, pixels that were already saturated get boosted less. This demo feeds one intensity value into both formulas and shows them side by side.

Vibrance is especially handy on portraits — skin tends to sit at low saturation already, so pushing vibrance barely touches it, while a muted shirt or background comes alive. Saturation is the one to reach for when you want a uniformly punchy, pop-art kind of palette across the whole frame.

Push saturation too far and already-intense colors — reds and oranges especially — blow past what a screen or print can reproduce and clip into a flat, ugly block, and skin turns orange and waxy. Vibrance suppresses some of that automatically, but overdo it and the previously muted areas alone start looking unnaturally loud.

When to use

Use vibrance when skin tones need protecting but the rest should pop, saturation when you want the whole frame uniformly punchy.