summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/aux.py6
-rw-r--r--src/blackbody.py175
2 files changed, 104 insertions, 77 deletions
diff --git a/src/aux.py b/src/aux.py
index a21ab5d..ff852ba 100644
--- a/src/aux.py
+++ b/src/aux.py
@@ -65,8 +65,11 @@ def linearly_interpolate_ramp(r, g, b): # TODO test, demo and document this
small_, large_ = len(small) - 1, len(large) - 1
if large_ > small_:
for i in range(len(large)):
+ # Scaling
j = i * small_ / large_
+ # Floor, weight, ceiling
j, w, k = int(j), j % 1, min(int(j) + 1, small_)
+ # Interpolation
large[i] = small[j] * (1 - w) + small[k] * w
return (R, G, B)
@@ -82,8 +85,11 @@ def functionise(rgb):
def fcurve(R_curve, G_curve, B_curve):
for curve, cur in curves(R_curve, G_curve, B_curve):
for i in range(i_size):
+ # Nearest neighbour
y = int(curve[i] * (len(cur) - 1) + 0.5)
+ # Truncation to actual neighbour
y = min(max(0, y), len(cur) - 1)
+ # Remapping
curve[i] = cur[y]
return lambda : fcurve(*rgb)
diff --git a/src/blackbody.py b/src/blackbody.py
index 41c6fe5..02972da 100644
--- a/src/blackbody.py
+++ b/src/blackbody.py
@@ -18,6 +18,7 @@
# This module implements support for colour temperature based
# calculation of white points
+import os
import math
from colour import *
@@ -38,13 +39,17 @@ def series_d(temperature):
@param temperature:float The blackbody temperature in kelvins, must be inside [4000, 7000]
@return :(float, float, float) The red, green and blue components of the white point
'''
- x = 0
- ks = ((0.244063, 0), (0.09911, 1), (2.9678, 2), (-4.6070, 3))
+ # Get coefficients for calculating the x component
+ # of the colour in the CIE xyY colour space
+ x, ks = 0, (0.244063, 0.09911, 2.9678, -4.6070)
if temperature > 7000:
- ks = ((0.237040, 0), (0.24748, 1), (1.9018, 2), (-2.0064, 3))
- for (k, d) in ks:
+ ks = (0.237040, 0.24748, 1.9018, -2.0064)
+ # Calculate the x component of the colour in the CIE xyY colour space
+ for d, k in enumerate(ks):
x += k * 10 ** (d * 3) / temperature ** d
+ # Calculate the y component of the colour in the CIE xyY colour space
y = 2.870 * x - 3.000 * x ** 2 - 0.275
+ # Convert to sRGB and return, with full illumination
return ciexyy_to_srgb(x, y, 1.0)
@@ -55,18 +60,14 @@ def simple_whitepoint(temperature):
@param temperature:float The blackbody temperature in kelvins, not guaranteed for values outside [1000, 40000]
@return :(float, float, float) The red, green and blue components of the white point
'''
- r, g, b = 1, 1, 1
- temp = temperature / 100
+ r, g, b, temp = 1, 1, 1, temperature / 100
if temp > 66:
- temp -= 60
- r = 1.292936186 * temp ** 0.1332047592
- g = 1.129890861 * temp ** -0.0755148492
+ r = 1.292936186 * (temp - 60) ** 0.1332047592
+ g = 1.129890861 * (temp - 60) ** -0.0755148492
else:
g = 0.390081579 * math.log(temp) - 0.631841444
- if temp <= 19:
- b = 0
- elif temp < 66:
- b = 0.543206789 * math.log(temp - 10) - 1.196254089
+ if temp < 66:
+ b = 0 if temp <= 19 else 0.543206789 * math.log(temp - 10) - 1.196254089
return (r, g, b)
@@ -75,30 +76,19 @@ def cmf_2deg(temperature):
'''
Calculate the colour for a blackbody temperature using raw CIE 1931 2 degree CMF data with interpolation
- Using `lambda t : divide_by_maximum(cmf_2deg(t))` as the algorithm is better than just `cmf_2deg`
+ Using `lambda t : divide_by_maximum(cmf_2deg(t))` as the algorithm is better than just `cmf_2deg`,
+ `lambda t : clip_whitepoint(divide_by_maximum(cmf_2deg(t)))` is even better if you plan to use really
+ low temperatures,
@param temperature:float The blackbody temperature in kelvins, clipped to [1000, 40000]
@return :(float, float, float) The red, green and blue components of the white point
'''
global cmf_2deg_cache
if cmf_2deg_cache is None:
- with open(DATADIR + '/2deg', 'rb') as file:
- cmf_2deg_cache = file.read()
- cmf_2deg_cache = cmf_2deg_cache.decode('utf-8', 'error').split('\n')
- cmf_2deg_cache = filter(lambda x : not x == '', cmf_2deg_cache)
- cmf_2deg_cache = [[float(x) for x in x_y.split(' ')] for x_y in cmf_2deg_cache]
- temp = min(max(1000, temperature), 40000)
- x, y = 0, 0
- if (temp % 100) == 0:
- (x, y) = cmf_2deg_cache[int((temp - 1000) // 100)]
- else:
- temp -= 1000
- (x1, y1) = cmf_2deg_cache[int(temp // 100)]
- (x2, y2) = cmf_2deg_cache[int(temp // 100 + 1)]
- temp = (temp % 100) / 100
- x = x1 * (1 - temp) + x2 * temp
- y = y1 * (1 - temp) + y2 * temp
- return ciexyy_to_srgb(x, y, 1.0)
+ # Load, parse and cache lookup table if not cached
+ cmf_2deg_cache = get_blackbody_lut('2deg')
+ # Calculate whitepoint
+ return cmf_xdeg(temperature, cmf_2deg_cache)
cmf_10deg_cache = None
@@ -106,30 +96,51 @@ def cmf_10deg(temperature):
'''
Calculate the colour for a blackbody temperature using raw CIE 1964 10 degree CMF data with interpolation
- Using `lambda t : divide_by_maximum(cmf_10deg(t))` as the algorithm is better than just `cmf_10deg`
+ Using `lambda t : divide_by_maximum(cmf_10deg(t))` as the algorithm is better than just `cmf_10deg`,
+ `lambda t : clip_whitepoint(divide_by_maximum(cmf_10deg(t)))` is even better if you plan to use really
+ low temperatures,
@param temperature:float The blackbody temperature in kelvins, clipped to [1000, 40000]
@return :(float, float, float) The red, green and blue components of the white point
'''
global cmf_10deg_cache
if cmf_10deg_cache is None:
- with open(DATADIR + '/10deg', 'rb') as file:
- cmf_10deg_cache = file.read()
- cmf_10deg_cache = cmf_10deg_cache.decode('utf-8', 'error').split('\n')
- cmf_10deg_cache = filter(lambda x : not x == '', cmf_10deg_cache)
- cmf_10deg_cache = [[float(x) for x in x_y.split(' ')] for x_y in cmf_10deg_cache]
- temp = min(max(1000, temperature), 40000)
- x, y = 0, 0
- if (temp % 100) == 0:
- (x, y) = cmf_10deg_cache[int((temp - 1000) // 100)]
+ # Load, parse and cache lookup table if not cached
+ cmf_10deg_cache = get_blackbody_lut('10deg')
+ # Calculate whitepoint
+ return cmf_xdeg(temperature, cmf_10deg_cache)
+
+
+def cmf_xdeg(temperature, lut, temp_min = 1000, temp_max = 40000, temp_step = 100):
+ '''
+ Calculate the colour for a blackbody temperature using
+ raw data in the CIE xyY colour space with interpolation
+
+ This function is intended as help functions for the two functions above this one in this module
+
+ @param temperature:float The blackbody temperature in kelvins
+ @param lut:list<[x:float, y:float]> Raw data lookup table
+ @param temp_min:float The lowest temperature in the lookup table
+ @param temp_max:float The highest temperature in the lookup table
+ @param temp_step:float The interval between the temperatures
+ @return :(r:float, g:float, b:float) The whitepoint in [0, 1] sRGB
+ '''
+ # Clip temperature to definition domain and remove offset
+ x, y, temp = 0, 0, min(max(temp_min, temperature), temp_max) - temp_min
+ if temp % temp_step == 0:
+ # Exact temperature is included in the lookup table
+ (x, y) = lut[int(temp // temp_step)]
else:
- temp -= 1000
- (x1, y1) = cmf_10deg_cache[int(temp // 100)]
- (x2, y2) = cmf_10deg_cache[int(temp // 100 + 1)]
- temp = (temp % 100) / 100
- x = x1 * (1 - temp) + x2 * temp
- y = y1 * (1 - temp) + y2 * temp
- return ciexyy_to_srgb(x, y, 1)
+ # x component floor and y component floor
+ floor = lut[int(temp // temp_step)]
+ # x component ceiling and y component ceiling
+ celiing = lut[int(temp // temp_step + 1)]
+ # Weight
+ temp = (temp % temp_step) / temp_step
+ # Interpolation
+ (x, y) = [c1 * (1 - temp) + c2 * temp for c1, c2 in zip(floor, ceilng)]
+ # Convert to sRGB
+ return ciexyy_to_srgb(x, y, 1.0)
redshift_cache, redshift_old_cache = None, None
@@ -144,43 +155,55 @@ def redshift(temperature, old_version = False, linear_interpolation = False):
@return :(float, float, float) The red, green and blue components of the white point
'''
global redshift_cache, redshift_old_cache
- cache = None
- if not old_version:
- if redshift_cache is None:
- with open(DATADIR + '/redshift', 'rb') as file:
- redshift_cache = file.read()
- redshift_cache = redshift_cache.decode('utf-8', 'error').split('\n')
- redshift_cache = filter(lambda x : not x == '', redshift_cache)
- redshift_cache = [[float(x) for x in r_g_b.split(' ')] for r_g_b in redshift_cache]
- cache = redshift_cache
- else:
- if redshift_old_cache is None:
- with open(DATADIR + '/redshift_old', 'rb') as file:
- redshift_old_cache = file.read()
- redshift_old_cache = redshift_old_cache.decode('utf-8', 'error').split('\n')
- redshift_old_cache = filter(lambda x : not x == '', redshift_old_cache)
- redshift_old_cache = [[float(x) for x in r_g_b.split(' ')] for r_g_b in redshift_old_cache]
- cache = redshift_old_cache
- temp = min(max(1000, temperature), 10000 if old_version else 25100)
+ # Retrieve cache
+ cache = redshift_old_cache if old_version else redshift_cache
+ if cache is None:
+ # Load and parse lookup table if not cached
+ cache = get_blackbody_lut('redshift_old' if old_version else 'redshift')
+ # Cache lookup table
+ if old_version: redshift_old_cache = cache
+ else: redshift_cache = cache
+ # Clip to definition domain and remove offset
+ temp = min(max(1000, temperature), 10000 if old_version else 25100) - 1000
r, g, b = 1, 1, 1
if (temp % 100) == 0:
- (r, g, b) = cache[int((temp - 1000) // 100)]
+ # Exact temperature is included in the lookup table
+ (r, g, b) = cache[int(temp // 100)]
else:
- temp -= 1000
- (r1, g1, b1) = cache[int(temp // 100)]
- (r2, g2, b2) = cache[int(temp // 100 + 1)]
+ # Floor
+ rgb1 = cache[int(temp // 100)]
+ # Ceiling
+ rgb2 = cache[int(temp // 100 + 1)]
+ # Weight
temp = (temp % 100) / 100
+ # Interpolation
if linear_interpolation:
- (r, g, b) = standard_to_linear(r, g, b)
- r = r1 * (1 - temp) + r2 * temp
- g = g1 * (1 - temp) + g2 * temp
- b = b1 * (1 - temp) + b2 * temp
+ (rgb1, rgb2) = [standard_to_linear(*rgb) for rgb in (rgb1, rgb2)]
+ (r, g, b) = [c1 * (1 - temp) + c2 * temp for c1, c2 in zip(rgb1, rgb2)]
if linear_interpolation:
(r, g, b) = linear_to_standard(r, g, b)
return (r, g, b)
+def get_blackbody_lut(filename):
+ '''
+ Load and parse a blackbody data lookup table
+
+ This function is intended as help functions for the functions above this one in this module
+
+ @param filename:str The filename of the lookup table
+ @return :list<list<float>> A float matrix of all values in the lookup table
+ '''
+ # Load lookup table
+ lut = None
+ with open(DATADIR + os.sep + filename, 'rb') as file:
+ lut = file.read().decode('utf-8', 'error').split('\n')
+ # Parse lookup table
+ return [[float(cell) for cell in line.split(' ')] for line in lut if not line == '']
+
+
+
def divide_by_maximum(rgb):
'''
Divide all colour components by the value of the most prominent colour component
@@ -189,9 +212,7 @@ def divide_by_maximum(rgb):
@return :[float, float, float] The three colour components divided by the maximum
'''
m = max([abs(x) for x in rgb])
- if m != 0:
- return [x / m for x in rgb]
- return rgb
+ return rgb if m == 0 else [x / m for x in rgb]
def clip_whitepoint(rgb):