HEX、RGB、HSL、そして新しい色空間

HEX、RGB、HSL、LCH、OKLCH が互いにどう関係するのか、HSL で作ったパレットが中間調で濁る理由、そして色域のクリッピングとアルファ合成が最終的な色に何をするのかを解説します。

CSS における色とは、3 つか 4 つの数値に、それを読み取るための規則、つまり色空間を加えたものです。画面で不ぞろいに見えるパレットは、たいてい視覚の仕組みに合っていない空間で段階付けされています。

HEX と RGB は同じ数値

#3B82F6 は 16 進数で書かれた 3 バイトです。3B は 59、82 は 130、F6 は 246 なので、rgb(59, 130, 246) は同じ色を 10 進数で書いたものであり、カラーコンバーターはこの間を無損失で変換します。

3 桁の省略記法は、各桁を 0 で埋めるのではなく複製します。そのため #F80#F08000 ではなく #FF8800 に展開されます。短縮形を持つ色は 4,096 色だけです。#RGBA は同じやり方でアルファの 1 桁を加え、#RRGGBBAA は 4 バイトすべてを書き出します。このアルファのバイトは 0 から 255 の範囲を取り (1A が 10%、80 が 50%、CC が 80%)、CSS の他のアルファはすべて 0 から 1 の範囲を取ります。新しい構文では、成分を空白区切りで書き、アルファをスラッシュの後ろに置くこともできます。rgb(59 130 246 / 50%) のような形です。

これらの数値は光の量ではありません。sRGB は各チャンネルをおよそ 2.2 の伝達曲線を通してエンコードするため、128 が発する光は 255 の半分ではなく約 21.6% です。生の値の上で計算されるブレンドはすべて、この曲線を引き継ぎます。

HSL、HSV、そして明度ではない明度

HSL は同じ RGB の立方体を円柱に並べ替えたものです。色相は角度 (0 が赤、120 が緑、240 が青)、彩度は中心のグレー軸からの距離、明度は 0 から純色である 50% を経て、白である 100% までを取ります。HSV は明度の代わりに明度値 (value) を使い、その上端は白ではなく純色なので、淡い色は彩度を下げて作ります。どちらも知覚については何も知りません。

HSL相対輝度OKLCH の明度
#FFFF00hsl(60 100% 50%)0.9280.97
#0000FFhsl(240 100% 50%)0.0720.45

どちらも明度 50% を名乗っていますが、一方は他方の約 13 倍の光を発しています。すべての色相を 50% に固定したトークンの列は、重みがばらばらの寄せ集めに見えます。

色相と彩度を固定したまま HSL の明度を刻む方法も、同じ理由で失敗します。さらにもう 1 つ理由があります。彩度はその明度で使える幅に対して測られるため、50% の段が最も色鮮やかになり、その両側はどちらも色が抜けていきます。色相角は保たれる一方で知覚される彩度は崩れ、これが中間の段を濁って見せます。それらは基準色を暗くしたものではなく、誰も選んでいない量だけ基準色をグレーに寄せたものです。

その最たる例が黄色です。HSL の明度を下げていくと、黄色はまっすぐオリーブへ向かいます。その色相角における暗い黄色とは、そもそもオリーブだからです。黄色のまま保つには、暗くしながら色相をオレンジ寄りに回す必要があります。青は逆の問題を抱えており、輝度が低すぎるため、使える変化の幅がすべて 50% より上に集まります。

LCH と OKLCH

LCH は CIE Lab を極座標で表したもので、等色実験から作られています。明度は 0 から 100、クロマ (色の鮮やかさ。固定の最大値はありません)、そして色相角から成ります。OKLCH は同じ形を Oklab の上に載せたもので、Lab で知られる色相のずれ、とりわけ青が明るくなるにつれて紫に寄っていく現象を補正します。

.a { color: oklch(0.62 0.21 260); }        /* L 0 to 1, C about 0 to 0.37, H in degrees */
.b { color: oklch(62% 0.21 260 / 50%); }   /* percentages and alpha both allowed */
.c { color: lch(52% 82 285); }             /* L runs 0 to 100 here */

