📁 File2. 이미지01 · 픽셀과 색

01 · 픽셀과 색

이 문서가 답하는 질문: 픽셀이란 무엇이고, RGB / RGBA / 알파 / 감마는 어떻게 얽혀 있는가?


한 줄 답 (Pyramid Top)

픽셀은 3채널 + α(불투명도) 의 숫자 묶음이지만, 그 숫자는 선형 빛이 아니라 감마로 휘어진 값이고, 알파는 프리멀티플라이드냐 스트레이트냐에 따라 합성 결과가 바뀐다. 이 세 가지를 모르고 RGB를 다루면 “왜 같은 이미지가 다른 색으로 보이지” 가 시작된다.


Why — 왜 픽셀이 단순하지 않은가

이미지는 “화면의 작은 점” 이라고 가르치지만, 실제로는 “빛의 측정값을 저장한 숫자” 다. 그리고 이 측정값은 사람 눈의 비선형성 때문에 그대로 저장되지 않는다.

  • 사람 눈은 어두운 영역의 차이에 민감하고, 밝은 영역의 차이에 둔하다.
  • 8bit(0~255)에서 선형으로 빛을 저장하면 어두운 부분이 거칠게 끊어진다 → 밴딩.
  • 그래서 모든 이미지 포맷은 감마 인코딩(어두운 값에 더 많은 비트 할당)으로 저장한다.

알파(α)는 더 미묘하다. “투명도” 라고 부르지만 합성 공식에 따라 두 가지 표현이 공존한다:

  • Straight alpha: RGBα가 독립
  • Premultiplied alpha: RGB에 이미 α가 곱해져 있음

이걸 섞으면 PNG의 가장자리에 검은 테두리가 생기는 고전적 버그가 발생한다.


How — 어떻게 픽셀이 만들어지고 저장되는가

1) 픽셀 = 채널의 묶음

모델채널
RGBRed, Green, Blue(255, 0, 0) = 빨강
RGBA+ Alpha(255, 0, 0, 128) = 반투명 빨강
YUV / YCbCr휘도 + 2 색차JPEG 내부 표현
CMYKCyan, Magenta, Yellow, Key(Black)인쇄용
GrayscaleLuminance 1채널흑백 사진

2) 감마(Gamma) — 빛의 비선형 저장

display_light = pixel_value^γ        (γ ≈ 2.2 for sRGB)
pixel_value   = display_light^(1/γ)  (저장할 때)

왜 2.2인가: CRT 모니터의 물리적 특성에서 유래했지만, 사람 눈의 비선형 민감도와 우연히 비슷해서 표준이 되었다. sRGB는 정확히는 x^2.4에 가까운 piecewise 함수.

3) 알파 합성 — Straight vs Premultiplied

같은 반투명 빨강 픽셀:

표현저장값
StraightRGB=(255, 0, 0), α=0.5
PremultipliedRGB=(127, 0, 0), α=0.5

합성 공식 (배경 위에 그릴 때):

Straight:        out = src.rgb * src.α + dst.rgb * (1 - src.α)
Premultiplied:   out = src.rgb        + dst.rgb * (1 - src.α)

Premultiplied가 연산이 1번 더 적고, 필터링(blur, scale)에서 정확하다. 그래서:

  • PNG 파일: Straight alpha
  • GPU 텍스처 / Canvas: Premultiplied alpha
  • <img> 디코딩: 브라우저가 Premultiplied로 변환해서 GPU에 올림

4) 비트심도 — 채널당 비트

비트심도채널당 값표현 가능 색 (RGB 합산)
8bit0~25516.7M (24bit color)
10bit0~10231.07B (30bit color) — HDR 표준
12bit0~409568.7B — Dolby Vision 마스터
16bit0~65535RAW / 사진 편집용
32bit float-∞ ~ +∞HDR 합성 / EXR

03-bit-depth-and-hdr.md에서 상세


What — 구체 사양과 수치

sRGB 픽셀의 디코딩 공식 (정확한 piecewise)

if v ≤ 0.04045:  linear = v / 12.92
else:            linear = ((v + 0.055) / 1.055)^2.4

JavaScript에서 sRGB 8bit 값을 선형 빛으로 변환:

