Chapter 18

UI와 데이터 시각화의 색

대시보드, 측정 GUI, 시뮬레이션 결과 플롯의 색은 장식이 아니라 데이터를 눈으로 전달하는 신호 채널이다. 채널의 품질은 취향으로 정하지 않는다. 텍스트는 휘도 대비로, 컬러맵은 명도의 단조성과 인접 색차의 균일성으로, 범주형 팔레트는 색각 이상 시뮬레이션 뒤에도 남는 최소 색차로 측정한다. 이 장은 그 기준들을 앞 장들의 측색 도구(상대 휘도, CIELAB, ΔE00, OKLab) 위에서 계산 가능한 형태로 정리한다.

색은 측정 가능한 신호 채널이다

그래프에서 색은 값(연속량)이나 범주(이산량)를 부호화한다. 이 부호화가 제대로 전달되는지는 세 가지 물리·지각량으로 판정할 수 있다. 첫째, 휘도 대비(luminance contrast) — 글자와 얇은 선이 배경에서 분리되는가. 둘째, 지각 색차(perceptual color difference) — 인접한 두 값 또는 두 범주의 색이 얼마나 다르게 보이는가(7장의 ΔE00). 셋째, 관측자 강건성(observer robustness) — 색각이 다른 관측자에게도 그 차이가 남는가.

세 기준이 모두 필요한 이유는 사람의 시각 채널이 대칭적이지 않기 때문이다. 2장에서 본 반대색 처리에서 무채색(명도) 채널은 공간 해상도가 가장 높고, 적–녹·황–청 채널은 해상도가 낮다. 따라서 가는 글자·얇은 선·작은 마커의 판독성은 거의 명도 차이로 결정되고, 색상 차이만으로 구분한 정보는 작은 크기에서 쉽게 사라진다. 또 남성의 약 8%가 적–녹 채널이 약하거나 없다. 그 사람들에게 남는 정보도 주로 명도와 청–황 방향의 차이다.

명도 L 만 변화 공간 해상도 최고 순서가 자명함 채도 C 만 변화 해상도 중간 단계 수가 적음 색상 h 만 변화 해상도 낮음 순서 없음 → 범주용 (C=0) (L 고정) (OKLab L≈0.70) 01 부호화할 데이터 값
그림 18-1. 같은 데이터 값을 색의 세 좌표 중 하나로만 부호화한 경우. 색상 띠는 OKLCh에서 L=0.70, C=0.11로 고정해 계산한 순환 경로다(CIELAB L*는 63.5–66.8). 명도 축만이 크기 순서를 자연스럽게 전달하고, 가장 높은 공간 주파수(가는 선·작은 글자)까지 살아남는다.

이 장의 계산은 모두 sRGB(IEC 61966-2-1) 디스플레이를 가정하고, 색차는 D65 백색 기준 CIELAB의 CIEDE2000(CIE 015:2018, ISO/CIE 11664-6)으로 잰다. 즉 화면의 8비트 코드값 \(\to\) 디코딩 \(\to\) 선형 RGB \(\to\) XYZ \(\to\) L*a*b*라는 6장·7장의 파이프라인이 그대로 쓰인다.

색각 유형원추세포남성 유병률(대략)혼동이 큰 축이 장의 시뮬레이션
1형 2색각 / 1형 이상 3색각 (protanopia / protanomaly)L 결손/이상≈1% / ≈1%적–녹, 빨강이 어둡게 보임Machado P, severity 1.0
2형 2색각 / 2형 이상 3색각 (deuteranopia / deuteranomaly)M 결손/이상≈1% / ≈5%적–녹Machado D, severity 1.0
3형 (tritanopia)S 결손≈0.01% 미만청–황Machado T, severity 1.0

유병률은 북유럽계 집단의 조사 수치로, 적–녹 색각 이상 전체가 남성 약 8%, 여성 약 0.5%다. 집단마다 다르지만 "사용자 열 명 중 한 명 가까이"라는 규모는 설계 기준으로 삼기에 충분하다.

공학자 관점

색 설계 리뷰를 "보기 좋은가"에서 "숫자가 기준을 넘는가"로 바꾸면 논쟁이 사라진다. 이 장에서 쓰는 지표는 다섯 개다: WCAG 대비비, APCA Lc, 컬러맵 L* 반전 수, 인접 ΔE00의 변동계수(CV), 팔레트의 CVD 후 최소 쌍별 ΔE00. 모두 CI에서 자동 검사할 수 있다.

텍스트 대비: WCAG 2.x 대비비

WCAG 2.x(Web Content Accessibility Guidelines, W3C)는 텍스트 판독성을 상대 휘도(relative luminance)의 비로 정의한다. 상대 휘도는 sRGB 코드값을 디코딩한 선형 값의 가중합, 즉 BT.709/sRGB 원색의 \(Y\) 행이다.

$$Y = 0.2126\,R_{\text{lin}} + 0.7152\,G_{\text{lin}} + 0.0722\,B_{\text{lin}},\qquad C_{\text{lin}} = \begin{cases} C/12.92 & C \le 0.04045 \\ \left(\dfrac{C+0.055}{1.055}\right)^{2.4} & C > 0.04045\end{cases}$$ $$\text{CR} = \frac{Y_{\max} + 0.05}{Y_{\min} + 0.05},\qquad 1 \le \text{CR} \le 21$$
여기서 \(C = \text{코드값}/255\). WCAG 상대 휘도 정의는 2021년 5월에 문턱값을 0.03928에서 IEC 61966-2-1과 같은 0.04045로 수정했다. 현재 WCAG 2.1·2.2는 0.04045를 쓰며, 이 변경은 8비트 코드의 결과에는 영향을 주지 않는다. 분모·분자의 0.05는 백색 Y=1 대비 5%의 주변 반사광(플레어)을 더한 것이다.

