Excel 今日のトリビア ExcelのHSLは白の色相を170と表示するバグがある。そのほかWithRGB HSL変換

# 前回
[Excel 今日のトリビア HSLも実は選べるがExcel色ダイアログボックスのHEXはインチキ](https://qiita.com/Q11Q/items/171e9c7104d69c8e3d32)
に続きこのHSLについて
# 白のRGBとHSL
白(&HFFFFFF)はRGB(255,255,255)
これを手動でHSL変換する
まずHUEは最大、最小がないので
Hue=0となる（はずである）
次にSaturationは
 `(nMax - nMin) / (nMax + nMin)`
 この式からSaturation=0となる
 さらにLightness
 は
 255
 でOK。３つが同じなら２つたして２つで割るからこうなる。

 すると
 HLS(0,0,255)
 が答えである。
 # Excelが表示する白のHSLの値
 それではExcelを見てみよう
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/1b2e4adc-a346-87cf-ecdb-21a8101d40e8.png)

ここでHSLに変換する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/4d89a919-ec2e-5adc-fc7d-46221f52f36a.png)
<b>170,0,255...</b>
ちなみに170の値を変化させても色は変わらない。
なぜならHSLの角度は無彩色には無関係だから。
純粋に彩度だけで決まる。


# もとからExcelのHSLはおかしい
次に、ExcelのHSLは非常におかしい
## Hue（色相）
彩度は角度で0～360（マイナスの時は360を足す）
これを無理やり0～255で表現している。
おそらくこの360を255に変換するときにバグがあるのだろう。

## Saturation（彩度） Lightness（輝度）
こちらは0～100だが、Excelはこれを0～255にする。

この結果、色相は360を255であらわすため不正確で、残りの２つは大体再現できるということになる。
というかHSLの場合この色相環が重要なのが…

http://yamatyuu.net/other/color/hsl/index.html
# Excel用のVBA RGB、HSL変換関数
これはまず色番号（１０進）をRGBに分解し、そのうえでHSLにするというもの
http://yamatyuu.net/other/color/hsl/index.html
を参考にしているが、無彩色の場合をの例外を加えている。

```vb
Public Type rgbnum
Red As Long
Gre As Long
BLu As Long
End Type
Public Type HSLNum
Hue As Double
Saturation As Double
Lightness As Double
End Type

Function ColorToRGB(num As Long) As rgbnum
'色番号をRGBに変換する
'https://excel.syogyoumujou.com/memorandum/rgb.html
With ColorToRGB
.Red = num Mod 256
.Gre = Int(num / 256) Mod 256
.BLu = Int(num / 256 / 256)
End With
End Function
Function RGBtoHSL2(num As Long) As HSLNum
Dim nMax As Long, nMin As Long, Ldash As Long, Sdash As Long
Dim ar As rgbnum
With ColorToRGB(num)
ar.Red = .Red
ar.Gre = .Gre
ar.BLu = .BLu
End With
If ar.Red = ar.Gre And ar.BLu = ar.Gre Then
RGBtoHSL2.Hue = 0
Else
nMax = WorksheetFunction.Max(ar.Red, ar.Gre, ar.BLu)
nMin = WorksheetFunction.Min(ar.Red, ar.Gre, ar.BLu)
Select Case nMax
Case Is = ar.Red
RGBtoHSL2.Hue = IIf(60 * (ar.Gre - ar.BLu) / (nMax - nMin) >= 0, 60 * (ar.Gre - ar.BLu) / (nMax - nMin), 60 * (ar.Gre - ar.BLu) / (nMax - nMin) + 360)
Case Is = ar.Gre
RGBtoHSL2.Hue = IIf((60 * (ar.BLu - ar.Red) / (nMax - nMin)) + 120 >= 0, (60 * (ar.BLu - ar.Red) / (nMax - nMin)) + 120, (60 * (ar.BLu - ar.Red) / (nMax - nMin)) + 120 + 360)
Case Is = ar.BLu
RGBtoHSL2.Hue = IIf((60 * (ar.Red - ar.Gre) / (nMax - nMin)) + 240 >= 0, (60 * (ar.Red - ar.Gre) / (nMax - nMin)) + 240, (60 * (ar.Red - ar.Gre) / (nMax - nMin)) + 240 + 360)
End Select
End If
If (nMax + nMin) / 2 < 127 Then
   If nMax = nMin Then
   RGBtoHSL2.Saturation = 0
   Else
   RGBtoHSL2.Saturation = (nMax - nMin) / (nMax + nMin)
   End If
Else
RGBtoHSL2.Saturation = Int((255 * ((nMax - nMin) / (510 - nMax - nMin))) + 0.5)
End If
If ar.Red = ar.Gre And ar.BLu = ar.Gre Then
RGBtoHSL2.Lightness = ar.Gre ' 無彩色の時例外処理
Else
RGBtoHSL2.Lightness = (nMax + nMin) / 2
End If
End Function
```

