<p>Ok, here's my take on calculating the color of a blackbody, or any spectrum in fact:</p>
<p>Disclaimer: I'm not a color theorist, and there may be more accurate methods. But the result, shown in the bottom, looks about right.</p>
<hr />
<h3>Spectrum</h3>
<p>First note that since color is a function of the relative intensity in various wavelength bands, it doesn't matter whether we express our spectrum in flux, flux density, intensity, luminance, or something else that only differs by constant values.</p>
<p>Let's consider a spectrum <span class="math-container">$S(\lambda)$</span> as a function of wavelength <span class="math-container">$\lambda$</span>. If you're interested in a Planck spectrum, first calculate this from the temperature <span class="math-container">$T$</span>:</p>
<p><span class="math-container">$$
S(\lambda) = B(\lambda,T) = \frac{2hc^2}{\lambda^5} \frac{1}{e^{hc/\lambda k_\mathrm{B}T} - 1},
$$</span>
where <span class="math-container">$h$</span>, <span class="math-container">$c$</span>, and <span class="math-container">$k_\mathrm{B}$</span> are Planck's constant, the speed of light, and Boltzmann's constant, respectively.</p>
<h3>CIE 1931 color space</h3>
<p>The CIE 1931 color space was the first attempt to quantify the relation between the distribution of wavelengths and the color perceived by humans. It makes use of the so-called <em>color matching functions</em> <span class="math-container">$\bar{x}$</span>, <span class="math-container">$\bar{y}$</span>, and <span class="math-container">$\bar{z}$</span>, which are functions of <span class="math-container">$\lambda$</span>, seen here:</p>
<p><a href="https://i.sstatic.net/vn5NJ.png" rel="noreferrer"><img src="https://i.sstatic.net/vn5NJ.png" alt="xyzbar" /></a>
<sup>Credit: <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space#Color_matching_functions" rel="noreferrer">Wikipedia</a></sup></p>
<p>You can find tabulated values of these functions on the internet.</p>
<p>The color (which is 3D because humans have three different cones) of any spectrum can be mapped to a 2D position <span class="math-container">$\{x,y\}$</span> in the CIE 1931 color space <em>chromaticity diagram</em> seen here:</p>
<p><a href="https://i.sstatic.net/hy8ax.png" rel="noreferrer"><img src="https://i.sstatic.net/hy8ax.png" alt="chromdia" /></a>
<sup>Credit: <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_RGB_color_space" rel="noreferrer">Wikipedia</a></sup></p>
<h3>Tristimulus values</h3>
<p>The pair <span class="math-container">$\{x,y\}$</span> are normalized versions of the <em>tristimulus values</em> <span class="math-container">$\{X,Y,Z\}$</span> defined by
<span class="math-container">$$
X = \int S \, \bar{x} \, d\lambda\\
Y = \int S \, \bar{y} \, d\lambda\\
Z = \int S \, \bar{z} \, d\lambda.
$$</span></p>
<p>(Actually they're defined using the <a href="https://en.wikipedia.org/wiki/Radiance#Spectral_radiance" rel="noreferrer">spectral radiance</a>), but this is equal to the intensity to within a constant factor.)</p>
<p>The lower-case <span class="math-container">$\{x,y\}$</span> are then given by:
<span class="math-container">$$
\begin{array}{rcl}
x &amp; = &amp; \frac{X}{X+Y+Z}\\
y &amp; = &amp; \frac{Y}{X+Y+Z}\\
z &amp; = &amp; \frac{Z}{X+Y+Z} = 1-x-y,
\end{array}
$$</span>
and the last of these relations is the reason only two values are needed.</p>
<h3>XYZ to RGB</h3>
<p>To go from <span class="math-container">$\{x,y\}$</span> or <span class="math-container">$\{X,Y,Z\}$</span> to RGB is a linear transformation which can be conducted by matrix multiplication. There are many variations here; my understanding is that is has to do with whatever device you're using to display you colors, but there might be more to it.</p>
<p>The <a href="https://en.wikipedia.org/wiki/SRGB" rel="noreferrer">sRGB</a> color space assumes that the <span class="math-container">$\{X,Y,Z\}$</span> set has been scaled to <span class="math-container">$\{0.9505, 1, 1.0890\}$</span> for the CIE <a href="https://en.wikipedia.org/wiki/Illuminant_D65" rel="noreferrer">illuminant D65</a> (&quot;white&quot;); that is, <span class="math-container">$Y\equiv 1$</span>.</p>
<p>With the above values, this new set can be calculated as
<span class="math-container">$$
\begin{array}{rcl}
Y &amp; \rightarrow &amp; 1\\
X &amp; \rightarrow &amp; x/y\\
Z &amp; \rightarrow &amp; z/y,
\end{array}
$$</span>
and the RGB values are then, using the conversion matrix from <a href="https://git.edevau.net/Ede_Vau/home-assistant-core/commit/191fc8f8d412320688edc5b5be391fbf9ff09450?lang=cs-CZ" rel="noreferrer">here</a> which cites <a href="http://www.developers.meethue.com/documentation/color-conversions-rgb-xy" rel="noreferrer">this</a>,
<span class="math-container">$$
\left[
\begin{array}{l}
R\\
G\\
B
\end{array}
\right]
=
\left[
\begin{array}{rrr}
 1.656492 &amp; -0.354851 &amp; -0.255038 \\
-0.707196 &amp;  1.655397 &amp;  0.036152 \\
 0.051713 &amp; -0.121364 &amp;  1.011530
\end{array}
\right]
\left[
\begin{array}{l}
X\\
Y\\
Z
\end{array}
\right]
$$</span></p>
<h3>Gamma correction</h3>
<p>I'm not 100% sure, but I think this set of RGB values needs to be <a href="https://en.wikipedia.org/wiki/Gamma_correction" rel="noreferrer">gamma-corrected</a>, which is a non-linear correction that account for the fact the the device displaying the colors shows different <em>relative</em> luminances than the human eye perceives. At least my result, shown in the bottom, looks about right, whereas it doesn't if I don't perform this correction.</p>
<p>The gamma correction is given by the function
<span class="math-container">$$
\gamma(u) = \left\{\begin{array}{ll}
12.92 u                 &amp; \mathrm{if\,} u \leq 0.0031308 \\
1.055 u^{1/2.4} - 0.055 &amp; \mathrm{otherwise}
\end{array}\right.
$$</span></p>
<p>and the perceived RGB values should then be
<span class="math-container">$$
\begin{array}{rcl}
R &amp; \rightarrow &amp; R\times\gamma(R)\\
G &amp; \rightarrow &amp; G\times\gamma(G)\\
B &amp; \rightarrow &amp; B\times\gamma(B)
\end{array}
$$</span></p>
<p>These values are <em>mostly</em> in the range <span class="math-container">$[0,1]$</span>, but if they fall outside, they're usually clipped. If you want your values to be in the range <span class="math-container">$[0,255]$</span> you just multiply them by 255.</p>
<h3>Result: the color of the Universe through time</h3>
<p>I implemented the above algorithm in Python, as a part of a larger code called <code>timeline</code> that calculates various properties of the Universe as a function of time after the Big Bang. You can find it <a href="https://github.com/anisotropela/Universe-timeline" rel="noreferrer">here on GitHub</a>.</p>
<p>With that code I calculated the color of the Universe as a function of time after the Big Bang, and hence as a function of temperature. Until star formation kicks in around 200 million years after the Big Bang, the color is given by the temperature of the cosmic microwave background.</p>
<p>When stars arrive, the average cosmic spectrum becomes more complicated and can't be calculated (yet) from first principles, but must be observed. I haven't found such observations at early times, but <a href="https://ui.adsabs.harvard.edu/abs/2002ApJ...569..582B" rel="noreferrer">Baldry et al (2002)</a> presents the spectrum at various times after an age of 11 billion years, at which time the Universe is a dull &quot;cosmic latte&quot;. So in this figure, I grayed out the color between <span class="math-container">$t=200\,\mathrm{Myr}$</span> and <span class="math-container">$t=11\,\mathrm{Gyr}$</span>:</p>
<p><a href="https://i.sstatic.net/jDYUH.png" rel="noreferrer"><img src="https://i.sstatic.net/jDYUH.png" alt="color-of-the-universe" /></a></p>
<p>But please note the disclaimer in the top of this answer :)</p>

CC BY-SA 4.0
2020-11-24T15:58:51.207Z
2020-11-24T21:48:14.940Z
2020-11-24T21:48:14.940Z
pela
<p>I created a program to convert the temperature (in Kelvin) of a blackbody to RGB color. However, it is slightly inaccurate, and the deviations increase for values greater than 10000K and less than 1500K. Is there an accurate mathematical function that generates RGB values for given temperature, ranging from ~500K to 20000+ K?</p>

CC BY-SA 4.0
2020-11-23T01:44:24.860Z
How do you know that is inaccurate? What result do you get, and what are you comparing it to? Have a look at [this answer](https://space.stackexchange.com/a/16639/12102) for how to transform a spectrum first to " CIE XYZ color space, then transform to linear RGB and then sRGB". Color is **hard,** so good luck!
uhoh
CC BY-SA 4.0
2020-11-23T13:53:55.303Z
I made such a program a few years ago. You're welcome to steal what you want from it; it's on [GitHub](https://github.com/anisotropela/Universe-timeline) :)
pela
CC BY-SA 4.0
2020-11-23T15:18:57.930Z
@pela You can use that in an answer, if you'd like.
WarpPrime
CC BY-SA 4.0
2020-11-23T19:00:29.583Z
Are you using [Wein's Displacement Law](https://en.wikipedia.org/wiki/Wien%27s_displacement_law) to calculate the wavelength of peak energy and then converting that wavelength to a direct color?
zephyr
CC BY-SA 4.0
2020-11-24T09:52:51.560Z
fasterthanlight: There are several different ways to calculate perceived colors, and I don't know enough about this to be confident in writing up an answer. Also, it's a while since I wrote the code, so I would have to read up on the theory behind my code. To quote the famous @uhoh: "Color is **hard**, so good luck!"
pela
CC BY-SA 4.0
2020-11-24T15:59:19.127Z
Okay, I had a go at it :)
pela
CC BY-SA 4.0
2020-11-24T17:38:26.957Z
It's an interesting question and many ways to answer it. Yet I would much appreciate it, and it would greatly improve your question, if you would share in your question your actual attempt and compare (visually) the output of what you implemented with what you expect. That would allow possibly to answer more to the point (even though pela covered it quite well, +1 for that)
planetmaker
CC BY-SA 4.0
2020-11-25T01:14:37.843Z
@pela this may not be the absolute best book for what's needed here exactly, but I found (some edition earlier than the 4th already) to be a fascinating read: [Billmeyer and Saltzman's Principles of Color Technology](https://www.wiley.com/en-us/Billmeyer+and+Saltzman%27s+Principles+of+Color+Technology%2C+4th+Edition-p-9781119366683)
uhoh
CC BY-SA 4.0
2020-11-25T11:13:55.313Z
@uhoh Looks interesting! I also found a freely available book [here](https://www.babelcolor.com/index_htm_files/A%20review%20of%20RGB%20color%20spaces.pdf) that I wish I had the time to read.
pela
CC BY-SA 4.0
2020-11-30T23:14:04.640Z
@pela oh that's excellent, concise and brave, *thanks!*
uhoh
<temperature><mathematics><black-body>
What is the RGB curve for blackbodies?
WarpPrime
# What is the RGB curve for blackbodies?

(Asked by: username_0 on 2020-11-23)

I created a program to convert the temperature (in Kelvin) of a blackbody to RGB color. However, it is slightly inaccurate, and the deviations increase for values greater than 10000K and less than 1500K. Is there an accurate mathematical function that generates RGB values for given temperature, ranging from ~500K to 20000+ K?

**Tags:** temperature, mathematics, black-body

> **Comments:**
> **username_1**: How do you know that is inaccurate? What result do you get, and what are you comparing it to? Have a look at [this answer](https://space.stackexchange.com/a/16639/12102) for how to transform a spectrum first to " CIE XYZ color space, then transform to linear RGB and then sRGB". Color is **hard,** so good luck!  
> **username_2**: I made such a program a few years ago. You're welcome to steal what you want from it; it's on [GitHub](https://github.com/anisotrousername_2/Universe-timeline) :)  
> **username_0**: @username_2 You can use that in an answer, if you'd like.  
> **username_3**: Are you using [Wein's Displacement Law](https://en.wikipedia.org/wiki/Wien%27s_displacement_law) to calculate the wavelength of peak energy and then converting that wavelength to a direct color?  
> **username_2**: fasterthanlight: There are several different ways to calculate perceived colors, and I don't know enough about this to be confident in writing up an answer. Also, it's a while since I wrote the code, so I would have to read up on the theory behind my code. To quote the famous @username_1: "Color is **hard**, so good luck!"  
> **username_2**: Okay, I had a go at it :)  
> **username_4**: It's an interesting question and many ways to answer it. Yet I would much appreciate it, and it would greatly improve your question, if you would share in your question your actual attempt and compare (visually) the output of what you implemented with what you expect. That would allow possibly to answer more to the point (even though username_2 covered it quite well, +1 for that)  
> **username_1**: @username_2 this may not be the absolute best book for what's needed here exactly, but I found (some edition earlier than the 4th already) to be a fascinating read: [Billmeyer and Saltzman's Principles of Color Technology](https://www.wiley.com/en-us/Billmeyer+and+Saltzman%27s+Principles+of+Color+Technology%2C+4th+Edition-p-9781119366683)  
> **username_2**: @username_1 Looks interesting! I also found a freely available book [here](https://www.babelcolor.com/index_htm_files/A%20review%20of%20RGB%20color%20spaces.pdf) that I wish I had the time to read.  
> **username_1**: @username_2 oh that's excellent, concise and brave, *thanks!*  

---

## 1 answers

### Answer by username_2 on 2020-11-24. Score: 10 | Accepted answer

Ok, here's my take on calculating the color of a blackbody, or any spectrum in fact:

Disclaimer: I'm not a color theorist, and there may be more accurate methods. But the result, shown in the bottom, looks about right.

---

### Spectrum

First note that since color is a function of the relative intensity in various wavelength bands, it doesn't matter whether we express our spectrum in flux, flux density, intensity, luminance, or something else that only differs by constant values.

Let's consider a spectrum $S(\lambda)$ as a function of wavelength $\lambda$. If you're interested in a Planck spectrum, first calculate this from the temperature $T$:

$$
S(\lambda) = B(\lambda,T) = \frac{2hc^2}{\lambda^5} \frac{1}{e^{hc/\lambda k\_\mathrm{B}T} - 1},
$$
where $h$, $c$, and $k\_\mathrm{B}$ are Planck's constant, the speed of light, and Boltzmann's constant, respectively.

### CIE 1931 color space

The CIE 1931 color space was the first attempt to quantify the relation between the distribution of wavelengths and the color perceived by humans. It makes use of the so-called *color matching functions* $\bar{x}$, $\bar{y}$, and $\bar{z}$, which are functions of $\lambda$, seen here:

[![xyzbar](https://i.sstatic.net/vn5NJ.png)](https://i.sstatic.net/vn5NJ.png)
Credit: [Wikipedia](https://en.wikipedia.org/wiki/CIE_1931_color_space#Color_matching_functions)

You can find tabulated values of these functions on the internet.

The color (which is 3D because humans have three different cones) of any spectrum can be mapped to a 2D position $\{x,y\}$ in the CIE 1931 color space *chromaticity diagram* seen here:

[![chromdia](https://i.sstatic.net/hy8ax.png)](https://i.sstatic.net/hy8ax.png)
Credit: [Wikipedia](https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_RGB_color_space)

### Tristimulus values

The pair $\{x,y\}$ are normalized versions of the *tristimulus values* $\{X,Y,Z\}$ defined by
$$
X = \int S \, \bar{x} \, d\lambda\\
Y = \int S \, \bar{y} \, d\lambda\\
Z = \int S \, \bar{z} \, d\lambda.
$$

(Actually they're defined using the [spectral radiance](https://en.wikipedia.org/wiki/Radiance#Spectral_radiance)), but this is equal to the intensity to within a constant factor.)

The lower-case $\{x,y\}$ are then given by:
$$
\begin{array}{rcl}
x & = & \frac{X}{X+Y+Z}\\
y & = & \frac{Y}{X+Y+Z}\\
z & = & \frac{Z}{X+Y+Z} = 1-x-y,
\end{array}
$$
and the last of these relations is the reason only two values are needed.

### XYZ to RGB

To go from $\{x,y\}$ or $\{X,Y,Z\}$ to RGB is a linear transformation which can be conducted by matrix multiplication. There are many variations here; my understanding is that is has to do with whatever device you're using to display you colors, but there might be more to it.

The [sRGB](https://en.wikipedia.org/wiki/SRGB) color space assumes that the $\{X,Y,Z\}$ set has been scaled to $\{0.9505, 1, 1.0890\}$ for the CIE [illuminant D65](https://en.wikipedia.org/wiki/Illuminant_D65) ("white"); that is, $Y\equiv 1$.

With the above values, this new set can be calculated as
$$
\begin{array}{rcl}
Y & \rightarrow & 1\\
X & \rightarrow & x/y\\
Z & \rightarrow & z/y,
\end{array}
$$
and the RGB values are then, using the conversion matrix from [here](https://git.edevau.net/Ede_Vau/home-assistant-core/commit/191fc8f8d412320688edc5b5be391fbf9ff09450?lang=cs-CZ) which cites [this](http://www.developers.meethue.com/documentation/color-conversions-rgb-xy),
$$
\left[
\begin{array}{l}
R\\
G\\
B
\end{array}
\right]
=
\left[
\begin{array}{rrr}
1.656492 & -0.354851 & -0.255038 \\
-0.707196 & 1.655397 & 0.036152 \\
0.051713 & -0.121364 & 1.011530
\end{array}
\right]
\left[
\begin{array}{l}
X\\
Y\\
Z
\end{array}
\right]
$$

### Gamma correction

I'm not 100% sure, but I think this set of RGB values needs to be [gamma-corrected](https://en.wikipedia.org/wiki/Gamma_correction), which is a non-linear correction that account for the fact the the device displaying the colors shows different *relative* luminances than the human eye perceives. At least my result, shown in the bottom, looks about right, whereas it doesn't if I don't perform this correction.

The gamma correction is given by the function
$$
\gamma(u) = \left\{\begin{array}{ll}
12.92 u & \mathrm{if\,} u \leq 0.0031308 \\
1.055 u^{1/2.4} - 0.055 & \mathrm{otherwise}
\end{array}\right.
$$

and the perceived RGB values should then be
$$
\begin{array}{rcl}
R & \rightarrow & R\times\gamma(R)\\
G & \rightarrow & G\times\gamma(G)\\
B & \rightarrow & B\times\gamma(B)
\end{array}
$$

These values are *mostly* in the range $[0,1]$, but if they fall outside, they're usually clipped. If you want your values to be in the range $[0,255]$ you just multiply them by 255.

### Result: the color of the Universe through time

I implemented the above algorithm in Python, as a part of a larger code called `timeline` that calculates various properties of the Universe as a function of time after the Big Bang. You can find it [here on GitHub](https://github.com/anisotrousername_2/Universe-timeline).

With that code I calculated the color of the Universe as a function of time after the Big Bang, and hence as a function of temperature. Until star formation kicks in around 200 million years after the Big Bang, the color is given by the temperature of the cosmic microwave background.

When stars arrive, the average cosmic spectrum becomes more complicated and can't be calculated (yet) from first principles, but must be observed. I haven't found such observations at early times, but [Baldry et al (2002)](https://ui.adsabs.harvard.edu/abs/2002ApJ...569..582B) presents the spectrum at various times after an age of 11 billion years, at which time the Universe is a dull "cosmic latte". So in this figure, I grayed out the color between $t=200\,\mathrm{Myr}$ and $t=11\,\mathrm{Gyr}$:

[![color-of-the-universe](https://i.sstatic.net/jDYUH.png)](https://i.sstatic.net/jDYUH.png)

But please note the disclaimer in the top of this answer :)


---

