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.
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.
var t = 4600 * ((1 / ((0.92 * bv) + 1.7)) +(1 / ((0.92 * bv) + 0.62)) );
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
var Y = (y == 0)? 0 : 1;
var X = (y == 0)? 0 : (x * Y) / y;
var Z = (y == 0)? 0 : ((1 - x - y) * Y) / y;
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;
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.
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.
There's also a fiddle now that you can play with.
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.
I use tabled interpolation instead. Some years back I found this table somewhere:
type r g b rrggbb B-V
O5(V) 155 176 255 #9bb0ff -0.32 blue
O6(V) 162 184 255 #a2b8ff
O7(V) 157 177 255 #9db1ff
O8(V) 157 177 255 #9db1ff
O9(V) 154 178 255 #9ab2ff
O9.5(V) 164 186 255 #a4baff
B0(V) 156 178 255 #9cb2ff
B0.5(V) 167 188 255 #a7bcff
B1(V) 160 182 255 #a0b6ff
B2(V) 160 180 255 #a0b4ff
B3(V) 165 185 255 #a5b9ff
B4(V) 164 184 255 #a4b8ff
B5(V) 170 191 255 #aabfff
B6(V) 172 189 255 #acbdff
B7(V) 173 191 255 #adbfff
B8(V) 177 195 255 #b1c3ff
B9(V) 181 198 255 #b5c6ff
A0(V) 185 201 255 #b9c9ff 0.00 White
A1(V) 181 199 255 #b5c7ff
A2(V) 187 203 255 #bbcbff
A3(V) 191 207 255 #bfcfff
A5(V) 202 215 255 #cad7ff
A6(V) 199 212 255 #c7d4ff
A7(V) 200 213 255 #c8d5ff
A8(V) 213 222 255 #d5deff
A9(V) 219 224 255 #dbe0ff
F0(V) 224 229 255 #e0e5ff 0.31 yellowish
F2(V) 236 239 255 #ecefff
F4(V) 224 226 255 #e0e2ff
F5(V) 248 247 255 #f8f7ff
F6(V) 244 241 255 #f4f1ff
F7(V) 246 243 255 #f6f3ff 0.50
F8(V) 255 247 252 #fff7fc
F9(V) 255 247 252 #fff7fc
G0(V) 255 248 252 #fff8fc 0.59 Yellow
G1(V) 255 247 248 #fff7f8
G2(V) 255 245 242 #fff5f2
G4(V) 255 241 229 #fff1e5
G5(V) 255 244 234 #fff4ea
G6(V) 255 244 235 #fff4eb
G7(V) 255 244 235 #fff4eb
G8(V) 255 237 222 #ffedde
G9(V) 255 239 221 #ffefdd
K0(V) 255 238 221 #ffeedd 0.82 Orange
K1(V) 255 224 188 #ffe0bc
K2(V) 255 227 196 #ffe3c4
K3(V) 255 222 195 #ffdec3
K4(V) 255 216 181 #ffd8b5
K5(V) 255 210 161 #ffd2a1
K7(V) 255 199 142 #ffc78e
K8(V) 255 209 174 #ffd1ae
M0(V) 255 195 139 #ffc38b 1.41 red
M1(V) 255 204 142 #ffcc8e
M2(V) 255 196 131 #ffc483
M3(V) 255 206 129 #ffce81
M4(V) 255 201 127 #ffc97f
M5(V) 255 204 111 #ffcc6f
M6(V) 255 195 112 #ffc370
M8(V) 255 198 109 #ffc66d 2.00
[edit1] heh just coincidentally come across this (original info I mentioned before)
[edit2] here is my approximation without any XYZ stuff
So the BV index is from < -0.4 , 2.0 >
here is mine (C++) code for conversion:
//---------------------------------------------------------------------------
void bv2rgb(double &r,double &g,double &b,double bv) // RGB <0,1> <- BV <-0.4,+2.0> [-]
{
double t; r=0.0; g=0.0; b=0.0; if (bv<-0.4) bv=-0.4; if (bv> 2.0) bv= 2.0;
if ((bv>=-0.40)&&(bv<0.00)) { t=(bv+0.40)/(0.00+0.40); r=0.61+(0.11*t)+(0.1*t*t); }
else if ((bv>= 0.00)&&(bv<0.40)) { t=(bv-0.00)/(0.40-0.00); r=0.83+(0.17*t) ; }
else if ((bv>= 0.40)&&(bv<2.10)) { t=(bv-0.40)/(2.10-0.40); r=1.00 ; }
if ((bv>=-0.40)&&(bv<0.00)) { t=(bv+0.40)/(0.00+0.40); g=0.70+(0.07*t)+(0.1*t*t); }
else if ((bv>= 0.00)&&(bv<0.40)) { t=(bv-0.00)/(0.40-0.00); g=0.87+(0.11*t) ; }
else if ((bv>= 0.40)&&(bv<1.60)) { t=(bv-0.40)/(1.60-0.40); g=0.98-(0.16*t) ; }
else if ((bv>= 1.60)&&(bv<2.00)) { t=(bv-1.60)/(2.00-1.60); g=0.82 -(0.5*t*t); }
if ((bv>=-0.40)&&(bv<0.40)) { t=(bv+0.40)/(0.40+0.40); b=1.00 ; }
else if ((bv>= 0.40)&&(bv<1.50)) { t=(bv-0.40)/(1.50-0.40); b=1.00-(0.47*t)+(0.1*t*t); }
else if ((bv>= 1.50)&&(bv<1.94)) { t=(bv-1.50)/(1.94-1.50); b=0.63 -(0.6*t*t); }
}
//---------------------------------------------------------------------------
[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: