노출

Exposure

사진 전체 밝기를 스톱(2배수) 단위로 곱해 올리거나 내리는 가장 기본적인 톤 조절.

다른 이름: Exposure compensation노출 보정
···
html
<div class="wrap">
  <div class="stage"><canvas id="c" width="300" height="180"></canvas></div>
  <div class="hud"><b id="ev">+0.0 EV</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.02;
  var ev = Math.sin(t) * 1.3;
  var factor = Math.pow(2, ev);
  var lut = new Uint8ClampedArray(256);
  for (var v = 0; v < 256; v++) lut[v] = Math.min(255, Math.round(v * factor));
  var out = ctx.createImageData(W, H);
  var od = out.data, id = orig.data;
  for (var i = 0; i < id.length; i += 4) { od[i] = lut[id[i]]; od[i + 1] = lut[id[i + 1]]; od[i + 2] = lut[id[i + 2]]; 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('ev').textContent = (ev >= 0 ? '+' : '') + ev.toFixed(1) + ' EV';
  requestAnimationFrame(frame);
}
frame();

노출 보정은 모든 픽셀 값에 같은 배수를 곱하는 선형 연산입니다. +1스톱은 모든 값에 2를 곱하고(밝기 2배), -1스톱은 절반으로 나눕니다. 이 데모는 그 배수를 룩업테이블로 만들어 픽셀에 곱하고 255에서 잘라(클램프) 다시 그립니다 — 레벨의 감마 곡선과 달리 중간톤이든 하이라이트든 모두 같은 비율로 움직입니다.

촬영 시 노출이 전체적으로 아쉬웠을 때(약간 어둡게·밝게 찍혔을 때) 톤 전체를 한 번에 맞추는 첫 단계로 씁니다. RAW 파일은 기록된 정보 안에서 이 곱셈을 해도 디테일 손실이 적지만, JPEG처럼 이미 압축된 파일은 여유가 훨씬 적습니다.

노출을 과하게 올리면 이미 밝았던 하이라이트가 255에서 뭉개지고(클리핑), 과하게 내리면 어두운 영역에 노이즈가 두드러집니다. 부분적으로만 밝기를 조절하고 싶다면 노출 대신 레벨·커브나 마스크를 쓰는 게 맞습니다 — 노출은 전체를 똑같이 밉니다.

언제 쓰나

촬영 전체가 고르게 밝거나 어두울 때 첫 조정으로 씁니다. 일부만 밝히고 싶다면 노출 대신 마스크나 커브를 쓰세요.