Sorting By Color in the LCH Color Space
When organizing reveals a very pleasant rabbit hole

The surprisingly complicated world of organizing things by color
A few months ago, I decided to organize my closet and bookshelves by color. ROYGBIV is the easiest sorting heuristic; however, ROYGBIV is too simplistic for an organizing task of any real size.
ROYGBIV: Red Orange Yellow Green Blue Indigo Violet
Most systems of describing color exist along more than 1 axis, which helps capture the real-world complexity of color that ROYGBIV misses. Instead, I'm using the LCH color space to organize my closet and bookshelves.[1]
LCH is an acronym for:
Luminance
A shade of red with a range of Luminance values
Chroma
A shade of red with a range of Chroma values
and Hue.
Three color samples with shifting Hue values
Together, Luminance, Chroma, and Hue create a three-dimensional color space. You can read more about LCH here.
When organizing things by color, the tough question is how to deconstruct a multi-dimensional space...
Colors in a three-dimensional space
... and then sort the individual colors along a single line in a way that looks nice and feels self-explanatory.
Many attempts at putting colors in a single line
My method for gathering color data
I'm using a device by Nix to grab the colors of my clothing and books. It returns data that looks like this:
The data is available as a CSV file, and the rest of the work happens in Excel.
My current sorting method
After many iterations, here's where I've currently landed for sorting.
Sort Level 0: Unsorted Colors
These are some unsorted colors.
Sort Level 1: Separate greys, blacks, whites
Any color with a Chroma value lower than 4.1 gets pushed to the front or back of the sort.[2]
All these colors have a Chroma value of 4.1 and are right on the border of grey, in my system
Sort Level 2: Hue
Hue values have been rounded up to increments of 20[3] and then sorted from smallest to largest.
Hue reminder
Sort Level 3: Luminance
Luminance values have been rounded to the nearest 15[4] and then sorted in reverse order, from largest to smallest.
Luminance reminder
Sort Level 4: Chroma
All other things being equal, Chroma sorts from smallest to largest.
Chroma reminder
Notes
Rounding
Rounding is necessary for the sort to work at all... the Nix device returns values with 4+ decimal points. Without rounding those precise values into big round numbers, the sorting method wouldn't move beyond Hue.
Excel VBA
To help visualize the color space and get an intuitive sense of how I wanted to group and sort colors, I used VBA to create a grid of LCH colors.
Since Excel doesn't use CIELCH or CIELAB to display colors, I had to convert from LCH ➡️ LAB ➡️ XYZ ➡️ RGB. It was annoying to figure out; here's my code and some websites that helped in case it's helpful for anyone else. (Or, likely, just me in the future.)
Sub FillLCHVariants()
Dim i As Integer, j As Integer
Dim lLch, cLch, hLch As Double
Dim rRGB As Double
Dim gRGB As Double
Dim bRGB As Double
Dim label As Double
lLch = 0
cLch = 0
hLch = 301.36
' Set Column/Row Labels
label = 0
For i = 2 To 12
Cells(i, 1).Value = "Luminance " & label
label = label + 10
Next i
label = 0
For j = 2 To 19
Cells(1, j).Value = "Chroma " & label
label = label + 10
Next j
' Loop through rows and columns
For i = 2 To 12
For j = 2 To 19
' Convert LCH to RGB
Call LCHtoRGB(lLch, cLch, hLch, rRGB, gRGB, bRGB)
' Call LCHtoRGB(50, 50, hLch, rRGB, gRGB, bRGB)
' Set background color of the cell
Cells(i, j).Interior.Color = RGB(rRGB, gRGB, bRGB)
cLch = cLch + 10
Next j
lLch = lLch + 10
cLch = 0
Next i
End Sub
Sub LCHtoRGB(ByVal l As Double, ByVal c As Double, ByVal h As Double, ByRef rRGB As Double, ByRef gRGB As Double, ByRef bRGB As Double)
Dim aLab, bLab As Double
Dim fx, fy, fz As Double
Dim x, y, z As Double
Dim tempx, tempy, tempz As Double
Dim EPSILON As Double
Dim KAPPA As Double
EPSILON = 216 / 24389
KAPPA = 24389 / 27
' Convert LCH To LAB
aLab = Math.Cos(h * 0.01745329251) * c
bLab = Math.Sin(h * 0.01745329251) * c
Debug.Print "LAB"
Debug.Print l
Debug.Print aLab
Debug.Print bLab
' Convert LAB to XYZ
fy = (l + 16) / 116
fx = (aLab / 500) + fy
fz = fy - (bLab / 200)
Debug.Print "fxyz"
Debug.Print fx
Debug.Print fy
Debug.Print fz
'x = f_inv(fx) * 0.950449218275099
If fx ^ 3 > EPSILON Then
x = fx ^ 3
Else
x = (116 * fx - 16) / KAPPA
End If
If l > 8 Then
y = fy ^ 3
Else
y = l / KAPPA '(24389 / 27) <-- is KAPPA
End If
' z = f_inv(fz) * 1.08891664843047
If fz ^ 3 > EPSILON Then
z = fz ^ 3
Else
z = (116 * fz - 16) / KAPPA
End If
Debug.Print "XYZ"
Debug.Print x
Debug.Print y
Debug.Print z
tempx = x * 0.9531874 + y * -0.0265906 + z * 0.0238731
tempy = x * -0.0382467 + y * 1.0288406 + z * 0.009406
tempz = x * 0.0026068 + y * -0.0030332 + z * 1.0892565
x = tempx
y = tempy
z = tempz
' Convert XYZ to SRGB
rRGB = x * 3.24081239889528 - y * 1.53730844562981 - z * 0.49858652290696
gRGB = x * -0.9692430170086 + y * 1.87596630290857 + z * 0.04155503085668
bRGB = x * 0.05563839843611 - y * 0.20400746093241 + z * 1.05712957028614
Debug.Print "preRGB"
Debug.Print rRGB
Debug.Print gRGB
Debug.Print bRGB
rRGB = gamma_compression(rRGB)
gRGB = gamma_compression(gRGB)
bRGB = gamma_compression(bRGB)
Debug.Print "postRGB"
Debug.Print rRGB
Debug.Print gRGB
Debug.Print bRGB
End Sub
' Helper Function
Private Function gamma_compression(ByVal linear As Double) As Byte
Dim v As Double
Dim tempGamma As Double
If linear <= 0.0031306684425 Then
' v = 3294.6 * linear
v = 12.92 * linear
Else
' v = 269.025 * linear ^ (5 / 12) - 14.025
v = 1.055 * (linear ^ 0.41666667) - 0.055
End If
v = v * 255
Debug.Print "Gamma pre Min Max"
Debug.Print v
tempGamma = WorksheetFunction.RoundDown(WorksheetFunction.Min(v, 255), 0)
tempGamma = WorksheetFunction.RoundUp(WorksheetFunction.Max(tempGamma, 0), 0)
gamma_compression = tempGamma
End Function
I referenced Rust code from mina86.com.
I tried using Perplexity.ai to convert Rust to VBA, which very much did not work, but Perplexity is still awesome and worth checking out.[5]
I used these pages from brucelindbloom.com to get the base formulas for conversion, which I referenced to manually rework the VBA code.
I used this page by Atmos for quick, one-off color conversions.
I haven't used this page by Ocean Optics Web Book, yet, but I think it may be useful in the future: From XYZ to RGB
If "RGB" or "CMYK" means anything to you, LCH is like those. ↩︎
Excel formula: =IFS(AND(J2<4.1,I2<50),3,AND(J2<4.1,I2>=50),1,J2>=4.1,2) -- column I contains Luminance data ↩︎
I used CEILING.MATH ↩︎
I'll test rounding Luminance up to increments of 15 in future iterations. ↩︎
Perplexity is like ChatGPT but better, in my opinion. ↩︎