2014-02-24 02:18:45 8 Comments

I'm trying to convert a star's B-V color index to an apparent RGB color. Besides look up tables and color ramps, it seems like there's no well known algorithm for doing this.

## What's a B-V color index?

It's a number astronomers assign to a star to indicate its apparent color. Hot stars (low B-V) are blue/purple and cool stars (high B-V) are red with those white/orange stars in between.

## Initial algorithm

### B-V to Kelvin

```
var t = 4600 * ((1 / ((0.92 * bv) + 1.7)) +(1 / ((0.92 * bv) + 0.62)) );
```

### Kelvin to xyY

If you model a star as a blackbody, then you can use a numerical approximation of the Planckian locus to compute the xy coordinates (CIE chromaticity)

```
// t to xyY
var x, y = 0;
if (t>=1667 && t<=4000) {
x = ((-0.2661239 * Math.pow(10,9)) / Math.pow(t,3)) + ((-0.2343580 * Math.pow(10,6)) / Math.pow(t,2)) + ((0.8776956 * Math.pow(10,3)) / t) + 0.179910;
} else if (t > 4000 && t <= 25000) {
x = ((-3.0258469 * Math.pow(10,9)) / Math.pow(t,3)) + ((2.1070379 * Math.pow(10,6)) / Math.pow(t,2)) + ((0.2226347 * Math.pow(10,3)) / t) + 0.240390;
}
if (t >= 1667 && t <= 2222) {
y = -1.1063814 * Math.pow(x,3) - 1.34811020 * Math.pow(x,2) + 2.18555832 * x - 0.20219683;
} else if (t > 2222 && t <= 4000) {
y = -0.9549476 * Math.pow(x,3) - 1.37418593 * Math.pow(x,2) + 2.09137015 * x - 0.16748867;
} else if (t > 4000 && t <= 25000) {
y = 3.0817580 * Math.pow(x,3) - 5.87338670 * Math.pow(x,2) + 3.75112997 * x - 0.37001483;
}
```

### xyY to XYZ (Y = 1)

```
// xyY to XYZ, Y = 1
var Y = (y == 0)? 0 : 1;
var X = (y == 0)? 0 : (x * Y) / y;
var Z = (y == 0)? 0 : ((1 - x - y) * Y) / y;
```

### XYZ to RGB

```
var r = 0.41847 * X - 0.15866 * Y - 0.082835 * Z;
var g = -0.091169 * X + 0.25243 * Y + 0.015708 * Z;
var b = 0.00092090 * X - 0.0025498 * Y + 0.17860 * Z;
```

## Question

I ran this algorithm with the B-V color indexes: 1.2, 1.0, 0.59, 0.0, -0.29. This is what I got as output.

**Why did I get this strange output?** Hot stars are bluish but cold stars are brownish and there doesn't seem to be white/orange intermediate stars.

## Update

Following on a comment by Ozan, it seemed like I was using a wrong matrix to convert XYZ to RGB. Since sRGB is the default color space on the web (or is it?), I'm now using the correct matrix followed by a gamma correction function (`a = 0.055`

).

I now get this nice color ramp,

but there's still no red/violet at the extremities.

## Demo

There's also a fiddle now that you can play with.

## Update 2

If use a gamma of 0.5 and extend the range of B-V color indexes to be from 4.7 to -0.5, I get red at one extreme but still no violet. Here's the updated fiddle.

### Related Questions

#### Sponsored Content

#### 35 Answered Questions

#### 15 Answered Questions

### [SOLVED] Programmatically Lighten or Darken a hex color (or rgb, and blend colors)

**2011-04-06 00:39:35****Pimp Trizkit****180835**View**500**Score**15**Answer- Tags: javascript colors hex

#### 15 Answered Questions

#### 49 Answered Questions

### [SOLVED] RGB to Hex and Hex to RGB

**2011-04-11 15:42:53****Sindar****457297**View**554**Score**49**Answer- Tags: javascript colors hex rgb

#### 14 Answered Questions

### [SOLVED] How to colorize diff on the command line?

**2012-01-10 08:49:10****daniel kullmann****172255**View**502**Score**14**Answer- Tags: unix command-line colors diff

## 8 comments

## @DocLeonard 2017-08-04 03:30:15

## You asked for an algorithm, you will get one.

I researched this topic when I was rendering the data from the HYG database in Python3.5, with Pyglet and MongoDB. I'm happy with how my stars look in my starmap. The colors can be found at the bottom of this answer.

## 1. Color Index (B-V) to Temperature (K)

This is the function I used on the B-V (ci) data from the HYG database. In this example, ci is a B-V value from a list I'm running through.

## 2. Get a big table.

I took this one and I suggest you do too. Select the temperature column and the RGB or rgb values column as reference

## 3. Preprocess the data.

From the rgb table data, I generated three ordered lists (n=391) (my method: cleanup and selection with spreadsheet software and a text editor capable of having millions of cursors at a time, then imported the resulting comma-separated file by mongoDB so I could easily work with the lists of values in python through the pymongo wrapper, without too much clutter in the script file). The benefit of the method I will be laying out is that you can pluck color data from other tables that might use CMYK or HSV and adapt accordingly. You could even cross-reference. However, you should end up with lists that look like this from the (s)RGB table I suggested;

