Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
255 views
1
'''
2
colormodels.py - Conversions between color models
3
4
Description:
5
6
Defines several color models, and conversions between them.
7
8
The models are:
9
10
xyz - CIE XYZ color space, based on the 1931 matching functions for a 2 degree field of view.
11
Spectra are converted to xyz color values by integrating with the matching functions in ciexyz.py.
12
13
xyz colors are often handled as absolute values, conventionally written with uppercase letters XYZ,
14
or as scaled values (so that X+Y+Z = 1.0), conventionally written with lowercase letters xyz.
15
16
This is the fundamental color model around which all others are based.
17
18
rgb - Colors expressed as red, green and blue values, in the nominal range 0.0 - 1.0.
19
These are linear color values, meaning that doubling the number implies a doubling of the light intensity.
20
rgb color values may be out of range (greater than 1.0, or negative), and do not account for gamma correction.
21
They should not be drawn directly.
22
23
irgb - Displayable color values expressed as red, green and blue values, in the range 0 - 255.
24
These have been adjusted for gamma correction, and have been clipped into the displayable range 0 - 255.
25
These color values can be drawn directly.
26
27
Luv - A nearly perceptually uniform color space.
28
29
Lab - Another nearly perceptually uniform color space.
30
31
As far as I know, the Luv and Lab spaces are of similar quality.
32
Neither is perfect, so perhaps try each, and see what works best for your application.
33
34
The models store color values as 3-element NumPy vectors.
35
The values are stored as floats, except for irgb, which are stored as integers.
36
37
Constants:
38
39
SRGB_Red
40
SRGB_Green
41
SRGB_Blue
42
SRGB_White -
43
Chromaticity values for sRGB standard display monitors.
44
45
PhosphorRed
46
PhosphorGreen
47
PhosphorBlue
48
PhosphorWhite -
49
Chromaticity values for display used in initialization.
50
These are the sRGB values by default, but other values can be chosen.
51
52
CLIP_CLAMP_TO_ZERO = 0
53
CLIP_ADD_WHITE = 1
54
Available color clipping methods. Add white is the default.
55
56
Functions:
57
58
'Constructor-like' functions:
59
60
xyz_color (x, y, z = None) -
61
Construct an xyz color. If z is omitted, set it so that x+y+z = 1.0.
62
63
xyz_normalize (xyz) -
64
Scale so that all values add to 1.0.
65
This both modifies the passed argument and returns the normalized result.
66
67
xyz_normalize_Y1 (xyz) -
68
Scale so that the y component is 1.0.
69
This both modifies the passed argument and returns the normalized result.
70
71
xyz_color_from_xyY (x, y, Y) -
72
Given the 'little' x,y chromaticity, and the intensity Y,
73
construct an xyz color. See Foley/Van Dam p. 581, eq. 13.21.
74
75
rgb_color (r, g, b) -
76
Construct a linear rgb color from components.
77
78
irgb_color (ir, ig, ib) -
79
Construct a displayable integer irgb color from components.
80
81
luv_color (L, u, v) -
82
Construct a Luv color from components.
83
84
lab_color (L, a, b) -
85
Construct a Lab color from components.
86
87
Conversion functions:
88
89
rgb_from_xyz (xyz) -
90
Convert an xyz color to rgb.
91
92
xyz_from_rgb (rgb) -
93
Convert an rgb color to xyz.
94
95
irgb_string_from_irgb (irgb) -
96
Convert a displayable irgb color (0-255) into a hex string.
97
98
irgb_from_irgb_string (irgb_string) -
99
Convert a color hex string (like '#AB13D2') into a displayable irgb color.
100
101
irgb_from_rgb (rgb) -
102
Convert a (linear) rgb value (range 0.0 - 1.0) into a 0-255 displayable integer irgb value (range 0 - 255).
103
104
rgb_from_irgb (irgb) -
105
Convert a displayable (gamma corrected) irgb value (range 0 - 255) into a linear rgb value (range 0.0 - 1.0).
106
107
irgb_string_from_rgb (rgb) -
108
Clip the rgb color, convert to a displayable color, and convert to a hex string.
109
110
irgb_from_xyz (xyz) -
111
Convert an xyz color directly into a displayable irgb color.
112
113
irgb_string_from_xyz (xyz) -
114
Convert an xyz color directly into a displayable irgb color hex string.
115
116
luv_from_xyz (xyz) -
117
Convert CIE XYZ to Luv.
118
119
xyz_from_luv (luv) -
120
Convert Luv to CIE XYZ. Inverse of luv_from_xyz().
121
122
lab_from_xyz (xyz) -
123
Convert color from CIE XYZ to Lab.
124
125
xyz_from_lab (Lab) -
126
Convert color from Lab to CIE XYZ. Inverse of lab_from_xyz().
127
128
Gamma correction:
129
130
simple_gamma_invert (x) -
131
Simple power law for gamma inverse correction.
132
Not used by default.
133
134
simple_gamma_correct (x) -
135
Simple power law for gamma correction.
136
Not used by default.
137
138
srgb_gamma_invert (x) -
139
sRGB standard for gamma inverse correction.
140
This is used by default.
141
142
srgb_gamma_correct (x) -
143
sRGB standard for gamma correction.
144
This is used by default.
145
146
Color clipping:
147
148
clip_rgb_color (rgb_color) -
149
Convert a linear rgb color (nominal range 0.0 - 1.0), into a displayable
150
irgb color with values in the range (0 - 255), clipping as necessary.
151
152
The return value is a tuple, the first element is the clipped irgb color,
153
and the second element is a tuple indicating which (if any) clipping processes were used.
154
155
Initialization functions:
156
157
init (
158
phosphor_red = SRGB_Red,
159
phosphor_green = SRGB_Green,
160
phosphor_blue = SRGB_Blue,
161
white_point = SRGB_White) -
162
163
Setup the conversions between CIE XYZ and linear RGB spaces.
164
Also do other initializations (gamma, conversions with Luv and Lab spaces, clipping model).
165
The default arguments correspond to the sRGB standard RGB space.
166
The conversion is defined by supplying the chromaticities of each of
167
the monitor phosphors, as well as the resulting white color when all
168
of the phosphors are at full strength.
169
See [Foley/Van Dam, p.587, eqn 13.27, 13.29] and [Hall, p. 239].
170
171
init_Luv_Lab_white_point (white_point) -
172
Specify the white point to use for Luv/Lab conversions.
173
174
init_gamma_correction (
175
display_from_linear_function = srgb_gamma_invert,
176
linear_from_display_function = srgb_gamma_correct,
177
gamma = STANDARD_GAMMA) -
178
179
Setup gamma correction.
180
The functions used for gamma correction/inversion can be specified,
181
as well as a gamma value.
182
The specified display_from_linear_function should convert a
183
linear (rgb) component [proportional to light intensity] into
184
displayable component [proportional to palette values].
185
The specified linear_from_display_function should convert a
186
displayable (rgb) component [proportional to palette values]
187
into a linear component [proportional to light intensity].
188
The choices for the functions:
189
display_from_linear_function -
190
srgb_gamma_invert [default] - sRGB standard
191
simple_gamma_invert - simple power function, can specify gamma.
192
linear_from_display_function -
193
srgb_gamma_correct [default] - sRGB standard
194
simple_gamma_correct - simple power function, can specify gamma.
195
The gamma parameter is only used for the simple() functions,
196
as sRGB implies an effective gamma of 2.2.
197
198
init_clipping (clip_method = CLIP_ADD_WHITE) -
199
Specify the color clipping method.
200
201
References:
202
203
Foley, van Dam, Feiner and Hughes. Computer Graphics: Principles and Practice, 2nd edition,
204
Addison Wesley Systems Programming Series, 1990. ISBN 0-201-12110-7.
205
206
Roy Hall, Illumination and Color in Computer Generated Imagery. Monographs in Visual Communication,
207
Springer-Verlag, New York, 1989. ISBN 0-387-96774-5.
208
209
Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and Formulae, 2nd edition,
210
John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN 0-471-39918-3.
211
212
Judd and Wyszecki, Color in Business, Science and Industry, 1975.
213
214
Kasson and Plouffe, An Analysis of Selected Computer Interchange Color Spaces,
215
ACM Transactions on Graphics, Vol. 11, No. 4, October 1992.
216
217
Charles Poynton - Frequently asked questions about Gamma and Color,
218
posted to comp.graphics.algorithms, 25 Jan 1995.
219
220
sRGB - http://www.color.org/sRGB.xalter - (accessed 15 Sep 2008)
221
A Standard Default Color Space for the Internet: sRGB,
222
Michael Stokes (Hewlett-Packard), Matthew Anderson (Microsoft), Srinivasan Chandrasekar (Microsoft),
223
Ricardo Motta (Hewlett-Packard), Version 1.10, November 5, 1996.
224
225
License:
226
227
Copyright (C) 2008 Mark Kness
228
229
Author - Mark Kness - [email protected]
230
231
This file is part of ColorPy.
232
233
ColorPy is free software: you can redistribute it and/or modify
234
it under the terms of the GNU Lesser General Public License as
235
published by the Free Software Foundation, either version 3 of
236
the License, or (at your option) any later version.
237
238
ColorPy is distributed in the hope that it will be useful,
239
but WITHOUT ANY WARRANTY; without even the implied warranty of
240
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
241
GNU Lesser General Public License for more details.
242
243
You should have received a copy of the GNU Lesser General Public License
244
along with ColorPy. If not, see <http://www.gnu.org/licenses/>.
245
'''
246
import math, numpy
247
248
# The xyz constructors have some special versions to handle some common situations
249
250
def xyz_color (x, y, z = None):
251
'''Construct an xyz color. If z is omitted, set it so that x+y+z = 1.0.'''
252
if z == None:
253
# choose z so that x+y+z = 1.0
254
z = 1.0 - (x + y)
255
rtn = numpy.array ([x, y, z])
256
return rtn
257
258
def xyz_normalize (xyz):
259
'''Scale so that all values add to 1.0.
260
This both modifies the passed argument and returns the normalized result.'''
261
sum_xyz = xyz[0] + xyz[1] + xyz[2]
262
if sum_xyz != 0.0:
263
scale = 1.0 / sum_xyz
264
xyz [0] *= scale
265
xyz [1] *= scale
266
xyz [2] *= scale
267
return xyz
268
269
def xyz_normalize_Y1 (xyz):
270
'''Scale so that the y component is 1.0.
271
This both modifies the passed argument and returns the normalized result.'''
272
if xyz [1] != 0.0:
273
scale = 1.0 / xyz [1]
274
xyz [0] *= scale
275
xyz [1] *= scale
276
xyz [2] *= scale
277
return xyz
278
279
def xyz_color_from_xyY (x, y, Y):
280
'''Given the 'little' x,y chromaticity, and the intensity Y,
281
construct an xyz color. See Foley/Van Dam p. 581, eq. 13.21.'''
282
return xyz_color (
283
(x/y)* Y,
284
Y,
285
(1.0-x-y)/(y) * Y)
286
287
# Simple constructors for the remaining models
288
289
def rgb_color (r, g, b):
290
'''Construct a linear rgb color from components.'''
291
rtn = numpy.array ([r, g, b])
292
return rtn
293
294
def irgb_color (ir, ig, ib):
295
'''Construct a displayable integer irgb color from components.'''
296
rtn = numpy.array ([ir, ig, ib], int)
297
return rtn
298
299
def luv_color (L, u, v):
300
'''Construct a Luv color from components.'''
301
rtn = numpy.array ([L, u, v])
302
return rtn
303
304
def lab_color (L, a, b):
305
'''Construct a Lab color from components.'''
306
rtn = numpy.array ([L, a, b])
307
return rtn
308
309
#
310
# Definitions of some standard values for colors and conversions
311
#
312
313
# Chromaticities of various standard phosphors and white points.
314
315
# sRGB (ITU-R BT.709) standard phosphor chromaticities
316
SRGB_Red = xyz_color (0.640, 0.330)
317
SRGB_Green = xyz_color (0.300, 0.600)
318
SRGB_Blue = xyz_color (0.150, 0.060)
319
SRGB_White = xyz_color (0.3127, 0.3290) # D65
320
321
# HDTV standard phosphors, from Poynton [Color FAQ] p. 9
322
# These are claimed to be similar to typical computer monitors
323
HDTV_Red = xyz_color (0.640, 0.330)
324
HDTV_Green = xyz_color (0.300, 0.600)
325
HDTV_Blue = xyz_color (0.150, 0.060)
326
# use D65 as white point for HDTV
327
328
# SMPTE phosphors
329
# However, Hall [p. 188] notes that TV expects values calibrated for NTSC
330
# even though actual phosphors are as below.
331
# From Hall p. 118, and Kasson p. 400
332
SMPTE_Red = xyz_color (0.630, 0.340)
333
SMPTE_Green = xyz_color (0.310, 0.595)
334
SMPTE_Blue = xyz_color (0.155, 0.070)
335
# use D65 as white point for SMPTE
336
337
# NTSC phosphors [original standard for TV, but no longer used in TV sets]
338
# From Hall p. 119 and Foley/Van Dam p. 589
339
NTSC_Red = xyz_color (0.670, 0.330)
340
NTSC_Green = xyz_color (0.210, 0.710)
341
NTSC_Blue = xyz_color (0.140, 0.080)
342
# use D65 as white point for NTSC
343
344
# Typical short persistence phosphors from Foley/Van Dam p. 583
345
FoleyShort_Red = xyz_color (0.61, 0.35)
346
FoleyShort_Green = xyz_color (0.29, 0.59)
347
FoleyShort_Blue = xyz_color (0.15, 0.063)
348
349
# Typical long persistence phosphors from Foley/Van Dam p. 583
350
FoleyLong_Red = xyz_color (0.62, 0.33)
351
FoleyLong_Green = xyz_color (0.21, 0.685)
352
FoleyLong_Blue = xyz_color (0.15, 0.063)
353
354
# Typical TV phosphors from Judd/Wyszecki p. 239
355
Judd_Red = xyz_color (0.68, 0.32) # Europium Yttrium Vanadate
356
Judd_Green = xyz_color (0.28, 0.60) # Zinc Cadmium Sulfide
357
Judd_Blue = xyz_color (0.15, 0.07) # Zinc Sulfide
358
359
# White points [all are for CIE 1931 for small field of view]
360
# These are from Judd/Wyszecki
361
WhiteA = xyz_color (0.4476, 0.4074) # approx 2856 K
362
WhiteB = xyz_color (0.3484, 0.3516) # approx 4874 K
363
WhiteC = xyz_color (0.3101, 0.3162) # approx 6774 K
364
WhiteD55 = xyz_color (0.3324, 0.3475) # approx 5500 K
365
WhiteD65 = xyz_color (0.3127, 0.3290) # approx 6500 K
366
WhiteD75 = xyz_color (0.2990, 0.3150) # approx 7500 K
367
368
# Blackbody white points [this empirically gave good results]
369
Blackbody6500K = xyz_color (0.3135, 0.3237)
370
Blackbody6600K = xyz_color (0.3121, 0.3223)
371
Blackbody6700K = xyz_color (0.3107, 0.3209)
372
Blackbody6800K = xyz_color (0.3092, 0.3194)
373
Blackbody6900K = xyz_color (0.3078, 0.3180)
374
Blackbody7000K = xyz_color (0.3064, 0.3166)
375
376
# MacBeth Color Checker white patch
377
# Using this as white point will force MacBeth chart entry to equal machine RGB
378
MacBethWhite = xyz_color (0.30995, 0.31596, 0.37409)
379
380
# Also see Judd/Wyszecki p.164 for colors of Planck Blackbodies
381
382
# Some standard xyz/rgb conversion matricies, which assume particular phosphors.
383
# These are useful for testing.
384
385
# sRGB, from http://www.color.org/sRGB.xalter
386
srgb_rgb_from_xyz_matrix = numpy.array ([
387
[ 3.2410, -1.5374, -0.4986],
388
[-0.9692, 1.8760, 0.0416],
389
[ 0.0556, -0.2040, 1.0570]
390
])
391
392
# SMPTE conversions, from Kasson p. 400
393
smpte_xyz_from_rgb_matrix = numpy.array ([
394
[0.3935, 0.3653, 0.1916],
395
[0.2124, 0.7011, 0.0865],
396
[0.0187, 0.1119, 0.9582]
397
])
398
smpte_rgb_from_xyz_matrix = numpy.array ([
399
[ 3.5064, -1.7400, -0.5441],
400
[-1.0690, 1.9777, 0.0352],
401
[ 0.0563, -0.1970, 1.0501]
402
])
403
404
#
405
# Conversions between CIE XYZ and RGB colors.
406
# Assumptions must be made about the specific device to construct the conversions.
407
#
408
409
# public - xyz colors of the monitor phosphors (and full white)
410
PhosphorRed = None
411
PhosphorGreen = None
412
PhosphorBlue = None
413
PhosphorWhite = None
414
415
rgb_from_xyz_matrix = None
416
xyz_from_rgb_matrix = None
417
418
def init (
419
phosphor_red = SRGB_Red,
420
phosphor_green = SRGB_Green,
421
phosphor_blue = SRGB_Blue,
422
white_point = SRGB_White):
423
'''Setup the conversions between CIE XYZ and linear RGB spaces.
424
Also do other initializations (gamma, conversions with Luv and Lab spaces, clipping model).
425
426
The default arguments correspond to the sRGB standard RGB space.
427
428
The conversion is defined by supplying the chromaticities of each of
429
the monitor phosphors, as well as the resulting white color when all
430
of the phosphors are at full strength.
431
432
See [Foley/Van Dam, p.587, eqn 13.27, 13.29] and [Hall, p. 239].
433
'''
434
global PhosphorRed, PhosphorGreen, PhosphorBlue, PhosphorWhite
435
PhosphorRed = phosphor_red
436
PhosphorGreen = phosphor_green
437
PhosphorBlue = phosphor_blue
438
PhosphorWhite = white_point
439
global xyz_from_rgb_matrix, rgb_from_xyz_matrix
440
phosphor_matrix = numpy.column_stack ((phosphor_red, phosphor_green, phosphor_blue))
441
# normalize white point to Y=1.0
442
normalized_white = white_point.copy()
443
xyz_normalize_Y1 (normalized_white)
444
# Determine intensities of each phosphor by solving:
445
# phosphor_matrix * intensity_vector = white_point
446
intensities = numpy.linalg.solve (phosphor_matrix, normalized_white)
447
# construct xyz_from_rgb matrix from the results
448
xyz_from_rgb_matrix = numpy.column_stack (
449
(phosphor_red * intensities [0],
450
phosphor_green * intensities [1],
451
phosphor_blue * intensities [2]))
452
# invert to get rgb_from_xyz matrix
453
rgb_from_xyz_matrix = numpy.linalg.inv (xyz_from_rgb_matrix)
454
#print 'xyz_from_rgb', str (xyz_from_rgb_matrix)
455
#print 'rgb_from_xyz', str (rgb_from_xyz_matrix)
456
457
# conversions between the (almost) perceptually uniform
458
# spaces (Luv, Lab) require the definition of a white point.
459
init_Luv_Lab_white_point (white_point)
460
461
# init gamma correction functions to default
462
init_gamma_correction()
463
464
# init color clipping method to default
465
init_clipping()
466
467
def rgb_from_xyz (xyz):
468
'''Convert an xyz color to rgb.'''
469
return numpy.dot (rgb_from_xyz_matrix, xyz)
470
471
def xyz_from_rgb (rgb):
472
'''Convert an rgb color to xyz.'''
473
return numpy.dot (xyz_from_rgb_matrix, rgb)
474
475
#
476
# Color model conversions to (nearly) perceptually uniform spaces Luv and Lab.
477
#
478
479
# Luv/Lab conversions depend on the specification of a white point.
480
481
_reference_white = None
482
_reference_u_prime = None
483
_reference_v_prime = None
484
485
def init_Luv_Lab_white_point (white_point):
486
'''Specify the white point to use for Luv/Lab conversions.'''
487
global _reference_white, _reference_u_prime, _reference_v_prime
488
_reference_white = white_point.copy()
489
xyz_normalize_Y1 (_reference_white)
490
(_reference_u_prime, _reference_v_prime) = uv_primes (_reference_white)
491
492
# Luminance function [of Y value of an XYZ color] used in Luv and Lab. See [Kasson p.399] for details.
493
# The linear range coefficient L_LUM_C has more digits than in the paper,
494
# this makes the function more continuous over the boundary.
495
496
L_LUM_A = 116.0
497
L_LUM_B = 16.0
498
L_LUM_C = 903.29629551307664
499
L_LUM_CUTOFF = 0.008856
500
501
def L_luminance (y):
502
'''L coefficient for Luv and Lab models.'''
503
if y > L_LUM_CUTOFF:
504
return L_LUM_A * math.pow (y, 1.0/3.0) - L_LUM_B
505
else:
506
# linear range
507
return L_LUM_C * y
508
509
def L_luminance_inverse (L):
510
'''Inverse of L_luminance().'''
511
if L <= (L_LUM_C * L_LUM_CUTOFF):
512
# linear range
513
y = L / L_LUM_C
514
else:
515
t = (L + L_LUM_B) / L_LUM_A
516
y = math.pow (t, 3)
517
return y
518
519
# Utility function for Luv
520
521
def uv_primes (xyz):
522
'''Luv utility.'''
523
x = xyz [0]
524
y = xyz [1]
525
z = xyz [2]
526
w_denom = x + 15.0 * y + 3.0 * z
527
if w_denom != 0.0:
528
u_prime = 4.0 * x / w_denom
529
v_prime = 9.0 * y / w_denom
530
else:
531
# this should only happen when x=y=z=0 [i.e. black] since xyz values are positive
532
u_prime = 0.0
533
v_prime = 0.0
534
return (u_prime, v_prime)
535
536
def uv_primes_inverse (u_prime, v_prime, y):
537
'''Inverse of form_uv_primes(). We will always have y known when this is called.'''
538
if v_prime != 0.0:
539
# normal
540
w_denom = (9.0 * y) / v_prime
541
x = 0.25 * u_prime * w_denom
542
y = y
543
z = (w_denom - x - 15.0 * y) / 3.0
544
else:
545
# should only happen when color is totally black
546
x = 0.0
547
y = 0.0
548
z = 0.0
549
xyz = xyz_color (x, y, z)
550
return xyz
551
552
# Utility function for Lab
553
# See [Kasson p.399] for details.
554
# The linear range coefficient has more digits than in the paper,
555
# this makes the function more continuous over the boundary.
556
557
LAB_F_A = 7.7870370302851422
558
LAB_F_B = (16.0/116.0)
559
# same cutoff as L_luminance()
560
561
def Lab_f (t):
562
'''Lab utility function.'''
563
if t > L_LUM_CUTOFF:
564
return math.pow (t, 1.0/3.0)
565
else:
566
# linear range
567
return LAB_F_A * t + LAB_F_B
568
569
def Lab_f_inverse (F):
570
'''Inverse of Lab_f().'''
571
if F <= (LAB_F_A * L_LUM_CUTOFF + LAB_F_B):
572
# linear range
573
t = (F - LAB_F_B) / LAB_F_A
574
else:
575
t = math.pow (F, 3)
576
return t
577
578
# Conversions between standard device independent color space (CIE XYZ)
579
# and the almost perceptually uniform space Luv.
580
581
def luv_from_xyz (xyz):
582
'''Convert CIE XYZ to Luv.'''
583
x = xyz [0]
584
y = xyz [1]
585
z = xyz [2]
586
y_p = y / _reference_white [1]; # actually reference_white [1] is probably always 1.0
587
(u_prime, v_prime) = uv_primes (xyz)
588
L = L_luminance (y_p)
589
u = 13.0 * L * (u_prime - _reference_u_prime)
590
v = 13.0 * L * (v_prime - _reference_v_prime)
591
luv = luv_color (L, u, v)
592
return luv
593
594
def xyz_from_luv (luv):
595
'''Convert Luv to CIE XYZ. Inverse of luv_from_xyz().'''
596
L = luv [0]
597
u = luv [1]
598
v = luv [2]
599
# invert L_luminance() to get y
600
y = L_luminance_inverse (L)
601
if L != 0.0:
602
# color is not totally black
603
# get u_prime, v_prime
604
L13 = 13.0 * L
605
u_prime = _reference_u_prime + (u / L13)
606
v_prime = _reference_v_prime + (v / L13)
607
# get xyz color
608
xyz = uv_primes_inverse (u_prime, v_prime, y)
609
else:
610
# color is black
611
xyz = xyz_color (0.0, 0.0, 0.0)
612
return xyz
613
614
# Conversions between standard device independent color space (CIE XYZ)
615
# and the almost perceptually uniform space Lab.
616
617
def lab_from_xyz (xyz):
618
'''Convert color from CIE XYZ to Lab.'''
619
x = xyz [0]
620
y = xyz [1]
621
z = xyz [2]
622
623
x_p = x / _reference_white [0]
624
y_p = y / _reference_white [1]
625
z_p = z / _reference_white [2]
626
627
f_x = Lab_f (x_p)
628
f_y = Lab_f (y_p)
629
f_z = Lab_f (z_p)
630
631
L = L_luminance (y_p)
632
a = 500.0 * (f_x - f_y)
633
b = 200.0 * (f_y - f_z)
634
Lab = lab_color (L, a, b)
635
return Lab
636
637
def xyz_from_lab (Lab):
638
'''Convert color from Lab to CIE XYZ. Inverse of lab_from_xyz().'''
639
L = Lab [0]
640
a = Lab [1]
641
b = Lab [2]
642
# invert L_luminance() to get y_p
643
y_p = L_luminance_inverse (L)
644
# calculate f_y
645
f_y = Lab_f (y_p)
646
# solve for f_x and f_z
647
f_x = f_y + (a / 500.0)
648
f_z = f_y - (b / 200.0)
649
# invert Lab_f() to get x_p and z_p
650
x_p = Lab_f_inverse (f_x)
651
z_p = Lab_f_inverse (f_z)
652
# multiply by reference white to get xyz
653
x = x_p * _reference_white [0]
654
y = y_p * _reference_white [1]
655
z = z_p * _reference_white [2]
656
xyz = xyz_color (x, y, z)
657
return xyz
658
659
# Gamma correction
660
#
661
# Non-gamma corrected rgb values, also called non-linear rgb values,
662
# correspond to palette register entries [although here they are kept
663
# in the range 0.0 to 1.0.] The numerical values are not proportional
664
# to the amount of light energy present.
665
#
666
# Gamma corrected rgb values, also called linear rgb values,
667
# do not correspond to palette entries. The numerical values are
668
# proportional to the amount of light energy present.
669
#
670
# This effect is particularly significant with CRT displays.
671
# With LCD displays, it is less clear (at least to me), what the genuinely
672
# correct correction should be.
673
674
# Gamma correction functions
675
display_from_linear_component = None
676
linear_from_display_component = None
677
gamma_exponent = None
678
679
# sRGB standard effective gamma. This exponent is not applied explicitly.
680
STANDARD_GAMMA = 2.2
681
682
# Although NTSC specifies a gamma of 2.2 as standard, this is designed
683
# to account for the dim viewing environments typical of TV, but not
684
# computers. Well-adjusted CRT displays have a true gamma in the range
685
# 2.35 through 2.55. We use the physical gamma value here, not 2.2,
686
# thus not correcting for a dim viewing environment.
687
# [Poynton, Gamma FAQ p.5, p.9, Hall, p. 121]
688
POYNTON_GAMMA = 2.45
689
690
# Simple power laws for gamma correction
691
692
def simple_gamma_invert (x):
693
'''Simple power law for gamma inverse correction.'''
694
if x <= 0.0:
695
return x
696
else:
697
return math.pow (x, 1.0 / gamma_exponent)
698
699
def simple_gamma_correct (x):
700
'''Simple power law for gamma correction.'''
701
if x <= 0.0:
702
return x
703
else:
704
return math.pow (x, gamma_exponent)
705
706
# sRGB gamma correction - http://www.color.org/sRGB.xalter
707
# The effect of the equations is to closely fit a straightforward
708
# gamma 2.2 curve with an slight offset to allow for invertability in
709
# integer math. Therefore, we are maintaining consistency with the
710
# gamma 2.2 legacy images and the video industry.
711
712
def srgb_gamma_invert (x):
713
'''sRGB standard for gamma inverse correction.'''
714
if x <= 0.00304:
715
rtn = 12.92 * x
716
else:
717
rtn = 1.055 * math.pow (x, 1.0/2.4) - 0.055
718
return rtn
719
720
def srgb_gamma_correct (x):
721
'''sRGB standard for gamma correction.'''
722
if x <= 0.03928:
723
rtn = x / 12.92
724
else:
725
rtn = math.pow ((x + 0.055) / 1.055, 2.4)
726
return rtn
727
728
def init_gamma_correction (
729
display_from_linear_function = srgb_gamma_invert,
730
linear_from_display_function = srgb_gamma_correct,
731
gamma = STANDARD_GAMMA):
732
'''Setup gamma correction.
733
The functions used for gamma correction/inversion can be specified,
734
as well as a gamma value.
735
736
The specified display_from_linear_function should convert a
737
linear (rgb) component [proportional to light intensity] into
738
displayable component [proportional to palette values].
739
740
The specified linear_from_display_function should convert a
741
displayable (rgb) component [proportional to palette values]
742
into a linear component [proportional to light intensity].
743
744
The choices for the functions:
745
display_from_linear_function -
746
srgb_gamma_invert [default] - sRGB standard
747
simple_gamma_invert - simple power function, can specify gamma.
748
linear_from_display_function -
749
srgb_gamma_correct [default] - sRGB standard
750
simple_gamma_correct - simple power function, can specify gamma.
751
752
The gamma parameter is only used for the simple() functions,
753
as sRGB implies an effective gamma of 2.2.'''
754
global display_from_linear_component, linear_from_display_component, gamma_exponent
755
display_from_linear_component = display_from_linear_function
756
linear_from_display_component = linear_from_display_function
757
gamma_exponent = gamma
758
759
#
760
# Color clipping - Physical color values may exceed the what the display can show,
761
# either because the color is too pure (indicated by negative rgb values), or
762
# because the color is too bright (indicated by rgb values > 1.0).
763
# These must be clipped to something displayable.
764
#
765
766
_clip_method = None
767
768
# possible color clipping methods
769
CLIP_CLAMP_TO_ZERO = 0
770
CLIP_ADD_WHITE = 1
771
772
def init_clipping (clip_method = CLIP_ADD_WHITE):
773
'''Specify the color clipping method.'''
774
global _clip_method
775
_clip_method = clip_method
776
777
def clip_rgb_color (rgb_color):
778
'''Convert a linear rgb color (nominal range 0.0 - 1.0), into a displayable
779
irgb color with values in the range (0 - 255), clipping as necessary.
780
781
The return value is a tuple, the first element is the clipped irgb color,
782
and the second element is a tuple indicating which (if any) clipping processes were used.
783
'''
784
clipped_chromaticity = False
785
clipped_intensity = False
786
787
rgb = rgb_color.copy()
788
789
# clip chromaticity if needed (negative rgb values)
790
if _clip_method == CLIP_CLAMP_TO_ZERO:
791
# set negative rgb values to zero
792
if rgb [0] < 0.0:
793
rgb [0] = 0.0
794
clipped_chromaticity = True
795
if rgb [1] < 0.0:
796
rgb [1] = 0.0
797
clipped_chromaticity = True
798
if rgb [2] < 0.0:
799
rgb [2] = 0.0
800
clipped_chromaticity = True
801
elif _clip_method == CLIP_ADD_WHITE:
802
# add enough white to make all rgb values nonnegative
803
# find max negative rgb (or 0.0 if all non-negative), we need that much white
804
rgb_min = min (0.0, min (rgb))
805
# get max positive component
806
rgb_max = max (rgb)
807
# get scaling factor to maintain max rgb after adding white
808
scaling = 1.0
809
if rgb_max > 0.0:
810
scaling = rgb_max / (rgb_max - rgb_min)
811
# add enough white to cancel this out, maintaining the maximum of rgb
812
if rgb_min < 0.0:
813
rgb [0] = scaling * (rgb [0] - rgb_min);
814
rgb [1] = scaling * (rgb [1] - rgb_min);
815
rgb [2] = scaling * (rgb [2] - rgb_min);
816
clipped_chromaticity = True
817
else:
818
raise ValueError, 'Invalid color clipping method %s' % (str(_clip_method))
819
820
# clip intensity if needed (rgb values > 1.0) by scaling
821
rgb_max = max (rgb)
822
# we actually don't overflow until 255.0 * intensity > 255.5, so instead of 1.0 use ...
823
intensity_cutoff = 1.0 + (0.5 / 255.0)
824
if rgb_max > intensity_cutoff:
825
# must scale intensity, so max value is intensity_cutoff
826
scaling = intensity_cutoff / rgb_max
827
rgb *= scaling
828
clipped_intensity = True
829
830
# gamma correction
831
for index in xrange (0, 3):
832
rgb [index] = display_from_linear_component (rgb [index])
833
834
# scale to 0 - 255
835
ir = round (255.0 * rgb [0])
836
ig = round (255.0 * rgb [1])
837
ib = round (255.0 * rgb [2])
838
# ensure that values are in the range 0-255
839
ir = min (255, max (0, ir))
840
ig = min (255, max (0, ig))
841
ib = min (255, max (0, ib))
842
irgb = irgb_color (ir, ig, ib)
843
return (irgb, (clipped_chromaticity, clipped_intensity))
844
845
#
846
# Conversions between linear rgb colors (range 0.0 - 1.0, values proportional to light intensity)
847
# and displayable irgb colors (range 0 - 255, values corresponding to hardware palette values).
848
#
849
# Displayable irgb colors can be represented as hex strings, like '#AB05B4'.
850
#
851
852
def irgb_string_from_irgb (irgb):
853
'''Convert a displayable irgb color (0-255) into a hex string.'''
854
# ensure that values are in the range 0-255
855
for index in xrange (0,3):
856
irgb [index] = min (255, max (0, irgb [index]))
857
# convert to hex string
858
irgb_string = '#%02X%02X%02X' % (irgb [0], irgb [1], irgb [2])
859
return irgb_string
860
861
def irgb_from_irgb_string (irgb_string):
862
'''Convert a color hex string (like '#AB13D2') into a displayable irgb color.'''
863
strlen = len (irgb_string)
864
if strlen != 7:
865
raise ValueError, 'irgb_string_from_irgb(): Expecting 7 character string like #AB13D2'
866
if irgb_string [0] != '#':
867
raise ValueError, 'irgb_string_from_irgb(): Expecting 7 character string like #AB13D2'
868
irs = irgb_string [1:3]
869
igs = irgb_string [3:5]
870
ibs = irgb_string [5:7]
871
ir = int (irs, 16)
872
ig = int (igs, 16)
873
ib = int (ibs, 16)
874
irgb = irgb_color (ir, ig, ib)
875
return irgb
876
877
def irgb_from_rgb (rgb):
878
'''Convert a (linear) rgb value (range 0.0 - 1.0) into a 0-255 displayable integer irgb value (range 0 - 255).'''
879
result = clip_rgb_color (rgb)
880
(irgb, (clipped_chrom,clipped_int)) = result
881
return irgb
882
883
def rgb_from_irgb (irgb):
884
'''Convert a displayable (gamma corrected) irgb value (range 0 - 255) into a linear rgb value (range 0.0 - 1.0).'''
885
# scale to 0.0 - 1.0
886
r0 = float (irgb [0]) / 255.0
887
g0 = float (irgb [1]) / 255.0
888
b0 = float (irgb [2]) / 255.0
889
# gamma adjustment
890
r = linear_from_display_component (r0)
891
g = linear_from_display_component (g0)
892
b = linear_from_display_component (b0)
893
rgb = rgb_color (r, g, b)
894
return rgb
895
896
def irgb_string_from_rgb (rgb):
897
'''Clip the rgb color, convert to a displayable color, and convert to a hex string.'''
898
return irgb_string_from_irgb (irgb_from_rgb (rgb))
899
900
# Multi-level conversions, for convenience
901
902
def irgb_from_xyz (xyz):
903
'''Convert an xyz color directly into a displayable irgb color.'''
904
return irgb_from_rgb (rgb_from_xyz (xyz))
905
906
def irgb_string_from_xyz (xyz):
907
'''Convert an xyz color directly into a displayable irgb color hex string.'''
908
return irgb_string_from_rgb (rgb_from_xyz (xyz))
909
910
#
911
# Initialization - Initialize to sRGB at module startup.
912
# If a different rgb model is needed, then the startup can be re-done to set the new conditions.
913
#
914
915
init()
916
# Default conversions setup on module load
917
918