Contrast vs. clarity

대비 vs 클래리티

Both get called "more contrast," but global contrast stretches the whole tone range while clarity only boosts local edge contrast in the midtones.

Also known as: Contrast vs Clarity전역 대비와 국소 대비
···
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 boxBlur(src, w, h, r) {
  var tmp = new Float32Array(src.length), out = new Float32Array(src.length);
  for (var y = 0; y < h; y++) {
    for (var x = 0; x < w; x++) {
      var sr = 0, sg = 0, sb = 0, n = 0;
      for (var k = -r; k <= r; k++) { var xx = x + k; if (xx < 0 || xx >= w) continue; var idx = (y * w + xx) * 4; sr += src[idx]; sg += src[idx + 1]; sb += src[idx + 2]; n++; }
      var o = (y * w + x) * 4; tmp[o] = sr / n; tmp[o + 1] = sg / n; tmp[o + 2] = sb / n;
    }
  }
  for (var x2 = 0; x2 < w; x2++) {
    for (var y2 = 0; y2 < h; y2++) {
      var sr2 = 0, sg2 = 0, sb2 = 0, n2 = 0;
      for (var k2 = -r; k2 <= r; k2++) { var yy = y2 + k2; if (yy < 0 || yy >= h) continue; var idx2 = (yy * w + x2) * 4; sr2 += tmp[idx2]; sg2 += tmp[idx2 + 1]; sb2 += tmp[idx2 + 2]; n2++; }
      var o2 = (y2 * w + x2) * 4; out[o2] = sr2 / n2; out[o2 + 1] = sg2 / n2; out[o2 + 2] = sb2 / n2; out[o2 + 3] = 255;
    }
  }
  return out;
}
var blurred = boxBlur(orig.data, W, H, 3);
var t = 0;
function frame() {
  t += 0.02;
  var amt = (Math.sin(t) + 1) / 2;
  var co = lx.createImageData(W, H);
  var cd = co.data, id = orig.data;
  var cf = 1 + amt * 1.1;
  for (var i = 0; i < id.length; i += 4) {
    cd[i] = Math.min(255, Math.max(0, (id[i] - 128) * cf + 128));
    cd[i + 1] = Math.min(255, Math.max(0, (id[i + 1] - 128) * cf + 128));
    cd[i + 2] = Math.min(255, Math.max(0, (id[i + 2] - 128) * cf + 128));
    cd[i + 3] = 255;
  }
  lx.putImageData(co, 0, 0);
  var ro = rx.createImageData(W, H);
  var rd = ro.data;
  var amount = amt * 2.2;
  for (var j = 0; j < id.length; j += 4) {
    for (var ch = 0; ch < 3; ch++) {
      var v = id[j + ch];
      var hiFreq = v - blurred[j + ch];
      var midW = 1 - Math.abs((v - 128) / 128);
      rd[j + ch] = Math.min(255, Math.max(0, v + hiFreq * amount * midW));
    }
    rd[j + 3] = 255;
  }
  rx.putImageData(ro, 0, 0);
  document.getElementById('amt').textContent = Math.round(amt * 100) + '%';
  requestAnimationFrame(frame);
}
frame();

Contrast is a global operation — pin the midpoint at 128, push values below it darker and above it brighter, which stretches the whole tone range. Clarity is the opposite, a local operation — blur the image into a low-frequency version, take the difference from the original (the edge detail), and add that back weighted toward the midtones. This demo runs both on the same photo, side by side.

Reach for contrast to set the overall mood of a photo — heavier, punchier. Reach for clarity when flat surfaces like sky or skin should stay smooth while textured surfaces like leaves, gravel or fabric should read sharper — the punch in a lot of landscape photography is clarity, not contrast.

Push contrast too far and shadows and highlights clip almost immediately. Push clarity too far and edges grow bright and dark halos, and on a portrait it exaggerates pores and fine lines into rough-looking skin — which is why clarity usually gets dialed back or masked off skin.

When to use

Use contrast to set overall mood, clarity to sharpen texture without touching flat areas. Dial clarity back — or mask it off — on portrait skin.