After this, I've applied some

Gaussian smoothingto these lists (it helps to get better polynomials, since it gets rid of some fluctuation), after which I applied thepolyfit()method (polynomial regression) from the numpy package to the temperature valueswith respect to the R, G and B values:That gives you (n) coefficients (a) for polynomials of form:

.

Come to think of it now, you could probably use polyfit to come up with the coefficients to convert CI straight to RGB... and skip the

CI to temperature conversionstep, but by converting to temp first, the relation between temperature and the chosen color space is more clear.## 4. The actual Algorithm: Plug temperature values into the RGB polynomials

As I said before, you can use other spectral data and other color spaces to fit polynomial curves to, this step would still be the same (with slight modifications)

Anyway, here's the simple code in full that I used (also, this is with k=20 polynomials):

## Oh, and some more notes and imagery...

The OBAFGKM black body temperature scale from my polynomials:

The plot for RGB [0-255] over temp [0-40000K],

+: table data## Here's the purple

As you can see, there's some deviation, but it is hardly noticeable with the naked eye and if you really want to improve on it (I don't), you have some other options:

I'm also happy with the overall performance of my polynomials. When I'm loading the ~120000 star objects of my starmap with at minimum 18 colored vertices each, it only takes a few seconds, much to my surprise. There is room for improvement, however. For a more realistic view (instead of just running with the blackbody light radiation), I could add gravitational lensing, atmospheric effects, relativistic doppler, etc...

Oh, and the PURPLE, as promised.

Some other useful links:

## @Moritz 2018-09-14 09:07:20

Also based on the list (http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html) the following function uses kotlin to get a color for a temperature based on the 2deg scale:

## @Spektre 2014-03-25 09:57:25

I use tabled interpolation instead. Some years back I found this table somewhere:

[edit1] heh just coincidentally come across this (original info I mentioned before)

[edit2] here is my approximation without any XYZ stuffSo the BV index is from

`< -0.4 , 2.0 >`

here is mine (C++) code for conversion:

[Notes]This BV color is blackbody of defined temperature illumination so this represents star color viewed from space relative with the star.

For visually correct colors you have to add atmospheric scattering effects of our atmosphere and Doppler effect for fast mowing stars!!!for example our Sun is 'White' but after light scatter the color varies from red (near horizon) to yellow (near nadir ... noon)In case you want to visually correct the color these

QAs might help:## @John Nilsson 2015-03-29 00:19:54

I'm trying to translate your C+ code to Python, and my C++ is not that strong. Could you explain how t is initialized? If I read this code correctly t is first initialized with whatever is in that memory location when the stack frame gets allocated and then you check if that value is within the [-0.4,2.0] range and puts it at the boundary if necessary. This makes no sense to me, why not just set it to 0.0 like you do r,g,b ?

## @Spektre 2015-03-29 07:15:21

@JohnNilsson (+1) I forget to rewrite it (nice catch) the first line is just case when

`bv`

is out of range it should be`bv`

inside`i`

f conditions instead of`t`

have repaired it## @Bersaelor 2017-03-29 22:14:54

@Spektre's answer in Swift 3.0:

## @AymericG 2017-01-03 20:18:16

As a correction to the code of @paddyg, which did not work for me (especially for color with bv < 0.4) : here is the exact same version of the C++ code of @Spektre, in Python :

## @Wayne Young 2016-02-08 01:13:19

In answer to the question why no violet? : I think the answer is that stars just aren't that colour. Or rather, they are not rendered that colour when we take pictures of them. The colours produced on this thread for various temperatures / B-V values seem pretty accurate to me. Take this picture I took of Albireo in Cygnus: https://www.flickr.com/photos/[email protected]/6939409750/in/photolist-bB54th-bzdhKG Albireo A (left) is a K type star with a B-V of 1.074 and Alberio B (right) is a B type star with a B-V of -0.06. Looking at the colours in the charts above for those B-V values, I'd say there's a pretty strong correlation with the picture. Also, don't forget that even for very hot stars, there will still be some output at longer wavelengths, which will tend to desaturate the "blueness". Black-body radiation is broad spectrum.

## @usr2564301 2016-02-08 01:21:03

Welcome to Stack Overflow! Please note that Stack Overflow

is not a regular forum- it is "a site for questions and answers" (see the tour) on practical, programming related problems. It seems to me this is a follow-up to one of the earlier answers, and not meant to answer the actual question. (But do check out Astronomy while you are here!)## @paddyg 2016-02-06 11:24:46

Just in case anybody else needs to convert the handy C++ of @Spektre to python. I have taken some of the duplication out (that the compiler would no doubt have fixed) and the discontinuities for g when

`bv>=2.0`

and b when`1.94<bv<1.9509`

## @Donald L. Klipstein 2015-01-29 06:13:44

Why no violet or deep blue? Infinite color temperature, before being made less bluish by our atmosphere, has 1931 CIE coordinates of X=.240, y=.234.

The spectrum of a blackbody at infinite color temperature has spectral power distribution, in power per unit wavelength of bandwidth, being inversely proportional to wavelength to the 4th power. At 700nm, this is 10.7% as great as at 400nm.