hslをrgbから計算して、色相・彩度・明度を理解する

## はじめに
hslはHue(色相)、Saturation(彩度)、lightness(明度)から成り立っています。

色相は赤、緑、青のような有彩色の色のみの性質を表すものです。
色相はCSSにおいて`<hue>`という型で表され、角度によって色の性質を決めます。

以下の画像は有彩色を環状に並べたものです。頂上の赤を0度として、黄が60度、青が240度のように有彩色が割り当てられています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/dd9c97b8-67d0-0b82-4910-afbc7495be03.png)
[引用](https://developer.mozilla.org/ja/docs/Web/CSS/hue)

彩度は色の鮮やかさの度合いです。無彩色はもっとも彩度が低い色です。彩度が大きくなるにつれて色味の強い鮮やかな色になります。

明度は色の明るさの度合いです。明度の最大値に白、最小値に黒をおき、その間に灰色のものを置くとグレースケールができます。有彩色の明度はグレースケールと比較した時の明るさで決まります。

彩度と明度はCSSにおいて`<percentage>`という型で表されます。

さて、色相は色のみの性質なので直感的に理解しやすいですが、明度と彩度をどこまで理解しているでしょうか？
私は言葉の意味、定義としての理解はしていますが、実際に同色相上の色についてその彩度・明度の大小関係を明確に答えることができないと思います。
下の画像は真ん中を基準に彩度と明度のどちらかを調整しています。どう変化しているか答えられますか？
![Group 4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/2a66d14f-7a9d-571a-8adf-a911c6e25285.png)

この記事では彩度と明度に対する理解を深めるために`rgb`から`hsl`に変換する計算式を確認します。

## rgbからhsl
rgbは色を赤・緑・青の成分から構成する方法です。CSSではそれぞれの成分を256等分して、赤だと`rgb(255, 0, 0)`のように表します。先ほど例示した真ん中の色は`rgb(80, 227, 210)`です。

赤・緑・青の成分を色相、彩度、明度に変換します。

事前準備として、赤・緑・青の成分を0~1の値で扱えるように255で割ります。

```math
\begin{align}
r = \frac{R}{255} \\
g = \frac{G}{255} \\
B = \frac{B}{255}
\end{align}
```

さらに、$r$・$g$・$b$の中で最大と最小の色とその差分、その平均を取得します。
```math
\begin{align}
cmax = max(r, g, b) \\
cmin = min(r, g, b) \\
cdelta = cmax - cmin
\end{align}
```

次に色相を求めます。
```math
\begin{equation}
  hue^{\prime\prime}=
  \begin{cases}
    0 & \text{if $cdelta=0$,} \\
    60 \times \frac{g - b}{cdelta} & \text{if $cmax=r$,} \\
    60 \times \frac{b - r}{cdelta} + 120 & \text{if $cmax=g$,} \\
    60 \times \frac{r - g}{cdelta} + 240 & \text{if $cmax=b$.} \\
  \end{cases}
\end{equation}
```
```math
hue^{\prime} = round(hue^{\prime\prime}) \\
```
```math
\begin{equation}
  hue=
  \begin{cases}
    hue^{\prime} & \text{if $hue^{\prime} \geq 0$,} \\
    $hue^{\prime} + 360 & \text{if $hue^{\prime} < 0$.} \\
  \end{cases}
\end{equation}
```
$round$は四捨五入を行う関数です。

最大と最小の差分がない時は赤・緑・青の成分が全て同じなので無彩色になります。色相の定義から、この時の値は0になります(実際はどんな値でも良いです)。

最大値が赤の場合、緑と青の差分が最小値と最大値の差分に対してどれくらいの割合か計算しています。この値は-1~1を取り、そこに60をかけています。すなわち、色相の値の範囲は赤に近い0度から60度、300度から360度になります。
青よりも緑が強ければ0度から60度側に、緑よりも青が強ければ300度から360度になるので、色相環から見ても直感的に理解できると思います。

最大の値が緑の場合も同じく、緑以外の値で差分を取って最小値と最大値の差分に対してどれくらいの割合か計算しています。$+120$は基準を緑を表す120度に移動させるための項です。
先ほどと同じく、青より赤が強ければ60度から120度側に、赤より青が強ければ120度から180度になります。

最大の値が青の時も同じです。$+240$によって基準が青に移動するので、緑より赤が強ければ240度から300度側に、赤より緑が強ければ180度から240度になります。

続いて明度を計算します。
```math
light^{\prime} = (cmax + cmin) / 2
```
明度は色の最大値と最小値の平均で表されます。`cmax`と`cmin`の両方が1であれば明度も1で白になり、`cmax`と`cmin`の両方が0であれば明度も0で黒になります。
明度は赤・緑・青の成分の強さによって決められ、中間の強さは寄与しないということが読み取れます。

最後に彩度を計算します。
```math
\begin{equation}
  sat^{\prime}=
  \begin{cases}
    0 & \text{if $cdelta = 0$,} \\
     \frac{cdelta}{1 - abs(2 * light^{\prime} - 1)} & \text{if $cdelta \neq 0$.} \\
  \end{cases}
\end{equation}
```

赤・緑・青の成分が全て同じときは無彩色になるので$0$になります(色相と異なり0でなければいけません)。

それ以外の時は明度と、色の最大値と最小値の差を用いて計算します。
分母の$1 - abs(2 * light^{\prime} - 1)$は明度が1の時0、0.5の時1、0の時0を取ります。分子の$cdelta$は色の最大値と最小値の差分です。

これらによって表される彩度は2つの条件の組み合わせて値が決まります。
- 明度が$0$(黒)・$1$(白)に近づくにつれ彩度は小さくなる(遠ざかるほど大きくなる)
- 色の最大値と最小値の差に比例する

彩度の最大値である$1$になるときは$delta$が$1$で明度が$0.5$の時です。この状況は赤・青・緑の少なくとも1つの成分が最大で少なくとも1つの成分が最小の時に値します。

明度と彩度が割合で記述されているので最後に百分率へ直します。
```math
\begin{align}
light = round(light^{\prime} * 100) \\
sat = round(sat^{\prime} * 100)
\end{align}
```

以上が計算式の紹介です。彩度と明度のどちらも色の最大値と最小値を使って計算しており、彩度は明度を用いて計算しているので言葉だけで理解しにくいのもそのはずですね。

## おわりに
理解が進みましたでしょうか。計算してみることで、明度・彩度の性質を身近に感じたのではないでしょうか。

最初に例示した画像をもう一度考えてみましょう。

![Group 4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/2a66d14f-7a9d-571a-8adf-a911c6e25285.png)

真ん中の色に対して左が彩度を20%下げたもの、右が明度を20%上げたものになっています。
少しでも色の理解に寄与していると幸いです。