Skip to content

Instantly share code, notes, and snippets.

@petrklus
Last active April 10, 2024 11:20
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save petrklus/b1f427accdf7438606a6 to your computer and use it in GitHub Desktop.
Save petrklus/b1f427accdf7438606a6 to your computer and use it in GitHub Desktop.
Kelvin to RGB in python
"""
Based on: http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
Comments resceived: https://gist.github.com/petrklus/b1f427accdf7438606a6
Original pseudo code:
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
"""
import math
def convert_K_to_RGB(colour_temperature):
"""
Converts from K to RGB, algorithm courtesy of
http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
"""
#range check
if colour_temperature < 1000:
colour_temperature = 1000
elif colour_temperature > 40000:
colour_temperature = 40000
tmp_internal = colour_temperature / 100.0
# red
if tmp_internal <= 66:
red = 255
else:
tmp_red = 329.698727446 * math.pow(tmp_internal - 60, -0.1332047592)
if tmp_red < 0:
red = 0
elif tmp_red > 255:
red = 255
else:
red = tmp_red
# green
if tmp_internal <=66:
tmp_green = 99.4708025861 * math.log(tmp_internal) - 161.1195681661
if tmp_green < 0:
green = 0
elif tmp_green > 255:
green = 255
else:
green = tmp_green
else:
tmp_green = 288.1221695283 * math.pow(tmp_internal - 60, -0.0755148492)
if tmp_green < 0:
green = 0
elif tmp_green > 255:
green = 255
else:
green = tmp_green
# blue
if tmp_internal >=66:
blue = 255
elif tmp_internal <= 19:
blue = 0
else:
tmp_blue = 138.5177312231 * math.log(tmp_internal - 10) - 305.0447927307
if tmp_blue < 0:
blue = 0
elif tmp_blue > 255:
blue = 255
else:
blue = tmp_blue
return red, green, blue
if __name__ == "__main__":
print("Preview requires matplotlib")
from matplotlib import pyplot as plt
step_size = 100
for i in range(0, 15000, step_size):
color = list(map(lambda div: div/255.0, convert_K_to_RGB(i))) + [1]
print(color)
plt.plot((i, i), (0, 1), linewidth=step_size/2.0, linestyle="-", color=color)
plt.show()
@sbremer
Copy link

sbremer commented Nov 9, 2016

Awesome, thanks :)

@fiveseven808
Copy link

Thank you so much for this!

@jaknz
Copy link

jaknz commented Mar 23, 2022

Oh bother, I found this minutes after I write a conversion myself. Oh well, great practice! Do you happen to have a pointer on linking colour temperature to time of day? I'm wanting to use this for a circadian clock project.

@xalbertoisorna
Copy link

do you have by any change the reverse function?

@petrklus
Copy link
Author

petrklus commented Jun 5, 2023

hi @xalbertoisorna , I had a similar use-case and I've just eye-balled it - used around 5500K or even a little bluer for midday, then progressively going down.

No algorithm I am afraid.

@Guimoute
Copy link

Guimoute commented Sep 7, 2023

I suggest using numpy for a much shorter code:

import numpy as np

def convert_K_to_RGB(colour_temperature: float) -> np.ndarray:
    """
    Converts from K to RGB, algorithm courtesy of
    http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
    """
    # Range check.
    colour_temperature = np.clip(colour_temperature, 1000, 40000)

    tmp_internal = colour_temperature / 100.0

    # Red.
    if tmp_internal <= 66:
        red = 255
    else:
        red = 329.698727446 * (tmp_internal - 60)**-0.1332047592

    # Green.
    if tmp_internal <= 66:
        green = 99.4708025861 * np.log(tmp_internal) - 161.1195681661
    else:
        green = 288.1221695283 * (tmp_internal - 60)**-0.0755148492

    # Blue.
    if tmp_internal >= 66:
        blue = 255
    elif tmp_internal <= 19:
        blue = 0
    else:
        blue = 138.5177312231 * np.log(tmp_internal - 10) - 305.0447927307

    return np.clip((red, green, blue), 0, 255)

I have also changed your main so that we can zoom in infinitely without seeing empty space between the color bars.

if __name__ == "__main__":

    from matplotlib import pyplot as plt

    STEP_SIZE = 100
    temperatures = np.arange(0, 15000, STEP_SIZE)
    colors = [convert_K_to_RGB(temperature)/255 for temperature in temperatures]
    fig, ax = plt.subplots()
    ax.set_xlim(temperatures[0], temperatures[-1])
    ax.set_ylim(0, 1)
    ax.bar(temperatures, height=1, width=STEP_SIZE, align="edge", color=colors)
    fig.show()

With 10 more minutes of work, we could make the function support polymorphism so that it works on a whole numpy array at once.

@xalbertoisorna
Copy link

xalbertoisorna commented Mar 8, 2024

If you are interested in the rgb_to_kelvin() I just reversed the values, with a step size of 100K, find it below as a header file to run it in C.

// temperature.h
#include <stdint.h>

#define HEIGHT 150
#define WIDTH  4