知覚的均等性とは、数値上の距離が等しければ、知覚される差もほぼ等しいということです。明度を 0.08 刻みで並べたランプは均等な段に見え、1 つの明度に固定した色相の列は 1 つの列としてまとまって見え、明度を変えても色相は指定した位置にとどまります。補間にもこの空間を使えます。linear-gradient(in oklch, #0000FF, #FFFF00) のような書き方です。1 つ注意があります。OKLCH の明度は HSL よりはるかに見た目の明るさに近いものの、WCAG の輝度の式ではありません。ですからテキストの組み合わせは、やはりコントラストチェッカーに通してください。

色域とクリッピング

色域とは、ある空間が表現できる色の集合です。sRGB は長らく既定として使われてきました。Display P3 は可視色をおよそ 4 分の 1 多くカバーし、その増分のほとんどは深い赤と緑にあります。OKLCH のクロマには上限がないため、どの画面にも表示できない色を書いてしまうことは簡単に起こります。

@supports (color: color(display-p3 1 1 1)) {
  .vivid { background: color(display-p3 0.9 0.1 0.14); }
}

チャンネルごとのクリッピングは、色域外の色を各チャンネル独立に上限で止めることで色域内に戻します。これは色相をずらし、隣り合う階調を 1 つの値に潰すため、グラデーションに縞が出てディテールが消えます。CSS Color 4 は代わりに写像を行います。明度と色相を保ったまま、OKLCH のクロマを色が収まるまで下げるのです。

鮮やかな写真のスクリーンショットが平板に見えることがあるのは、これが理由です。写真は P3 のプロファイルを持ち、ディスプレイはそれを余さず表示します。sRGB に変換して書き出すと、彩度の高い画素がより小さな体積に押し込められ、赤と緑は最も長い距離を移動することになります。

アルファ合成

背景の上に半透明の色を重ねると、チャンネルごとの計算によって、第 3 の不透明な色ができます。

result = alpha * source + (1 - alpha) * backdrop

白の上に不透明度 50% の黒を重ねると (127.5, 127.5, 127.5)、つまり #808080 になります。この偶然が「黒の不透明度 50% はグレー」という言い方の出どころですが、これが成り立つのは白の上だけです。同じ黒を #3B82F6 の上に重ねると約 #1E417B という濃い青になりますし、写真の上に重ねれば画素ごとに違う色になります。半透明の黒はグレーではなく、暗くするための演算子です。

合成はエンコードされた値の上で行われるため、この #808080 の相対輝度は 0.5 ではなく約 0.216 です。測るのは合成後の HEX であって、宣言した色ではありません。アルファの重なり方も直感どおりにはなりません。50% のレイヤーを 2 枚重ねると 75% になります。2 枚目は、1 枚目が通した分にしか作用しないからです。

ティント、シェード、トーン、配色調和

用語従来の作り方OKLCH では
ティント基準色に白を足すL を上げ、C をわずかに下げる
シェード基準色に黒を足すL を下げる
トーン基準色にグレーを足すL を固定したまま C を下げる

配色調和は色相のオフセットにすぎません。アクセントを選ぶ役には立ちますが、システムを組み立てるものではありません。

調和の型オフセット向いている用途
補色+180°主となる色相に対するアクセント 1 色。ただし両方の彩度が高いとちらつく
類似色±30°落ち着いたまとまり、背景、同系統の中での状態表現
三色配色+120°、+240°グラフの系列のようなカテゴリの集合

RGB の色相環でのオフセットは知覚的に均等ではありません。この色相環は緑に不釣り合いなほど広い弧を割いているからです。ですから計算で得た調和は下書きとして扱い、目で見て調整してください。1 つの基準色からティント、シェード、調和を組み立てるパレットジェネレーターを使えば、その下書きをすばやく画面に出せます。

ニュートラルのランプには別途の判断が要ります。赤と緑と青が等しい純粋なグレーは、色のある UI の隣に置くと、死んだような、わずかに汚れた印象になります。ニュートラルにはブランドの色相に近いところで、OKLCH でおよそ 0.005 ~ 0.02 の小さなクロマを与えてください。60° ~ 90° 付近の暖かいグレーは紙のように見え、250° 付近の冷たいグレーは技術的な印象になります。背景が色を帯びて見えないよう、明るい段に向かうにつれてクロマを細らせてください。

色覚特性

男性のおよそ 12 人に 1 人、女性のおよそ 200 人に 1 人が色覚異常を持っています。そのほとんどすべてが赤緑型で、なかでも第二色弱が圧倒的多数です。青黄型である第三色覚異常は 1 万人に 1 人未満です。

UI で問題になる混同は、明度が近い赤と緑、緑と茶やオレンジ、紫と青、ピンクとグレーです。第一色覚では赤が暗く見えるため、緊急性を伝えるために選んだ赤が、画面上で最も目立たない要素になってしまうこともあります。

したがって、色だけが意味を運ぶ手段になってはいけません。すべての状態にラベル、アイコン、形、位置のいずれかを添え、対になる状態は色相だけでなく明度でも差を付けてください。OKLCH の明度がそろった赤と緑は、考えうる最悪の組み合わせです。スクリーンショットをグレースケールに変換すれば、こうした衝突は数秒で見つかります。