## Converting temperature (Kelvin) to RGB: an overview

If you don’t know what “color temperature” is, start here.

While working on a “Color Temperature” tool for PhotoDemon, I spent an evening trying to track down a simple, straightforward algorithm for converting between temperature (in Kelvin) and RGB values. This seemed like an easy algorithm to find, since many photo editors provide tools for correcting an image’s color temperature in post-production, and every modern camera – including smartphones – provides a way to adjust white balance based on the lighting conditions of a shot.

Little did I know, but it’s pretty much impossible to find a reliable temperature to RGB conversion formula. Granted, there are some algorithms out there, but most work by converting temperature to the XYZ color space, to which you could add your own RGB transformation after the fact. Such algorithms seem to be based off AR Robertson’s method, one implementation of which is here, while another is here.

Unfortunately, that approach isn’t really a mathematical formula – it’s just glorified look-up table interpolation. That might be a reasonable solution under certain circumstances, but when you factor in the additional XYZ -> RGB transformation required, it’s just too slow and overwrought for simple real-time color temperature adjustment.

So I wrote my own algorithm, and it works pretty damn well. Here’s how I did it.

## Caveats for using this algorithm

**Caveat 1**: my algorithm provides a high-quality approximation, but it’s not accurate enough for serious scientific use. It’s designed primarily for photo manipulation – so don’t try and use it for astronomy or medical imaging.

**Caveat 2**: due to its relative simplicity, this algorithm is fast enough to work in real-time on reasonably sized images (I tested it on 12 megapixel shots), but for best results you should apply mathematical optimizations specific to your programming language. I’m presenting it here *without* math optimizations so as to not over-complicate it.

**Caveat 3**: this algorithm is only designed to be used between 1000 K and 40000 K, which is a nice spectrum for photography. (Actually, it’s way larger than most photographic situations will ever call for.) While it will work for temperatures outside these ranges, the estimation quality will decline.

## Special thanks to Mitchell Charity

First off, I owe a big debt of gratitude to the source data I used to generate these algorithms – Mitchell Charity’s raw blackbody datafile at http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html. Charity provides two datasets, and my algorithm uses the CIE 1964 10-degree color matching function. A discussion of the CIE 1931 2-degree CMF with Judd Vos corrections versus the 1964 10-degree set is way beyond the scope of this article, but you can start here for a more comprehensive analysis if you’re so inclined.

## The Algorithm: sample output

Here’s the output of the algorithm from 1000 K to 40000 K:

Here’s a more detailed shot of the algorithm in the interesting photographic range, which is 1500 K to 15000 K:

As you can see, banding is minimal – which is a big improvement over the aforementioned look-up table methods. The algorithm also does a great job of preserving the slightly yellow cast leading up to the white point, which is important for imitating daylight in post-production photo manipulation.

## How I arrived at this algorithm

My first step in reverse-engineering a reliable formula was to plot Charity’s original blackbody values. You can download my whole worksheet here in LibreOffice / OpenOffice .ods format (430kb).

Here’s how the data looks when plotted:

From this, it’s easy to note that there are a few floors and ceilings that make our algorithm easier. Specifically:

- Red values below 6600 K are always 255
- Blue values below 2000 K are always 0
- Blue values above 6500 K are always 255

It’s also important to note that for purposes of fitting a curve to the data, green is best treated as two separate curves – one for temperatures below 6600 K, and a separate one for temperatures above that point.

From here, I separated the data (without the “always 0” and “always 255” segments) into individual color components. In a perfect world, a curve could then be fitted to each set of points, but unfortunately it wasn’t that simple. Because there’s a large disparity between the X and Y values in the plot – the x-values are all over 1000, and they are plotted in 100 point segments, while the y values all fall between 255 and 0 – it was necessary to transpose the x data in order to get a better fit. For optimization purposes, I stuck to first dividing the x value (the temperature) by 100 across each color, followed by an additional subtraction if it led to a significantly better fit. Here are the resultant charts for each curve, along with the best-fit curve and corresponding R-squared value:

Apologies for the horrifically poor font kerning and hinting in those charts. I love LibreOffice for many things, but its inability to do font aliasing on charts is downright shameful. I also don’t like having to extract charts from screenshots because they don’t have an export option, but that’s a rant best saved for some other day.

As you can see, the curves all fit reasonably well, with R-square values above .987. I could have spent more time really tweaking the curves, but for purposes of photo manipulation these are plenty close enough. No layperson is going to be able to tell that the curves don’t exactly fit raw idealized blackbody observations, right?

## The algorithm

Using that data, here’s the algorithm, in all its glory.

First, pseudocode:

```
Start with a temperature, in Kelvin, somewhere between 1000 and 40000. (Other values may work,
but I can't make any promises about the quality of the algorithm's estimates above 40000 K.)
Note also that the temperature and color variables need to be declared as floating-point.
Set Temperature = Temperature \ 100
Calculate Red:
If Temperature <= 66 Then
Red = 255
Else
Red = Temperature - 60
Red = 329.698727446 * (Red ^ -0.1332047592)
If Red < 0 Then Red = 0
If Red > 255 Then Red = 255
End If
Calculate Green:
If Temperature <= 66 Then
Green = Temperature
Green = 99.4708025861 * Ln(Green) - 161.1195681661
If Green < 0 Then Green = 0
If Green > 255 Then Green = 255
Else
Green = Temperature - 60
Green = 288.1221695283 * (Green ^ -0.0755148492)
If Green < 0 Then Green = 0
If Green > 255 Then Green = 255
End If
Calculate Blue:
If Temperature >= 66 Then
Blue = 255
Else
If Temperature <= 19 Then
Blue = 0
Else
Blue = Temperature - 10
Blue = 138.5177312231 * Ln(Blue) - 305.0447927307
If Blue < 0 Then Blue = 0
If Blue > 255 Then Blue = 255
End If
End If
```

In the pseudocode above, note that Ln() means natural logarithm. Note also that you can omit the “If color < 0” checks if you will only ever supply temperatures in the recommended range. (You still need to leave the “If color > 255” checks, though.)

As for actual code, here’s the exact Visual Basic function I’m using in PhotoDemon. It’s not yet optimized (for example, the logarithms would be much faster via look-up table) but at least the code is short and readable:

```
'Given a temperature (in Kelvin), estimate an RGB equivalent
Private Sub getRGBfromTemperature(ByRef r As Long, ByRef g As Long, ByRef b As Long, ByVal tmpKelvin As Long)
Static tmpCalc As Double
'Temperature must fall between 1000 and 40000 degrees
If tmpKelvin < 1000 Then tmpKelvin = 1000
If tmpKelvin > 40000 Then tmpKelvin = 40000
'All calculations require tmpKelvin \ 100, so only do the conversion once
tmpKelvin = tmpKelvin \ 100
'Calculate each color in turn
'First: red
If tmpKelvin <= 66 Then
r = 255
Else
'Note: the R-squared value for this approximation is .988
tmpCalc = tmpKelvin - 60
tmpCalc = 329.698727446 * (tmpCalc ^ -0.1332047592)
r = tmpCalc
If r < 0 Then r = 0
If r > 255 Then r = 255
End If
'Second: green
If tmpKelvin <= 66 Then
'Note: the R-squared value for this approximation is .996
tmpCalc = tmpKelvin
tmpCalc = 99.4708025861 * Log(tmpCalc) - 161.1195681661
g = tmpCalc
If g < 0 Then g = 0
If g > 255 Then g = 255
Else
'Note: the R-squared value for this approximation is .987
tmpCalc = tmpKelvin - 60
tmpCalc = 288.1221695283 * (tmpCalc ^ -0.0755148492)
g = tmpCalc
If g < 0 Then g = 0
If g > 255 Then g = 255
End If
'Third: blue
If tmpKelvin >= 66 Then
b = 255
ElseIf tmpKelvin <= 19 Then
b = 0
Else
'Note: the R-squared value for this approximation is .998
tmpCalc = tmpKelvin - 10
tmpCalc = 138.5177312231 * Log(tmpCalc) - 305.0447927307
b = tmpCalc
If b < 0 Then b = 0
If b > 255 Then b = 255
End If
End Sub
```

This function was used to generate the sample output near the start of this article, so I can guarantee that it works.

## Sample images

Here’s a great example of what color temperature adjustments can do. The image below – a promotional poster for the HBO series True Blood – nicely demonstrates the potential of color temperature adjustments. On the left is the original shot; on the right, a color temperature adjustment using the code above. In one click, a nighttime scene can been recast in daylight.

The actual color temperature tool in my PhotoDemon project looks like this:

Download it here to see it in action.

## addendum October 2014

Renaud Bédard has put together a great online demonstration of this algorithm. Check it out here, and thanks to Renaud for sharing!

## addendum April 2015

Thank you to everyone who has suggested improvements to the original algorithm. I know there are a lot of comments on this article, but they’re worth reading if you’re planning on implementing your own version.

I’d like to call out two specific improvements. First, Neil B has helpfully provided a better version of the original curve-fitting functions, which results in slightly modified temperature coefficients. His excellent article describes the changes in detail.

Next, Francis Loch has added some comments and sample images below, which are very helpful if you want to apply these corrections to a photograph. His modifications produce a much more detailed image, as his sample images demonstrate.

Very nice article and implementation. Good job. I am curious as to once you compute the RGB values for a color temperature, what method did you use to correct the image?

Hi Chris: glad you liked the article. For the actual color correction, I use the temperature formula to calculate corresponding R, G, and B values. Then I loop through the image and alpha-blend the existing RGB values with the temperature RGB values at the strength specified by the user. (The maximum blend is a 50/50 split between the two color values.)

Unfortunately, this has the side-effect of universally brightening the image, so it’s necessary to use the HSL color space to maintain luminance in the image. To do that, I use the following steps:

– Calculate temperature RGB

– Loop through the image one pixel at a time. For each pixel:

— Get RGB values

