Color

Bruce Lindbloom has a ton of equations, but I just want the big ones on one page. We’ll assume sRGB, which implies using D65 as white (if you’re using Bruce Lindbloom’s calculator to check your implementation, make sure to set these).

RGB ↔ Linear RGB

Let \(\Xi\) (one of \(R\), \(G\), and \(B\)) be an RGB component in the range \([0, 1]\). (This is an obnoxious variable choice, but I’m trying to not overload any variable names in this entire post.) If you have RGB values in \([0, 255]\), divide them by 255. It can be converted to/from the linearized component \(\xi\) (one of \(r\), \(g\), and \(b\)) as:

\[\xi = \begin{cases} \Xi/12.92 & \text{if }\Xi \leq 0.04045 \\ ((\Xi + 0.055)/1.055)^{2.4} & \text{if }\Xi > 0.04045 \end{cases}\]

\[\Xi = \begin{cases} 12.92\xi & \text{if }\xi \leq 0.0031308 \\ 1.055v^{1/2.4} - 0.055 & \text{if }\xi > 0.0031308 \end{cases}\]

This is called “companding”.

However, you can use \(\xi = \Xi^{2.4}\) and \(\Xi = \xi^{1/2.4}\) in a pinch.

Linear RGB ↔ XYZ

Convert between XYZ and linearized RGB. Again, this assumes sRGB and D65.

\[\begin{align*} X &= 0.4124564r + 0.3575761g + 0.1804375b \\ Y &= 0.2126729r + 0.7151522g + 0.0721750b \\ Z &= 0.0193339r + 0.1191920g + 0.9503041b \end{align*}\]

\[\begin{align*} r &= \phantom{-}3.2404542X + -1.5371385Y + -0.4985314Z \\ g &= -0.9692660X + \phantom{-}1.8760108Y + \phantom{-}0.0415560Z \\ b &= \phantom{-}0.0556434X + -0.2040259Y + \phantom{-}1.0572252Z \end{align*}\]

Copy-pasteable:

X = 0.4124564*r + 0.3575761*g + 0.1804375*b
Y = 0.2126729*r + 0.7151522*g + 0.0721750*b
Z = 0.0193339*r + 0.1191920*g + 0.9503041*b

r =  3.2404542X + -1.5371385Y + -0.4985314Z
g = -0.9692660X +  1.8760108Y +  0.0415560Z
b =  0.0556434X + -0.2040259Y +  1.0572252Z

If you need more decimal points, you can get them from conversions.js in the CSSWG draft.

XYZ ↔ Lab and Luv

We use the reference white D65 \[\begin{align*}X_r &= 0.9504559270516716\\Y_r &= 1.0\\Z_r &= 1.0890577507598784.\end{align*}\]

X_r = 0.9504559270516716
Y_r = 1.0
Z_r = 1.0890577507598784

We also have constants \[\begin{align*}\epsilon &= 216/24389 = (6/29)^3\\\kappa &= 24389/27 = (29/3)^3.\end{align*}\] First normalize against the reference: \[\begin{align*}x_r &= X/X_r \\ y_r &= Y/Y_r \\ z_r &= Z/Z_r.\end{align*}\] Then let \[f(w) = \begin{cases} \sqrt[3]{w} & \text{if }w > \epsilon \\ \frac{\kappa w + 16}{116} & \text{else} \end{cases}\] and let \(f_x = f(x_r)\), \(f_y = f(y_r)\), \(f_z = f(z_r)\). Then \[\begin{align*} L &= 116f_y - 16 \\ a^* &= 500(f_x - f_y) \\ b^* &= 200(f_y - f_z). \end{align*}\] (Out of caution, the star distinguishes \(b^*\) from linearized blue component \(b\).) \(L\) can actually be slightly simplified (\(L\) is the same in the Lab and Luv representations), so some work can be shared between Lab and Luv: \[L = \begin{cases} 116\sqrt[3]{y_r} - 16 & \text{if }y_r > \epsilon \\ \kappa y_r & \text{else} \end{cases}.\] But \(u\) and \(v\) have their own formulae. Let \[\begin{align*} u_0 &= \frac{4X_r}{X_r + 15Y_r + 3Z_r} \\ v_0 &= \frac{9Y_r}{X_r + 15Y_r + 3Z_r}\end{align*}\] (note that these only depend on the reference white, i.e. if we chose D65, they’re constants. Then \[\begin{align*} u^* &= 13L\left(\frac{4X}{X + 15Y + 3Z} - u_0\right) \\ v^* &= 13L\left(\frac{9Y}{X + 15Y + 3Z} - v_0\right) \end{align*}\]

To invert from Lab to XYZ: \[\begin{align*} f_y &= \frac{L + 16}{116} \\ f_z &= f_y - \frac{b^*}{200} \\ f_x &= f_y + \frac{a^*}{500} \end{align*}\] Then apply \[f^{-1}(w) = \begin{cases} w^3 & \text{if }w^3 > \epsilon \\ \frac{116w - 16}{\kappa} & \text{else} \end{cases}\] to \(x_r = f^{-1}(f_x)\), \(y_r = f^{-1}(f_y)\), \(z_r = f^{-1}(f_z)\). Finally \[\begin{align*} X &= x_rX_r\\ Y &= y_rY_r\\ Z &= z_rZ_r. \end{align*}\]

To invert from Luv to XYZ, let \[\begin{align*} \alpha &= \frac{1}{3}\left(\frac{52L}{u^* + 13Lu_0} - 1\right) \\ Y &= \begin{cases} \left(\frac{L + 16}{116}\right)^3 &\text{if }L > \kappa\epsilon \\ \frac{L}{\kappa} &\text{else} \end{cases} \\ X &= \frac{117LY}{(3\alpha + 1)(v^* + 13Lv_0)} \\ Z &= X\alpha - 5Y \end{align*}\]

Lab ↔ LCHab; Luv ↔ LCHuv

These are actually just naive polar-Cartesian transforms! The L is the same in all four. I’ll type them out for you anyway.

\[\begin{align*} C_{ab} &= \sqrt{a^2 + b^2} \\ H_{ab} &= \text{arctan}(b/a) \bmod{360^\circ} \\ a &= C_{ab} \cos H_{ab} \\ b &= C_{ab} \sin H_{ab} \end{align*}\]

\[\begin{align*} C_{uv} &= \sqrt{u^2 + v^2} \\ H_{uv} &= \text{arctan}(v/u) \bmod{360^\circ} \\ u &= C_{uv} \cos H_{uv} \\ v &= C_{uv} \sin H_{uv} \end{align*}\]

If you want things explicitly written out for code:

\[\begin{align*} C_{ab} &= \sqrt{a^2 + b^2} \\ H_{ab} &= \left(\frac{180^\circ}{\pi}\right)((\texttt{atan2}(b, a) + 2\pi) \,\texttt{\%}\, 2\pi) \\ a &= C_{ab} \cos \left(H_{ab} \cdot \frac{\pi}{180^\circ}\right) \\ b &= C_{ab} \sin \left(H_{ab} \cdot \frac{\pi}{180^\circ}\right) \end{align*}\]

(note: the commenting setup here is experimental and I may not check my comments often; if you want to tell me something instead of the world, email me!)