대비비는 플레어를 더한 휘도의 비이므로 로그 휘도 축 위의 거리다. 흑(Y=0)과 백(Y=1)이 1.05/0.05 = 21:1을 만든다. 기준값은 다음과 같다.

WCAG 2.x 성공 기준대상최소 대비비
1.4.3 대비(최소), AA본문 텍스트4.5 : 1
1.4.3 대비(최소), AA대형 텍스트: 18 pt(24 px) 이상, 또는 14 pt(약 18.7 px) 이상 굵게3 : 1
1.4.6 대비(강화), AAA본문 텍스트 / 대형 텍스트7 : 1 / 4.5 : 1
1.4.11 비텍스트 대비, AA (2.1부터)UI 컴포넌트 경계, 상태 표시, 그래프의 필수 요소3 : 1
1.4.1 색의 사용, A색만으로 정보를 전달하지 말 것—
Y=0 Y=1 4.5:1 (Y=0.183, #767676) 3:1 (Y=0.300) 7:1 (Y=0.100) 흰 배경과 log 거리 = log₁₀(4.5) 가로축: log₁₀(Y + 0.05) — 대비비는 이 축 위의 거리의 지수
그림 18-2. 흰 배경(Y=1)에서 AA 본문(4.5:1)을 통과하려면 글자 휘도가 Y ≤ 0.1833이어야 한다. 이는 회색 #767676(Y=0.181, 4.54:1)에 해당한다. #777777은 4.48:1로 아깝게 떨어진다.

아래 검사기는 CB.relLum, CB.contrast로 대비비를 계산하고, 같은 쌍의 APCA Lc 값(다음 절)도 함께 보여 준다. 아래쪽 두 그래프는 글자색을 고정하고 배경을 회색 0→255로 훑었을 때 두 지표가 어떻게 변하는지를 그린다.

SIMULATOR

대비 검사기: WCAG 대비비와 APCA Lc

예제
WCAG 대비비—
AA 본문 4.5—
AA 대형·UI 3.0—
AAA 본문 7.0—
APCA Lc—
Y 글자 / 배경—
해볼 것: ① "검정/회색"과 "흰/회색"을 비교하라. WCAG는 검정 글자(4.69)가 흰 글자(4.48)보다 낫다고 판정하지만, APCA는 흰 글자(Lc −76.6)가 검정 글자(Lc 33.0)보다 훨씬 읽기 쉽다고 본다. 화면의 견본을 직접 비교해 보라. ② 아래 그래프에서 검정·흰 글자 곡선이 교차하는 배경 회색 값을 읽어라. WCAG는 코드값 118 부근, APCA는 164 부근이다. ③ "파랑/다크"는 3.61:1로 대형 텍스트만 통과한다 — 같은 #0072b2가 흰 배경에서는 5.19:1이다.
흔한 실수

(1) 감마 인코딩된 코드값의 평균이나 HSL의 L로 "밝기"를 비교하는 것. HSL L은 #0000ff와 #ffff00을 같은 50%로 주지만 상대 휘도는 0.072 대 0.928, L*는 32.3 대 97.1이다. (2) 반투명 전경색을 그대로 넣는 것. 대비는 알파 합성 후의 불투명 색으로 계산해야 하고, 합성 자체도 원칙적으로 선형광에서 해야 한다(브라우저의 기본 합성은 인코딩 값에서 일어나므로 실제 화면 색을 기준으로 잰다). (3) 안티앨리어싱된 1 px 선이나 얇은 서체는 실제로 표시되는 대비가 계산값보다 낮다.

대비비의 한계와 APCA

WCAG 2.x 공식은 단순하고 재현 가능하다는 큰 장점이 있지만, 지각과 어긋나는 세 가지 구조적 한계가 있다.

WCAG 3의 후보로 개발 중인 APCA(Accessible Perceptual Contrast Algorithm)는 휘도에 극성별로 다른 지수를 적용한 차이로 명도 대비 Lc(lightness contrast)를 정의한다. 아래는 공개 구현 0.0.98G-4g의 상수 그대로다.

$$Y_s = 0.2126729\,(R/255)^{2.4} + 0.7151522\,(G/255)^{2.4} + 0.0721750\,(B/255)^{2.4},\qquad Y_s \leftarrow Y_s + (0.022 - Y_s)^{1.414}\ \ (Y_s < 0.022)$$ $$S = \begin{cases} 1.14\,\big(Y_{bg}^{0.56} - Y_{txt}^{0.57}\big) & Y_{bg} > Y_{txt}\ \text{(밝은 배경)} \\ 1.14\,\big(Y_{bg}^{0.65} - Y_{txt}^{0.62}\big) & Y_{bg} < Y_{txt}\ \text{(어두운 배경)} \end{cases} \qquad L_c = 100\times\begin{cases} 0 & |S| < 0.1 \\ S - 0.027 & S > 0 \\ S + 0.027 & S < 0 \end{cases}$$
\(|Y_{bg}-Y_{txt}| < 0.0005\)이면 Lc = 0. 지수 2.4는 sRGB 구간 함수가 아닌 단순 거듭제곱이고, 흑 근처의 소프트 클램프(0.022, 1.414)가 플레어 역할을 한다. 부호는 극성을 나타낸다: 양수 = 밝은 배경의 어두운 글자, 음수 = 반대.

검증 수치: 흰 배경의 검정 글자는 Lc 106.04, 검정 배경의 흰 글자는 Lc −107.88이다. #888888 글자/흰 배경은 Lc 63.1, 흰 글자/#888888 배경은 Lc −68.5로, WCAG가 둘 다 3.54:1로 같게 보는 쌍을 APCA는 구별한다.

|Lc|APCA 문서가 제시하는 대략적 용도(단순화 기준)
90본문 텍스트 권장(작은 글자 포함)
75본문 텍스트 최소
60본문 외 콘텐츠 텍스트, 대략 24 px 일반 / 16 px 굵게
45대형 제목, 큰 아이콘·필수 비텍스트 요소
30판독이 필수가 아닌 부가 텍스트, 비활성 요소, 플레이스홀더의 하한
15비텍스트 요소(구분선 등)가 보이기 위한 하한

APCA의 실제 권장값은 폰트 크기·굵기별 조회표로 주어지며 버전에 따라 바뀌어 왔다. 위 표는 개념 이해용 요약이고, 규정 준수 판단은 여전히 WCAG 2.x(현재 법령·조달 기준이 참조하는 문서)로 해야 한다.

심화: 두 지표의 교차점이 다른 이유

배경이 회색일 때 검정 글자와 흰 글자 중 어느 쪽이 나은가? WCAG에서는 \((Y+0.05)/0.05 = 1.05/(Y+0.05)\)에서 \(Y = \sqrt{1.05\times0.05} - 0.05 = 0.179\), 즉 코드값 약 118의 회색이 경계다. APCA는 극성마다 지수가 달라 경계가 코드값 약 164(APCA의 \(Y_s\)≈0.35)로 밝은 쪽으로 이동한다. 코드값 118–164 사이의 중간 회색·주황·밝은 파랑 배경에서 두 지표는 반대 결론을 낸다. 실무에서 "흰 글자 버튼이 더 읽기 쉬운데 WCAG 검사기는 검정을 추천한다"는 불일치가 바로 이 구간이다.

색각 이상 시뮬레이션

2색각자는 원추세포 한 종류가 없어 색 공간이 3차원에서 2차원으로 줄어든다. LMS 공간에서 결손된 원추 축 방향으로 놓인 색들은 모두 같게 보이며, 이 직선들을 혼동선(confusion lines)이라 한다(2장). 시뮬레이션은 각 색을 혼동선을 따라 "2색각자가 보는 2차원 면" 위로 사영해, 정상 3색각자에게 같은 인상을 주는 색을 찾는 문제다.

이 장은 Machado 2009의 severity 1.0 행렬을 쓴다.

$$\mathbf{M}_P=\begin{bmatrix}0.152286&1.052583&-0.204868\\0.114503&0.786281&0.099216\\-0.003882&-0.048116&1.051998\end{bmatrix}\quad \mathbf{M}_D=\begin{bmatrix}0.367322&0.860646&-0.227968\\0.280085&0.672501&0.047413\\-0.011820&0.042940&0.968881\end{bmatrix}$$ $$\mathbf{M}_T=\begin{bmatrix}1.255528&-0.076749&-0.178779\\-0.078411&0.930809&0.147602\\0.004733&0.691367&0.303900\end{bmatrix},\qquad \mathbf{c}'_{\text{lin}} = \operatorname{clip}_{[0,1]}\!\big(\mathbf{M}\,\mathbf{c}_{\text{lin}}\big)$$
각 행의 합은 1(소수점 6자리 반올림 범위)이므로 백색과 무채색 축은 보존된다. \(\mathbf{c}_{\text{lin}}\)은 sRGB 코드값을 디코딩한 선형 RGB다.
sRGB 8비트#d62728 디코딩IEC 61966-2-1 선형 RGB.672 .020 .021 M_D · c+ clip 인코딩#8b7c1f 잘못된 경로: 코드값/255에 바로 M을 곱함 → #675826 원색 올바른 D 감마 오류 D (ΔE00 15.1)
그림 18-3. 시뮬레이션 행렬은 선형광(원추 흥분량에 비례하는 양) 사이의 관계이므로 반드시 디코딩한 값에 적용한다. tab10의 빨강 #d62728을 2형으로 변환할 때 인코딩 값에 곱하면 결과가 ΔE00 15.1만큼 어긋나 실제보다 훨씬 어둡게 나온다.
SIMULATOR

CVD 시뮬레이터: 정상 / 1형 / 2형 / 3형

테스트 이미지
핵심색 최소 ΔE00 · 정상—
1형—
2형—
3형—
감마 오류 평균 ΔE00 (2형)—
해볼 것: ① "선 차트"(matplotlib tab10 앞 6색)를 1형으로 보면 주황·초록·빨강·갈색 선의 구분이 거의 사라진다. 최소 ΔE00가 정상 16대에서 한 자릿수 초반으로 떨어지는 것을 확인하라. ② "히트맵" 왼쪽(jet)은 2형에서 노랑–초록 영역이 평탄해지고, 오른쪽(viridis 근사)은 명도 기울기가 그대로 남는다. ③ severity를 낮추면 이상 3색각을 근사한다. 이 장의 중간 severity는 Machado 원 논문의 단계별 행렬이 아니라 단위 행렬과 severity 1.0 행렬의 선형 보간이므로 정성적으로만 쓴다. ④ 감마 오류를 켜면 채도 높은 색일수록 결과가 크게 어긋난다.
공학자 관점

CVD 시뮬레이션은 "그 사람이 보는 색"을 재현하는 것이 아니라 "정상 관측자에게 같은 정보를 남기는 색"을 만드는 것이다. 설계 검증 용도로는 이것으로 충분하다: 시뮬레이션 후의 ΔE00가 작으면 실제로도 구별이 어렵다. 2색각 시뮬레이션은 가장 심한 경우이므로, 이것을 통과한 팔레트는 훨씬 흔한 이상 3색각(2형 이상 약 5%)에도 안전하다. 3형은 드물지만 청–황 축을 쓰는 발산형 컬러맵을 검사할 때 확인한다.

컬러맵의 네 가지 유형과 품질 지표

컬러맵(colormap)은 스칼라 \(t\in[0,1]\)을 색으로 보내는 곡선 \(\mathbf{c}(t)\)다. 데이터의 구조에 따라 곡선이 가져야 할 모양이 다르다.

순차형 L* 단조 증가 예: 밀도, 강도 발산형 중심에서 L* 최대, 대칭 예: 오차, 상관계수 순환형 양 끝 같은 색, L* 일정 예: 위상, 풍향 범주형 점 집합, 최소 ΔE 최대화 예: 계열, 클래스 아래 상자: 가로축 t, 세로축 L* (개념도)
그림 18-4. 네 유형의 컬러맵과 명도 프로파일. 순차·발산·순환 띠는 이 장의 OKLCh 생성기로 계산한 실제 색이고, 범주형은 Okabe–Ito 팔레트다.

연속 컬러맵의 품질은 두 곡선으로 진단한다. 하나는 명도 프로파일 \(L^*(t)\), 다른 하나는 \(t\)를 \(N\)등분했을 때 인접 견본 사이의 색차 \(\Delta E_{00}(t_i, t_{i+1})\)다.

$$\text{단조성: } \operatorname{sign}\!\Big(\tfrac{dL^*}{dt}\Big)=\text{const},\qquad \text{균일성: } \mathrm{CV} = \frac{\sigma(\Delta E_{00,i})}{\overline{\Delta E_{00,i}}},\qquad \text{분해능: } \textstyle\sum_i \Delta E_{00,i}$$
인접 ΔE가 일정하면(CV→0) 같은 데이터 차이가 어디서나 같은 지각 차이로 보인다. 합 \(\sum\Delta E_{00}\)은 곡선의 지각 길이로, 구별 가능한 단계 수에 비례한다.
050100 L* L* ≈ 90–95 평탄 구간 jet viridis(근사) t (데이터 값)
그림 18-5. 계산된 L* 프로파일(33점). jet는 t≈0.38–0.66에서 L*가 90–95로 거의 평탄하다가 t≈0.66 이후 급락한다. 평탄 구간에서는 데이터가 변해도 명도가 변하지 않아 구조가 묻히고, 꺾이는 지점(t≈0.38, 0.66 부근)에서는 데이터에 없는 경계가 보인다. viridis(9개 공개 기준점을 OKLab 보간한 근사)는 L*가 14.9→90.9로 거의 직선이다.
SIMULATOR

컬러맵 분석기: 색 띠, L* 프로파일, 인접 ΔE00

컬러맵
지표 계산 관측자
L* 범위—
L* 반전 수—
인접 ΔE00 최소/최대—
CV—
ΣΔE00 (지각 길이)—
해볼 것: ① jet에서 ΔE00 그래프가 0.1 부근까지 떨어지는 구간(평탄)과 10 가까이 튀는 구간(가짜 경계)을 찾아라. 같은 데이터 차이가 지각적으로 100배 다르게 보인다는 뜻이다. ② 관측자를 2형으로 바꾸면 jet의 ΣΔE00가 234→177로 줄고, OKLCh 순환 맵은 최소 ΔE00가 0.03까지 떨어진다(명도가 일정해 색상 정보만 남기 때문). ③ gray도 CV가 0.22로 완전히 균일하지 않다. sRGB 인코딩은 L*와 비슷하지만 같지 않고, ΔE00의 명도 가중 \(S_L\)이 L*=50에서 멀수록 차이를 작게 평가하기 때문이다.
컬러맵 (65점)L* 범위L* 반전인접 ΔE00 최소–최대CVΣΔE00 정상ΣΔE00 2형
gray0–10000.63–1.580.2275.275.2
jet12.9–95.830.09–9.870.49234.3177.0
HSV 무지개 (240°→0°)32.3–97.130.13–6.190.63198.9137.6
viridis (9점 근사)14.9–90.901.24–2.410.16119.789.7
OKLCh 순차 (이 장)14.5–94.401.41–2.080.12113.089.2
OKLCh 발산 (이 장)33.9–95.11 (중심)1.53–4.230.24121.4113.9

jet의 ΣΔE00(234)가 viridis(120)의 두 배라는 점은 흥미롭다. "jet가 더 많은 색을 쓰니 더 많은 단계를 구별한다"는 주장은 숫자로 보면 일부 맞다. 문제는 그 길이가 고르게 분배되지 않고(CV 0.49), 명도가 데이터 순서와 무관하게 오르내린다는 데 있다. 판독자는 명도 변화를 값의 변화로, 색상 경계를 데이터의 경계로 읽는다.

흔한 실수

MATLAB은 2014b에서 기본 컬러맵을 jet에서 parula로, matplotlib은 2.0에서 viridis로 바꿨다. 그러나 오래된 스크립트, 열화상 카메라 GUI, CFD 후처리 도구의 기본값에는 여전히 jet·rainbow가 많다. 리포트에 jet를 쓰면 흑백 인쇄 시에도 값의 순서가 사라진다(양 끝이 모두 어둡다: L* 12.9와 25.4).

같은 데이터, 다른 그림: 가짜 경계와 숨은 구조

컬러맵의 결함은 실제 2차원 데이터에서 가장 잘 드러난다. 아래 시뮬레이터는 매끄러운 합성 필드(두 개의 넓은 가우시안 언덕 + 선형 경사)에 진폭이 작은 좁은 봉우리 하나를 더한 것을 두 컬러맵으로 나란히 그린다. 데이터는 어디에도 불연속이 없다.

$$f(x,y) = 0.6\,e^{-\frac{(x-0.3)^2+(y-0.45)^2}{2(0.22)^2}} + 0.45\,e^{-\frac{(x-0.75)^2+(y-0.7)^2}{2(0.16)^2}} + 0.25\,x + a\,e^{-\frac{(x-0.62)^2+(y-0.28)^2}{2(0.045)^2}}$$
\(a\)는 "찾아야 할 작은 구조"의 진폭. 표시 범위는 \(a=0\)일 때의 최소–최대로 고정하고 넘치는 값은 잘라낸다.
SIMULATOR

합성 2D 필드 렌더 비교

관측자
봉우리 중심 데이터 값 t—
봉우리 ΔE00 · 왼쪽—
봉우리 ΔE00 · 오른쪽—
봉우리 ΔL* 왼쪽 / 오른쪽—
해볼 것: ① jet 쪽에서 청록·노랑 띠가 마치 날카로운 등고선처럼 보이는지 확인하고, 등고선을 겹쳐 실제 데이터 간격과 비교하라. ② 진폭 a를 0.02로 줄여 봉우리가 어느 쪽에서 먼저 사라지는지 보라. 봉우리가 놓인 값 근처에서 컬러맵의 국소 ΔE00/Δt가 작을수록 구조가 묻힌다. 읽음값의 ΔE00가 1 근처면 사실상 보이지 않는다. ③ 2형으로 바꾸면 jet의 녹–황–적 구간이 한 덩어리로 합쳐진다.

국소 가시성은 컬러맵의 기울기 \(\|d\mathbf{c}/dt\|\)(지각 단위)와 데이터 진폭의 곱이다. 균일 컬러맵은 이 기울기가 어디서나 같아 "어느 값 근처의 구조든 같은 크기로 보인다"는 보장을 준다. 반대로 jet는 특정 값 대역(청록 0.3–0.4, 주황 0.7–0.8)을 확대하고 녹–황 대역을 압축하는 비선형 확대경과 같다. 의도적으로 특정 대역을 강조하고 싶다면 그것은 컬러맵이 아니라 데이터 변환(예: 로그, 히스토그램 평활화)으로 하고, 그 사실을 범례에 밝혀야 한다.

지각 균일 컬러맵 설계: OKLCh 경로

지각 균일 컬러맵을 직접 만드는 가장 간단한 방법은 균등 색공간 안에서 곡선을 그리는 것이다. OKLab(7장)의 극좌표 OKLCh에서 명도 \(L\)을 \(t\)에 선형으로, 색상 \(h\)를 일정 속도로 돌리고, 채도 \(C\)는 양 끝에서 줄인다(아주 어둡거나 밝은 영역에서는 sRGB 가멋이 좁기 때문).

$$L(t) = L_0 + (L_1 - L_0)\,t,\qquad h(t) = h_0 + (h_1 - h_0)\,t,\qquad C(t) = C_{\max}\,\sin\!\big(\pi(0.15 + 0.7t)\big)$$ $$\mathbf{c}(t) = \text{OKLab}^{-1}\big(L,\ C\cos h,\ C\sin h\big),\qquad \text{가멋 밖이면 } C \leftarrow \max\{C' \le C : \mathbf{c}\in[0,1]^3\}$$
가멋 매핑은 \(L\)과 \(h\)를 유지한 채 채도만 이분 탐색으로 줄인다. 명도 단조성은 보존되지만 ΔE 균일성은 깨질 수 있으므로, 필요하면 누적 ΔE00로 \(t\)를 재매개화한다.

ΔE 등간격 재매개화: 곡선을 촘촘히(256점) 샘플링하고 누적 지각 길이 \(s(t)=\sum_{j\le i}\Delta E_{00,j}\)를 구한 뒤, \(s\)가 등간격이 되도록 \(t\)를 다시 정한다. 결과 맵은 인접 ΔE00가 거의 일정해진다. 다만 ΔE00 자체가 작은 색차용 공식이므로, 재매개화는 "같은 거리 = 같은 지각 차이"의 1차 근사다.

SIMULATOR

지각 균일 컬러맵 생성기 (OKLCh)

유형
가멋 매핑된 샘플—
L* 범위 / 반전—
인접 ΔE00 CV—
ΣΔE00 정상 / 2형—
대칭점 t=0.25↔0.75 ΔE00 정상 / 2형—
—
해볼 것: ① 기본값(h 300°→100°, 보라→노랑)은 viridis와 비슷한 경로다. ΣΔE00가 2형에서도 크게 줄지 않는지 확인하라 — 명도가 정보를 대부분 실어 나르기 때문이다. ② 최대 채도를 0.3까지 올리면 가멋 매핑 비율이 커지고 ΔE CV가 나빠진다. 재매개화를 켜서 CV가 얼마나 회복되는지 보라. ③ 발산형에서 중심 대칭인 두 점(t=0.25, 0.75)은 명도가 같으므로 색상만으로 부호(+/−)를 구별해야 한다. h₀=255°(파랑), h₁=30°(빨강)이면 2형에서도 이 ΔE00가 38 정도로 남지만, h₀=140°(초록)로 바꾸면 정상 43에서 2형 0.6으로 붕괴한다 — 양수와 음수가 같은 색이 된다. 발산형의 두 팔은 청–황(또는 청–적) 축으로 고른다. ④ 아래 hex 목록은 matplotlib LinearSegmentedColormap.from_list 등에 그대로 넣을 수 있는 9개 기준점이다.
공학자 관점

컬러맵을 직접 만들 때의 체크리스트: (1) L* 단조(순차) 또는 대칭(발산), 반전 0; (2) 인접 ΔE00 CV ≲ 0.2; (3) 2형 시뮬레이션 후 ΣΔE00가 정상의 70% 이상; (4) 흑백 인쇄 시 순서 유지(= 조건 1); (5) 가장 어두운 끝이 배경과 구별될 것 — 다크 테마에서는 L₀를 올리거나 밝은→어두운 방향으로 뒤집는다. 이 장의 기본 OKLCh 순차 맵은 (1)–(4)를 만족한다(CV 0.12, 2형 ΣΔE00 79%).

범주형 팔레트 설계와 진단

범주형 팔레트는 \(n\)개의 색 \(\{\mathbf{c}_k\}\)로, 목표는 가장 비슷한 두 색의 차이를 최대화하는 것이다. 평균 색차가 아니라 최솟값이 판독 오류를 결정하기 때문이다. 관측자 집합 \(\mathcal{V}\)=\{정상, 1형, 2형\}에 대해 강건하게 쓰면 다음과 같다.

$$\max_{\{\mathbf{c}_k\}}\ \min_{v\in\mathcal{V}}\ \min_{i\lt j}\ \Delta E_{00}\big(\mathrm{Lab}(\mathbf{M}_v\mathbf{c}_i),\ \mathrm{Lab}(\mathbf{M}_v\mathbf{c}_j)\big)\quad \text{s.t.}\ \ \mathbf{c}_k\in\text{sRGB},\ \ \text{CR}(\mathbf{c}_k,\text{bg})\ge 3$$
마지막 제약은 WCAG 1.4.11(그래프의 필수 요소는 배경과 3:1). 이 문제는 최대-최소 거리 배치(max-min dispersion)로 NP-난해이지만, 탐욕적 최원점 선택(farthest-point)이 실용적으로 충분히 좋은 해를 준다.

설계 원칙은 계산 결과에서 바로 나온다. (1) 명도를 다양화하라. CVD 시뮬레이션 후 남는 차이의 대부분은 명도와 청–황 방향이므로, 모든 색을 같은 명도에 두면(HSL에서 L만 고정한 팔레트처럼) 2형에서 붕괴한다. (2) 빨강과 초록을 같은 명도로 짝짓지 마라. (3) 색 수를 줄여라. sRGB 가멋 안에서 세 관측자 모두에 대해 최소 ΔE00 ≥ 15를 유지할 수 있는 색은 대략 8개 안팎이다(아래 최적화기로 직접 확인할 수 있다). 그 이상은 모양·선 종류·직접 레이블과 결합해야 한다.

SIMULATOR

팔레트 진단기: 쌍별 ΔE00 행렬

예제
행렬 표시 관측자
최소 ΔE00 · 정상—
1형—
2형—
3형—
L* 범위—
해볼 것: ① tab10을 1형으로 보면 주황 #ff7f0e와 초록 #2ca02c의 ΔE00가 1.25로 사실상 같은 색이다. ② Okabe–Ito(색각 이상을 고려해 설계된 8색)는 세 관측자 모두 최소 11 이상을 유지한다. ③ "최대-최소 팔레트 생성"은 OKLCh 격자(L 0.42–0.86, C 0.06–0.22, h 12° 간격)의 후보 중에서 정상·1형·2형 최솟값을 동시에 최대화하는 색을 탐욕적으로 고른다. n=8에서 세 관측자 최소 약 18.5를 얻는다. 3:1 제약을 켜면 밝은 후보가 빠져 최솟값이 내려간다 — 대비와 구별성의 트레이드오프다. 행렬 칸의 빨간 테두리는 ΔE00 < 10이다.
팔레트n최소 ΔE00 정상1형2형3형최악 쌍(1형/2형)
matplotlib tab101016.21.33.39.5 /
Okabe–Ito821.712.311.610.9 /
빨강·주황·초록 신호등326.51.34.614.5 /
원색 6색 (#ff0000 …)623.44.18.79.3
탐욕 최대-최소 (이 장)818.818.918.56.13형은 목적함수에서 제외

"ΔE00가 얼마면 범주로 구별되는가"에 대한 표준값은 없다. ΔE00 1–3은 인쇄·디스플레이 허용차 영역이고(7장), 범주 판독은 훨씬 큰 차이를 요구하며 마커 크기에 강하게 의존한다. 이 장에서는 작은 마커·얇은 선 기준으로 ΔE00 ≥ 10을 경고 문턱, ≥ 15를 목표로 쓰는 경험적 기준을 채택한다.

색에만 의존하지 말 것: 중복 부호화

WCAG 1.4.1은 색을 정보 전달의 유일한 수단으로 쓰지 말라고 요구한다. 그래프에서는 계열마다 마커 모양·선 종류를 다르게 하고, 범례 대신 선 끝에 직접 레이블을 다는 것이 가장 효과적이다. 범례 대조는 작업 기억을 쓰지만 직접 레이블은 색 구별 자체가 필요 없다.

색만 사용 범례 대조 필요, 1형에서 세 선 혼동 색 + 모양 + 선 종류 + 직접 레이블 A B C 어떤 색각에서도, 흑백 인쇄에서도 판독 가능
그림 18-6. 중복 부호화. 오른쪽은 Okabe–Ito의 주황·파랑·검정을 쓰고, 계열마다 마커 모양과 선 종류를 달리하며, 선 끝에 레이블을 붙였다.

UI 상태 색과 다크 모드

오류/경고/성공/정보의 관습적 색인 빨강/주황/초록/파랑은 색각 이상의 최악 축 위에 놓여 있다. 흔히 쓰이는 상태색 예 #d32f2f(오류)와 #2e7d32(성공)는 정상 관측자에게 ΔE00 70 이상 떨어져 있지만, L*가 47.1과 46.3으로 거의 같아 2형 시뮬레이션 후 ΔE00 9.1, 1형 후 10.7로 줄어든다. 작은 아이콘이나 얇은 테두리라면 구별이 어렵다.

나쁜 예: 색 점만 서버 A 서버 B 서버 C 2형: 점 세 개가 거의 같은 갈색 좋은 예: 모양 + 텍스트 + 색 !서버 A · 오류 서버 B · 정상 서버 C · 정상 아이콘 형태와 단어가 1차 정보 색 쌍 바꾸기 ▲ +3.2% ▼ −1.4% 상승/하락에 파랑/주황 2형 ΔE00 56.6 기호(▲▼)도 함께
그림 18-7. 상태 표시의 해결 패턴. (1) 아이콘 형태(삼각형 경고, 원 안 체크)와 텍스트를 1차 정보로, 색은 보조로; (2) 대립 쌍이 꼭 필요하면 빨강–초록 대신 파랑–주황(#0072b2/#d55e00: 2형 후 ΔE00 56.6); (3) 빨강–초록을 유지해야 하면 명도를 크게 벌린다(어두운 빨강 + 밝은 초록).

다크 모드에서는 같은 색이 다른 대비를 갖는다. 흰 배경에서 대비를 확보하려고 어둡게 만든 색은 어두운 배경(#121212, Y=0.006)에서 대비가 떨어진다. 위 상태색은 흰 배경에서 3.1–5.1:1이지만 #121212 위에서는 오류 3.76, 성공 3.65로 본문 기준(4.5)을 밑돈다. 반대로 흰 배경에서 3.11인 주황은 다크에서 6.02다. 대비비는 배경을 기준으로 한 명도 거리이므로, 배경 명도가 뒤집히면 팔레트의 명도 배치도 뒤집어야 한다.

표준적인 처방은 OKLCh에서 색상각 \(h\)는 유지하고 명도를 올리며 채도를 낮추는 것이다. 어두운 주변에서는 밝은 고채도 색이 눈부시게 보이고(9장의 주변 효과), 밝은 영역의 sRGB 가멋은 채도 여유가 적어 어차피 채도를 줄여야 한다.

$$L' = \max(L,\ L_{\min}),\qquad C' = k\,C,\qquad h' = h,\qquad \text{가멋 밖이면 } C' \text{ 축소}$$
\(L_{\min}\)=0.72, \(k\)=0.8이면 네 상태색이 #121212 위에서 모두 7:1 이상(오류 #f9786d 7.08, 경고 #ed854b 7.19, 성공 #7ab679 7.85, 정보 #5eace8 7.62)이 된다. 대신 이 색들은 흰 배경에서 2.4–2.7:1로 떨어지므로 테마별로 별도 토큰을 둔다.
SIMULATOR

다크 모드 팔레트 조정기

팔레트
최소 대비 · 라이트 원본—
다크 원본—
다크 조정—
평균 이동 ΔE00 (원본→조정)—
조정 후 최소 쌍별 ΔE00 (정상/2형)—
해볼 것: ① 상태색을 그대로 다크 배경에 두면 오류·성공이 4.5를 밑돈다. L_min을 0.72로 올리면 모두 7 이상이 된다. ② L_min을 0.9까지 올리면 대비는 계속 오르지만 색들이 서로 가까워져(모두 밝은 파스텔) 쌍별 ΔE00가 줄어든다. 대비와 범주 구별성 사이의 최적점을 찾아라. ③ 다크 배경을 0(순흑)으로 내리면 대비비는 오르지만, APCA 관점에서는 순흑 배경의 밝은 글자가 번져 보이는 할레이션이 생기므로 실무에서는 #121212 안팎의 짙은 회색을 쓴다.
공학자 관점

디자인 토큰은 "red-500" 같은 색 이름이 아니라 "status-error-fg-on-surface" 같은 역할로 정의하고, 테마마다 대비비를 검사한 값을 넣는다. CI에서 모든 (전경 토큰, 배경 토큰) 쌍에 대해 contrast ≥ 4.5(텍스트)와 ≥ 3(아이콘·테두리)를 단위 테스트로 돌리면 테마 추가 시 회귀를 막을 수 있다. 그래프 팔레트도 같은 방식으로 CVD 후 최소 ΔE00를 테스트한다.

핵심 정리

  1. WCAG 2.x 대비비는 \((Y_{\max}+0.05)/(Y_{\min}+0.05)\)이며 \(Y\)는 디코딩한 선형 sRGB의 가중합이다. 본문 4.5:1(AA), 대형 텍스트·UI 컴포넌트 3:1, 본문 AAA 7:1. 흰 배경 위 AA의 경계는 #767676(4.54:1)이다.
  2. WCAG 대비비는 극성을 무시하고 어두운 색끼리의 대비를 과대평가한다. APCA Lc는 극성별 지수를 써서 흰 바탕 검정 106.0, 검정 바탕 흰색 −107.9를 준다. 검정/흰 글자 선택의 경계 회색은 WCAG 코드값 약 118, APCA 약 164다.
  3. 색각 이상 시뮬레이션(Machado 2009 등)은 선형 RGB에 3×3 행렬을 곱하는 것이다. 인코딩 값에 곱하면 tab10 빨강에서 ΔE00 15 오차가 난다.
  4. 컬러맵은 L* 프로파일(단조성)과 인접 ΔE00(CV)로 진단한다. jet는 L* 반전 3회, 인접 ΔE00 0.09–9.87(CV 0.49)로 가짜 경계와 평탄 구간을 만든다. viridis 근사와 OKLCh 순차 맵은 반전 0, CV 0.12–0.16이다.
  5. OKLCh에서 L을 선형, h를 일정 속도로 바꾸고 채도만 줄이는 가멋 매핑을 하면 지각 균일 맵을 직접 만들 수 있다. 누적 ΔE00 재매개화로 균일성을 더 높인다.
  6. 범주형 팔레트는 CVD 시뮬레이션 후의 최소 쌍별 ΔE00로 평가한다. tab10은 1형에서 1.3까지 떨어지고, Okabe–Ito는 세 관측자 모두 11 이상이다. 명도를 다양화하고, 8색 안팎을 넘으면 모양·레이블로 중복 부호화한다.
  7. 빨강–초록 상태색은 명도가 비슷하면 2형에서 ΔE00 9 수준으로 붕괴한다. 아이콘·텍스트를 1차 정보로, 대립 쌍은 파랑–주황으로, 다크 모드에서는 OKLCh 명도를 올리고 채도를 낮춘 별도 토큰을 쓴다.

확인 퀴즈

흰 배경(#ffffff)에 회색 글자를 쓸 때 WCAG AA 본문 기준(4.5:1)을 만족하는 가장 밝은 회색에 가장 가까운 것은?

4.5:1이 되려면 \(Y \le 1.05/4.5 - 0.05 = 0.183\). 코드값 118(#767676)은 Y=0.181로 4.54:1, 119(#777777)는 4.48:1로 떨어진다. #888888은 3.54:1로 대형 텍스트만 통과한다.

WCAG 2.x 대비비 공식의 분자·분모에 더해지는 0.05의 역할은?

0.05는 백색의 5%에 해당하는 플레어 항이다. 이 때문에 최대 대비가 21:1로 제한되고, 반대로 어두운 색끼리의 비가 지각보다 크게 계산되는 한계도 생긴다.

배경이 중간 회색 #777777일 때, WCAG와 APCA의 판정으로 옳은 것은?

WCAG의 검정/흰 경계는 Y≈0.179(코드값 약 118)이고 #777777은 그보다 약간 밝아 검정이 근소하게 이긴다. APCA는 어두운 배경-밝은 글자 극성에 다른 지수를 써서 경계가 코드값 약 164로 이동한다.

Machado 2009 행렬로 색각 이상을 시뮬레이션할 때 올바른 처리 순서는?

행렬은 선형광(원추 흥분에 비례하는 양) 사이의 선형 관계다. 인코딩 값에 곱하면 비선형 오차가 생겨, tab10 빨강의 2형 결과가 ΔE00 15만큼 어긋난다. a*=0은 2색각 모델이 아니다.

jet 컬러맵이 과학 데이터 표시에 부적합한 가장 핵심적인 이유는?

jet의 ΣΔE00(234)는 오히려 viridis 근사(120)보다 크다. 문제는 분배: L*가 3회 반전하고 인접 ΔE00가 0.09–9.87로 100배 차이 난다. 흑백 인쇄에서는 양 끝이 모두 어두워 순서도 사라진다.

상관계수(−1~+1)를 히트맵으로 표시할 때 가장 적합한 컬러맵 유형은?

의미 있는 중심값(0)이 있는 양은 발산형이다. 두 팔의 색상은 2형에서도 구별되도록 청–적 또는 청–황 축을 쓰고, 두 팔의 명도 프로파일을 대칭으로 맞춘다.

OKLCh 경로로 컬러맵을 만들 때 가멋 밖 색을 "L과 h를 유지하고 C만 줄여" 매핑하는 이유는?

채널별 클리핑은 명도와 색상을 함께 바꿔 L* 반전을 만들 수 있다. 채도만 줄이면 L(t)가 그대로 남아 단조성이 유지된다. 대가로 ΔE 균일성이 깨질 수 있어 재매개화로 보정한다.

범주형 팔레트를 평가하는 지표로 가장 적절한 것은?

판독 오류는 가장 비슷한 두 색에서 생긴다. tab10은 정상 최소 16.2지만 1형에서 주황–초록이 1.25로 붕괴한다. 평균이나 색상각 간격은 이를 드러내지 못한다.

라이트 테마의 상태색(#d32f2f, #2e7d32)을 다크 테마(#121212)에 그대로 쓸 때의 문제와 처방으로 옳은 것은?

흰 배경용으로 어둡게 잡은 색은 어두운 배경과의 명도 거리가 작다. OKLCh에서 L≥0.72, C×0.8로 조정하면 7:1 이상이 된다. 순흑 배경은 대비비는 올리지만 할레이션과 눈부심 문제를 만든다.

WCAG 1.4.1 "색의 사용"을 그래프에서 만족시키는 방법이 아닌 것은?

1.4.1은 색을 정보의 유일한 수단으로 쓰지 말라는 기준이다. 안전한 팔레트는 좋은 출발점이지만 그것만으로는 여전히 색에만 의존한다.