— Calculate luminance of those RGB values (Luminance = (Max(R,G,B) + Min(R,G,B)) / 2)

— Alpha-blend the RGB values with the temperature RGB values at the requested strength (max strength = 50/50 blend)

— Calculate HSL equivalents for the newly blended RGB values

— Convert those HSL equivalents back to RGB, but substitute the ORIGINAL luminance value (this maintains the luminance of the pixel)

The exact source code of my technique is available here, in the “ApplyTemperatureToImage” method: https://github.com/tannerhelland/PhotoDemon/blob/master/Forms/Adjustments_Color_Temperature.frm

Thank you for the insight. One more quick question. I assume by alpha blend, the procedure BlendColors for red is basically:

r = r * tempStrength + tmpR * (1 – tempStrength);

Thanks again.

Yes, that’s the exact formula. (And tempStrength is a user-submitted value between 1 and 100, then divided by 200 to yield a floating-point in the range [0.005, 0.5])

EDIT 31 Oct 2013: thank you to commenter “zwi q” below, who helpfully pointed out that the formula should actually be “r = r * (1-tempStrength) + tmpR * tempStrength”, so that the original RGB values are returned when the strength of the transformation is zero.Hi tanner, i was looking on applying temperature to the pixelated graphics since i am working on a day night mode. the link you posted on applying temperature to image is broken. I am working on QT to create this effect on pixel graphics. Can you help me.?

Apologies for the broken link. I’ve updated that comment, and here’s the link again for those who may need it: https://github.com/tannerhelland/PhotoDemon/blob/master/Forms/Adjustments_Color_Temperature.frm

Is there a Python implementation of this algorithm.

Hi John. If there is a Python-specific implementation, I’m not aware of it. Sorry…

Here is a quick implementation in Python:

https://gist.github.com/petrklus/b1f427accdf7438606a6

It’s fairly simple to convert to python, Here is what I did to get this code to work with Cinema 4D:

`import c4d, math`

from math import log

from c4d import gui

#Welcome to the world of Python

obj = doc.GetActiveObject()

col = obj[c4d.ID_USERDATA,2]

Temp = 27000

Temp = Temp/100

# Red

if Temp <= 66:

R = 255

else:

R = Temp - 60

R = 329.698727446 * (R** -0.1332047592)

if R 255:

R = 255

# Green

if Temp <= 66:

G = Temp

G = 99.4708025861 * log(G) - 161.1195681661

if G 255:

G = 255

else:

G = Temp - 60

G = 288.1221695283 * (G** -0.0755148492)

if G 255:

G = 255

#Blue

if Temp >= 66:

B = 255

else:

if Temp <= 19:

B = 0

else:

B = Temp - 10

B = 138.5177312231 * log(B) - 305.0447927307

if B 255:

B = 255

`obj[c4d.ID_USERDATA,2] = c4d.Vector(R/255,G/255,B/255)`

print c4d.Vector(R/255,G/255,B/255)

c4d.EventAdd()

Very cool. I’m looking for the reverse, ie RGB -> color temperature. However it looks like your code isn’t ‘reversible’ because each of the three values might imply a different temperature.

Is it the case that the colour temperature can be calculated from the ratios of the three?

Great question. I’m no expert on color temperature but here’s my $0.02.

Colors produced by ideal black bodies only cover a small subset of RGB values. Black body radiation simply doesn’t produce certain colors – green or brown, for example. In fact, the vast majority of RGB values never map to a specific color temperature. So broadly speaking, I doubt it’s possible to assign a temperature value to every possible RGB triplet.

What you could do, theoretically, is find the

nearestcolor temperature for a given RGB value. A naive approach would involve comparing the RGB value to a range of temperature-produced RGB values (say, each color produced by 100 degree steps from 1000k to 15000k) and seeing which temperature yields the closest color. A more rigorous approach would involve finding a function to describe the RGB values that *do* have color temperatures, fitting it to some kind of line (a 3D line through the RGB color space), then finding the distance between a given RGB value and that line…?Hi,

Very cool article. I was searching for an algorithm to do conversion between real temperatures and modify my lighting to it. Allthough it is not perfect for this purpose my testings of your code is working quite well!

I’m no expert in colors nor color temperature, but playing with it is a lot of fun. I’m going no further then some articles about it, and not even the scientific ones.

I’ve used your code in my domotica project i’m creating with a friend of mine, which is written in Java, so i did a quick conversion from your code the a version. To be respectful to you as the original author, i published the code on my blog with a resource link to your code.

http://pidome.wordpress.com/2013/08/21/fun-with-heating-and-cooling-light-colors/

I want to thank you for your effort in creating this nice piece of code!

Best regards,

John.

Thanks for the kind comment, John. I’m really glad the code was useful to you.

Thank you for writing and sharing this piece of code!

Some lcd monitors have adjustable color temperature but only through RGB values. I was able to adjust mine according to my printer thanks to your algorithm.

You’re welcome, madjid. Great idea on adjusting your monitor’s color temperature – I’m glad it worked for you!

Really thank you. I used it for expressing critical, i.e, near to a hot separating surface, software testing data, and cold (not critical) testing data (far away from the limiting surfaces).

Hi,

I wanted to know more about temperature corrections and find your post very informative. I have a question:

You use RGB and HSL in the calculation. Could you just use YUV? I.e. blend in just the UV components from the temperature into the pixel UV.

Hi Jon. Yes, you are absolutely correct – YUV, or Lab, or any color space with a dedicated luminance component would work just fine for the color blending step. I had HSL space code on hand so I used that, but I would be curious to see if other color spaces give a better result!

Hi,

I mention YUV because I think it is a most useful space for a imaging program. In working with CHDK I realized that YUV is used in Canon cameras and probably other manufacturer’s cameras as well. It is fast to convert to (unlike LAB) and allows things like smoothing to be separated into either color or luminance.

I imagine the reason most camera include YUV transforms (which are probably YCbCr mislabeled as YUV!) is because the JPEG format encodes images in the YCbCr color space.

I’ll admit to not knowing which transform out of RGB < -> YUV or RGB < -> HSL conserves more information about luminance and color. (Considering how many different HSL transformations there are, I imagine the answer is “it depends on the implementation.”) In my case, where my software is intended for casual users, I tend to use HSL more frequently because it is a more intuitive way to present color changes to a user, and the transform is sufficiently fast for my purposes.

For background code like this, however, the specific method shouldn’t really matter, unless as you say one is much faster than another. If you do implement the algorithm in YUV, please do share a link so we can compare the two. (Thank you!)

Hi,

I checked out using YUV and it does not work as well. At a temp of 6600 the temp color is white: (255,255,255) = YUV (255,0,0) or HSL (0,0,1). This color blends (with brightness conservation) to the original image colors when done in HSL space, but not in YUV space.

Jon

Interesting observation. Thanks for checking this, Jon.

Hi, I just checked and the HSL version does not blend to the original, but I like the results better than the YUV version

Excellent article. Two quick questions:

1) Isn’t the alpha-blend formula by Chris (Oct 18,2012) incorrect? Shouldn’t it be r = r * (1-tempStrength) + tmpR * tempStrength ? I would expect to get the original r when tempStrength=0.

2) Could we use the HSV coordinates instead of HSL? So we enforce the original V value after the alpha-blend? (I am asking since I already have rgb2hsv converters available.

1) (Does double-take.) Dammit, you’re absolutely right. :) That’s what I get for using stupid variable names like “r” and “tmpR”! Thanks for pointing this out – I’ll edit the comment to make that clear.

2) Yes! HSV should work just fine.

Thanks for the kind comment, and for helping me correct a mistake – always appreciated.

Hi Tanner,

Have you noticed that temperature corrections in other programs have cool colors for low temps and warm colors for high temps. Just the opposite of Photodemon. I looked at ufraw’s code and see that instead of blending, the image pixel values are divided by the temperature RGB values. This gives OK results except for high temps where the correction never gets very warm. Other programs may use a different temp curve.

Yeah, it’s a bit of a conundrum. “Warm tones” and “cool tones” have completely opposite colloquial and technical definitions! In PhotoDemon, I chose to go with “technically correct”, so by increasing the temperature, you actually get cooler tones. If I did this the opposite way, the temperature values effectively have no meaning. (I debated this for some time before settling on the current implementation.)

In my case, the effect is meant to be more artistic than technical, which is why I chose to simply blend the resulting tones. It actually works similarly to the “photo filter” tool in Photoshop.

Thanks for mentioning ufraw’s approach – I’ll have to take a look at it.

Hi Tanner,

I think raw development programs are trying to correct for the temperature of the illumination, hence the division.

Hi,

Here is a nice web page giving some background on color temperature.

http://www.red.com/learn/red-101/eyesight-4k-resolution-viewing

I have tried to implemt this, but I have doub: For neutral temperature color ( white ) it still changes a bit the original image for any strenght but zero. I guess this should not be the case?

Hi JSoler. It depends on how you implement the color blending. If you simply blend the reference temperature color with the source image color, then yes – all values, including 6500 K, will result in changes. (This may be desirable, since you can blend with white to remove color casts caused by non-6500 K lighting.)

A more complex algorithm might try and figure out the current average temperature of an image, then apply a new temperature cast selectively, but I’m afraid you’d have to come up with such an algorithm on your own, as I don’t know of a good reference implementation.

Hi Tanner,

ufraw is a good source for algorithms. It has a routine that takes a color and calculates a temperature for the color. It seems to use the Temperature_to_RGB routine (which calculates an RGB value for a temperature) in a search for a temperature that gives a similar blue/red ratio. As a bonus it gives a green/red value. I think the idea is that temperature and green/red are more or less perpendicular axes in the color space.

void RGB_to_Temperature(double RGB[3], double *T, double *Green)

