There are many themes, but this one is yours
테마는 많지만, Pi는 당신의 테마를 따릅니다
Pi의 새 기본 테마는 터미널 색상에서 색상 계열을 가져오고, 가독성에 맞춰 명도와 채도를 조정합니다. 글에서는 ANSI 팔레트만으로 복잡한 터미널 UI의 대비를 보장하기 어려운 이유와 OKLCH·OKHSL을 활용한 생성 과정을 설명합니다.
- 주제
AI 요약
Earendil은 터미널에서 쓰는 색상과 어울리는 Pi 테마를 만들었습니다. Pi는 시작할 때 터미널의 전경색, 배경색, ANSI 색상을 읽고, 색상 계열은 유지하면서 UI 각 요소가 읽히도록 명도를 조정합니다. 글은 이 과정의 중심에 있는 대비 계산과 색 공간 변환을 설명합니다.
ANSI 팔레트만으로 부족한 이유
ANSI 표준은 검정, 빨강, 초록, 노랑, 파랑, 자홍, 청록, 흰색이라는 이름만 정하고 색의 모양이나 배경과의 관계는 정하지 않습니다. 밝은 색상 8종도 원래 표준에 없었습니다. 일부 터미널이 굵은 글자를 더 밝게 표시하던 관행이 먼저 생겼고, 이후 aixterm이 밝은 색을 직접 선택하는 코드를 추가했습니다.
밝은 색이 더 높은 대비를 보장하지는 않습니다. Ghostty에 포함된 460개가 넘는 테마를 살펴본 결과, 어두운 테마에서는 밝은 색 변형이 기본 색보다 대비가 큰 경우가 약 60%였습니다. 밝은 테마에서는 약 4분의 1에 그쳤습니다. 밝은 배경에서 ‘밝은’ 색은 배경과 더 비슷해질 수 있기 때문입니다. 테마별 차이도 있습니다. Gruvbox Dark에서는 밝은 파랑의 대비가 더 크고, Catppuccin Mocha에서는 더 작으며, Tokyo Night에서는 두 색이 같습니다.
대비는 WCAG 2 대비 비율로 측정할 수 있습니다. 값은 대비가 없는 1:1부터 검정과 흰색 사이의 21:1까지입니다. 일반 텍스트는 최소 4.5:1, 큰 글자와 UI 요소는 3:1을 권장합니다. 많은 소프트웨어가 보조 텍스트에 쓰는 밝은 검정은 어두운 테마 대부분에서 3:1에도 못 미칩니다. ANSI 팔레트는 단순한 앱 출력에 색을 입히도록 만들어졌고, 테마도 대비 기준보다 보기 좋은 색상을 우선하는 경우가 많습니다. Pi처럼 약 60개의 색상 역할을 쓰는 복잡한 TUI에서는 이 팔레트만으로 모든 전경색과 배경색 조합의 가독성을 보장하기 어렵습니다.
대비에 맞춰 명도를 정하는 방식
RGB는 모니터가 색을 표시하도록 설계된 표현이라 사람의 색 지각을 직접 나타내지 않습니다. 예를 들어 흰 배경에서 #0000ff의 WCAG 대비는 8.6:1이지만 #00ff00은 1.4:1입니다. 두 색 모두 RGB 채널 하나가 최대값이라는 점만으로는 사람 눈에 보이는 밝기 차이를 설명할 수 없습니다.
Pi는 사람의 지각에 맞춘 OKLCH 색 공간을 사용합니다. OKLCH는 명도(Lightness), 채도(Chroma), 색상(Hue)을 축으로 삼습니다. Pi는 색상 역할마다 어떤 배경 위에 나타나는지 규칙을 적고, 각 조합에서 필요한 대비를 계산합니다. 예를 들어 오류 메시지는 기본 배경뿐 아니라 선택된 행과 도구 패널 위에서도 읽혀야 합니다. 패널은 터미널 배경과 구분될 만큼 대비가 있어야 하지만, 화면을 산만하게 만들 정도로 강해서는 안 됩니다.
일반적인 대비 알고리즘은 두 색을 입력받아 대비를 계산합니다. Pi에는 배경색과 필요한 대비가 주어졌을 때 적절한 색을 찾아내는 역방향 계산이 필요합니다. 먼저 패널 색상을 배경에 맞춰 정한 뒤, 각 규칙에서 요구하는 명도를 구하고 그중 가장 엄격한 값을 선택합니다.
처음에는 이미 알려진 지각 기반 대비 알고리즘을 그대로 적용한 프로토타입을 만들었습니다. Ghostty 테마 여러 개로 화면을 확인한 뒤, 배포할 때는 더 단순한 계산을 쓰고 싶었지만 간단한 측정 방식으로 결과를 재현하지 못했습니다. 대신 기준 알고리즘에 흰색부터 검은색까지 회색 배경을 입력해 각 대비 단계에 필요한 명도를 기록하고, 다섯 차 다항식으로 맞췄습니다. Pi에는 기준 알고리즘 대신 이 다항식의 계수만 들어갑니다.
색상과 채도의 보정
Pi는 터미널 색상을 RGB에서 OKLCH와 OKHSL로 변환해 원래 명도, 색상, 채도를 읽습니다. 대비 규칙으로 새 명도를 구한 다음, 원래 색상 계열을 유지하면서 OKHSL에서 색을 다시 만듭니다. 명도가 검정이나 흰색에 가까워질수록 채도를 낮추는 곡선도 적용합니다. 마지막에는 원래 색보다 더 선명해지지 않도록 OKLCH의 채도를 제한합니다.
이 제한은 Catppuccin Frappé의 분홍색에서 발견한 문제를 해결했습니다. 원본 #f4b8e4는 높은 명도 때문에 화면이 표현할 수 있는 색도 범위가 좁습니다. OKHSL 채도는 84%였지만, Pi가 가독성을 위해 색을 어둡게 옮기면 같은 84%가 더 넓은 색도 범위를 기준으로 계산돼 원본보다 훨씬 선명해졌습니다. 그 결과 색이 #eb76d1로 바뀌고 색도는 거의 두 배가 됐습니다. 원래 색도의 상한을 적용하자 강조색은 #cc92bd가 됐습니다.
색상 계열은 그대로 유지하지만, 화면이 해당 명도에서 표현할 수 있는 범위와 검정·흰색 방향의 채도 감소, 원본 색도의 상한을 차례로 적용합니다. 따라서 같은 채도 비율이라도 명도에 따라 실제 색도는 달라질 수 있습니다. 경고용 노랑과 강조용 보라에는 서로 다른 감소 곡선을 적용합니다.
적용 방법과 대비 설문
Pi는 터미널이 밝은 테마와 어두운 테마 사이를 바꾸면 테마도 다시 만듭니다. 터미널이 배경색만 제공하면 Pi가 자체 색상 계열을 사용합니다. 아무 색상도 보고하지 않으면 ANSI 색상을 사용하고 터미널이 직접 색을 그리도록 맡깁니다. 사용자가 테마를 따로 고르지 않았다면 시스템 테마가 기본으로 적용됩니다. 설정의 Theme 항목에서도 선택할 수 있습니다.
글에 나온 대비 수치는 기존 대비 지각 알고리즘이 추정한 평균값입니다. 실제 화면에서 사람들이 대비를 어떻게 느끼는지 보여주는 공개 데이터가 없어서, Earendil은 약 3분짜리 설문으로 응답을 모으고 있습니다. 참여자는 응답을 다른 참여자와 비교해 볼 수 있으며, 응답 데이터는 공개 대비 알고리즘을 학습하는 데 쓰입니다.
Lobsters 반응
- @mitsuhiko — 먼저 밝힙니다. 이 글은 우리 회사 블로그 글이고 코딩 에이전트와 추상적으로 관련은 있지만, AI와는 사실상 관계가 없습니다. 흥미롭게도 제가 여기서 배운 점은 지금 쓸 만한 대비 알고리즘이 없다는 것입니다. 그래서 실제 환경의 데이터를 모아 누구나 자유롭게 쓸 수 있는 오픈소스 대비 알고리즘을 만들려고 설문 참여를 부탁하고 있습니다. 참여해 주실 분은 여기로 가세요: https://contrastsurvey.earendil.com/
- @cesarandreu — 몇몇 항목은 정말 헷갈립니다. 절반쯤 진행했을 때부터 블록 하나가 ‘눈에 띈다’는 게 무슨 뜻인지 의문이 들기 시작했습니다. 여러분이 결과를 잘 정리한 블로그 글을 공개하길 바랍니다. 이제 조사 결과가 궁금해졌습니다. 덧붙여, 어떤 테마를 쓰시나요?
- @mitsuhiko — 몇몇 항목은 정말 헷갈립니다. 절반쯤 진행했을 때부터 블록 하나가 ‘눈에 띈다’는 게 무슨 뜻인지 의문이 들기 시작했습니다. 네, 길다는 건 압니다. 그래도 더 짧게 만들 수 있을지 잘 모르겠습니다. 그래도 뭔가 배울 수 있겠죠! 설문을 일찍 시험해 봤는데 악명 높은 알고리즘 하나가 다른 알고리즘보다 훨씬 잘 맞는다는 점이 확인됐습니다. 데이터가 모이고 있는 것 같습니다. 덧붙여, 어떤 테마를 쓰시나요? 저는 Pi 시스템 테마를 쓰고 있고 Ghostty 테마에 맞춰 낮에는 “Ayu Light”, 밤에는 “rapture”로 바뀌게 했습니다.
- @Gracana — 설문이 너무 깁니다. ‘약 60번 탭’이라고 쓰여 있지만, 한참 진행한 뒤 사각형과 단어를 비교하는 50회짜리 구간에 도달했습니다. 그것만으로도 많다고 느꼈는데, 움직이거나 서로 바뀌는 사각형 8회가 더 이어져서 그만뒀습니다. [수정] 아, 다른 댓글에서 이 점을 다루셨군요.
- @neeasade — 정말 좋습니다. 기존 밝은 테마와 어두운 테마의 대비 차이에 관한 설명을 봤는데, 한 가지 고려할 점은 제 생각에 어두운 테마에서는 대비가 더 중요하다는 것입니다. 같은 가독성을 얻으려면 대비를 더 크게 높여야 합니다(시각적 경험상). 제 Emacs 색상 라이브러리에 OKLCH 지원을 넣었지만 아직 써보지는 않았습니다. 더 최근에 나온 HCT 색 공간, Google Material 쪽에 관심을 빼앗겼습니다. HSLuv를 쓸 때 색역이 너무 좁아 채도를 적용해도 색이 전반적으로 탁해지는 문제가 있었던 걸로 기억합니다. OKHSL은 그 문제를 고쳤을지도 모르겠네요. 어쨌든 이런 동적 대비 조정은 정말 멋집니다. 프로그래머들은 색 하나를 고친 뒤 다른 색도 전부 손봐야 한다는 걸 깨닫고 끝없이 파고드는 일을 오래 겪어 왔으니까요.
원문: Earendil / 번역·요약: Trawling