# ついでにChatGPTがつくったやつ（もちろん修正）
r,g,bをDoubleにしていたが255までの整数値しか相手にしないので、Integerにしている。
他もおかしい
```vb
    ' RGB値を0-1の範囲に正規化
  rNorm = r / 255
  gNorm = g / 255
  bNorm = b / 255
```
この部分はr,g,bはDoubleと言いながら0～1の値に変換しており、最初からRGB的な値を前提としている。
本来ここで１を超えることはあり得ないはずである。


```vb
' RGBからHSLに変換する関数
Function RGBtoHSL(ByVal r As Integer, ByVal g As Integer, ByVal b As Integer) As Variant
    Dim maxRGB As Double
    Dim minRGB As Double
    Dim delta As Double
    Dim h As Double
    Dim s As Double
    Dim l As Double
    Dim rNorm As Double
    Dim gNorm As Double
    Dim bNorm As Double

    ' RGB値を0-1の範囲に正規化
    rNorm = r / 255
    gNorm = g / 255
    bNorm = b / 255

    ' 最大値と最小値を取得
    maxRGB = Excel.Application.WorksheetFunction.Max(rNorm, gNorm, bNorm)
    minRGB = Excel.Application.WorksheetFunction.Min(rNorm, gNorm, bNorm)

    ' 輝度の計算
    l = (maxRGB + minRGB) / 2

    If maxRGB = minRGB Then
        ' グレースケールの色の場合
        h = 0
        s = 0
    Else
        ' 差分の計算
        delta = maxRGB - minRGB

        ' 彩度の計算
        If l < 0.5 Then
            s = delta / (maxRGB + minRGB)
        Else
            s = delta / (2 - maxRGB - minRGB)
        End If

        ' 色相の計算
        Select Case maxRGB
            Case rNorm
                h = (gNorm - bNorm) / delta + (IIf(gNorm < bNorm, 6, 0))
            Case gNorm
                h = (bNorm - rNorm) / delta + 2
            Case bNorm
                h = (rNorm - gNorm) / delta + 4
        End Select

        h = h / 6
    End If

    ' HSL値を返す
    RGBtoHSL = Array(h * 360, s, l)
End Function
```
# 実際に使うコード
Excelのセルの色を横にRGB、16進数RGB、色番号、色番号Excel版、HSL（ChatGPT）、HSL（作成版）
で変換し出力する。

## chatGPTは変換が必要
色相は角度で出力されるので255に変換
ほかも、ChatGPTの変換関数は0～１の値で出てくるためそれを255を乗じて変換している。
当方で作成したものはこの問題を解決している。

## Intで切れるか？
いくつか試験して調整しているが、端数処理の関係で正しい値と1違う可能性がある
特に角度は誤差が大きい。端数が.5でも切り捨てたほうが表示される値と一致する。

## Excel試験用コード

```vb
Sub test()
Dim hsl As Variant
Dim r As Integer
Dim g As Integer
Dim b As Integer

    
With ColorToRGB(ActiveCell.Interior.Color)
ActiveCell.Offset(, 1) = .Red
r = .Red: g = .Gre: b = .BLu
ActiveCell.Offset(, 2) = .Gre
ActiveCell.Offset(, 3) = .BLu
ActiveCell.Offset(, 4) = ActiveCell.Interior.Color
ActiveCell.Offset(, 5) = Hex(ActiveCell.Interior.Color)
With Excel.Application.WorksheetFunction
ActiveCell.Offset(, 6) = .Dec2Hex(ActiveCell.Offset(, 1), 2) & .Dec2Hex(ActiveCell.Offset(, 2), 2) & .Dec2Hex(ActiveCell.Offset(, 3), 2)
End With
End With
hsl = RGBtoHSL(r, g, b)
ActiveCell.Offset(, 7) = Int(hsl(0) / 360 * 255)
ActiveCell.Offset(, 8) = Int(hsl(1) * 255)
ActiveCell.Offset(, 9) = Int(hsl(2) * 255)
With RGBtoHSL2(ActiveCell.Interior.Color)
ActiveCell.Offset(, 10) = .Hue
ActiveCell.Offset(, 11) = .Saturation
ActiveCell.Offset(, 12) = .Lightness
End With
End Sub
```

# タイトル回収
そうすると白は次のような並びになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/eaf5312a-9c78-fdc5-3e53-22c0f78c3b3e.png)
255	255	255	16777215	FFFFFF	FFFFFF	0	0	255	0	0	0
つまり、HSLは0,0,255が正しい
にも拘わらず170となる。一体どういうことなのか？

## 170はExcelしか表示しない
この無意味な170は度数法で言うと240度である。ラジアンで言うと$2/3π$となる。
しかしHLSで白の色相は<b>常に0であり、240度、$2/3π$、0.75とするサイトは一つもない</b>
おそらく、何らかの形で0で処理が止まるため、それを回避したものと思われる。