{

double Tmax, Tmin, testRGB[3];

Tmin = 2000;

Tmax = 23000;

for (*T = (Tmax + Tmin) / 2; Tmax – Tmin > 0.1; *T = (Tmax + Tmin) / 2) {

Temperature_to_RGB(*T, testRGB);

if (testRGB[2] / testRGB[0] > RGB[2] / RGB[0])

Tmax = *T;

else

Tmin = *T;

}

*Green = (testRGB[1] / testRGB[0]) / (RGB[1] / RGB[0]);

if (*Green 2.5) *Green = 2.5;

}

Ok, Thank you for the fast reply.

You’re welcome. I don’t know if you saw it, but yesterday a visitor named Jon shared a comment about how the UFRAW library calculates temperature (and tint) of a given RGB value – maybe that will be helpful to you? (And thanks, Jon, for the helpful tip!)

Hi Tanner!

Thanks for the share! I’ve made a little lighting project on Arduino based on your algorithm, in which I linked back to you on the post, if it’s ok :)

http://davidhsiehlo.com/blog/2014/03/25/dagr-a-time-of-the-day-based-led-lighting-system/

Hi David. Wow, excellent project! Congrats on getting everything working.

You have definitely given me something to investigate for my own office… :)

I just requested this be used to implement color temps in CyanogenMod:

https://jira.cyanogenmod.org/browse/CYAN-3929

I’m playing with this product. It’s cool, but not quite what I want. http://developers.meethue.com/index.html

But this will be good platform to play around with color temperature, and the effects on the intensity.

John

Hi, may i know, your algorithm and sample code about convert temperature to RGB is suitable for using in Matlab ?

Can you explain all steps to do it?

Thank you.

Hi Hanna. I’m afraid I’m not familiar with Matlab’s syntax. You might have better luck asking your question at a community site like one of the Stack Exchange websites.

function sRGB = temperatureRGB(temperature)

%

% Author: Tanner Helland, MATLAB Translator: Christopher Wells UCSD Fall 2014

%

% http://www.TannerHelland.com/4435/convert-temperature-rgb-algorithm-code/

%

% Start with a temperature, in Kelvin, somewhere between 1000 and 40000°K

% Note also that the temperature and color variables need to be declared

% as floating-point. The algorithm interpolates between three RGB curves.

%

% Mitchell Charity’s original Temperature (K) to RGB (sRGB) data appeared

% in the CIE 1964 10-degree CMFs in 100° K stepsize increments and scaled

% Temperature to Hundreds {100,200…1000, 1100,1200…2000,2100….40000}

%

% http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html

%

%

% Initialize Function Parameters

%

temperature = double(temperature / 100);

%

sRGB = [0,0,0];

%

red = 0; green = 0; blue = 0;

%

% Calculate RED:

%

%

if temperature <= 66, red = 255;

else

red = temperature – 60;

red = 329.698727446 * (red ^ -0.1332047592);

if red 255, red = 255;

end

end

%

% Calculate GREEN:

%

if temperature <= 66

green = temperature;

green = (99.4708025861 * reallog(green)) – 161.1195681661;

if green 255, green = 255;

end

else

green = temperature – 60;

green = 288.1221695283 * (green ^ -0.0755148492);

end

if green 255, green = 255;

end

%

% Calculate BLUE:

%

if temperature >= 66, blue = 255;

elseif temperature <= 19, blue = 0;

else

blue = temperature – 10;

blue = (138.5177312231 * reallog(blue)) – 305.0447927307;

end

if blue 255, blue = 255;

end

%

% Return RGB Vector

%

sRGB = [red,green,blue];

end

How would I go about adapting your numbers herein to reflect a linear float colorspace? (0 to 1 and scene referred as opposed to display referred (linear not sRGB etc.))

Hi Robert. Depending on the seriousness of your work, I’d start by trying to use the original formula, and simply dividing the RGB output by 255 to yield a [0, 1] range. The slight gamma curve of the sRGB space may not matter enough for this output, but without knowing details about what you’re doing, I can’t say for certain.

Because my entire goal here was to provide [0, 255] RGB output, you’d probably be better off investigating one of the original XYZ transforms I mention toward the top of the article. This one might have what you need.

C# version….

void getRGBfromTemperature(ref uint r, ref uint g, ref uint b, uint tmpKelvin)

{

double tmpCalc;

//Temperature must fall between 1000 and 40000 degrees

if ( tmpKelvin > 40000 ) tmpKelvin = 40000;

//All calculations require tmpKelvin \ 100, so only do the conversion once

tmpKelvin /= 100;

//Calculate each color in turn

//First: red

if ( tmpKelvin <= 66 )

r = 255;

else

{

//Note: the R-squared value for this approximation is .988

tmpCalc = tmpKelvin - 60;

tmpCalc = 329.698727446 * Math.Pow(tmpCalc, -0.1332047592);

r = (uint)tmpCalc;

if ( r > 255 ) r = 255;

}

//Second: green

if ( tmpKelvin <= 66 )

{

//Note: the R-squared value for this approximation is .996

tmpCalc = tmpKelvin;

tmpCalc = 99.4708025861 * Math.Log(tmpCalc) - 161.1195681661;

g = (uint)tmpCalc;

if ( g > 255 ) g = 255;

}

else

{

//Note: the R-squared value for this approximation is .987

tmpCalc = tmpKelvin - 60;

tmpCalc = 288.1221695283 * Math.Pow(tmpCalc, -0.0755148492);

g = (uint)tmpCalc;

if ( g > 255 ) g = 255;

}

//Third: blue

if ( tmpKelvin >= 66 )

b = 255;

else if ( tmpKelvin <= 19 )

b = 0;

else

{

//Note: the R-squared value for this approximation is .998

tmpCalc = tmpKelvin - 10;

tmpCalc = 138.5177312231 * Math.Log(tmpCalc) - 305.0447927307;

`b = (uint)tmpCalc;`

if ( b > 255 ) b = 255;

}

}