const unsigned colorTable[HEIGHT][WIDTH] = {
    {181,205,255,14900},
    {182,205,255,14800},
    {182,206,255,14500},
    {182,206,255,14600},
    {182,206,255,14700},
    {183,206,255,14300},
    {183,206,255,14400},
    {183,207,255,14200},
    {184,207,255,13900},
    {184,207,255,14000},
    {184,207,255,14100},
    {185,207,255,13800},
    {185,208,255,13600},
    {185,208,255,13700},
    {186,208,255,13300},
    {186,208,255,13400},
    {186,208,255,13500},
    {187,209,255,13000},
    {187,209,255,13100},
    {187,209,255,13200},
    {188,209,255,12900},
    {188,210,255,12700},
    {188,210,255,12800},
    {189,210,255,12400},
    {189,210,255,12500},
    {189,210,255,12600},
    {190,211,255,12200},
    {190,211,255,12300},
    {191,211,255,12000},
    {191,211,255,12100},
    {192,212,255,11700},
    {192,212,255,11800},
    {192,212,255,11900},
    {193,213,255,11500},
    {193,213,255,11600},
    {194,213,255,11300},
    {194,213,255,11400},
    {195,214,255,11100},
    {195,214,255,11200},
    {196,214,255,11000},
    {196,215,255,10900},
    {197,215,255,10700},
    {197,215,255,10800},
    {198,216,255,10600},
    {199,216,255,10500},
    {199,217,255,10400},
    {200,217,255,10200},
    {200,217,255,10300},
    {201,218,255,10100},
    {202,218,255,9900},
    {202,218,255,10000},
    {203,219,255,9800},
    {204,219,255,9700},
    {205,220,255,9500},
    {205,220,255,9600},
    {206,221,255,9400},
    {207,221,255,9300},
    {208,222,255,9200},
    {209,222,255,9100},
    {210,223,255,9000},
    {211,223,255,8900},
    {212,224,255,8800},
    {213,225,255,8700},
    {214,225,255,8600},
    {215,226,255,8500},
    {216,227,255,8400},
    {217,227,255,8300},
    {218,228,255,8200},
    {220,229,255,8100},
    {221,230,255,8000},
    {223,231,255,7900},
    {224,232,255,7800},
    {226,233,255,7700},
    {228,234,255,7600},
    {230,235,255,7500},
    {232,236,255,7400},
    {234,237,255,7300},
    {237,239,255,7200},
    {240,240,255,7100},
    {243,242,255,7000},
    {246,244,255,6900},
    {250,246,255,6800},
    {254,249,255,6700},
    {255,68,0,0},
    {255,68,0,100},
    {255,68,0,200},
    {255,68,0,300},
    {255,68,0,400},
    {255,68,0,500},
    {255,68,0,600},
    {255,68,0,700},
    {255,68,0,800},
    {255,68,0,900},
    {255,68,0,1000},
    {255,77,0,1100},
    {255,86,0,1200},
    {255,94,0,1300},
    {255,101,0,1400},
    {255,108,0,1500},
    {255,115,0,1600},
    {255,121,0,1700},
    {255,126,0,1800},
    {255,132,0,1900},
    {255,137,14,2000},
    {255,142,27,2100},
    {255,146,39,2200},
    {255,151,50,2300},
    {255,155,61,2400},
    {255,159,70,2500},
    {255,163,79,2600},
    {255,167,87,2700},
    {255,170,95,2800},
    {255,174,103,2900},
    {255,177,110,3000},
    {255,180,117,3100},
    {255,184,123,3200},
    {255,187,129,3300},
    {255,190,135,3400},
    {255,193,141,3500},
    {255,195,146,3600},
    {255,198,151,3700},
    {255,201,157,3800},
    {255,203,161,3900},
    {255,206,166,4000},
    {255,208,171,4100},
    {255,211,175,4200},
    {255,213,179,4300},
    {255,215,183,4400},
    {255,218,187,4500},
    {255,220,191,4600},
    {255,222,195,4700},
    {255,224,199,4800},
    {255,226,202,4900},
    {255,228,206,5000},
    {255,230,209,5100},
    {255,232,213,5200},
    {255,234,216,5300},
    {255,236,219,5400},
    {255,237,222,5500},
    {255,239,225,5600},
    {255,241,228,5700},
    {255,243,231,5800},
    {255,244,234,5900},
    {255,246,237,6000},
    {255,248,240,6100},
    {255,249,242,6200},
    {255,251,245,6300},
    {255,253,248,6400},
    {255,254,250,6500},
    {255,255,255,6600}
};

// function that takes an RGB value, 
// checks the lowest distance to the colorTable and returns the temperature 
unsigned rgb_to_kelvin(unsigned r, unsigned g, unsigned b) {
    unsigned minDist = 0xFFFFFF;
    unsigned minTemp = 0;
    for (unsigned i = 0; i < HEIGHT; i++) {
        unsigned rT = colorTable[i][0];
        unsigned gT = colorTable[i][1];
        unsigned bT = colorTable[i][2];
        unsigned dist = (r-rT)*(r-rT) + (g-gT)*(g-gT) + (b-bT)*(b-bT);
        if (dist < minDist) {
            minDist = dist;
            minTemp = colorTable[i][3];
        }
    }
    return minTemp;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment