-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathColorPy.html
1868 lines (1783 loc) · 89.2 KB
/
ColorPy.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>ColorPy</title>
<style type="text/css">
<!--
/* center headings */
h1, h2, h3, h4 {
text-align: center;
}
-->
</style>
</head>
<body>
<p>
<h3>ColorPy - A Python package for handling physical descriptions of color and light spectra.</h3>
</p>
<p>
<h4>Introduction and Motivation</h4>
</p>
<p>
ColorPy is a Python package that can convert physical descriptions of light -
spectra of light intensity vs. wavelength - into RGB colors that can be drawn on
a computer screen. It provides a nice set of attractive plots that you can
make of such spectra, and some other color related functions as well. All
of the plots in this documentation were created with ColorPy.</p>
<p>
ColorPy is free software. ('Free' as in speech <i>and</i> beer.) It is
released under the GNU Lesser GPL license. You are free to use ColorPy
for any application that you like, including commercial applications. If
you modify ColorPy, you should release the source code for your modifications.
You have no obligation to release any source for your products that just <i>use</i>
ColorPy, however. </p>
<p>
Several years ago, I developed some C++ code to do these kinds of physical color
calculations. Recently, I decided to port the code to Python, and publish
the library as open source under the GNU LGPL license. I decided to make
use of (and assume the existence of) NumPy and MatPlotLib for this. These
libraries make it easy to make some nice, attractive, and informative, plots of
spectra. Besides, Python is just more fun than C++.</p>
<p>
So what can ColorPy do? The short answer, is to scan this document, and
examine the various plots of spectra and their colors. You can use ColorPy
to make the same kinds of plots, for whatever spectra you have and are
interested in. ColorPy also provides conversions between several important
three-dimensional 'color spaces', specifically RGB, XYZ, Luv, and Lab.
(There can be many different RGB spaces, depending on the particular display
used to view the results. By default, ColorPy uses the sRGB space, but you
can configure it to use other RGB spaces if you like.)</p>
<p align="center">
<a href="#download">Download ColorPy</a>
</p>
<p>
<h4>License</h4>
</p>
<p align="center">
Copyright (C) 2008 Mark Kness<br>
Author - Mark Kness - <a href="mailto:[email protected]">[email protected]</a>
</p>
<p>
ColorPy is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
ColorPy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ColorPy. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p>
<h4>Prerequisites</h4>
</p>
<p>
To use ColorPy, you must have installed the following:
<a href="http://www.python.org/">Python</a>,
<a href="http://www.scipy.org/NumPy">NumPy</a>, and
<a href="http://matplotlib.sourceforge.net/">MatPlotLib</a>.
Typically, <a href="http://www.scipy.org/">SciPy</a>
is installed along with NumPy and MatPlotLib.
ColorPy doesn't use SciPy explicitly, although MatPlotLib may require SciPy.
(I am not sure.) ColorPy is a 'pure' Python distribution, so you do not
need any extra software to build it. I have tested ColorPy both on Windows
XP and Ubuntu Linux, and it should run on any system where you can install the
prerequisites. If, for some reason, you can only install NumPy but not
MatPlotLib, you should still be able to do many of the calculations, but will
not be able to make any of the nice plots.</p>
<p>
<h4>Types and Units</h4>
</p>
<p>
ColorPy generally uses wavelengths measured in nanometers (nm), 10<sup>-9</sup> m.
Otherwise, typical metric units are used. For descriptions of spectra,
ColorPy uses two-dimensional NumPy arrays, with two columns and an arbitrary
number of rows. Each row of these arrays represents the light intensity
for one wavelength, with the value in the first column being the wavelength in
nm, and the value in the second column being the light intensity at that
wavelength. ColorPy can provide a blank spectrum array, via
<tt>colorpy.ciexyx.empty_spectrum()</tt>, which will have rows for each wavelength from 360 nm to
830 nm, at 1 nm increments. (Wavelengths outside this range are generally
ignored, as the eye cannot see them.) However, you can create your own spectrum
arrays with any set of wavelengths you like.</p>
<p>
Color values are represented as three-component NumPy vectors.
(One-dimensional arrays). Typically, these are vectors of floats, with the
exception of displayable irgb colors, which are arrays of integers (in the range
0 - 255).</p>
<p>
<h4>Fundamentals - Mapping spectra to three-dimensional color values</h4>
</p>
<p>
We are interested in working with physical descriptions of light spectra, that
is, functions of intensity vs. wavelength. However, color is perceived as
a three-dimensional quantity, as there are three sets of color receptors in the
eye, which respond approximately to red, green and blue light. So how do
we reduce a function of intensity vs. wavelength to a three-dimensional value?</p>
<p>
This fundamental step is done by integrating the intensity function with a set
of three matching functions. The standard matching functions were defined
by the <i>Commission Internationale de l'Eclairage
(CIE)</i>, based on experiments with viewers matching the color of single wavelength
lights. The matching functions generally used in computer graphics are
those developed in 1931, which used a 2 degree field of view. (There is
also a set of matching functions developed in 1964, covering a field of view of
10 degrees, but the larger field of view does not correspond to typical
conditions in viewing computer graphics.) So the mapping is done as
follows:</p>
<p align="center">
<tt>
X = ∫ I (λ) * CIE-X (λ) * dλ<br/>
Y = ∫ I (λ) * CIE-Y (λ) * dλ<br/>
Z = ∫ I (λ) * CIE-Z (λ) * dλ</tt></p>
<p>
where I (λ) is the spectrum of light intensity vs. wavelength, and CIE-X (λ),
CIE-Y (λ) and CIE-Z (λ) are the matching functions. The CIE matching
functions are defined over the interval of 360 nm to 830 nm, and are zero for
all wavelengths outside this interval, so these are the bounds for the
integrals.</p>
<p>
So what do these matching functions look like? Let's take a look at a plot
(made with ColorPy, of course.)</p>
<p align="center">
<img border="0" src="CIEXYZ_Matching.png"><br>
Figure 1 - The 1931 CIE XYZ matching functions.
</p>
<p>
This plot shows the three matching functions vs. wavelength. The colors
underneath the curve, at each wavelength, are the (approximate) colors that the
human eye will perceive for a pure spectral line at that wavelength, of constant
intensity. The apparent brightness of the color at each wavelength
indicates how strongly the eye perceives that wavelength - the intensity for
each wavelength is the same. (The next section will explain how we get the
RGB values for the colors.)</p>
<p>
Each of the three plots was generated via <tt>colorpy.plots.spectrum_subplot (spectrum)</tt>,
where <tt>spectrum</tt> is the value of the matching function vs. wavelength.</p>
<p>
All three of the matching functions are zero or positive everywhere. Since
the light intensity at any wavelength is never negative, this means that the
resulting XYZ color values are never negative. Also, the Y matching
function corresponds exactly to the luminous efficiency of the eye - the eye's
response to light of constant luminance. (These facts are some of the
reasons that make this particular set of matching functions so useful.)</p>
<p>
So now we can map a spectrum of intensity vs. wavelength into a
three-dimensional value. Before we consider how to convert this into an
RGB color value that we can draw, we will first discuss some typical scaling
operations on XYZ colors.</p>
<p>
Often, it is useful to consider the 'chromaticity' of a color, that is, the hue
and saturation, independent of the intensity. This is typically done by
scaling the XYZ values so that their sum is 1.0. The resulting scaled
values are conventionally written as lower case letters x,y,z. With this
scaling, x+y+z = 1.0. The chromaticity can be
specified by the resulting x and y values, and the z component can be reconstructed as z = 1.0 - x - y.
It is also common to specify colors with their chromaticity (x and y), as well
as the total brightness (Y). Occasionally, one also wants to scale an XYZ
color so that the resulting Y value is 1.0.</p>
<p>
ColorPy represents XYZ colors (and other types of colors) as three-component
vectors. There are some 'constructor' like functions to create such
arrays, and perform these kinds of scaling:</p>
<p>
<tt>
colorpy.colormodels.xyz_color (x, y, z = None)<br>
colorpy.colormodels.xyz_normalize (xyz)<br>
colorpy.colormodels.xyz_color_from_xyY (x, y, Y)<br>
colorpy.colormodels.xyz_normalize_Y1 (xyz)
</tt></p>
<p>
Notice that color types are generally specified in ColorPy with lower case
letters, as this is more readable. (I.e., <tt>xyz_color</tt> instead of <tt>XYZ_color</tt>.)
The user must keep track of the particular normalization that applies in each
situation.</p>
<p>
<h4>Fundamentals - Converting XYZ colors to RGB colors</h4>
</p>
<p>
So how do we convert one of these XYZ colors to an RGB color that I can draw on
my computer?</p>
<p>
The short answer, is to call <tt>colorpy.colormodels.irgb_from_xyz (xyz)</tt>, where <tt>xyz</tt> is the
XYZ color vector. This will return a three element integer vector, with
each component in the range 0 - 255. There is also a function
<tt>colorpy.colormodels.irgb_string_from_xyz (xyz)</tt> that will return a hex string, such as
'#FF0000' for red.</p>
<p>
There are several subtleties and approximations in the behavior of these
functions, which are important to understand what is happening.</p>
<p>
The first step in the conversion, is to convert the XYZ color to a 'linear' RGB
color. By 'linear', we mean that the light intensity is proportional to
the numerical color values. ColorPy represents such linear RGB values as
floats, with the nominal range of 0.0 - 1.0 covering the range of intensity that the
monitor display can produce. (This implies an assumption as to the
physical brightness of the display.) The conversion from XYZ to linear RGB
is done by multiplication by a 3x3 element array. So, which array to use?
The specific values of the array depend on the physical display in question,
specifically the chromaticities of the monitor phosphors. Not all displays
have the exact same red, green and blue monitor primaries, and so any conversion
matrix cannot apply to all displays. This can be a considerable
complication, but fortunately, there is a specification of monitor
chromaticities that we can assume, part of the sRGB standard, and are likely to
be a close match to most actual displays. ColorPy uses this assumption by
default, although you can change the assumed monitor chromaticities to nearly
anything you like.</p>
<p>
So for now, let's assume the standard sRGB chromaticities, which gives us the
correct 3x3 matrix, and so we can convert our XYZ colors to linear RGB colors.</p>
<p>
We then come to the next obstacle... The RGB values that we get from this
process are often out of range - meaning that they are either greater than 1.0,
or even that they are negative! The first case is fairly straightforward,
it means that the color is too bright for the display. The second case
means that the color is too saturated and vivid for the display. The
display must compose all colors from some combination of positive amounts of the
colors of its red, green and blue phosphors. The colors of these phosphors
are not perfectly saturated, they are washed out, mixed with white, to some
extent. So not all colors can be displayed accurately. As an
example, the colors of pure spectral lines, all have some negative component.
Something must be done to put these values into the 0.0 - 1.0 range that can
actually be displayed, known as color clipping.</p>
<p>
In the first case, values larger than 1.0, ColorPy scales the color so that the
maximum component is 1.0. This reduces the brightness without changing the
chromaticity. The second case requires some change in chromaticity.
By default, ColorPy will add white to the color, just enough to make all of the
components non-negative. (You can also have ColorPy clamp the negative
values to zero. My personal, qualitative, assessment is that adding white
produces somewhat better results. There is also the potential to develop a
better clipping function.)</p>
<p>
So now we have linear RGB values in the range 0.0 - 1.0. The next subtlety
in the conversion process, is that the intensity of colors on the display is not
simply proportional to the color values given to the hardware. This
situation is known as 'gamma correction', and is particularly significant for
CRT displays. The voltage on the electron gun in the CRT display is
proportional to the RGB values given to the hardware to display, but the
intensity of the resulting light is *not* proportional to this voltage, in fact
the relationship is a power law. The particular correction for this
depends on the physical display in question. LCD displays add another
complication, as it is not clear (at least to me) what the correct conversion is
in this case. Again, we rely on the sRGB standard to decide what to do.
That standard assumes a physical 'gamma' exponent of about 2.2, and ColorPy
applies this correction by default. You can change this to a different
exponent if you like.</p>
<p>
The final step after gamma correction, is to convert the RGB components from the
range 0.0 - 1.0 to 0 - 255, which is the typical range needed to pass to the
hardware. This is done with simple scaling and rounding. The final
result of all of these conversions, RGB color values in the range 0 - 255, is
referred to as an <tt>irgb_color</tt>. This is the color type that can be passed to
drawing functions.</p>
<p>
Summarizing of these conversions, with the functions that ColorPy uses
internally:</p>
<p>
<tt>colorpy.colormodels.rgb_from_xyz (xyz)</tt> - Converts an XYZ color to a linear RGB color,
with components in the nominal range 0.0 - 1.0, but possibly out of range
(greater than 1.0, or negative). The resulting linear RGB color cannot be
directly passed to drawing functions.</p>
<p>
<tt>colorpy.colormodels.irgb_from_rgb (rgb)</tt> - Converts a linear RGB color in the nominal
range 0.0 - 1.0 to a displayable irgb color, definitely in the range 0 - 255.
Color clipping may be applied (intensity as well as chromaticity), and gamma
correction is accounted for. This result can be passed to drawing
functions.</p>
<hr>
<p>
With all of this, let's plot some real colors. First, consider the pure
spectral lines - that is, spectra that are all black (zero
intensity), except at a single wavelength. We consider all the wavelengths
from 360 nm to 830 nm, which covers the range of human vision (and the range of
the CIE XYZ matching functions.)</p>
<p>
The two-part plot below shows the result. The top section, shows the best
colors that ColorPy can draw for each wavelength. The amount of light
intensity for each wavelength is the same. But since the human eye has
different sensitivity to different wavelengths, the apparent brightness looks different
for different colors. For example, the color for 750 nm is quite dark,
while the color for 550 nm is quite bright. They represent lines with the
same physical luminance, however. The bottom section shows the
linear RGB values corresponding to each wavelength. You can see that there
are negative RGB values on this plot. In fact, there is a negative
component at every wavelength - none of the pure spectral lines can be displayed
with full saturation. (The overall intensity scale is arbitrary, and has
been chosen so that the largest RGB component for any wavelength is 1.0.)</p>
<p align="center">
<img border="0" src="VisibleSpectrum.png"><br>
Figure 2 - RGB values for the pure spectral lines.</p>
<p>
This specific plot was made with <tt>colorpy.plots.visible_spectrum_plot ()</tt>, and the real
work was done with <tt>colorpy.plots.color_vs_param_plot (param_list, rgb_colors, title,
filename, tight=False, plotfunc=pylab.plot, xlabel='param', ylabel='RGB Color')</tt>.
This function accepts two lists, one of an arbitrary parameter (wavelength in
this case), and one of linear RGB colors. (The two lists must be of the
same size.) You also must supply a title and filename for the plot.
Optional arguments include a request that the x-axis be 'tightened' to only
include the range of the parameters, a different plotting function from the
default, and different labels for the axes. This is a very handy function,
useful for many other plots besides this one.</p>
<p>
You can see that there are negative RGB values for these colors, and those
actually drawn have been clipped to something displayable.</p>
<p>
Another way to understand the limited color gamut (range of displayable colors) of physical displays, is to consider
the 'shark fin' CIE chromaticity diagram. On this plot, we draw the chromaticities of the pure
spectral lines. These trace out a fin shaped region. The low
wavelength colors start at the lower left corner of the fin, and as the
wavelength increases, moves up on the plot towards green, and then down and
to the right towards yellow and red. The longest wavelength corresponds to
the red corner at the far right. The straight line connecting the long
wavelength red to the short wavelength blue is not composed of pure spectral
lines, rather these 'purples' are a linear combination of the extreme red and
blue colors. The outer boundary of this diagram represents the spectrally
pure colors. Just inside this boundary, we draw the best color match for
each wavelength.</p>
<p>
The triangle inside the fin represents the range (gamut) of colors that the
physical display can show. The vertices labeled Red, Green and Blue
represent the chromaticities of the monitor primaries, and the point labeled
White represents the white point with all primaries at full strength.
(This plot assumes the standard sRGB primaries and white point.) The
points inside the inner triangle are the only colors that the display can render
accurately. (The figure would be clearer if the colors inside the triangle
were shown, as these are the truly displayable colors. So this figure
could use a little work. It would also be nice to label the outer boundary
of the fin with the corresponding wavelength.) The points outside the
inner triangle are colors that must be approximated. (Points outside the
outer 'fin' do not correspond to any color at all.)</p>
<p>
You can see that the standard monitor is much more limited in displaying greens
than blues and reds. If someone is able to invent a much purer green
colored phosphor, with low persistence so it is suitable for animations and
hence real displays, then the world of computer graphics will get significantly
more richly colored! Also notice that there is no possible set of of three
monitor phosphor chromaticities that can cover the entire visible gamut.
For any three points inside the 'fin', the enclosed triangle must necessarily
exclude some of the pure spectral colors, even if the monitor phosphors were
perfectly spectrally pure.</p>
<p align="center">
<img border="0" src="ChromaticityDiagram.png">
<br>
Figure 3 - CIE chromaticity diagram of the visible gamut.<br>
Colors inside
the inner triangle can be accurately drawn on the display, points outside (but
inside the fin) must be approximated.</p>
<p>
This figure is drawn with <tt>colorpy.plots.shark_fin_plot()</tt>. This is kind of a
specialized figure, probably not that useful for other things.
Now, let's consider some more examples.</p>
<hr>
<p>
<h4>MacBeth ColorChecker Chart</h4>
</p>
<p align="center">
<img border="0" src="MacBeth.png"><br>
Figure 4 - MacBeth ColorChecker Chart.</p>
<p>
The simplest way that ColorPy can be used to display colors, is to display a set
of known XYZ (or RGB) colors. As an example, we use the MacBeth ColorChecker Chart.
This is a set of standard reference colors that is used in photography and
video. (You can buy the physical chart from photographic suppliers.
It is not particularly cheap.) It is used to verify that colors are being
reproduced accurately on film. [Hall, p. 119] provides a set of xy colors for
the patches on this chart (which must assume some particular lighting
environment), and ColorPy can convert these into displayable colors. This
serves as a test that the xyz to rgb conversions are operating correctly, which
is analogous to the sort of thing the real chart is used for.</p>
<p>
This 'patch' plot is made with <tt>colorpy.plots.xyz_patch_plot (xyz_colors, color_names,
title, filename, patch_gap=0.05, num_across=6)</tt>, where <tt>xyz_colors</tt> and <tt>color_names</tt>
are two lists, the first with the XYZ color values to draw, and the second with
names to draw on the plot. The two lists must be of the same size, but you
can pass <tt>None</tt> for the second list to skip the labels. You also must supply
a title and filename, and there are optional arguments to fine-tune the size and
arrangement of the patches. There is also a similar function
<tt>colorpy.plots.rgb_patch_plot()</tt> when you have known RGB values that you want to draw.
</p>
<hr>
<p>
<h4>Blackbody Radiation</h4>
</p>
<p>
For a more interesting example, one that involves physical light spectra, consider the colors of thermal
blackbodies. A 'blackbody' is an object that is in thermal equilibrium
with its environment, at some temperature T. Such an object will radiate
light energy, with a particular spectrum of intensity vs. wavelength. This
spectrum depends only on the temperature of the blackbody, and is completely
independent of the composition of the blackbody. The theory of blackbodies
was important in the development of quantum mechanics, the first application of
quantum mechanics was by Max Planck in deriving the blackbody spectrum. Many real light
sources are approximately blackbodies.</p>
<p>
Blackbody theory shows that the 'monochromatic specific intensity'
B<sub>λ</sub>(T), the power at wavelength λ radiated per unit wavelength
per unit solid angle, is [Shu p. 78]:</p>
<p align="center">
B<sub>λ</sub>(T) = (2hc<sup>2</sup>)/(λ<sup>5</sup>) * (1 / (exp(hc/λkT) - 1))</p>
<p>
where h = Planck's constant, c = speed of light, k
= Boltzman's constant, λ = wavelength, and T = temperature.
</p>
<p>
Using this relation, we can construct a spectrum in ColorPy, and then determine
the rgb color of the blackbody radiator. The module <tt>blackbody</tt> provides the
appropriate functions. First, the function <tt>blackbody.blackbody_specific_intensity (wl_nm, T_K)</tt>
calculates the B<sub>λ</sub>(T) above, for any wavelength and temperature. This is
then converted into a spectrum of intensity vs. wavelength. The function
<tt>ciexyz.empty_spectrum()</tt> is called to get a NumPy array to hold the
spectrum. This array has one row for each wavelength to be used,
from 360 to 830 nm, with an increment of 1 nm. The first column is already
filled in with the wavelength (in nm), the second column is to be filled with
the intensity, and is initially zero. Since B<sub>λ</sub>(T) represents the power per unit wavelength, this
must be multiplied by the width of the interval, which is 1 nm. This
resulting spectrum is then converted into an xyz color with
<tt>ciexyz.xyz_from_spectrum()</tt>. This can then be converted to a
displayable irgb color and
drawn. The whole process is performed by <tt>colorpy.blackbody.blackbody_spectrum()</tt>,
which is listed below.</p>
<p>
<tt>def blackbody_spectrum (T_K):<br>
'''Get the spectrum of a blackbody, as a numpy array.'''<br>
spectrum = ciexyz.empty_spectrum()<br>
(num_rows, num_cols) = spectrum.shape<br>
for i in xrange (0, num_rows):<br>
specific_intensity = blackbody_specific_intensity (spectrum [i][0], T_K)<br>
# scale by size of wavelength interval<br>
spectrum [i][1] = specific_intensity * ciexyz.delta_wl_nm * 1.0e-9<br>
return spectrum</tt><br>
</p>
<p>
So let's look at some results of these calculations, which also introduce a new
type of ColorPy plot, the spectrum plot. First, consider a
blackbody with a temperature of 5778 K. This is the surface temperature of
the Sun [<a href="http://en.wikipedia.org/wiki/Sun">Wikipedia</a>], and this spectrum approximates that of the sun. Figure 5 below
shows both the overall color of the resulting spectrum, with a graph of the
spectrum itself below the color patch. The overall color is nearly white.
The spectrum shows that the peak intensity is in the green region, around 500 nm
or so, but with significant contributions from both lower and higher
wavelengths. The colors in the spectrum plot are indicate the extent to
which the eye is sensitive to the particular wavelength. Each color band
has the same amount of luminance, so the apparent brightness of the color indicates the
extent to which the eye is sensitive to that wavelength. The eye has a
very low sensitivity to wavelengths below 400 nm or so, and to wavelengths above
700 nm or so. Thus, the resulting color in the spectrum plot is nearly
black. With a wavelength of 550 nm, on the other hand, the eye is quite
sensitive to this wavelength, and the resulting color is therefore bright
(green). This way of drawing the spectrum is intended to help show the
contributions of each wavelength in the spectrum to the overall color. For
example, in this case, there is a considerable amount of power at wavelengths
from 700 nm to 830 nm. However, the eye has a low sensitivity to these,
and so these wavelengths do not contribute much to the overall color.</p>
<p>
These kinds of plots are made with <tt>colorpy.plots.spectrum_plot (spectrum, title,
filename, xlabel, ylabel)</tt>, where <tt>spectrum</tt> is the numpy array with the spectrum
data, and the other parameters are the title, filename, and axis labels.</p>
<p align="center">
<img border="0" src="BlackbodySpectrum-5778K.png"><br>
Figure 5 - Color of a 5778 K blackbody. This approximates the spectrum of
the Sun.</p>
<p>
Since the temperature is just a parameter to the function calls, we can do the
same thing for any other temperature that we like. The nearby star Proxima
Centauri is much cooler than the sun, it has a surface temperature of about 3000
K.
[<a href="http://en.wikipedia.org/wiki/Proxima_Centauri">Wikipedia</a>]. The spectrum of a 3000 K blackbody is shown below,
with the same kind of plot.
The overall color in this case is orange, and the spectrum is concentrated at
longer wavelengths.</p>
<p align="center">
<img border="0" src="BlackbodySpectrum-3000K.png"><br>
Figure 6 - Spectrum of 3000 K blackbody. This approximates the spectrum of Proxima Centauri.
</p>
<p>
We can also evaluate this at higher temperatures. The star Rigel, in the
constellation Orion, has a
surface temperature of 11000 K [<a href="http://en.wikipedia.org/wiki/Rigel">Wikipedia</a>], and the resulting blackbody spectrum is shown
below. The overall color is now blue-white, and the intensity is
concentrated at low wavelengths.</p>
<p align="center">
<img border="0" src="BlackbodySpectrum-11000K.png"><br>
Figure 7 - Spectrum of 11000 K blackbody. This approximates the spectrum
of the star Rigel.</p>
<p>
But why limit ourselves to just a handful of temperatures? We can
calculate the blackbody spectrum for very many temperatures, and ColorPy allows
us to plot the resulting color vs. temperature, while also showing a plot of the
rgb color values. In the figure below, we have calculated the blackbody
color for temperatures ranging from 1200 K to 16000 K. The top subplot
shows the resulting color, as a function of temperature, while the lower plot
shows the linear RGB values.
For low temperatures, the blackbody is red to orange, and as the temperature
increases, the color becomes white, and then blue.</p>
<p>
This style plot was generated with <tt>colorpy.plots.color_vs_param_plot (param_list,
rgb_colors, title, filename, tight, plotfunc, xlabel, ylabel)</tt>. The
arguments <tt>tight</tt>, <tt>plotfunc</tt>, <tt>xlabel</tt> and <tt>ylabel</tt> are optional, in this case we set
<tt>tight=True</tt> and <tt>plotfunc=pylab.semilogy</tt> to obtain the semilog plot, which is
needed for the very large range of color values covered.<br>
</p>
<p align="center">
<img border="0" src="Blackbody-Colors.png"><br>
Figure 8 - Color of blackbody vs. temperature.</p>
<p>
Let's also consider similar plots over different temperature ranges.
First, consider the range 950 K to 1200 K, shown below. The colors are all
nearly red, and are brighter for higher temperatures.</p>
<p>
This provides another example to discuss the color intensity scaling that ColorPy
uses. ColorPy attempts to calculate a color value that, when displayed on a
computer monitor, will match the physical brightness of the spectrum.
In this plot, the red value reaches 1.0 at about 1150 K. This means
(approximately) that an 1150 K blackbody is as bright as the monitor at full
intensity. Similarly, a 950 K blackbody is about 1.5% as bright as the
monitor. This, by the way, suggests an experimental test of whether the
display brightness assumed by ColorPy is correct - a 1150 K blackbody is
predicted to be as bright as the display monitor at full red.</p>
<p>
Also notice that there is no blue line on this plot. For temperatures as
low as 1200 K (and all lower), the correct blue value is negative. (This
means that the monitor is not capable of displaying the color at full saturation
- it is necessarily washed out.) Since negative values cannot be plotted
on a log axis, there is no blue on this plot. The green value also becomes
negative near the left edge of the plot.</p>
<p align="center">
<img border="0" src="Blackbody-CoolColors.png"><br>
Figure 9 - Colors of relatively cool blackbodies.</p>
<p>
Now consider a high temperature range, 10000 K to 40000 K, shown in the plot
below. In this situation, the colors are far in excess of that
displayable on the monitor. A 40000 K blackbody is far far brighter than
any computer monitor! In fact, the intensity is on the order of 10<sup>9</sup> times
that of what the monitor can display. When displaying very bright colors
such as this, ColorPy will scale them to the brightest color, of the same hue
and saturation, that can be displayed. In this case, where all the colors
are scaled like this, the resulting RGB values all have similar brightness.
So, all the colors on this chart are of similar brightness. However, there
is actually a large range in physical brightness - a 40000 K blackbody is much
brighter than a 10000 K blackbody. Note how this contrasts with the
situation on the cool blackbody plot, where the intensity of the displayed
colors varied with temperature. There is a physical variation in intensity
in both cases, but ColorPy can only show this when the colors are in the range
displayable by the monitor.</p>
<p align="center">
<img border="0" src="Blackbody-HotColors.png"><br>
Figure 10 - Colors of relatively hot blackbodies.
</p>
<hr>
<p>
<h4>Example 2 - Rayleigh Scattering</h4>
</p>
<p>
For a second example, consider Rayleigh scattering. This concerns the
scattering of light by small particles (much smaller than the wavelength of the
light.) In this situation, the amount of scattering is inversely
proportional to the fourth power of the wavelength. To compute the
spectrum resulting from Rayleigh scattering, we need a description of the
original light source, the 'Illuminant', as the power emitted as a function of
wavelength. Then, the spectrum resulting from Rayleigh scattering is [van de Hulst,
p. 65]:</p>
<p align="center">
Scattered Intensity (λ) = Illuminant (λ) * a * (1 / λ<sup>4</sup>)
</p>
<p>
where a is a proportionality constant. (In the plots below, we have
arbitrarily taken the constant a so that the value of the Rayleigh scattering
term at 550 nm is 1.0.)</p>
<p>
Often, the intensity of the illuminant is arbitrary. The illuminants
provided by ColorPy are scaled so that they have Y = 1.0, which means that they
are about as bright as the monitor at full white. One obvious choice for
an illuminant is a blackbody. ColorPy will provide an illuminant for a
blackbody of any temperature you like, with <tt>colorpy.illuminants.get_blackbody_illuminant (T_K)</tt>.
(Remember that these illuminants are scaled to have a Y = 1.0, rather than the
(typically) much larger brightness of a true blackbody.)</p>
<p>
A familiar illuminant is the one for a 5778 K blackbody, which approximates the
illumination of the Sun. The plot below shows the effect of Rayleigh
scattering of this illuminant. The overall color is sky blue, with the
spectrum concentrated at low wavelengths. This result is as expected, as
Rayleigh scattering is the basic physical
reason that the sky is blue.
</p>
<p align="center">
<img border="0" src="Rayleigh-Spectrum-5778K.png"><br>
Figure 11 - Rayleigh scattering of a 5778 K blackbody. Why the sky is blue.</p>
<p>
But why limit ourselves to the color of our Sun? With ColorPy, we can do
the same calculations for a blackbody of any temperature we like. So we
repeat the process, first for a temperature of 3000 K (Proxima Centauri), and
then for a temperature of 11000 K (Rigel). The plots below show the
results. If we lived on a planet around Proxima Centauri, the sky would be
nearly white. On the other hand, if we lived on a planet near Rigel, the
sky would also be blue, but a deeper shade of blue than we have on Earth.</p>
<p align="center">
<img border="0" src="Rayleigh-Spectrum-3000K.png"><br>
Figure 12 - Rayleigh scattering of a 3000 K blackbody. The color of the sky
from near Proxima Centauri.</p>
<p align="center">
<img border="0" src="Rayleigh-Spectrum-11000K.png"><br>
Figure 13 - Rayleigh scattering of a 11000 K blackbody. The color of the
sky from near Rigel.</p>
<p>
We can also make a plot for many temperatures, and get a plot of the color of
the sky vs. blackbody illuminant temperature, similar to the plot we made of the
color of the blackbody itself vs. temperature. The range of sky colors is
about the same as the range of blackbody colors, but the sky colors are bluer
than the blackbody colors, for any given temperature.</p>
<p align="center">
<img border="0" src="Rayleigh-SkyColors.png"><br>
Figure 14 - Color of the sky for various temperature blackbody illuminants.
</p>
<hr>
<p>
<h4>Illuminants</h4>
</p>
<p>ColorPy provides several different 'illuminant' functions that can be used as
light sources. In addition to the blackbody illuminants, ColorPy provides
the CIE standard Illuminant D65. Illuminant D65 is intended to provide a
good approximation for normal daylight. Illuminant D65 is recommended as
the general-purpose, default, illumination for daytime conditions (on Earth only
however!)</p>
<p>Illuminant D65 is given by a table of intensity vs. wavelength, rather than
a mathematical formula. ColorPy provides this illuminant, normalized so
that Y = 1.0, as usual. The spectrum of D65 is shown below. Note
that the overall color is white. In fact, D65 is used as the white point
for determining the xyz to rgb conversion matrix, so D65 is in fact the very
definition of white!</p>
<p align="center">
<img border="0" src="Illuminant-D65.png"><br>
Figure 15 - Spectrum of CIE Illuminant D65.</p>
<hr>
<p>
<h4>Example 3 - Thin Film Interference</h4>
</p>
<p>
In this example, we will calculate the colors produced by interference films,
such as a soap bubble, or an oil slick on water. This time, we will use
D65 as the illuminant.</p>
<p>
The physical situation can be described by illumination from above, passing
through a dielectric medium with some index of refraction n1. As the wave
propagates, it meets an interface where the index of refraction changes to a new
value n2. At the interface, part of the original wave is reflected, and
part continues into the new medium. The material n2 is considered to be in
a thin layer. After passing through this thin layer, the wave meets a
second interface, where the index of refraction changes from n2 to n3.
Again, part of the incident wave is reflected from the interface, and part
continues to propagate.</p>
<p>
We will assume that the regions where the index is n1 and n3 are infinite in
extent, while the region where the index is n2 is limited to a thin layer, with
thickness t.</p>
<p>
Some typical indices of refraction of real materials are:<br>
n = 1.000 - Vacuum<br>
n = 1.003 - Air<br>
n = 1.33 - Water<br>
n = 1.44 - Oil<br>
n = 1.50 - Glass</p>
<p>
The total reflected wave from the system is a combination of the wave reflected
from the first interface (n1 to n2), and the wave reflected from the second
interface (n2 to n3).</p>
<p>
There may be multiple reflections - e.g. the wave reflected from
the second interface will not fully pass through the (now reversed) interface
from n2 to n1, rather only part will pass, while some will reflect again and
head back to the interface from n2 to n3. The calculations in ColorPy
consider all numbers of multiple reflections, not just a single reflection.</p>
<p>
What makes the interesting colors, is that the two waves travel through a
different path length, and this results in them being out of phase. The
exact change in phase depends on both the wavelength of the light, the thickness
of the layer, and the index of refraction of the layer. Depending on the
specifics, the two waves may constructively interfere, resulting in a large
amplitude of the reflected wave, or the waves may destructively interfere,
resulting in a small (or zero) amplitude of the reflected wave, or something in
between.</p>
<p>
Whether the interference is constructive or destructive depends on the
wavelength, and for thin films, part of the spectrum will be reduced from
destructive interference, and part enhanced from constructive interference,
resulting in a significant color shift.</p>
<p>
First, consider a soap bubble. In this situation, material 1 is air,
material 2 is a solution of soap in water, while material 3 is air again.
(The inside of the bubble.) So, n1 = 1.003, n2 = 1.33, and n3 = 1.003.
Calculating the color of the total reflection, with an illuminant of D65, as a
function of the thickness of the layer, results in the plot below. Note
that the RGB components oscillate as the thickness is varied. </p>
<p>
The phase relationship between the red, green and blue components affects the
resulting color.</p>
<p>
For the most
part, these stay within the displayable range of 0.0 to 1.0, but there are a few
places where the red component becomes negative, in the most vivid green
regions. These vivid green colors cannot be properly displayed on the
monitor, the true color is more saturated than what is shown. Most of the
other colors can be displayed properly.</p>
<p align="center">
<img border="0" src="ThinFilm-SoapBubble.png"><br>
Figure 16 - Color of reflections from a soap bubble. The illuminant is D65.</p>
<p>
For a particular thickness of the film, we can plot the reflectance spectrum.
The plots below show the spectrum for some of the particularly vivid colors, for
thicknesses (not wavelengths!) of 400 nm and 500 nm. For these plots, we
used an illuminant that is constant over wavelength, rather than D65. The
only reason is to avoid the jaggedness of the D65 curve from making the plot
more confusing.</p>
<p align="center">
<img border="0" src="ThinFilm-Spectrum-400nm.png"><br>
Figure 17 - Spectrum of soap bubble reflection for a thickness of 400 nm.
The illuminant is constant over wavelength.</p>
<p align="center">
<img border="0" src="ThinFilm-Spectrum-500nm.png"><br>
Figure 18 - Spectrum of soap bubble reflection for a thickness of 500 nm.
The illuminant is constant over wavelength.</p>
<p>
We can do the same thing for an oil slick floating on water. In this case,
medium 1 is air (n = 1.003), medium 2 is oil (n = 1.44), and medium 3 is water
(n = 1.33). The result is shown below. Note that the colors are not
as saturated and vivid as for the reflection from a soap bubble. Since
these colors are less saturated than the soap bubble reflections, all of them
are properly displayable on the computer.</p>
<p align="center">
<img border="0" src="ThinFilm-OilSlick.png"><br>
Figure 19 - Color of reflections from an oil slick. The illuminant is D65.</p>
<hr>
<p>
<h4>(Nearly) Perceptually Uniform Color Spaces - Luv and Lab</h4>
</p>
<p>
ColorPy also provides some color manipulation functions, that are not directly
related to physical color calculations. The most important of these are
routines to convert colors into the nearly perceptually uniform color spaces Luv
and Lab.</p>
<p>
The common color spaces rgb and xyz are not very perceptually uniform.
This means that, if you calculate the distance between two colors
C<sub>1</sub> and C<sub>2</sub>, using the usual Euclidean metric, namely:</p>
<p align="center">
Distance = √ ((Red<sub>2</sub> - Red<sub>1</sub>)<sup>2</sup> +
(Green<sub>2</sub> - Green<sub>1</sub>)<sup>2</sup> +
(Blue<sub>2</sub> - Blue<sub>1</sub>)<sup>2</sup>)
</p>
<p>
the calculated distance values do not agree well with the psychological apparent
differences in the colors. I.e., the mind may see two colors as very
different, but where they have a small distance, or alternately the mind may see
two colors as similar, but where they have a large color distance. This
causes difficulties in calculating the 'closest color'. (Since xyz and rgb
values are linearly related, the same distance issues apply to both of those
spaces.)</p>
<p>
The color spaces Luv and Lab are designed to be a much more perceptually uniform
color space than rgb or xyz. If colors are transformed into Luv or Lab
space, then mathematical distance calculations, using the Euclidean metric, on
Luv and Lab values will provide a much better assessment of how different the
colors appear.</p>
<p>
These perceptually uniform color spaces are not perfect. (The fact
that there are two of them, is a clear sign that they are imperfect.)
However, they should do a substantially better job in measuring the apparent
differences in colors.</p>
<p>
ColorPy provides routines to transform color values from xyz into both Luv and
Lab, and also routines to convert back to xyz. Coupled with the
conversions between xyz and rgb, one can convert between any of the desired
color spaces. Most descriptions of the Luv and Lab models only provide the
transformation from xyz to Luv and Lab, but ColorPy also provides the inverses.
The conversion routines are:</p>
<p>
<tt>colorpy.colormodels.luv_from_xyz (xyz)</tt><br>
<tt>colorpy.colormodels.xyz_from_luv (luv)</tt><br>
<tt>colorpy.colormodels.lab_from_xyz (xyz)</tt><br>
<tt>colorpy.colormodels.xyz_from_lab (Lab)</tt></p>
<p>
The Luv and Lab conversions depend on the definition of the white point.
By default, the white point used in specifying the rgb to xyz conversions is
used, this is CIE D65 by default. You can change this, by passing the
desired chromaticity of the white point, to:</p>
<p>
<tt>colorpy.colormodels.init_Luv_Lab_white_point (white_point)</tt></p>
<p>
One potential application of these spaces, would be an improvement in the
color clipping algorithm. When ColorPy needs to clip an undisplayable
color value (with rgb values either negative or greater than 1.0), the best
action would probably be to choose the displayable color that is perceptually
closest to the desired color. If this was done with these color spaces,
rather than the current clipping algorithm, a better selection of out-of-gamut
colors might result.</p>
<hr>
<p>
<h4><a name="download">Download ColorPy</a></h4>
</p>
<p>
<b>Binary distribution for Windows (32-bit):
<a href="dist/colorpy-0.1.0.win32.exe">ColorPy-0.1.0.win32.exe</a></b></p>
<p>
<b>Source distribution for Windows:
<a href="dist/colorpy-0.1.0.zip">ColorPy-0.1.0 zip</a></b></p>
<p>
<b>Source distribution for Linux:
<a href="dist/colorpy-0.1.0.tar.gz">ColorPy-0.1.0 tarball</a></b></p>
<p>
Installation:</p>
<p>
If you are installing from the Windows binary distribution, all you need to do
is double-click the executable, and follow the installation prompts.
Otherwise, you must first unpack the distribution, and then install.</p>
<p>
Unpacking the source distributions:</p>
<p>
Windows -<br>
Unzip the .zip distribution. Recent versions of Windows (XP or later), will
unpack the directory automatically, you can simply enter the directory in
Windows Explorer. You will probably need to copy the uncompressed files into
another directory.</p>
<p>
Linux -<br>
The distribution is a compressed tar archive, uncompress it as follows:<br>
<br>
<tt>gunzip -c colorpy-0.1.0.tar.gz | tar xf -</tt><br>
<tt>cd colorpy-0.1.0</tt></p>
<p>
Installing from the source distribution:</p>
<p>
From the directory in which the files are unpacked, run:<br>
<br>
<tt>python setup.py install</tt></p>
<p>
It is possible that you may need to supply a path to the Python executable.
You will probably need administrator privileges to do this.
This should complete the installation.</p>
<p>
After downloading and installing, I recommend that you run the test cases, and
then create the sample figures. These will provide a check that the module
is working correctly.</p>
<p>
<tt>import colorpy.test</tt><br>
<tt>colorpy.test.test()</tt></p>
<p>
This will run all the test cases.</p>
<p>
<tt>import colorpy.figures</tt><br>
<tt>colorpy.figures.figures()</tt></p>
<p>
This will generate the sample figures (typically .png files), including all
those in this documentation, as well as several others.</p>
<hr>
<p>
<h4>Module Reference</h4>
</p>
<p>
<tt>colorpy.colormodels.py</tt> - Conversions between color models<br>
<br>
Description:<br>
<br>
Defines several color models, and conversions between them.<br>
<br>
The models are:<br>
<br>
xyz - CIE XYZ color space, based on the 1931 matching functions for a 2 degree
field of view.<br>
Spectra are converted to xyz color values by integrating with the matching
functions in ciexyz.py.<br>
<br>
xyz colors are often handled as absolute values, conventionally written with
uppercase letters XYZ,<br>
or as scaled values (so that X+Y+Z = 1.0), conventionally written with lowercase
letters xyz.<br>
<br>
This is the fundamental color model around which all others are based.<br>
<br>
rgb - Colors expressed as red, green and blue values, in the nominal range 0.0 -
1.0.<br>
These are linear color values, meaning that doubling the number implies a
doubling of the light intensity.<br>
rgb color values may be out of range (greater than 1.0, or negative), and do not
account for gamma correction.<br>
They should not be drawn directly.<br>
<br>
irgb - Displayable color values expressed as red, green and blue values, in the
range 0 - 255.<br>
These have been adjusted for gamma correction, and have been clipped into the
displayable range 0 - 255.<br>
These color values can be drawn directly.<br>
<br>
Luv - A nearly perceptually uniform color space.<br>
<br>
Lab - Another nearly perceptually uniform color space.<br>
<br>
As far as I know, the Luv and Lab spaces are of similar quality.<br>
Neither is perfect, so perhaps try each, and see what works best for your
application.<br>
<br>
The models store color values as 3-element NumPy vectors.<br>
The values are stored as floats, except for irgb, which are stored as integers.<br>
<br>
Constants:<br>
<br>
<tt>SRGB_Red</tt><br>
<tt>SRGB_Green</tt><br>
<tt>SRGB_Blue</tt><br>
<tt>SRGB_White</tt> -<br>
Chromaticity values for sRGB standard display monitors.<br>
<br>
<tt>PhosphorRed</tt><br>
<tt>PhosphorGreen</tt><br>
<tt>PhosphorBlue</tt><br>
<tt>PhosphorWhite</tt> -<br>
Chromaticity values for display used in initialization.<br>
These are the sRGB values by default, but other values can be chosen.<br>
<br>
<tt>CLIP_CLAMP_TO_ZERO = 0</tt><br>
<tt>CLIP_ADD_WHITE = 1</tt><br>
Available color clipping methods. Add white is the default.<br>
<br>
Functions:<br>
<br>
'Constructor-like' functions:<br>
<br>
<tt>xyz_color (x, y, z = None)</tt> -<br>
Construct an xyz color. If z is omitted, set it so that x+y+z = 1.0.<br>
<br>
<tt>xyz_normalize (xyz)</tt> -<br>
Scale so that all values add to 1.0.<br>
This both modifies the passed argument and returns the normalized result.<br>
<br>
<tt>xyz_normalize_Y1 (xyz)</tt> -<br>
Scale so that the y component is 1.0.<br>
This both modifies the passed argument and returns the normalized result.<br>
<br>
<tt>xyz_color_from_xyY (x, y, Y)</tt> -<br>
Given the 'little' x,y chromaticity, and the intensity Y,<br>