sorry, the “>” and “<" disappeared when I posted it. I don't think I used the "code" tags correctly.

BTW – this function works really well. Thank you!

Thanks for the kind words, Ryan, and thanks for sharing your code! I tried to insert all the missing > and < symbols – let me know if I missed any…

Thank you for the great article.

What do you think about multiplication the original RGB with the temperature RGB? As soon as at 6600 K your algorithm gives RGB(1,1,1) then it will give the possibility to make the original picture warmer/cooler. Your current algorithm as I understand just lets to specify temperature of the picture, it doesn’t let to preserve the original image (unless strength is set to 0)?

Hi Roman. Thanks for the kind comment.

Yes, you are correct that my version of the algorithm doesn’t exactly preserve the original image unless strength is set to 0. This is by design, as the image has to be modified somewhat in order to shift its temperature. I like a “strength” parameter, as it lets the user apply anything from subtle adjustments, to very strong ones – but I can understand wanting a simpler solution.

The problem with multiplying RGB as floating-point values on the range [0, 1] is that multiplying will always make pixels darker, unless multiplying by pure white (which has no effect). I think you’ll find better luck with some kind of “blend” formula, that moves pixels between their original value, and a temperature-adjusted value. How you calculate the temperature-adjusted value is up to you, but in my case, it worked well to calculate a “temperature color” using the algorithm from the article, then perform a luminance-preserving blend between each pixel and that calculated temperature RGB.

Hi,

Really nice algorithm! Any ideas how to add more vibrance to cool side? If you look the Lightroom temperature slider, it almost removes all the warm colors when temp is -100. I have tried to add more “blue” but not happy with the results so far.

thank you !!!!

I use this translate to the chinese .

link : http://www.smalinuxer.net/?p=327

so cool !!!!

Very nice algorithm for adjusting the temperature! I’m curious though as to why the luminance is calculated using

`(Max(R,G,B) + Min(R,G,B)) / 2`

as I feel this gives a less than optimal output.I tweaked your algorithm using the following for calculating luminance:

luminance.f = gam_sRGB(0.212655 * inv_gam_sRGB(r) + 0.715158 * inv_gam_sRGB(g) + 0.072187 * inv_gam_sRGB(b)) / 255

The pseudo-code for the other functions used are as follows:

Dim inv_gam_sRGB.f(255)

Procedure PrecalculateInverseGammas()

For i = 0 To 255

inv_gam_sRGB(i) = calc_inv_gam_sRGB(i)

Next

EndProcedure

Procedure.f calc_inv_gam_sRGB(ic.l)

c.f = ic / 255

If c > 0.04045

ProcedureReturn ((c + 0.055) / (1.055)) ^ 2.4

Else

ProcedureReturn c / 12.92

EndIf

EndProcedure

Procedure gam_sRGB(v.f)

If v > 0.0031308

v = 1.055 * v ^ (1.0 / 2.4) – 0.055

Else

v * 12.92

EndIf

ProcedureReturn v * 255 + 0.5

EndProcedure

This luminance calculation is based on information I got from http://www.w3.org/Graphics/Color/sRGB

Using the True Blood image as an example, the greens of the tree foliage in the background, in my opinion, end up looking quite flat using the method you used. Using the method I’ve described above I think gives a much better result. Please see the example images below:

Original image:

https://www.dropbox.com/s/e2cswv9wembucvu/TrueBlood_OriginalLuma.jpg?dl=0

Original Luminance Algorithm:

https://www.dropbox.com/s/e2cswv9wembucvu/TrueBlood_OriginalLuma.jpg?dl=0

sRGB Luminance Algorithm

https://www.dropbox.com/s/8ovbo1hm2p4qfp5/TrueBlood_sRGBLuma.jpg?dl=0

Hi Francis. Thanks for sharing your great sample images. “Pictures are worth a thousand words,” etc etc. :)

Your result does look significantly better, no doubt about that. My original implementation was just a copy/paste HSL transform, and HSL uses the (max + min) / 2 calculation for luminance. A custom calculation using something like the BT.709 formula you’ve using, will definitely do a better job of preserving luminance.

That change, combined with the improved curve fits suggested at this link, should provide the best possible implementation of this adjustment.