function srgbToLinear(v) {
  v /= 255;
  return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}

알파 변환 — Straight ↔ Premultiplied

// Straight → Premultiplied
const r_pm = r * a;
const g_pm = g * a;
const b_pm = b * a;
 
// Premultiplied → Straight  (a == 0 일 때 RGB 정보 손실됨)
const r = a > 0 ? r_pm / a : 0;

Canvas 2D의 기본값

API알파 형태
getImageData()Straight (RGBA, 0~255)
WebGL 텍스처 (기본)Premultiplied
canvas.toDataURL("image/png")Straight (PNG 표준)

What-if — 잘못 다루면 어떻게 깨지는가

사례 1) PNG 가장자리에 검은 테두리

증상: 둥근 아이콘 PNG를 흰 배경에 올리면 가장자리가 어둡다.

원인:

  1. PNG는 Straight alpha
  2. 디자이너가 PNG를 만들 때 α=0인 픽셀의 RGB를 검정(0,0,0)으로 저장
  3. 브라우저가 Premultiplied로 변환 → α≠0인 가장자리 픽셀이 검은색과 섞임

해결: α=0 영역의 RGB를 색상에 맞게 채워서 export하거나, premultiplied PNG 사용 (드물다).

사례 2) 그라디언트에 줄무늬 (밴딩)

증상: 노을 사진의 하늘에 계단 모양 줄무늬.

원인:

  • 8bit × sRGB 감마 = 어두운 영역에서 인접 값 간격이 너무 큼
  • 256단계가 부드러운 그라디언트를 표현하기에 부족

해결:

  • 10bit 이상 (HDR) 저장
  • Dithering (의도적 노이즈 추가)
  • 03-bit-depth-and-hdr.md

사례 3) box blur가 더러워 보임

증상: blur 필터를 적용했는데 가장자리가 어둡거나 색이 흐려짐.

원인: 감마 공간에서 평균을 냄. 빛은 선형이라 (0 + 255) / 2 = 127이 아니라, 선형 변환 후 평균 → 다시 감마 인코딩이 정확하다.

올바른 blur:

linear = srgbToLinear(pixel)
blurred_linear = average(linear)
out = linearToSrgb(blurred_linear)

게임엔진(Unreal, Unity)은 모든 셰이더 연산을 선형 공간에서 함. 웹 Canvas는 기본이 sRGB라서 부정확하지만 빠른 blur를 한다.


Insight — 픽셀의 역사

“24bit color는 어쩌다 표준이 되었나”

1987년 VGA가 256색 (8bit indexed) → 1995년 Windows 95가 16bit High Color → 2000년대 24bit True Color. 사람 눈이 구별할 수 있는 색이 약 1000만 개라서 16.7M color는 충분히 많다고 여겨졌고, 메모리 정렬상 8bit×3이 자연스러웠다. HDR 시대(2015~)부터 비로소 24bit가 부족하다는 것이 드러나기 시작.

“감마 2.2의 우연”

CRT 전자총의 비선형성(γ≈2.5)을 보정하기 위해 카메라가 1/2.5로 미리 인코딩 → 우연히 사람 눈의 비선형 민감도(Stevens 법칙)와 거의 일치. LCD/OLED는 본질적으로 선형이지만, 모든 콘텐츠가 감마 인코딩되어 있어서 디스플레이가 흉내내준다. → “감마는 사라지지 않는다, 표준화될 뿐”

“Premultiplied가 GPU에서 표준인 이유”

Mip-mapping(텍스처 다운스케일) 시 Straight alpha는 가장자리에서 색이 새어나간다. Premultiplied는 색과 α가 함께 줄어들어 정확하게 보간된다. → Renderman, OpenEXR, Apple Core Animation 모두 Premultiplied.


한 단락 요약

픽셀은 단순한 RGB가 아니라 색공간 × 비트심도 × 감마 × 알파 모드의 4중 컨텍스트를 가진 숫자다. 이 컨텍스트 중 하나만 어긋나도 (예: PNG가 Straight인데 GPU가 Premultiplied로 가정) 가장자리가 검어지고 그라디언트에 줄이 가고 blur가 더러워진다. 다음 문서(02-color-spaces.md)는 이 중 색공간 레이어를 본격적으로 다룬다.