Thanks for the tip!

No problem Tanner! And thanks for that link. :)

Sorry, the link for the original image should be: https://www.dropbox.com/s/qb2m2l27y1kln9w/TrueBlood_Poster.jpg?dl=0

Adapted for use with an Arduino sketch…

byte Red = 0; // LED Red color value

byte Green = 0; // LED Green color value

byte Blue = 0; // LED Blue color value

int kelvin = 1500; // Set the beginning Kelvin temperature for the daylight sequence

int kelvintmp;

double tmpCalc;

if (tdaylight = 10000){ kelvin = 1400; }

kelvin = kelvin + 50;

}

//All calculations require kelvintmp \ 100, so only do the conversion once

kelvintmp = kelvin / 100;

//Calculate each color in turn

//First: red

if (kelvintmp 255) Red = 255;

}

//Second: green

if (kelvintmp 255) Green = 255;

}

else

{

//Note: the R-squared value for this approximation is .987

tmpCalc = kelvintmp – 60;

tmpCalc = 288.1221695283 * pow(tmpCalc, -0.0755148492);

Green = tmpCalc;

if (Green > 255) Green = 255;

}

//Third: blue

if (kelvintmp >= 66){

Blue = 255;

}

else if (kelvintmp 255) Blue = 255;

}

Nathan,

That code looks like it didn’t survive a copy paste. I’d be interested in using this on an Arduino if you could paste it somewhere easier to use such as pastebin.com

Regards

email me if you could. nate@natesnetwork.com

gracias por compartir ideas muy buenas y un trabajo muy bueno!!! saludos

Thanks for the algorithm. For anyone interested, I’ve just done a quick port to Swift, with a very basic iOS demo project:

https://github.com/davidf2281/ColorTempToRGB

Cheers,

David.

Its very interesting, because your color temperature tool has only. two settings. Temperature scale and strength. For example, my Acer DLP projector has 5200K colortemperature at white mode, but 86percentage or unit shift at Tint band (Green-Magenta). How can it handle?

I have a problem.

I need the formula what describe the difference of two CCT value in RGB. I see that it able to a kind of pair betweein one CCT value directly to an RGB value, but I need the difference method. If I set a custom WB value with WB cap to an about white lightsource I get a medium grey mass meaning about one medium grey color value with auto EV zero (Av,Tv in still camera for.examp.) If I shoot a warmer light source like tungsten, with WB cap, and with EV zero I get a warm color mass. I check these two colors properties. In Lab space, the Luminance is same ( same EV zero wb check method). In HSV they have differences even S and V also, but I cant check it in HSL (HSL is closest to Lab the combined Black-to-Light axis) The maint thing is, that If I made this procedure with an LCD (DLP projector) what is a RGB color controlled xenon lamp, and output the warm color picture directly, the two light source i use is will be totally same temperatured, and create totally correct coloured. It happened at the first tryout, blown my mind. So I know the two lightsource’s egzact CCT, and a colour picture with same Luminance with medium grey. We know the same colour RGB picture can be produce from another CCT pair difference, because one of the CCT I use set to the white balance.. What forume describe that CCT difference in RGB? or Lab ( I saw the grey and amber color difference is in only a and b values… but I am dumb in Lab..)

That was great! Hi. Do you possibly know how to convert the gray scale image to temperature? Can you help me with that? Thank you so much

I have similar question too. Did you have a solution? my thought is the “gray” image is still a RGB image which R=G=B. from this article, when you have RGB result of a CCT, get the R/G, B/G value, make you gray image close to that value. do you agree?

Maybe others will know more, but I don’t think it’s physically possible to correlate grayscale data to temperature. As a silly example, an ice cube is blue and an extremely hot flame is blue. How can you tell these apart in a grayscale photo?

With a greyscale image there is no colour information per se. A greyscale image only has brightness information so Tanner’s algorithm won’t be able to do anything with that.

What you can do is tint the image to a desired colour using the following algorithm, where ‘Colour’ is the desired tint colour (an RGB value, e.g. 0xff8833), and ‘Amount’ is how much you want to apply the tint (value range should be between 0 and 1, e.g. 0.5 for half amount):

// Get the component colours of the tint colour

rTint = Red(Colour)

gTint = Green(Colour)

bTint = Blue(Colour)

// Get the component colours of the current pixel

r = Red(pixel)

g = Green(pixel)

b = Blue(pixel)

// Apply the tint

r = r * (1 - Amount) + rTint * Amount

g = g * (1 - Amount) + gTint * Amount

b = b * (1 - Amount) + bTint * Amount

// Plot the tinted pixel

`pixel = RGB(r, g, b)`

Hopefully that makes sense.

Kind regards,

Francis

Thank you Tanner and Francis, it is a random gray scale image, if we use a certain scene like a white paper chart under a light source, then if we get the image, could we calculate the temperature by using your reversed method? Thank you.

Quick JS version (view source to get the code:) ):

http://tommitytom.co.uk/colourtemp/

Great formula, what if my LED also has Amber, White and UV component? Is there a way to do a curve with RGBW and RGBWAU?

Well documented and comprehensive, as every algorithm should be.

Would you mind if I translated it to Java and added to my set of opensource libraries? The code documentation will of course have a link to this post.

Translate away, Christophe! :) As far as I’m concerned, the algorithm is public domain. Thanks for linking back to this post if you can. Others have done that, and it’s led to lots of good feedback and subtle improvements.

And there you go!

https://github.com/Saucistophe/libs/blob/master/global-lib/src/main/java/org/saucistophe/utils/ColorUtils.java

The file requires some other files in the lib, but it should be not be complicated to copypaste it for those who don’t want to get the whole lib.

You’re ColorUtils class looks very useful. Is it possible to include a method to convert the other direction– RGB to Temperature?

Very interesting article (and comments)! I am wondering though, if you are blending the RGB channels and then correcting only the luminescence when converting back to HSV, doesn’t it mean that you might actually also change the saturation of the pixels? In which case, wouldn’t it be simpler to just blend the hue component?

(By the way, I am developing an open-source image manipulation software; if I find the time to get to the temperature control, would it be okay if I used your algorithm with a link to this post in the documentation?)

I have thought about it in the meantime and yes, the saturation is modified, but yes, it also makes perfect sense (although it did not appear to be obvious to me at first glance). Here are my thoughts:

Let us consider a white background. RGB would be (255,255,255), and HSV would be something like (H,0,1) if normalized, where H is whatever hue you desire. If you were to change the color temperature, the background would either get more blueish or yellowish, which would of course affect the hue component, but also the saturation by actually increasing it in this example (otherwise, the hue would not be visible and the background would remain white). This example is enough to show that saturation should be modified in the general case, and that your algorithm is indeed correct!

Hi Benoit. Looks like you have solved this on your own! :) Thank you for sharing your thoughts.

Here’s another thing to consider re: blending in HSL space (which is a non-trivial exercise). If we use the convention where we describe Hue as an angle on the range [0, 359], and we have two colors with Hue [0] and Hue [340], how do we blend these? In RGB space, we can simply calculate an average, but because Hue is circular, the correct blend would resolve to [350]. The circular nature of Hue makes blending complicated because of this.

And yes, you are of course welcome to use this algorithm in your own software.

Blending in HSV is indeed not as easy at it seemed. One of the reason is the non-linearity of the hue computation (using inverse tangent function and ratios) which makes a simple average in the RGB representation… a not so simple computation in the HSV representation. Actually, I could not find a simple way to express the transformation in HSV based on the one in RGB.

However, if you only consider the issue of computing the average of two hue components, a number of solutions can be found. I came up with three different algorithms I implemented using Python.

The first idea is to adopt a very simple -yet efficient- phase unwrapping approach. We first unwrap the data if needed (that is if the derivative -or the difference here- exceeds a threshold, 180 in our case), perform our computation and we finally make sure we remain in the correct domain.

The second idea starts to reintroduce the idea of cyclicity. The integer B is switched to a domain centered around A+-180, the average is performed, and we switch back to the original domain.

The third idea completely reintroduces the idea of cyclicity, using the complex representation (which seems only natural since the hue is represented by a phase). We thus create our associated complex numbers and perform the geometric average (which leads to a arithmetic average for the phase).

I have not thorougly tested these algorithms, and some border effects might remain (because of all the different domains used), but the general ideas seem to work!

You can find my code here: https://github.com/bporteboeuf/master-python/blob/master/sumUnwrap.py

Thanks for some good explanations. I ended up here while searching for how to mix two white lights of different kelvin colors. It sounds like RGB would be more fun :)

This looks great, but there are some important differences between the table and the result from the above code. The table has for 6500K: 255, 249, 253, but the formula returns: 255, 254, 250

Looks like the G and B are inverted or am I missing an extra conversion?

Did anybody noticed Mitchell 2016-Mar-30 update?

Would D58 white point improves anything for these scenarios?

Or is it only useful in astronomical contexts? http://photo.stackexchange.com/q/23335

http://www.science20.com/solar_fun_of_the_heliochromologist/the_color_of_the_sun_revelation#comment-83463

@above, your are missing the fact this wasn’t the best of possible interpolations, check April 2015 addendum

Quick and dirty node.js port:

https://gist.github.com/EDais/1ba1be0fe04eca66bbd588a6c9cbd666

Thanks for this; it was very helpful.

Building on what you’ve done, I fitted Charity’s data myself, using Mathematica (you can find that work in https://github.com/rgatkinson/ParticleDmxNeopixel/tree/master/ColorTemperature and https://github.com/rgatkinson/ParticleDmxNeopixel/blob/master/src/Color.h).

One thing worth noting is that I found fitting cubics and quadratics to the pieces of Charity’s curves worked significantly better than fitting a + b x + c Ln[x].

Why does green never reach 255? Doesn’t that mean that a monitor that’s well calibrated so sRGB can never show true white? I’d think that for 6500 K the RGB values should all be 255.