line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
PDL::Transform::Color - Useful color system conversions for PDL |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
### Shrink an RGB image with proper linear interpolation: |
8
|
|
|
|
|
|
|
### DEcode the sRGB image values, then interpolate, then ENcode sRGB |
9
|
|
|
|
|
|
|
$im = rpic("big_colorimage.jpg"); |
10
|
|
|
|
|
|
|
$im2 = $im->invert(t_srgb())->match([500,500],{m=>'g'})->apply(t_srgb()); |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 DESCRIPTION |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
PDL::Transform::Color includes a variety of useful color conversion |
15
|
|
|
|
|
|
|
transformations. It can be used for simple hacks on machine-native |
16
|
|
|
|
|
|
|
color representations (RGB <-> HSV, etc.), for simple |
17
|
|
|
|
|
|
|
encoding/decoding of machine-native color representations such as |
18
|
|
|
|
|
|
|
sRGB, or for more sophisticated manipulation of absolute color |
19
|
|
|
|
|
|
|
standards including large-gamut or perceptual systems. |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
The color transforms in this module can be used for converting between |
22
|
|
|
|
|
|
|
proper color systems, for gamma-converting pixel values, or for |
23
|
|
|
|
|
|
|
generating pseudocolor from one or two input parameters. In addition |
24
|
|
|
|
|
|
|
to transforming color data between different representations, Several |
25
|
|
|
|
|
|
|
named "color maps" (also called "color tables") are provided. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
The module uses linearized sRGB (lsRGB) as a fundamental color basis. |
28
|
|
|
|
|
|
|
sRGB is the standard color system used by most consumer- to mid-grade |
29
|
|
|
|
|
|
|
computer equipment, so casual users can use this color representation |
30
|
|
|
|
|
|
|
without much regard for gamuts, colorimetric standards, etc. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
Most of the transform generators convert from lsRGB to various |
33
|
|
|
|
|
|
|
other systems. Notable simple ones are HSV (Hue, Saturation, Value), |
34
|
|
|
|
|
|
|
HSL (Hue, Saturation, Lightness), and CMYK (Cyan, Magenta, Yellow, |
35
|
|
|
|
|
|
|
blacK). |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
If you aren't familiar with PDL::Transform, you should read that POD |
38
|
|
|
|
|
|
|
now, as this is a subclass of PDL::Transform. Transforms represent |
39
|
|
|
|
|
|
|
and encapsulate vector transformations -- one- or two-way vector |
40
|
|
|
|
|
|
|
functions that may be applied, composed, or (if possible) inverted. |
41
|
|
|
|
|
|
|
They are created through constructor methods that often allow |
42
|
|
|
|
|
|
|
parametric adjustment at creation time. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
If you just want to "manipulate some RGB images" and not learn about |
45
|
|
|
|
|
|
|
the esoterica of color representations, you can treat all the routines |
46
|
|
|
|
|
|
|
as working "from RGB" on the interval [0,1], and use C to |
47
|
|
|
|
|
|
|
import/export color images from/to "24-bit color" that your computer |
48
|
|
|
|
|
|
|
probably expects. If you care about the esoterica, read on. |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
The output transfer function for sRGB is nonlinear -- the luminance of |
51
|
|
|
|
|
|
|
a pixel on-screen varies somewhat faster than the square of the input |
52
|
|
|
|
|
|
|
value -- which is inconvenient for blending, merging, and manipulating |
53
|
|
|
|
|
|
|
color. Many common operations work best with a linear photometric |
54
|
|
|
|
|
|
|
representation. PDL::Transform::Color works with an internal model |
55
|
|
|
|
|
|
|
that is a floating-point linear system representing pixels as |
56
|
|
|
|
|
|
|
3-vectors whose components are proportional to photometric brightness |
57
|
|
|
|
|
|
|
in the sRGB primary colors. This system is called "lsRGB" within the |
58
|
|
|
|
|
|
|
module. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Note that, in general, RGB representations are limited to a particular |
61
|
|
|
|
|
|
|
narrow gamut of physically accessible values. While the human eye has |
62
|
|
|
|
|
|
|
three dominant colorimetric input channels and hence color can be |
63
|
|
|
|
|
|
|
represented as a 3-vector, the human eye does not cleanly separate the |
64
|
|
|
|
|
|
|
spectra responsible for red, green, and blue stimuli. As a result, no |
65
|
|
|
|
|
|
|
trio of physical primary colors (which must have positive-definite |
66
|
|
|
|
|
|
|
spectra and positive-definite overall intensities) can represent every |
67
|
|
|
|
|
|
|
perceivable color -- even though they form a basis of color space. |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
But in digital representation, there is no hard limit on the values |
70
|
|
|
|
|
|
|
of the RGB vectors -- they can be negative or arbitrarily large. This |
71
|
|
|
|
|
|
|
permits representation of out-of-gamut values using negative or |
72
|
|
|
|
|
|
|
over-unity intensities. So floating-point lsRGB allows you to |
73
|
|
|
|
|
|
|
represent literally any color value that the human eye can perceive, |
74
|
|
|
|
|
|
|
and many that it can't. This is useful even though many such colors |
75
|
|
|
|
|
|
|
can't be rendered on a monitor. For example, you can change between |
76
|
|
|
|
|
|
|
several color representations and not be limited by the formal gamut |
77
|
|
|
|
|
|
|
of each representation -- only by the final export standard. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Three major output formats are supported: sRGB (standard "24-bit |
80
|
|
|
|
|
|
|
color" with the industry standard transfer function); bRGB (bytescaled |
81
|
|
|
|
|
|
|
RGB with a controllable gamma function (default 2.2, matching the |
82
|
|
|
|
|
|
|
average gamma value of most CRTs and calibrated flat monitors); or |
83
|
|
|
|
|
|
|
CMYK (direct linear inversion of the RGB values, with byte |
84
|
|
|
|
|
|
|
scaling). These are created by applying the transforms C, |
85
|
|
|
|
|
|
|
C, and C, respectively, to an lsRGB color triplet. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
The C export routine will translate represented colors in |
88
|
|
|
|
|
|
|
floating-point lsRGB to byte-encoded sRGB (or, if inverted, vice |
89
|
|
|
|
|
|
|
versa), using the correct (slightly more complicated than gamma |
90
|
|
|
|
|
|
|
functions) nonlinear scaling. In general, you can use C to |
91
|
|
|
|
|
|
|
import existing images you may have found lying around the net; |
92
|
|
|
|
|
|
|
manipulate their hue, etc.; and re-export with C. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
If you prefer to work with direct gamma functions or straight |
95
|
|
|
|
|
|
|
scaling, you can import/export from/to byte values with C |
96
|
|
|
|
|
|
|
instead. For example, to export a color in the CIE RGB system |
97
|
|
|
|
|
|
|
(different primaries than sRGB), use C. |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
There are also some pseudocolor transformations, which convert a |
100
|
|
|
|
|
|
|
single data value to normalized RGB. These transformations are |
101
|
|
|
|
|
|
|
C for photometric (typical scientific) values and C for |
102
|
|
|
|
|
|
|
perceptual (typical consumer camera) values. They are described |
103
|
|
|
|
|
|
|
below, along with a collection of named pseudocolor maps that are |
104
|
|
|
|
|
|
|
supplied with the module. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=head1 OVERVIEW OF COLOR THEORY |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Beacuse of the biophysics of the human eye, color is well represented |
109
|
|
|
|
|
|
|
as a 3-vector of red, green, and blue brightness values representing |
110
|
|
|
|
|
|
|
brightness in the long, middle, and short portions of the visible |
111
|
|
|
|
|
|
|
spectrum. However, the absorption/sensitivity bands overlap |
112
|
|
|
|
|
|
|
significantly, therefore no physical light (of any wavelength) can |
113
|
|
|
|
|
|
|
form a proper "primary color" (orthonormal basis element) of this |
114
|
|
|
|
|
|
|
space. While any vector in color space can be represented as a linear |
115
|
|
|
|
|
|
|
sum of three indepenent basis vectors ("primary colors"), there is no |
116
|
|
|
|
|
|
|
such thing as a negative intensity and therefore any tricolor |
117
|
|
|
|
|
|
|
representation of the color space is limited to a "gamut" that can be |
118
|
|
|
|
|
|
|
formed by I linear combinations of the selected primary colors. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
Some professional color representations (e.g. 5- and 7-color dye |
121
|
|
|
|
|
|
|
processes) expand this gamut to better match the overall spectral |
122
|
|
|
|
|
|
|
response of the human eye, at the cost of over-determining color |
123
|
|
|
|
|
|
|
values in what is fundamentally a 3-space. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
RGB color representations require the specification of particular |
126
|
|
|
|
|
|
|
primary colors that represent particular spectral profiles. The |
127
|
|
|
|
|
|
|
choice of primaries depends on the technical solution being used for |
128
|
|
|
|
|
|
|
I/O. The most universal "standard" representation is the CIE RGB |
129
|
|
|
|
|
|
|
standard developed in 1931 by the Commission Internationale de |
130
|
|
|
|
|
|
|
l'Eclairage (CIE; International Commission on Illumination). The 1931 |
131
|
|
|
|
|
|
|
CIE RGB system is also called simply CIERGB by many sources. It uses |
132
|
|
|
|
|
|
|
primary wavelengths of 700nm (red), 546.1 nm (green), and 435.8 nm |
133
|
|
|
|
|
|
|
(blue). |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
The most universal "computer" representation is the sRGB standard |
136
|
|
|
|
|
|
|
defined by Anderson et al. (1996), which uses on slightly different |
137
|
|
|
|
|
|
|
primary colors than does the 1931 CIE RGB standard. This is because |
138
|
|
|
|
|
|
|
sRGB is based on the colorimetric output of color television phosphors |
139
|
|
|
|
|
|
|
in CRTs, while CIE RGB was developed based on easily lab-reproducible |
140
|
|
|
|
|
|
|
spectra. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
The C transformations are all relative to the |
143
|
|
|
|
|
|
|
sRGB color basis. Negative values are permitted, allowing |
144
|
|
|
|
|
|
|
representation of all colors -- possible or impossible. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
CIE defined several other important color systems: first, an XYZ |
147
|
|
|
|
|
|
|
system based on nonphysical primaries X, Y, and Z that correspond to |
148
|
|
|
|
|
|
|
red, green, and blue, respectively. The XYZ system can represent all |
149
|
|
|
|
|
|
|
colors detectable to the human eye with positive-definite intensities |
150
|
|
|
|
|
|
|
of the "primaries": the necesary negative intensities are hidden in |
151
|
|
|
|
|
|
|
the formal spectrum of each of the primaries. The Y primary of this |
152
|
|
|
|
|
|
|
system corresponds closely to green, and is used by CIE as a proxy for |
153
|
|
|
|
|
|
|
overall luminance. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
The CIE also separated "chrominance" and "luminance" signals, in a |
156
|
|
|
|
|
|
|
separate system called "xyY", which represents color as sum-normalized |
157
|
|
|
|
|
|
|
vectors "x=X/(X+Y+Z), "y=Y/(X+Y+Z)", and "z=Z/(X+Y+Z)". By construction, |
158
|
|
|
|
|
|
|
x+y+z=1, so "x" and "y" alone describe the color range of the system, and |
159
|
|
|
|
|
|
|
"Y" stands in for overall luminance. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
A linear RGB system is specified exactly by the chrominance (CIE XYZ |
162
|
|
|
|
|
|
|
or xyY) coordinates of the three primaries, and a white point |
163
|
|
|
|
|
|
|
chrominance. The white point chrominance sets the relative scaling |
164
|
|
|
|
|
|
|
between the brightnesses of the primaries to achieve a color-free |
165
|
|
|
|
|
|
|
("white") luminance. Different systems with the same R, G, B primary |
166
|
|
|
|
|
|
|
vectors can have different gains between those colors, yielding a |
167
|
|
|
|
|
|
|
slightly different shade of color at the R=G=B line. This "white" |
168
|
|
|
|
|
|
|
reference chrominance varies across systems, with the most common |
169
|
|
|
|
|
|
|
"white" standard being CIE's D65 spectrum based on a 6500K black body |
170
|
|
|
|
|
|
|
-- but CIE, in particular, specifies a large number of white |
171
|
|
|
|
|
|
|
standards, and some systems use none of those but instead specify CIE |
172
|
|
|
|
|
|
|
XYZ values for the white point. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Similarly, real RGB systems typically use dynamic range compression |
175
|
|
|
|
|
|
|
via a nonlinear transfer function which is most typically a "gamma |
176
|
|
|
|
|
|
|
function". A built-in database tracks about 15 standard named |
177
|
|
|
|
|
|
|
systems, so you can convert color values between them. Or you can |
178
|
|
|
|
|
|
|
specify your own system with a standard hash format (see C). |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Provision exists for converting between different RGB systems with |
181
|
|
|
|
|
|
|
different primaries and different white points, by linearizing and |
182
|
|
|
|
|
|
|
then scaling. The most straightforward way to use this module to |
183
|
|
|
|
|
|
|
convert between two RGB systems (neither of which is lsRGB) is to |
184
|
|
|
|
|
|
|
inverse-transform one to lsRGB, then transform forward to the other. |
185
|
|
|
|
|
|
|
This is accomplished with the C transform. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Many other representations than RGB exist to separate chromatic |
188
|
|
|
|
|
|
|
value from brightness. In general, these can be divided into polar |
189
|
|
|
|
|
|
|
coordinates that represent hue as a single value divorced from the rgb |
190
|
|
|
|
|
|
|
basis, and those that represent it as a combination of two values like |
191
|
|
|
|
|
|
|
the 'x' and 'y' of the CIE xyY space. These are all based on the |
192
|
|
|
|
|
|
|
Munsell and Ostwald color systems, which were worked out at about the |
193
|
|
|
|
|
|
|
same time as the CIE system. Both Ostwald and Munsell worked around |
194
|
|
|
|
|
|
|
the start of the 20th century pioneered colorimetric classification. |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Ostwald worked with quasi-linear representations of chromaticity as a |
197
|
|
|
|
|
|
|
2-vector independent of brightness; these representations relate to |
198
|
|
|
|
|
|
|
CIERGB, CIEXYZ, and related systems via simple geometric projection; |
199
|
|
|
|
|
|
|
the CIE xyY space is an example. The most commonly used variant of |
200
|
|
|
|
|
|
|
xyY is CIELAB, a perceptual color space that separates color into a |
201
|
|
|
|
|
|
|
perceived lightness parameter L, and separate chromaticities 'a' and |
202
|
|
|
|
|
|
|
'b'. CIELAB is commonly used by graphic artists and related |
203
|
|
|
|
|
|
|
professions, because it is an absolute space like XYZ (so that each |
204
|
|
|
|
|
|
|
LAB value corresponds to a particular perceivable color), and because |
205
|
|
|
|
|
|
|
the Cartesian norm between vectors in LAB space is approximately |
206
|
|
|
|
|
|
|
proportional to perceived difference between the corresponding colors. |
207
|
|
|
|
|
|
|
The system is thus useful for communicating color values precisely |
208
|
|
|
|
|
|
|
across different groups or for developing perceptually-uniform display |
209
|
|
|
|
|
|
|
maps for generated data. The L, A, and B coordinates are highly |
210
|
|
|
|
|
|
|
nonlinear to approximately match the typical human visual system. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Other related systems include YUV, YPbPr, and YCbCr -- which are used |
213
|
|
|
|
|
|
|
for representing color for digital cinema and for video transmission. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
Munsell developed a color system based on separating the "hue" of a |
216
|
|
|
|
|
|
|
color into a single value separate from both its brightness and |
217
|
|
|
|
|
|
|
saturation level. This system is closely related to cylindrical polar |
218
|
|
|
|
|
|
|
coordinates in an RGB space, with the center of the cylinder on top of |
219
|
|
|
|
|
|
|
the line of values corresponding to neutral shades from "black" |
220
|
|
|
|
|
|
|
through "grey" to "white". |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
Two simple Munsell-like representations that work within the gamut of |
223
|
|
|
|
|
|
|
a particular RGB basis are HSL and HSV. Both of these systems are |
224
|
|
|
|
|
|
|
loose representations that are best defined relative to a particular |
225
|
|
|
|
|
|
|
RGB system. They are both designed specifically to represent an entire |
226
|
|
|
|
|
|
|
RGB gamut with a quasi-polar coordinate system, and are based on |
227
|
|
|
|
|
|
|
hexagonal angle -- i.e. they are not exactly polar in nature. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
HSL separates "Hue" and "Saturation" from "Lightness". Hue represents |
230
|
|
|
|
|
|
|
the spectral shade of the color as a direction from the central white |
231
|
|
|
|
|
|
|
reference line through RGB space: the R=G=B line. Saturation is a |
232
|
|
|
|
|
|
|
normalized chromaticity measuring fraction of the distance from the |
233
|
|
|
|
|
|
|
white locus to the nearest edge of the RGB gamut at a particular hue |
234
|
|
|
|
|
|
|
and lightness. Lightness is an approximately hue- independent measure |
235
|
|
|
|
|
|
|
of total intensity. Deeply objectively "saturated" colors are only |
236
|
|
|
|
|
|
|
accessible at L=0.5; the L=0.5 surface includes all the additive and |
237
|
|
|
|
|
|
|
subtractive primary colors of the RGB system. Darker colors are |
238
|
|
|
|
|
|
|
less-saturated shades, while brighter colors fade to pastels. |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
HSV is similar to HSL, but tracks only the brightest component among |
241
|
|
|
|
|
|
|
the RGB triplet as "Value" rather than the derived "Lightness". As a |
242
|
|
|
|
|
|
|
result, highly saturated HSV values have lower overall luminance than |
243
|
|
|
|
|
|
|
unsaturated HSV values with the same V, and the V=1 surface includes |
244
|
|
|
|
|
|
|
all the primary and secondary colors of the parent RGB system. This system takes |
245
|
|
|
|
|
|
|
advantage of the of the "Helmholtz-Kolhrausch effect" that |
246
|
|
|
|
|
|
|
I brightness increases with saturation, so V better |
247
|
|
|
|
|
|
|
approximates perceived brightness at a given hue and saturation, than |
248
|
|
|
|
|
|
|
does L. |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
Modern display devices generally produce physical brightnesses that |
251
|
|
|
|
|
|
|
are proportional not to their input signal, but to a nonlinear |
252
|
|
|
|
|
|
|
function of the input signal. The most common nonlinear function is a |
253
|
|
|
|
|
|
|
simple power law ("gamma function"): output is approximately |
254
|
|
|
|
|
|
|
proportional to the "gamma" power of the input. Raising a signal |
255
|
|
|
|
|
|
|
value to the power "1/gamma" is C it, and raising it |
256
|
|
|
|
|
|
|
to the power "gamma" is C it. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
The sRGB 24-bit color standard specifies a slightly more complicated |
259
|
|
|
|
|
|
|
transfer curve, that consists of a linear segment spliced onto a |
260
|
|
|
|
|
|
|
horizontally-offset power law with gamma=2.4. This reduces |
261
|
|
|
|
|
|
|
quantization noise for very dark pxels, but approximates an overall |
262
|
|
|
|
|
|
|
power law with gamma=2.2. Hence, C (which supports general |
263
|
|
|
|
|
|
|
power law transfer functions) defaults to an output gamma of 2.2, but |
264
|
|
|
|
|
|
|
C yields a more accurate export transfer in typical use. The |
265
|
|
|
|
|
|
|
gamma value of 2.2 was selected in the early days of the television |
266
|
|
|
|
|
|
|
era, to approximately match the perceptual response of the human eye, |
267
|
|
|
|
|
|
|
and for nearly 50 years cathode-ray-tube (CRT) displays were |
268
|
|
|
|
|
|
|
specifically designed for a transfer gamma of 2.2 between applied |
269
|
|
|
|
|
|
|
voltage at the electron gun input stage and luminance (luminous energy |
270
|
|
|
|
|
|
|
flux) at the display screen. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
Incidentally, some now-obsolete display systems (early MacOS systems |
273
|
|
|
|
|
|
|
and Silcon Graphics displays) operated with a gamma factor of 1.8, |
274
|
|
|
|
|
|
|
slightly less nonlinear than the standard. This derives from early |
275
|
|
|
|
|
|
|
use of checkerboard (and similar) pixelwise dithering to achieve a |
276
|
|
|
|
|
|
|
higher-bit-depth color palette than was otherwise possible, with early |
277
|
|
|
|
|
|
|
equipment. The display gamma of 2.2 interacted with direct dithering |
278
|
|
|
|
|
|
|
of digital values in the nonlinear space, to produce an effective gamma |
279
|
|
|
|
|
|
|
closer to 1.8 than 2.2. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head1 STANDARD OPTIONS |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=over 3 |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=item gamma |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
This is a gamma correction exponent used to get physical luminance |
289
|
|
|
|
|
|
|
values from the represented RGB values in the source RGB space. Most |
290
|
|
|
|
|
|
|
color manipulation is performed in linear (gamma=1) representation -- |
291
|
|
|
|
|
|
|
i.e. if you specify a gamma to a conversion transform, the normalized |
292
|
|
|
|
|
|
|
RGB values are B to linear physical values before processing |
293
|
|
|
|
|
|
|
in the forward direction, or B after processing in the |
294
|
|
|
|
|
|
|
reverse direction. |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
For example, to square the normalized floating-point lsRGB values |
297
|
|
|
|
|
|
|
before conversion to bRGB, use C2)>. The "gamma" |
298
|
|
|
|
|
|
|
option specifies that the desired brightness of the output device |
299
|
|
|
|
|
|
|
varies as the square of the pixel value in the stored data. |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
Since lsRGB is the default working space for most transforms, you |
302
|
|
|
|
|
|
|
don't normally need to specify C -- the default value of 1.0 |
303
|
|
|
|
|
|
|
is correct. |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
Contrariwise, the C export transform has a C option |
306
|
|
|
|
|
|
|
that specifies the gamma function for the output bytes. Therefore, |
307
|
|
|
|
|
|
|
C2)> square-roots the data before export (so that |
308
|
|
|
|
|
|
|
squaring them would yield numbers proportional to the desired luminance |
309
|
|
|
|
|
|
|
of a display device). |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
The C option is kept for completeness, but unless you know it's |
312
|
|
|
|
|
|
|
what you really want, you probably don't actually want it: instead, |
313
|
|
|
|
|
|
|
you should consider working in a linear space and decoding/encoding |
314
|
|
|
|
|
|
|
the gamma of your import/export color space only as you read in or write |
315
|
|
|
|
|
|
|
out values. For example, generic images found on the internet are |
316
|
|
|
|
|
|
|
typically in the sRGB system, and can be imported to lsRGB via the |
317
|
|
|
|
|
|
|
C transform or exported with C -- or other |
318
|
|
|
|
|
|
|
gamma-corrected 24-bit color systems can be handled directly with |
319
|
|
|
|
|
|
|
C and its C option. |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
=back |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head1 AUTHOR |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
Copyright 2017, Craig DeForest (deforest@boulder.swri.edu). This |
326
|
|
|
|
|
|
|
module may be modified and distributed under the same terms as PDL |
327
|
|
|
|
|
|
|
itself. The module comes with NO WARRANTY. |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=head1 FUNCTIONS |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=cut |
332
|
|
|
|
|
|
|
|
333
|
1
|
|
|
1
|
|
14126
|
use PDL::Transform; |
|
1
|
|
|
|
|
172510
|
|
|
1
|
|
|
|
|
6
|
|
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
package PDL::Transform::Color; |
336
|
|
|
|
|
|
|
|
337
|
1
|
|
|
1
|
|
159
|
use PDL::Core ':Internal'; # load "topdl" (internal routine) |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
4
|
|
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
@ISA = ( 'Exporter', 'PDL::Transform' ); |
340
|
|
|
|
|
|
|
our $VERSION = '1.003'; |
341
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
BEGIN { |
344
|
|
|
|
|
|
|
package PDL::Transform::Color; |
345
|
1
|
|
|
1
|
|
112
|
use base 'Exporter'; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
102
|
|
346
|
1
|
|
|
1
|
|
3
|
@EXPORT_OK = qw/ t_gamma t_brgb t_srgb t_shift_illuminant t_shift_rgb t_cmyk t_rgi t_cieXYZ t_xyz t_xyY t_xyy t_lab t_xyz2lab t_hsl t_hsv t_pc t_pcp/; |
347
|
1
|
|
|
|
|
2
|
@EXPORT = @EXPORT_OK; |
348
|
1
|
|
|
|
|
19
|
%EXPORT_TAGS = (Func=>[@EXPORT_OK]); |
349
|
|
|
|
|
|
|
}; |
350
|
|
|
|
|
|
|
|
351
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
14
|
|
352
|
1
|
|
|
1
|
|
6
|
use PDL; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
4
|
|
353
|
1
|
|
|
1
|
|
2041
|
use PDL::Transform; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2
|
|
354
|
1
|
|
|
1
|
|
118
|
use PDL::MatrixOps; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
3
|
|
355
|
1
|
|
|
1
|
|
79
|
use PDL::Options; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
37
|
|
356
|
1
|
|
|
1
|
|
3
|
use PDL::NiceSlice; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
8
|
|
357
|
|
|
|
|
|
|
|
358
|
1
|
|
|
1
|
|
51327
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6919
|
|
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
our $PI = $PDL::Transform::PI; |
361
|
|
|
|
|
|
|
our $DEG2RAD = $PDL::Transform::DEG2RAD; |
362
|
|
|
|
|
|
|
our $RAD2DEG = $PDL::Transform::RAD2DEG; |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# Some matrix values of use in RGB conversions... |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
# Matrix to convert CIE RGB to CIE XYZ |
368
|
|
|
|
|
|
|
our($crgb2cxyz_mat) = |
369
|
|
|
|
|
|
|
pdl( [0.49000, 0.31000, 0.20000], |
370
|
|
|
|
|
|
|
[0.17697, 0.81240, 0.01063], |
371
|
|
|
|
|
|
|
[0.00000, 0.01000, 0.99000] |
372
|
|
|
|
|
|
|
) / 0.17697; |
373
|
|
|
|
|
|
|
our($crgb2ciexyz_inv) = $crgb2cxyz_mat->inv; |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# Matrix to convert CIE XYZ to sRGB |
376
|
|
|
|
|
|
|
our($srgb2cxyz_inv) = |
377
|
|
|
|
|
|
|
pdl( [ 3.2410, -1.5374, -0.4986], |
378
|
|
|
|
|
|
|
[-0.9692, 1.8760, 0.0416], |
379
|
|
|
|
|
|
|
[ 0.0556, -0.2040, 1.0570] |
380
|
|
|
|
|
|
|
); |
381
|
|
|
|
|
|
|
our($srgb2cxyz_mat) = $srgb2cxyz_inv->inv; |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub _strval { |
385
|
0
|
|
|
0
|
|
0
|
my($me) = shift; |
386
|
0
|
|
|
|
|
0
|
$me->stringify(); |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
|
389
|
15
|
|
|
15
|
|
29
|
sub _new { new('PDL::Transform::Color',@_) } |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
sub new { |
392
|
15
|
|
|
15
|
0
|
18
|
my($class) = shift; |
393
|
15
|
|
|
|
|
13
|
my($parse) = pop; |
394
|
15
|
|
|
|
|
12
|
my($name) = pop; |
395
|
15
|
|
|
|
|
32
|
my($me) = PDL::Transform::new($class); |
396
|
15
|
|
|
|
|
174
|
$me->{name} = $name; |
397
|
15
|
|
|
|
|
24
|
$me->{u_opt} = {@_}; |
398
|
15
|
|
|
|
|
15
|
$me->{idim} = 3; |
399
|
15
|
|
|
|
|
16
|
$me->{odim} = 3; |
400
|
|
|
|
|
|
|
|
401
|
15
|
|
|
|
|
32
|
my %opt = parse($parse, $me->{u_opt}); |
402
|
15
|
|
|
|
|
2116
|
$me->{params} = \%opt; |
403
|
|
|
|
|
|
|
|
404
|
15
|
|
|
|
|
27
|
return $me; |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
## Compose with gamma correction if necessary |
409
|
|
|
|
|
|
|
sub gammify { |
410
|
10
|
|
|
10
|
0
|
10
|
my $me = shift; |
411
|
|
|
|
|
|
|
|
412
|
10
|
100
|
33
|
|
|
65
|
if( exists($me->{params}->{gamma}) && |
|
|
|
66
|
|
|
|
|
413
|
|
|
|
|
|
|
defined($me->{params}->{gamma}) && |
414
|
|
|
|
|
|
|
$me->{params}->{gamma} != 1 ) { |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# Decode gamma from source |
417
|
1
|
|
|
|
|
3
|
return ( $me x t_gamma($me->{params}->{gamma}) ); |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
} else { |
420
|
|
|
|
|
|
|
|
421
|
9
|
|
|
|
|
25
|
return $me; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
############################## |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=head2 t_gamma |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=for usage |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
$t = t_gamma($gamma); |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=for ref |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
This is an internal generator that is used to implement the standard |
437
|
|
|
|
|
|
|
C parameter for all color transforms. It is exported as well |
438
|
|
|
|
|
|
|
because many casual users just want to apply a gamma curve to existing |
439
|
|
|
|
|
|
|
data rather than doing anything more rigorous. |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
In the forward direction, C applies/decodes the gamma correction |
442
|
|
|
|
|
|
|
indicated -- e.g. if the C<$gamma> parameter at generation time is 2, |
443
|
|
|
|
|
|
|
then the forward direction squares its input, and the inverse direction |
444
|
|
|
|
|
|
|
takes the square root (encodes the gamma correction). |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
Gamma correction is implemented using a sign-tolerant approach: |
447
|
|
|
|
|
|
|
all values have their magnitude scaled with the power law, regardless |
448
|
|
|
|
|
|
|
of the sign of the value. |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=cut |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub t_gamma { |
453
|
2
|
|
|
2
|
1
|
425
|
my $gamma = shift; |
454
|
2
|
|
|
|
|
6
|
my ($me) = _new("gamma",{}); |
455
|
|
|
|
|
|
|
|
456
|
2
|
|
|
|
|
5
|
$me->{params} = {gamma=>$gamma}; |
457
|
2
|
|
|
|
|
19
|
$me->{name} .= sprintf("=%g",$gamma); |
458
|
2
|
|
|
|
|
2
|
$me->{idim} = 3; |
459
|
2
|
|
|
|
|
2
|
$me->{odim} = 3; |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
$me->{func} = sub { |
462
|
3
|
|
|
3
|
|
1498
|
my ($in, $opt) = @_; |
463
|
3
|
|
|
|
|
9
|
my $out = $in->new_or_inplace; |
464
|
3
|
50
|
|
|
|
94
|
if($opt->{gamma} != 1) { |
465
|
3
|
|
|
|
|
163
|
$out *= ($in->abs + ($in==0)) ** ($opt->{gamma}-1); |
466
|
|
|
|
|
|
|
} |
467
|
3
|
|
|
|
|
46
|
$out; |
468
|
2
|
|
|
|
|
7
|
}; |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
$me->{inv} = sub { |
471
|
2
|
|
|
2
|
|
1187
|
my ($in, $opt) = @_; |
472
|
2
|
|
|
|
|
6
|
my $out = $in->new_or_inplace; |
473
|
2
|
50
|
|
|
|
46
|
if($opt->{gamma} != 1) { |
474
|
2
|
|
|
|
|
59
|
$out *= ($in->abs + ($in==0)) ** (1.0/$opt->{gamma} - 1); |
475
|
|
|
|
|
|
|
} |
476
|
2
|
|
|
|
|
6
|
}; |
477
|
|
|
|
|
|
|
|
478
|
2
|
|
|
|
|
7
|
$me; |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
############################## |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=head2 t_brgb |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=for usage |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
$t = t_brgb(); |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=for ref |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
Convert lsRGB (normalized to [0,1]) to byte-scaled RGB ( [0,255] ). |
492
|
|
|
|
|
|
|
By default, C prepares byte values tuned for a display gamma |
493
|
|
|
|
|
|
|
of 2.2, which approximates sRGB (the standard output color coding for |
494
|
|
|
|
|
|
|
most computer displays). The difference between C and |
495
|
|
|
|
|
|
|
C in this usage is that C uses the actual |
496
|
|
|
|
|
|
|
spliced-curve approximation specified in the sRGB standard, while |
497
|
|
|
|
|
|
|
C uses a simple gamma law for export. |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
C accepts the following options, all of which may be abbreviated: |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=over 3 |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=item gamma (default 1) |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
If set, this is a gamma-encoding value for the original lsRGB, which |
506
|
|
|
|
|
|
|
is decoded before the transform. |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=item display_gamma (default 2.2) |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
If set, this is the gamma of the display for which the output is |
511
|
|
|
|
|
|
|
intended. The default compresses the brightness vector before output |
512
|
|
|
|
|
|
|
(taking approximately the square root). This matches the "standard |
513
|
|
|
|
|
|
|
gamma" applied by MacOS and Microsoft Windows displays, and approximates |
514
|
|
|
|
|
|
|
the sRGB standard. See also C. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=item clip (default 1) |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
If set, the output is clipped to [0,256) in the forward direction and |
519
|
|
|
|
|
|
|
to [0,1] in the reverse direction. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=item byte (default 1) |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
If set, the output is converted to byte type in the forward direction. |
524
|
|
|
|
|
|
|
This is a non-reversible operation, because precision is lost in the |
525
|
|
|
|
|
|
|
conversion to bytes. (The reverse transform always creates a floating |
526
|
|
|
|
|
|
|
point value, since lsRGB exists on the interval [0,1] and an integer |
527
|
|
|
|
|
|
|
type would be useless.) |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=back |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=cut |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
sub t_brgb { |
534
|
4
|
|
|
4
|
1
|
2464
|
my($me) = _new(@_,'encode bytescaled RGB', |
535
|
|
|
|
|
|
|
{clip=>1, |
536
|
|
|
|
|
|
|
byte=>1, |
537
|
|
|
|
|
|
|
gamma=>1.0, |
538
|
|
|
|
|
|
|
display_gamma=>2.2, |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
); |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
$me->{func} = sub { |
543
|
4
|
|
|
4
|
|
272
|
my($in, $opt) = @_; |
544
|
4
|
|
|
|
|
11
|
my $out = $in->new_or_inplace; |
545
|
|
|
|
|
|
|
|
546
|
4
|
100
|
|
|
|
116
|
if($opt->{display_gamma} != 1) { |
547
|
1
|
|
|
|
|
17
|
$out *= ($out->abs)**(1.0/$opt->{display_gamma} - 1); |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
4
|
|
|
|
|
17
|
$out *= 255.0; |
551
|
|
|
|
|
|
|
|
552
|
4
|
50
|
|
|
|
47
|
if($opt->{byte}) { |
|
|
0
|
|
|
|
|
|
553
|
4
|
|
|
|
|
36
|
$out = byte($out->rint->clip(0,255)); |
554
|
|
|
|
|
|
|
} elsif($opt->{clip}) { |
555
|
0
|
|
|
|
|
0
|
$out->inplace->clip(0,255.49999); |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
4
|
|
|
|
|
355
|
$out; |
559
|
4
|
|
|
|
|
18
|
}; |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
$me->{inv} = sub { |
562
|
2
|
|
|
2
|
|
1106
|
my($in,$opt) = @_; |
563
|
|
|
|
|
|
|
|
564
|
2
|
|
|
|
|
25
|
my $out = $in / 255.0; |
565
|
|
|
|
|
|
|
|
566
|
2
|
50
|
|
|
|
9
|
if($opt->{display_gamma} != 1) { |
567
|
0
|
|
|
|
|
0
|
$out *= ($out->abs)**($opt->{display_gamma}-1); |
568
|
|
|
|
|
|
|
} |
569
|
|
|
|
|
|
|
|
570
|
2
|
50
|
|
|
|
6
|
if($opt->{clip}) { |
571
|
2
|
|
|
|
|
6
|
$out->inplace->clip(0,1); |
572
|
|
|
|
|
|
|
} |
573
|
2
|
|
|
|
|
69
|
$out; |
574
|
4
|
|
|
|
|
11
|
}; |
575
|
|
|
|
|
|
|
|
576
|
4
|
|
|
|
|
6
|
return gammify($me); |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=head2 t_srgb |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
=for ref |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
Converts lsRGB (the internal floating-point base representation) to |
584
|
|
|
|
|
|
|
sRGB - the typical RGB encoding used by most computing devices. Since |
585
|
|
|
|
|
|
|
most computer terminals use sRGB, the representation's gamut is well |
586
|
|
|
|
|
|
|
matched to most computer monitors. |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
sRGB is a spliced standard, rather than having a direct gamma |
589
|
|
|
|
|
|
|
correction. Hence there is no way to adjust the output gamma. If you |
590
|
|
|
|
|
|
|
want to do that, use C instead. |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
C accepts the following options, all of which may be abbreviated: |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
=over 3 |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=item gamma (default 1) |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
If set, this is a gamma-encoding value for the original lsRGB, which |
599
|
|
|
|
|
|
|
is decoded before the transform. |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
=item byte (default 1) |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
If set, this causes the output to be clipped to the range [0,255] and rounded |
604
|
|
|
|
|
|
|
to a byte type PDL ("24-bit color"). (The reverse transform always creates |
605
|
|
|
|
|
|
|
a floating point value, since lsRGB exists on the interval [0,1] and an integer |
606
|
|
|
|
|
|
|
type would be useless.) |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=item clip (default 0) |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
If set, this causes output to be clipped to the range [0,255] even if the |
611
|
|
|
|
|
|
|
C option is not set. |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=back |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
=cut |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
# Helper routines do encoding on the domain [0,1]. These |
618
|
|
|
|
|
|
|
# are slow and lame with the multiplicative masking -- would do better as a PP routine... |
619
|
|
|
|
|
|
|
sub _srgb_encode { |
620
|
2
|
|
|
2
|
|
471
|
my $a = shift; |
621
|
2
|
100
|
|
|
|
9
|
my $b = ($a->is_inplace ? $a->new_or_inplace : $a->copy); |
622
|
2
|
|
|
|
|
78
|
my $sgn = 2*(0.5-($a<0)); |
623
|
2
|
|
|
|
|
16
|
$b->inplace->abs; |
624
|
2
|
|
|
|
|
236
|
$b .= ( |
625
|
|
|
|
|
|
|
($b <= 0.00304) * (12.92 * $b ) + |
626
|
|
|
|
|
|
|
($b > 0.00304) * ( |
627
|
|
|
|
|
|
|
(1.055 * ( $b * (($b+1e-30) ** (1.0/2.4 - 1)) ) ) - 0.055 |
628
|
|
|
|
|
|
|
) |
629
|
|
|
|
|
|
|
); |
630
|
2
|
|
|
|
|
44
|
$b *= $sgn; |
631
|
2
|
|
|
|
|
17
|
return $b; |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
sub _srgb_decode { |
635
|
1
|
|
|
1
|
|
842
|
my $a = shift; |
636
|
1
|
50
|
|
|
|
8
|
my $b = ($a->is_inplace ? $a->new_or_inplace : $a->copy); |
637
|
1
|
|
|
|
|
37
|
my $sgn = 2*(0.5-($a<0)); |
638
|
1
|
|
|
|
|
9
|
$b->inplace->abs; |
639
|
1
|
|
|
|
|
26
|
my $c = ($b+0.055)/1.055; |
640
|
1
|
|
|
|
|
68
|
$b .= ( |
641
|
|
|
|
|
|
|
($b <= 0.03928) * ( $b / 12.92 ) + |
642
|
|
|
|
|
|
|
($b > 0.03928) * ( |
643
|
|
|
|
|
|
|
$c * ( $c->abs ** 1.4 ) |
644
|
|
|
|
|
|
|
) |
645
|
|
|
|
|
|
|
); |
646
|
1
|
|
|
|
|
20
|
$b *= $sgn; |
647
|
1
|
|
|
|
|
9
|
return $b; |
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
sub t_srgb { |
651
|
1
|
|
|
1
|
1
|
5
|
my($me) = _new(@_,'encode 24-bit sRGB', |
652
|
|
|
|
|
|
|
{clip=>0, |
653
|
|
|
|
|
|
|
byte=>1, |
654
|
|
|
|
|
|
|
gamma=>1.0 |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
); |
657
|
|
|
|
|
|
|
$me->{func} = sub { |
658
|
1
|
|
|
1
|
|
6
|
my($in,$opt) = @_; |
659
|
|
|
|
|
|
|
# Convert from CIE RGB to sRGB primaries |
660
|
1
|
|
|
|
|
3
|
my($rgb) = $in->new_or_inplace(); |
661
|
|
|
|
|
|
|
# Slow and lame -- would work far better as a pp routine... |
662
|
1
|
|
|
|
|
27
|
_srgb_encode($rgb->inplace); |
663
|
1
|
|
|
|
|
2
|
my $out; |
664
|
|
|
|
|
|
|
|
665
|
1
|
|
|
|
|
2
|
$rgb *= 255; |
666
|
1
|
50
|
|
|
|
11
|
if($opt->{byte}) { |
|
|
0
|
|
|
|
|
|
667
|
1
|
|
|
|
|
13
|
$out = byte( $rgb->rint->clip(0,255) ); |
668
|
|
|
|
|
|
|
} elsif($opt->{clip}) { |
669
|
0
|
|
|
|
|
0
|
$out = $rgb->clip(0,255.49999); |
670
|
|
|
|
|
|
|
} else { |
671
|
0
|
|
|
|
|
0
|
$out = $rgb; |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
|
674
|
1
|
|
|
|
|
83
|
$out; |
675
|
1
|
|
|
|
|
5
|
}; |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
$me->{inv} = sub { |
678
|
0
|
|
|
0
|
|
0
|
my($in,$opt) = @_; |
679
|
|
|
|
|
|
|
|
680
|
0
|
|
|
|
|
0
|
my $rgb = $in / pdl(255.0); |
681
|
|
|
|
|
|
|
|
682
|
0
|
|
|
|
|
0
|
_srgb_decode($rgb->inplace); |
683
|
|
|
|
|
|
|
|
684
|
0
|
|
|
|
|
0
|
$rgb; |
685
|
1
|
|
|
|
|
3
|
}; |
686
|
|
|
|
|
|
|
|
687
|
1
|
|
|
|
|
2
|
return gammify($me); |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
###################################################################### |
692
|
|
|
|
|
|
|
###################################################################### |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=head2 t_pc and t_pcp |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=for ref |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
These two transforms implement a general purpose pseudocolor |
699
|
|
|
|
|
|
|
transformation. You input a monochromatic value (zero active dims) |
700
|
|
|
|
|
|
|
and get out an RGB value (one active dim, size 3). Because the most |
701
|
|
|
|
|
|
|
common use case is to generate sRGB values, the default output is sRGB |
702
|
|
|
|
|
|
|
-- you have to set a flag for lsRGB output, for example if you want to |
703
|
|
|
|
|
|
|
produce output in some other system by composing t_pc with a color |
704
|
|
|
|
|
|
|
transformation. |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
C generates pseudocolor transforms ("color maps") with |
707
|
|
|
|
|
|
|
a photometric interpretation of the input: the input data are |
708
|
|
|
|
|
|
|
considered to be proportional to some kind of measured luminance |
709
|
|
|
|
|
|
|
or similar physical parameter. This produces "correct" renderings |
710
|
|
|
|
|
|
|
of scenes captured by scientific cameras and similar instrumentation. |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
C generates pseudocolor transforms ("color maps") with a |
713
|
|
|
|
|
|
|
perceptual interpretation of the input: the input data are considered |
714
|
|
|
|
|
|
|
to be proportional to the *perceptual* variation desired across the |
715
|
|
|
|
|
|
|
display. This produces "correct" renderings of many non-luminant |
716
|
|
|
|
|
|
|
types of data, such as temperature, Doppler shift, frequency plots, |
717
|
|
|
|
|
|
|
etc. |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
Both C and C generate transforms based on a collection |
720
|
|
|
|
|
|
|
of named transformations stored in an internal database (the global |
721
|
|
|
|
|
|
|
hash ref C<$PDL::Transform::Color::pc_tab>). The transformations |
722
|
|
|
|
|
|
|
come in two basic sorts: quasi-photometric transformations, |
723
|
|
|
|
|
|
|
which use luminance as the dominant varying parameter; and non- |
724
|
|
|
|
|
|
|
photometric transformations, which use hue or saturation as the |
725
|
|
|
|
|
|
|
dominant varying parameter. Only the photometric transformations |
726
|
|
|
|
|
|
|
get modified by C vs C -- for example, C |
727
|
|
|
|
|
|
|
will yield the same transform as C. |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
Some of the color transformations are "split" and intended for display of signed |
730
|
|
|
|
|
|
|
data -- for example, the C transformation fades red-to-white-to-blue and |
731
|
|
|
|
|
|
|
is intended for display of Doppler or similar signals. |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
NOTE: C and C work BACKWARDS from most of the |
734
|
|
|
|
|
|
|
transformations in this package: they convert FROM a data value TO sRGB |
735
|
|
|
|
|
|
|
or lsRGB. |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
There are options to adjust input gamma and the domain of the |
738
|
|
|
|
|
|
|
transformation (e.g. if your input data are on [0,1000] instead of |
739
|
|
|
|
|
|
|
[0,1]). |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
If you feed in no arguments at all, either C or C will |
742
|
|
|
|
|
|
|
list a collection of named pseudocolor transformations that work, on |
743
|
|
|
|
|
|
|
the standard output. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
Options accepted are: |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=over 3 |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
=item gamma (default 1) - presumed encoding gamma of the input |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
The input is *decoded* from this gamma value. 1 treats it as linear |
752
|
|
|
|
|
|
|
in luminance. |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=item lsRGB (default 0) - produce lsRGB output instead of sRGB. |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
(this may be abbreviated "l" for "linear") |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=item domain - domain of the input; synonym for irange. |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
=item irange (default [0,1]) - input range of the data |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
Input data are by default clipped to [0,1] before application of the |
763
|
|
|
|
|
|
|
color map. Specifying an undefined value causes the color map to be |
764
|
|
|
|
|
|
|
autoscaled to the input data, e.g. C[0,undef]> causes the color map |
765
|
|
|
|
|
|
|
to be scaled from 0 to the maximum value of the input. For full |
766
|
|
|
|
|
|
|
autoscaling, use C[]>. |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
=item combination (default 0) - recombine r,g,b post facto |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
This option allows you to perturb maps you like by mixing up r, g, and |
771
|
|
|
|
|
|
|
b after all the other calculations are done. You feed in a number |
772
|
|
|
|
|
|
|
from 0 to 5. If it's nonzero, you get a different combination of the |
773
|
|
|
|
|
|
|
three primaries. You can mock this up more compactly by appending |
774
|
|
|
|
|
|
|
C<-Cn> to the (possibly abbreviated) name of the table. (Replace |
775
|
|
|
|
|
|
|
the 'n' with a number). |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
For example, if you speciy the color table C or C you'll |
778
|
|
|
|
|
|
|
get the sepiatone color table. If you specify C you'll get |
779
|
|
|
|
|
|
|
almost the exact same color table as C. |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
=back |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
You can abbreviate color table names with unique abbreviations. |
784
|
|
|
|
|
|
|
Tables currently accepted, and their intended uses are: |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
=over 3 |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=item QUASI-PHOTOMETRIC PSEUDOCOLOR MAPS FOR NORMAL USE |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
=over 3 |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
=item grey, gray, or mono (photometric) |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
Simple monochrome. |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
=item sepia, blepia, grepia, vepia, ryg - sepiatone and variants |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
These use color scaling to enhance contrast in a simple luminance |
799
|
|
|
|
|
|
|
transfer. C is a black-brown-white curve reminiscent of sepia |
800
|
|
|
|
|
|
|
ink. The others are similar, but emphasize different primary colors. |
801
|
|
|
|
|
|
|
The 'ryg' duplicates sepiatone, but with green highlights to increase |
802
|
|
|
|
|
|
|
contrast in near-saturated parts of an image. |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
=item heat |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
This black-red-yellow-white is reminiscent of blackbody curves |
807
|
|
|
|
|
|
|
(but does not match them rigorously). |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
=item pm3d, voy |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
"pm3d" is the default color table for Gnuplot. It's a colorblind-friendly, |
812
|
|
|
|
|
|
|
highly saturated table with horrible aesthetics but good contrast throughout. |
813
|
|
|
|
|
|
|
"voy" is violet-orange-yellow. It's a more aesthetically pleasing colorblind- |
814
|
|
|
|
|
|
|
friendly map with a ton of contrast throughout the range. |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
=item ocean |
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
deep green through blue to white |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
=item spring, summer, autumn, winter |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
These are reminiscent of the "seasonal" colors provided by MatLab. The |
823
|
|
|
|
|
|
|
"spring" is horrendous but may be useful for certain aesthetic presentations. |
824
|
|
|
|
|
|
|
Summer and Winter are similar to the sepia-like tables, but with different |
825
|
|
|
|
|
|
|
color paths. Autumn is similar to heat, but less garish. |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
=back |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
=item SPLIT PSEUDOCOLOR MAPS FOR SIGNED QUANTITIES |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
=over 3 |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
=item dop, dop1, dop2, dop3 |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
These are various presentations of signed infromation, originally |
836
|
|
|
|
|
|
|
intended to display Doppler shift. They are all quasi-photometric |
837
|
|
|
|
|
|
|
and split. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
=item vbg |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
This is a violet-black-green signed fade useful for non-Doppler |
842
|
|
|
|
|
|
|
signed quantities. Quasi-photometric and split. |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
=back |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
=item NON-PHOTOMETRIC PSEUDOCOLOR MAPS |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
=over 3 |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
=item rainbow |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
Colors of the rainbow, red through "violet" (magenta) |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
=item wheel |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
The full "color wheel", including the controversial magenta-to-red segment |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
=back |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
=back |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
=cut |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
## pc_tab defines transformation subs for R, G, B from the grayscale. |
868
|
|
|
|
|
|
|
## The initial few are translated direct from the C<$palettesTab> in |
869
|
|
|
|
|
|
|
## C; others follow. Input is on the domain |
870
|
|
|
|
|
|
|
## [0,1]. Output is clipped to [0,1] post facto. |
871
|
|
|
|
|
|
|
## |
872
|
|
|
|
|
|
|
## names should be lowercase. |
873
|
|
|
|
|
|
|
## |
874
|
|
|
|
|
|
|
## Meaning of fields: |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
## type Color system being used ('rgb' or 'hsv' at present) |
877
|
|
|
|
|
|
|
## subs List ref containing three subs that accept scaled input [0,1] and |
878
|
|
|
|
|
|
|
## return each color coordinate value (e.g. r, g, b) |
879
|
|
|
|
|
|
|
## doc Short one-line string describing the pseudocolor map |
880
|
|
|
|
|
|
|
## igamma Scaled input is *decoded* from this gamma (raised to this power) if present |
881
|
|
|
|
|
|
|
## ogamma Output is *encoded to this gamma (rooted by this power) if present |
882
|
|
|
|
|
|
|
## phot Flag: if set, this pseudocolor map is approximately photometric and can be |
883
|
|
|
|
|
|
|
## scaled differently by the direct and perceptual color table methods |
884
|
|
|
|
|
|
|
## split This is the "zero point" on [0-1] of the color map. Default is 0. Useful |
885
|
|
|
|
|
|
|
## for gamma scaling etc; primarily used by doppler and other signed tables. |
886
|
|
|
|
|
|
|
## (Note that it's the user's responsibility to make sure the irange places |
887
|
|
|
|
|
|
|
## the zero here, since the subs accept pre-scaled input on [0,1] |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
our $PI = 3.141592653589793238462643383279502; |
890
|
|
|
|
|
|
|
our $pc_tab = { |
891
|
|
|
|
|
|
|
gray => { type=>'rgb', subs=> [ sub{$_[0]}, sub{$_[0]}, sub{$_[0]} ], |
892
|
|
|
|
|
|
|
doc=>"greyscale", phot=>1 }, |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
grey => { type=>'rgb', subs=> [ sub{$_[0]}, sub{$_[0]}, sub{$_[0]} ], |
895
|
|
|
|
|
|
|
doc=>"greyscale", phot=>1 }, |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
blepia => { type=>'rgb', subs=> [ sub{$_[0]**2}, sub{$_[0]}, sub{sqrt($_[0])} ], |
898
|
|
|
|
|
|
|
doc=>"a simple sepiatone, in blue" , phot=>1, igamma=>0.75 }, |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
dop => { type=>'rgb', subs=> [ sub{2-2*$_[0]}, sub{1-abs($_[0]-0.5)*2}, sub{2*$_[0]} ], |
901
|
|
|
|
|
|
|
doc=>"red-white-blue fade", ogamma=>1.5, igamma=>0.6, phot=>1, split=>.5}, |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
dop1 => { type=>'rgb', subs=> [ sub{2-2*$_[0]}, sub{1-abs($_[0]-0.5)*2}, sub{2*$_[0]} ], |
904
|
|
|
|
|
|
|
doc=>"dop synonym", ogamma=>1.5, igamma=>0.6, phot=>1, split=>.5}, |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
dop2 => { type=>'rgb', subs=> [ sub{(1-2*$_[0])}, sub{(($_[0]-0.5)->abs->clip(0,0.5))**2}, sub{(-1+2*$_[0])} ], |
907
|
|
|
|
|
|
|
doc=>'red-black-blue fade (mostly saturated)', ogamma=>1.5, igamma=>0.5, phot=>1, split=>0.5 }, |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
dop3 => { type=>'rgb', subs=> [ sub{1-$_[0]*2}, sub{(0.1+abs($_[0]-0.5))**2}, sub{-1+$_[0]*2} ], |
910
|
|
|
|
|
|
|
doc=>'orange-black-lightblue fade (lightly saturated)', ogamma=>1.5, igamma=>0.5, phot=>1, split=>0.5 }, |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
vbg => { type=>'rgb', subs=> [ sub{1 - (2*$_[0])}, sub{abs($_[0]-0.5)*1.5}, sub{1 - 2*$_[0]} ], |
913
|
|
|
|
|
|
|
doc=>'violet-black-green signed fade', ogamma=>1.5, igamma=>0.5, phot=>1, split=>0.5 }, |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
grepia => { type=>'rgb', subs=> [ sub{$_[0]}, sub{sqrt($_[0])}, sub{$_[0]**2} ], |
918
|
|
|
|
|
|
|
doc=>"a simple sepiatone, in green", igamma=>0.9, phot=>1 }, |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
heat => { type=>'rgb', subs=> [ sub{2*$_[0]}, sub{2*$_[0]-0.5}, sub{2*$_[0]-1} ], |
921
|
|
|
|
|
|
|
doc=>"heat-map (AFM): black-red-yellow-white", phot=>1, igamma=>0.667 }, |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
pm3d => { type=>'rgb', subs=> [ sub{sqrt($_[0])}, sub{$_[0]**3}, sub{sin($_[0]*2*$PI)} ], |
924
|
|
|
|
|
|
|
doc=>"duplicates the PM3d colortable in gnuplot (RG colorblind)", phot=>1}, |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
grv => { type=>'rgb', subs=> [ sub{sqrt($_[0]*0.5)}, sub{1-2*$_[0]}, sub{$_[0]**3.5} ], |
927
|
|
|
|
|
|
|
doc=>"green-red-violet", igamma=>0.75, phot=>1 }, |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
mono => { type=>'rgb', subs=> [ sub{$_[0]}, sub{$_[0]}, sub{$_[0]} ], |
930
|
|
|
|
|
|
|
doc=>"synonym for grey"}, |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
ocean => { type=>'rgb', subs=> [ sub{(3*$_[0]-2)->clip(0) ** 2}, sub{$_[0]}, sub{$_[0]**0.33*0.5+$_[0]*0.5} ], |
933
|
|
|
|
|
|
|
doc=>"green-blue-white", phot=>1, igamma=>0.8}, |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
rainbow => { type=>'hsv', subs=> [ sub{$_[0]*0.82}, sub{pdl(1)}, sub{pdl(1)} ], |
936
|
|
|
|
|
|
|
doc=>"rainbow red-yellow-green-blue-violet"}, |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
rgb => { type=>'rgb', subs=> [ sub{cos($_[0]*$PI/2)}, sub{sin($_[0]*$PI)}, sub{sin($_[0]*$PI/2)} ], |
939
|
|
|
|
|
|
|
doc=>"red-green-blue fade", phot=>1 }, |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
sepia => { type=>'rgb', subs=> [ sub{sqrt($_[0])}, sub{$_[0]}, sub{$_[0]**2} ], |
942
|
|
|
|
|
|
|
doc=>"a simple sepiatone", phot=>1 }, |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
vepia => { type=>'rgb', subs=> [ sub{$_[0]}, sub{$_[0]**2}, sub{sqrt($_[0])} ], |
945
|
|
|
|
|
|
|
doc=>"a simple sepiatone, in violet", phot=>1, ogamma=>0.9 }, |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
wheel => { type=>'hsv', subs=> [ sub{$_[0]}, sub{pdl(1)}, sub{pdl(1)} ], |
948
|
|
|
|
|
|
|
doc=>"full color wheel red-yellow-green-blue-violet-red" }, |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
ryg => { type=>'hsv', subs=> [ sub{ (0.5*($_[0]-0.333/2))%1 }, sub{0.8+0.2*$_[0]}, sub{$_[0]} ], |
951
|
|
|
|
|
|
|
doc=>"A quasi-sepiatone (R/Y) with green highlights",phot=>1, igamma=>0.7 }, |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
voy => { type=>'rgb', subs=> [ sub{pdl(1)*$_[0]}, sub{$_[0]**2*$_[0]}, sub{(1-$_[0])**4 * $_[0]}], |
954
|
|
|
|
|
|
|
doc=>"A colorblind-friendly map with lots of contrast", phot=>1, igamma=>0.7}, |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
### Seasons: these are sort of like the Matlab colortables of the same names... |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
spring => { type=>'rgb', subs=> [ sub{pdl(1)}, sub{$_[0]**2}, sub{(1-$_[0])**4}], |
959
|
|
|
|
|
|
|
doc=>"Springy colors fading from magenta to yellow", phot=>1, igamma=>0.45}, |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
summer => { type=>'hsv', subs=> [ sub{ 0.333*(1- $_[0]/2) }, sub{0.7+0.1*$_[0]}, sub{0.01+0.99*$_[0]} ], |
962
|
|
|
|
|
|
|
doc=>"Summery colors fading from dark green to light yellow",phot=>1, igamma=>0.8 }, |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
autumn => { type=>'hsv', subs=> [ sub { $_[0] * 0.333/2 }, sub{pdl(1)}, sub{0.01+0.99*$_[0]} ], |
965
|
|
|
|
|
|
|
doc=>"Autumnal colors fading from dark red through orange to light yellow",phot=>1,igamma=>0.7}, |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
winter => { type=>'hsv', subs=> [ sub { 0.667-0.333*$_[0] }, sub{1.0-sin($PI/2*$_[0])**2*0.2}, sub{$_[0]}], |
968
|
|
|
|
|
|
|
doc=>"Wintery colors fading from dark blue through lightish green",phot=>1,igamma=>0.5}, |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
}; |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
# Generate the abbrevs table: find minimal substrings that match only one result. |
973
|
|
|
|
|
|
|
our $pc_tab_abbrevs = {}; |
974
|
|
|
|
|
|
|
{ |
975
|
|
|
|
|
|
|
my $pc_tab_foo = {}; |
976
|
|
|
|
|
|
|
for my $k(keys %$pc_tab) { |
977
|
|
|
|
|
|
|
for my $i(0..length($k)){ |
978
|
|
|
|
|
|
|
my $s = substr($k,0,$i); |
979
|
|
|
|
|
|
|
if($pc_tab_foo->{$s} and length($s)
|
980
|
|
|
|
|
|
|
# collision with earlier string -- if that's a real abbreviation, zap it. |
981
|
|
|
|
|
|
|
delete($pc_tab_abbrevs->{$s}) |
982
|
|
|
|
|
|
|
unless( length($pc_tab_abbrevs->{$s}) == length($s) ); |
983
|
|
|
|
|
|
|
} else { |
984
|
|
|
|
|
|
|
# no collision -- figure it's a valid abbreviation. |
985
|
|
|
|
|
|
|
$pc_tab_abbrevs->{$s} = $k; |
986
|
|
|
|
|
|
|
} |
987
|
|
|
|
|
|
|
$pc_tab_foo->{$s}++; |
988
|
|
|
|
|
|
|
} |
989
|
|
|
|
|
|
|
} |
990
|
|
|
|
|
|
|
} |
991
|
|
|
|
|
|
|
# Hand-code some abbreviations.. |
992
|
|
|
|
|
|
|
$pc_tab_abbrevs->{g} = "grey"; |
993
|
|
|
|
|
|
|
for(qw/m monoc monoch monochr monochro monochrom monochrome/) {$pc_tab_abbrevs->{_} = "mono";} |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
### t_pcp - t_pc, but perceptual flag defaults to 1 |
997
|
|
|
|
|
|
|
sub t_pcp { |
998
|
0
|
|
|
0
|
1
|
0
|
my $name; |
999
|
0
|
0
|
|
|
|
0
|
if(0+@_ % 2) { |
1000
|
0
|
|
|
|
|
0
|
$name = shift; |
1001
|
|
|
|
|
|
|
} else { |
1002
|
0
|
|
|
|
|
0
|
$name = undef; |
1003
|
|
|
|
|
|
|
} |
1004
|
0
|
|
|
|
|
0
|
my %opt = @_; |
1005
|
0
|
|
|
|
|
0
|
$opt{perceptual} = 1; |
1006
|
|
|
|
|
|
|
|
1007
|
0
|
0
|
|
|
|
0
|
if(defined($name)) { |
1008
|
0
|
|
|
|
|
0
|
return t_pc($name,%opt); |
1009
|
|
|
|
|
|
|
} else { |
1010
|
0
|
|
|
|
|
0
|
return t_pc(%opt); |
1011
|
|
|
|
|
|
|
} |
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
our @_t_pc_combinatorics =( |
1015
|
|
|
|
|
|
|
[0,1,2],[1,2,0],[2,0,1],[0,2,1],[2,1,0],[1,0,2] |
1016
|
|
|
|
|
|
|
); |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
sub t_pc { |
1019
|
|
|
|
|
|
|
# No arguments |
1020
|
2
|
100
|
|
2
|
1
|
632
|
unless(0+@_){ |
1021
|
1
|
|
|
|
|
2
|
my $s = "Usage: 't_pc(\$colortab_name, %opt)'. Named pseudocolor mappings available:\n"; |
1022
|
1
|
|
|
|
|
3
|
$s .= " (tables marked 'phot' are luminance based. Use t_pc for photometric data, or\n t_pcp for near-constant perceptual shift per input value.\n Add '-c' suffix (n in [0..5]) for RGB combinatoric variations.)\n"; |
1023
|
1
|
|
|
|
|
1
|
our $pc_tab; |
1024
|
1
|
|
|
|
|
2
|
for my $k(sort keys %{$pc_tab}) { |
|
1
|
|
|
|
|
13
|
|
1025
|
25
|
100
|
|
|
|
64
|
$s .= sprintf(" %8s - %s%s\n",$k,$pc_tab->{$k}->{doc},($pc_tab->{$k}->{phot}?" (phot)":"")); |
1026
|
|
|
|
|
|
|
} |
1027
|
1
|
|
|
|
|
29
|
die $s."\n"; |
1028
|
|
|
|
|
|
|
} |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
# Parse the color table name. |
1032
|
|
|
|
|
|
|
# Odd number of params -- expect a table name and options. |
1033
|
|
|
|
|
|
|
# even number of params -- just options. |
1034
|
1
|
50
|
|
|
|
5
|
my $lut_name = ((0+@_) % 2) ? shift() : "monochrome"; |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
### |
1038
|
|
|
|
|
|
|
# Table names can have combinatoric modifiers. Parse those out. |
1039
|
1
|
|
|
|
|
2
|
my $mod_combo = undef; |
1040
|
1
|
50
|
|
|
|
4
|
if( $lut_name =~ s/\-C([0-5])$//i ) { |
1041
|
|
|
|
|
|
|
# got a combinatoric modifier |
1042
|
0
|
|
|
|
|
0
|
$mod_combo = $1; |
1043
|
|
|
|
|
|
|
} |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
## Look up the table by name |
1046
|
1
|
|
|
|
|
4
|
$lut_name = $pc_tab_abbrevs->{lc($lut_name)}; |
1047
|
1
|
50
|
|
|
|
3
|
unless($lut_name) { |
1048
|
0
|
|
|
|
|
0
|
t_pc(); # generate usage message |
1049
|
|
|
|
|
|
|
} |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
# Generate the object |
1053
|
1
|
|
|
|
|
11
|
my($me) = _new(@_, "pseudocolor sRGB encoding ($lut_name)", |
1054
|
|
|
|
|
|
|
{ |
1055
|
|
|
|
|
|
|
clip=>1, |
1056
|
|
|
|
|
|
|
byte=>1, |
1057
|
|
|
|
|
|
|
gamma=>1.0, |
1058
|
|
|
|
|
|
|
lsRGB=>0, |
1059
|
|
|
|
|
|
|
domain=>undef, |
1060
|
|
|
|
|
|
|
irange=>[0,1], |
1061
|
|
|
|
|
|
|
perceptual=>0, |
1062
|
|
|
|
|
|
|
combination=>0 |
1063
|
|
|
|
|
|
|
} |
1064
|
|
|
|
|
|
|
); |
1065
|
|
|
|
|
|
|
|
1066
|
1
|
|
|
|
|
3
|
$me->{params}->{lut_name} = $lut_name; |
1067
|
1
|
|
|
|
|
2
|
$me->{params}->{lut} = $pc_tab->{$lut_name}; |
1068
|
1
|
50
|
|
|
|
3
|
unless(defined($pc_tab->{$lut_name})){ |
1069
|
0
|
|
|
|
|
0
|
die "t_pc: internal error (name $lut_name resolves but points to nothing)"; |
1070
|
|
|
|
|
|
|
} |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
# Handle domain-irange synonym |
1073
|
1
|
50
|
|
|
|
4
|
$me->{params}->{irange} = $me->{params}->{domain} if(defined($me->{params}->{domain})); |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
# Check that range is correct |
1076
|
1
|
50
|
|
|
|
2
|
$me->{params}->{irange} = [] unless(defined($me->{params}->{irange})); |
1077
|
1
|
50
|
|
|
|
3
|
unless( ref($me->{params}->{irange}) eq 'ARRAY' |
1078
|
|
|
|
|
|
|
){ |
1079
|
0
|
|
|
|
|
0
|
die "t_pc: 'domain' or 'irange' parameter must be an array ref "; |
1080
|
|
|
|
|
|
|
} |
1081
|
1
|
0
|
0
|
|
|
3
|
if($me->{params}->{irange}->[0] == $me->{params}->{irange}->[1] and |
|
|
|
33
|
|
|
|
|
1082
|
|
|
|
|
|
|
(defined($me->{params}->{irange}->[0]) && defined($me->{params}->{irange}->[1]))) { |
1083
|
0
|
|
|
|
|
0
|
die "t_pc: 'domain' or 'irange' parameter must specify a nonempty range"; |
1084
|
|
|
|
|
|
|
} |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
# Check the RGB recombination parameter |
1088
|
1
|
50
|
|
|
|
3
|
if($mod_combo) { |
1089
|
0
|
0
|
|
|
|
0
|
die "t_pc / t_pcp: can't specify RGB combinatorics in both parameters and table\n suffix at the same time" if( $me->{params}->{combination} ); |
1090
|
0
|
|
|
|
|
0
|
$me->{params}->{combination} = $mod_combo; |
1091
|
|
|
|
|
|
|
} |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
|
1094
|
1
|
50
|
33
|
|
|
7
|
if($me->{params}->{combination} < 0 || $me->{params}->{combination} > 5) { |
1095
|
0
|
|
|
|
|
0
|
die "t_pc/t_pcp: 'combination' parameter must be between 0 and 5 inclusive"; |
1096
|
|
|
|
|
|
|
} |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
# Copy the conversion subs from the map table entry to the object, with combinatorics as |
1099
|
|
|
|
|
|
|
# needed. |
1100
|
|
|
|
|
|
|
|
1101
|
1
|
50
|
|
|
|
4
|
if($me->{params}->{lut}->{type} eq 'hsv') { |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
# hsv - copy subs in from table, and implement combinatorics with a hue transform |
1104
|
|
|
|
|
|
|
|
1105
|
0
|
|
|
|
|
0
|
$me->{params}->{subs} = [ @{$me->{params}->{lut}->{subs}} ]; # copy the subs for the map |
|
0
|
|
|
|
|
0
|
|
1106
|
0
|
0
|
|
|
|
0
|
if($me->{params}->{combination}) { |
1107
|
0
|
|
|
|
|
0
|
my $s0 = $me->{params}->{subs}->[0]; |
1108
|
|
|
|
|
|
|
$me->{params}->{subs}->[0] = |
1109
|
|
|
|
|
|
|
sub { |
1110
|
0
|
|
|
0
|
|
0
|
my $a = &$s0(@_); |
1111
|
0
|
|
|
|
|
0
|
$a += 0.33 * $me->{params}->{combination}; |
1112
|
0
|
0
|
|
|
|
0
|
$a *= -1 if($me->{params}->{combination} > 2); |
1113
|
0
|
|
|
|
|
0
|
$a .= $a % 1; |
1114
|
0
|
|
|
|
|
0
|
return $a; |
1115
|
0
|
|
|
|
|
0
|
}; |
1116
|
|
|
|
|
|
|
} # end of 'combination' handler for hsv |
1117
|
|
|
|
|
|
|
} else { |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
# rgb - do any combinatorics as needed |
1120
|
1
|
|
|
|
|
2
|
$me->{params}->{subs} = [ @{$me->{params}->{lut}->{subs}}[ (@{ $_t_pc_combinatorics[$me->{params}->{combination}] }) ] ]; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3
|
|
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
} |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
# Generate the forward transform |
1125
|
|
|
|
|
|
|
$me->{func} = sub { |
1126
|
1
|
|
|
1
|
|
209
|
my($in,$opt) = @_; |
1127
|
|
|
|
|
|
|
|
1128
|
1
|
|
|
|
|
4
|
my $in2 = $in->new_or_inplace; |
1129
|
|
|
|
|
|
|
|
1130
|
1
|
|
|
|
|
28
|
my ($min,$max) = @{$opt->{irange}}; |
|
1
|
|
|
|
|
3
|
|
1131
|
|
|
|
|
|
|
|
1132
|
1
|
50
|
33
|
|
|
7
|
unless(defined($min) || defined($max)) { |
|
|
50
|
50
|
|
|
|
|
1133
|
0
|
|
|
|
|
0
|
($min,$max) = $in->minmax; |
1134
|
0
|
|
|
|
|
0
|
} elsif( !defined($min) ){ |
1135
|
0
|
|
|
|
|
0
|
$min = $in->min; |
1136
|
|
|
|
|
|
|
} elsif( !defined($max) ) { |
1137
|
|
|
|
|
|
|
$max = $in->max; |
1138
|
|
|
|
|
|
|
} |
1139
|
|
|
|
|
|
|
|
1140
|
1
|
50
|
33
|
|
|
24
|
if($min==$max || !isfinite($min) || !isfinite($max)) { |
|
|
|
33
|
|
|
|
|
1141
|
0
|
|
|
|
|
0
|
die "t_pc transformation: range is zero or infinite ($min to $max)! Giving up!"; |
1142
|
|
|
|
|
|
|
} |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
# Translate to (0,1) |
1145
|
1
|
|
|
|
|
121
|
$in2 -= $min; |
1146
|
1
|
|
|
|
|
14
|
$in2 /= $max; |
1147
|
|
|
|
|
|
|
|
1148
|
1
|
|
|
|
|
10
|
my $split = 0; |
1149
|
|
|
|
|
|
|
# Deal with split color tables |
1150
|
1
|
50
|
|
|
|
3
|
if($opt->{lut}->{split}) { |
1151
|
0
|
|
|
|
|
0
|
$split = $opt->{lut}->{split}; |
1152
|
0
|
|
|
|
|
0
|
$in2 -= $split; |
1153
|
0
|
0
|
|
|
|
0
|
if($split==0.5) { |
1154
|
0
|
|
|
|
|
0
|
$in2 *= 2; |
1155
|
|
|
|
|
|
|
} else { |
1156
|
0
|
|
|
|
|
0
|
$in2->where($in2<0) /= $split; |
1157
|
0
|
|
|
|
|
0
|
$in2->where($in2>0) /= (1.0-$split); |
1158
|
|
|
|
|
|
|
} |
1159
|
|
|
|
|
|
|
} |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
# Default to sRGB coding for perceptual curves |
1162
|
1
|
50
|
33
|
|
|
6
|
if($opt->{lut}->{phot} && $opt->{perceptual}) { |
1163
|
0
|
|
|
|
|
0
|
_srgb_decode($in2->inplace); |
1164
|
|
|
|
|
|
|
} |
1165
|
|
|
|
|
|
|
|
1166
|
1
|
50
|
|
|
|
3
|
if($opt->{clip}) { |
1167
|
1
|
50
|
|
|
|
2
|
if($split) { |
1168
|
0
|
|
|
|
|
0
|
$in2->inplace->clip( -1,1 ); |
1169
|
|
|
|
|
|
|
} else { |
1170
|
1
|
|
|
|
|
4
|
$in2->inplace->clip(0,1); |
1171
|
|
|
|
|
|
|
} |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
|
1174
|
1
|
50
|
|
|
|
34
|
if(defined($opt->{lut}->{igamma})) { |
1175
|
0
|
|
|
|
|
0
|
$in2 *= ($in2->abs+1e-10) ** ($opt->{lut}->{igamma} - 1); |
1176
|
|
|
|
|
|
|
} |
1177
|
|
|
|
|
|
|
|
1178
|
1
|
50
|
|
|
|
2
|
if($split) { |
1179
|
0
|
0
|
|
|
|
0
|
if($split==0.5) { |
1180
|
0
|
|
|
|
|
0
|
$in2 /=2; |
1181
|
|
|
|
|
|
|
} else { |
1182
|
0
|
|
|
|
|
0
|
$in2->where($in2<0) *= $split; |
1183
|
0
|
|
|
|
|
0
|
$in2->where($in2>0) *= (1.0-$split); |
1184
|
0
|
|
|
|
|
0
|
$in2 += $split; |
1185
|
|
|
|
|
|
|
} |
1186
|
0
|
|
|
|
|
0
|
$in2 += $split; |
1187
|
|
|
|
|
|
|
|
1188
|
0
|
0
|
|
|
|
0
|
if($opt->{clip}) { |
1189
|
0
|
|
|
|
|
0
|
$in2->clip(0,1); |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
} |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
# apply the transform |
1194
|
1
|
|
|
|
|
9
|
my $out = zeroes(3,$in2->dims); |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
## These are the actual transforms. They're figured by the constructor, |
1197
|
|
|
|
|
|
|
## which does any combinatorics in setting up the subs. |
1198
|
1
|
|
|
|
|
57
|
$out->((0)) .= &{$opt->{subs}->[0]}($in2)->clip(0,1); |
|
1
|
|
|
|
|
12
|
|
1199
|
1
|
|
|
|
|
60
|
$out->((1)) .= &{$opt->{subs}->[1]}($in2)->clip(0,1); |
|
1
|
|
|
|
|
10
|
|
1200
|
1
|
|
|
|
|
35
|
$out->((2)) .= &{$opt->{subs}->[2]}($in2)->clip(0,1); |
|
1
|
|
|
|
|
10
|
|
1201
|
|
|
|
|
|
|
|
1202
|
1
|
50
|
|
|
|
34
|
if(defined($opt->{lut}->{ogamma})) { |
1203
|
0
|
|
|
|
|
0
|
$out *= ($out->abs) ** ($opt->{lut}->{ogamma}-1); |
1204
|
|
|
|
|
|
|
} |
1205
|
1
|
|
|
|
|
3
|
return $out; |
1206
|
1
|
|
|
|
|
5
|
}; |
1207
|
|
|
|
|
|
|
|
1208
|
1
|
|
|
|
|
2
|
my $out = $me; |
1209
|
|
|
|
|
|
|
|
1210
|
1
|
50
|
|
|
|
4
|
if($me->{params}->{lut}->{type} eq 'hsv') { |
1211
|
0
|
|
|
|
|
0
|
$out = (!t_hsv()) x $out; |
1212
|
|
|
|
|
|
|
} |
1213
|
|
|
|
|
|
|
|
1214
|
1
|
50
|
|
|
|
5
|
if(abs($me->{params}->{gamma}-1.0) > 1e-5) { |
1215
|
0
|
|
|
|
|
0
|
$out = $out x t_gamma($me->{params}->{gamma}); |
1216
|
|
|
|
|
|
|
} |
1217
|
|
|
|
|
|
|
|
1218
|
1
|
50
|
|
|
|
3
|
unless($me->{params}->{lsRGB}) { |
1219
|
1
|
|
|
|
|
3
|
$out = t_srgb(clip=>$me->{params}->{clip}, byte=>$me->{params}->{byte}) x $out; |
1220
|
|
|
|
|
|
|
} |
1221
|
|
|
|
|
|
|
|
1222
|
1
|
|
|
|
|
49
|
return $out; |
1223
|
|
|
|
|
|
|
} |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
################################################################################ |
1226
|
|
|
|
|
|
|
################################################################################ |
1227
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
############################## |
1231
|
|
|
|
|
|
|
|
1232
|
|
|
|
|
|
|
=head2 t_cieXYZ, t_xyz |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
=for ref |
1235
|
|
|
|
|
|
|
|
1236
|
|
|
|
|
|
|
The C transform (also C, which is a synonym) |
1237
|
|
|
|
|
|
|
converts the module-native lsRGB to the CIE XYZ representation. CIE |
1238
|
|
|
|
|
|
|
XYZ is a nonphysical RGB-style system that minimally represents every |
1239
|
|
|
|
|
|
|
physical color it is possible for humans to perceive in steady |
1240
|
|
|
|
|
|
|
illumination. It is related to sRGB by a linear transformation |
1241
|
|
|
|
|
|
|
(i.e. matrix multiplication) and forms the basis of many other color |
1242
|
|
|
|
|
|
|
systems (such as CIE xyY). |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
CIE XYZ values are defined in such a way that they are positive |
1245
|
|
|
|
|
|
|
definite for all human-perceptible colors, at the cost that the |
1246
|
|
|
|
|
|
|
primaries are nonphysical (they correspond to no possible spectral |
1247
|
|
|
|
|
|
|
color) |
1248
|
|
|
|
|
|
|
|
1249
|
|
|
|
|
|
|
C accepts the following options: |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
=over 3 |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
=item gamma (default 1) |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
This is taken to be a coded gamma value in the original lsRGB, which |
1256
|
|
|
|
|
|
|
is decoded before conversion to the CIE XYZ system. |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
=item rgb_system (default undef) |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
If present, this must be either the name of an RGB system or an RGB system |
1261
|
|
|
|
|
|
|
descriptor hash as described in C. If none is specified, then |
1262
|
|
|
|
|
|
|
the standard linearized sRGB used by the rest of the module is assumed. |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
=item use_system_gamma (default 0) |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
If this flag is set, and C is set also, then the RGB side |
1267
|
|
|
|
|
|
|
of the transform is taken to be gamma-encoded with the default value for |
1268
|
|
|
|
|
|
|
that RGB system. Unless you explicitly specify an RGB system (with a name |
1269
|
|
|
|
|
|
|
or a hash), this flag is ignored. |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
=back |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
=cut |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
*t_cieXYZ = \&t_xyz; |
1277
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
sub t_xyz { |
1279
|
2
|
|
|
2
|
1
|
751
|
my ($me) = _new(@_, 'CIE XYZ', |
1280
|
|
|
|
|
|
|
{gamma=>1, |
1281
|
|
|
|
|
|
|
rgb_system=>undef, |
1282
|
|
|
|
|
|
|
use_system_gamma=>0 |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
); |
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
# shortcut the common case |
1287
|
2
|
50
|
|
|
|
6
|
unless(defined($me->{params}->{rgb_system})) { |
1288
|
|
|
|
|
|
|
|
1289
|
2
|
|
|
|
|
3
|
$me->{params}->{mat} = $srgb2cxyz_mat; |
1290
|
2
|
|
|
|
|
2
|
$me->{params}->{inv} = $srgb2cxyz_inv; |
1291
|
|
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
} else { |
1293
|
0
|
|
|
|
|
0
|
my $rgb = get_rgb($me->{params}->{rgb_system}); |
1294
|
|
|
|
|
|
|
|
1295
|
0
|
|
|
|
|
0
|
my ($xr,$yr) = ($rgb->{r}->((0)),$rgb->{r}->((1))); |
1296
|
0
|
|
|
|
|
0
|
my ($xg,$yg) = ($rgb->{g}->((0)),$rgb->{g}->((1))); |
1297
|
0
|
|
|
|
|
0
|
my ($xb,$yb) = ($rgb->{b}->((0)),$rgb->{b}->((1))); |
1298
|
|
|
|
|
|
|
|
1299
|
0
|
|
|
|
|
0
|
my $Xr = $xr / ($yr + ($yr==0)); |
1300
|
0
|
|
|
|
|
0
|
my $Yr = 1; |
1301
|
0
|
|
|
|
|
0
|
my $Zr = (1 - $xr - $yr)/($yr+($yr==0)); |
1302
|
0
|
|
|
|
|
0
|
my $Xg = $xg / ($yg + ($yg==0)); |
1303
|
0
|
|
|
|
|
0
|
my $Yg = 1; |
1304
|
0
|
|
|
|
|
0
|
my $Zg = (1 - $xg - $yg)/($yg+($yg==0)); |
1305
|
0
|
|
|
|
|
0
|
my $Xb = $xb / ($yb + ($yb==0)); |
1306
|
0
|
|
|
|
|
0
|
my $Yb = 1; |
1307
|
0
|
|
|
|
|
0
|
my $Zb = (1 - $xb - $yb)/($yb+($yb==0)); |
1308
|
|
|
|
|
|
|
|
1309
|
0
|
|
|
|
|
0
|
my $M = pdl( [ $Xr, $Xg, $Xb ], [$Yr, $Yg, $Yb], [$Zr, $Zg, $Zb] ); |
1310
|
0
|
|
|
|
|
0
|
my $Minv = $M->inv; |
1311
|
|
|
|
|
|
|
|
1312
|
0
|
|
|
|
|
0
|
my ($xw, $yw, $Yw) = ($rgb->{w}->((0)),$rgb->{w}->((1)),$rgb->{w}->((2))); |
1313
|
0
|
|
|
|
|
0
|
my $Xw = $xw * $Yw / ($yw + ($yw==0)); |
1314
|
0
|
|
|
|
|
0
|
my $Zw = (1 - $xw - $yw)*$Yw / ($yw+($yw==0)); |
1315
|
0
|
|
|
|
|
0
|
my $XYZw = pdl($Xw,$Yw,$Zw); |
1316
|
|
|
|
|
|
|
|
1317
|
0
|
|
|
|
|
0
|
my $Srgb = ($Minv x $XYZw->(*1))->((0)); # row vector |
1318
|
0
|
|
|
|
|
0
|
$M *= $Srgb; |
1319
|
0
|
|
|
|
|
0
|
$me->{params}->{mat} = $M; |
1320
|
0
|
|
|
|
|
0
|
$me->{params}->{inv} = $M->inv; |
1321
|
|
|
|
|
|
|
|
1322
|
0
|
0
|
|
|
|
0
|
if($me->{params}->{use_system_gamma}) { |
1323
|
0
|
|
|
|
|
0
|
$me->{params}->{gamma} = $rgb->{gamma}; |
1324
|
|
|
|
|
|
|
} |
1325
|
|
|
|
|
|
|
} |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
# func and inv get linearized versions (gamma handled below) |
1328
|
|
|
|
|
|
|
$me->{func} = sub { |
1329
|
1
|
|
|
1
|
|
10
|
my($in, $opt) = @_; |
1330
|
|
|
|
|
|
|
|
1331
|
1
|
|
|
|
|
4
|
my $out = ( $opt->{mat} x $in->(*1) )->((0))->sever; |
1332
|
|
|
|
|
|
|
|
1333
|
1
|
50
|
|
|
|
91
|
if($in->is_inplace) { |
1334
|
0
|
|
|
|
|
0
|
$in .= $out; |
1335
|
0
|
|
|
|
|
0
|
$out = $in; |
1336
|
|
|
|
|
|
|
} |
1337
|
1
|
|
|
|
|
3
|
return $out; |
1338
|
2
|
|
|
|
|
9
|
}; |
1339
|
|
|
|
|
|
|
|
1340
|
|
|
|
|
|
|
$me->{inv} = sub { |
1341
|
1
|
|
|
1
|
|
9
|
my($in, $opt) = @_; |
1342
|
1
|
|
|
|
|
4
|
my $out = ( $opt->{inv} x $in->(*1) )->((0))->sever; |
1343
|
|
|
|
|
|
|
|
1344
|
1
|
50
|
|
|
|
55
|
if($in->is_inplace) { |
1345
|
0
|
|
|
|
|
0
|
$in .= $out; |
1346
|
0
|
|
|
|
|
0
|
$out = $in; |
1347
|
|
|
|
|
|
|
} |
1348
|
1
|
|
|
|
|
2
|
return $out; |
1349
|
2
|
|
|
|
|
3
|
}; |
1350
|
|
|
|
|
|
|
|
1351
|
2
|
|
|
|
|
5
|
return gammify($me); |
1352
|
|
|
|
|
|
|
} |
1353
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
|
1356
|
|
|
|
|
|
|
=head2 t_rgi |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
=for ref |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
Convert RGB to RG chroma with a separate intensity channel. |
1361
|
|
|
|
|
|
|
|
1362
|
|
|
|
|
|
|
Note that intensity is just the average of the R, G, and B values. |
1363
|
|
|
|
|
|
|
If you want perceptible luminance, use t_rgl or t_ycbcr instead. |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
=cut |
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
sub t_rgi { |
1368
|
1
|
|
|
1
|
1
|
431
|
my($me) = _new(@_, 'RGI', |
1369
|
|
|
|
|
|
|
{gamma=>1, |
1370
|
|
|
|
|
|
|
} |
1371
|
|
|
|
|
|
|
); |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
$me->{func} = sub { |
1374
|
1
|
|
|
1
|
|
192
|
my($in,$opt) = @_; |
1375
|
1
|
|
|
|
|
13
|
my $i = $in->sumover->(*1); |
1376
|
1
|
|
|
|
|
14
|
my $out = zeroes($in); |
1377
|
1
|
|
|
|
|
48
|
$out->(0:1) .= $in(0:1) / ($i+($i==0)); |
1378
|
1
|
|
|
|
|
48
|
$out->(2) .= $i/3; |
1379
|
1
|
50
|
|
|
|
28
|
if($in->is_inplace) { |
1380
|
0
|
|
|
|
|
0
|
$in .= $out; |
1381
|
0
|
|
|
|
|
0
|
return $in; |
1382
|
|
|
|
|
|
|
} |
1383
|
1
|
|
|
|
|
4
|
return $out; |
1384
|
1
|
|
|
|
|
6
|
}; |
1385
|
|
|
|
|
|
|
$me->{inv} = sub { |
1386
|
0
|
|
|
0
|
|
0
|
my($in,$opt) = @_; |
1387
|
0
|
|
|
|
|
0
|
my $out = zeroes($in); |
1388
|
0
|
|
|
|
|
0
|
$out->(0:1) .= $in(0:1); |
1389
|
0
|
|
|
|
|
0
|
$out->((2)) .= 1 - $in(0:1)->sumover; |
1390
|
0
|
|
|
|
|
0
|
$out *= $in->(2) * 3; |
1391
|
0
|
0
|
|
|
|
0
|
if($in->is_inplace) { |
1392
|
0
|
|
|
|
|
0
|
$in .= $out; |
1393
|
0
|
|
|
|
|
0
|
return $in; |
1394
|
|
|
|
|
|
|
} |
1395
|
0
|
|
|
|
|
0
|
return $out; |
1396
|
1
|
|
|
|
|
4
|
}; |
1397
|
|
|
|
|
|
|
|
1398
|
1
|
|
|
|
|
1
|
return $me; |
1399
|
|
|
|
|
|
|
} |
1400
|
|
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
|
1403
|
|
|
|
|
|
|
=head2 t_xyy and t_xyY |
1404
|
|
|
|
|
|
|
|
1405
|
|
|
|
|
|
|
=for ref |
1406
|
|
|
|
|
|
|
|
1407
|
|
|
|
|
|
|
Convert from sRGB to CIE xyY. The C system is part of the CIE |
1408
|
|
|
|
|
|
|
1931 color specification. Luminance is in the 2 coordinate, and |
1409
|
|
|
|
|
|
|
chrominance x and y are in the 0 and 1 coordinates. |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
This is the coordinate system in which "chromaticity diagrams" are |
1412
|
|
|
|
|
|
|
plotted. It is capable of representing every illuminant color that |
1413
|
|
|
|
|
|
|
can be perceived by the typical human eye, and also many that can't, |
1414
|
|
|
|
|
|
|
with positive-definite coordinates. |
1415
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
Most of the domain space (which runs over [0-1] in all three dimensions) |
1417
|
|
|
|
|
|
|
is inaccessible to most displays, because RGB gamuts are generally |
1418
|
|
|
|
|
|
|
smaller than the actual visual gamut, which in turn is a subset of the |
1419
|
|
|
|
|
|
|
actual xyY data space. |
1420
|
|
|
|
|
|
|
|
1421
|
|
|
|
|
|
|
=cut |
1422
|
|
|
|
|
|
|
|
1423
|
|
|
|
|
|
|
*t_xyY = \&t_xyy; |
1424
|
|
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
sub t_xyy { |
1426
|
0
|
|
|
0
|
1
|
0
|
my ($me) = _new(@_, 'CIE xyY', |
1427
|
|
|
|
|
|
|
{gamma=>1, |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
); |
1430
|
|
|
|
|
|
|
|
1431
|
|
|
|
|
|
|
$me->{func} = sub { |
1432
|
0
|
|
|
0
|
|
0
|
my($XYZ, $opt) = @_; |
1433
|
0
|
|
|
|
|
0
|
my $out = $XYZ/$XYZ->sumover->(*1); |
1434
|
0
|
|
|
|
|
0
|
$out->((2)) .= $XYZ->((1)); |
1435
|
0
|
0
|
|
|
|
0
|
if($XYZ->is_inplace) { |
1436
|
0
|
|
|
|
|
0
|
$XYZ .= $out; |
1437
|
0
|
|
|
|
|
0
|
$out = $XYZ; |
1438
|
|
|
|
|
|
|
} |
1439
|
0
|
|
|
|
|
0
|
return $out; |
1440
|
0
|
|
|
|
|
0
|
}; |
1441
|
|
|
|
|
|
|
|
1442
|
|
|
|
|
|
|
$me->{inv} = sub { |
1443
|
0
|
|
|
0
|
|
0
|
my($in,$opt) = @_; |
1444
|
|
|
|
|
|
|
# make xYy |
1445
|
0
|
|
|
|
|
0
|
my $XYZ = zeroes($in); |
1446
|
|
|
|
|
|
|
|
1447
|
|
|
|
|
|
|
# stuff X and Z in there. |
1448
|
0
|
|
|
|
|
0
|
my $in1 = $in->((1))+($in->((1))==0); |
1449
|
0
|
|
|
|
|
0
|
$XYZ->((0)) .= $in->((0)) * $in->((2)) / $in1; |
1450
|
0
|
|
|
|
|
0
|
$XYZ->((1)) .= $in->((2)); |
1451
|
0
|
|
|
|
|
0
|
$XYZ->((2)) .= $in->((2)) * (1 - $in->((0)) - $in->((1))) / $in1; |
1452
|
|
|
|
|
|
|
|
1453
|
0
|
0
|
|
|
|
0
|
if($in->is_inplace) { |
1454
|
0
|
|
|
|
|
0
|
$in .= $XYZ; |
1455
|
0
|
|
|
|
|
0
|
$XYZ = $in; |
1456
|
|
|
|
|
|
|
} |
1457
|
0
|
|
|
|
|
0
|
return $XYZ; |
1458
|
0
|
|
|
|
|
0
|
}; |
1459
|
0
|
|
|
|
|
0
|
return gammify( $me x t_xyz() ); |
1460
|
|
|
|
|
|
|
} |
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
|
1463
|
|
|
|
|
|
|
###################################################################### |
1464
|
|
|
|
|
|
|
|
1465
|
|
|
|
|
|
|
=head2 t_cielab or t_lab |
1466
|
|
|
|
|
|
|
|
1467
|
|
|
|
|
|
|
=for usage |
1468
|
|
|
|
|
|
|
|
1469
|
|
|
|
|
|
|
$t = t_cielab(); |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
=for ref |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
Convert RGB to CIE Lab colors. C stands for Lightness, |
1474
|
|
|
|
|
|
|
"a", and "b", representing the overall luminance detection and |
1475
|
|
|
|
|
|
|
two opponent systems (a: red/green, and b:yellow/blue) in the human |
1476
|
|
|
|
|
|
|
eye. Lab colors are approximately perceptually uniform: they're |
1477
|
|
|
|
|
|
|
mapped using a nonlinear transformation involving cube roots. Lab |
1478
|
|
|
|
|
|
|
has the property that Euclidean distances of equal size in the space |
1479
|
|
|
|
|
|
|
yield approximately equal perceptual shifts in the represented color. |
1480
|
|
|
|
|
|
|
|
1481
|
|
|
|
|
|
|
Lightness runs 0-100, and the a and b opponent systems run -100 to +100. |
1482
|
|
|
|
|
|
|
|
1483
|
|
|
|
|
|
|
The Lab space includes the entire CIE XYZ gamut and many "impossible colors". |
1484
|
|
|
|
|
|
|
that cannot be represented directly with physical light. Many of these |
1485
|
|
|
|
|
|
|
"impossible colors" (also "chimeric colors") can be experienced directly |
1486
|
|
|
|
|
|
|
using visual fatigue effects, and can be classified using Lab. |
1487
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
Lab is easiest to convert directly from XYZ space, so the C constructor |
1489
|
|
|
|
|
|
|
returns a compound transform of C and C. |
1490
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
=cut |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
sub f_lab { |
1494
|
0
|
|
|
0
|
0
|
0
|
my $in = shift; |
1495
|
0
|
|
|
|
|
0
|
my $delta = 6/29; |
1496
|
0
|
|
|
|
|
0
|
my $delta3 = $delta * $delta * $delta; |
1497
|
|
|
|
|
|
|
return ( |
1498
|
0
|
|
|
|
|
0
|
($in > $delta3) * ( $in * (($in->abs+($in==0)) ** (0.333-1)) ) + |
1499
|
|
|
|
|
|
|
($in <= $delta3) * ( $in / (3 * $delta * $delta) + 4/29 ) |
1500
|
|
|
|
|
|
|
); |
1501
|
|
|
|
|
|
|
} |
1502
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
sub f_lab_inv { |
1505
|
0
|
|
|
0
|
0
|
0
|
my $in = shift; |
1506
|
0
|
|
|
|
|
0
|
my $delta = 6/29; |
1507
|
|
|
|
|
|
|
|
1508
|
|
|
|
|
|
|
return ( |
1509
|
0
|
|
|
|
|
0
|
($in > $delta) * ($in*$in*$in) + |
1510
|
|
|
|
|
|
|
($in <= $delta) * (3 * $delta * $delta * ($in - 4/29)) |
1511
|
|
|
|
|
|
|
); |
1512
|
|
|
|
|
|
|
} |
1513
|
|
|
|
|
|
|
|
1514
|
|
|
|
|
|
|
=head2 t_xyz2lab |
1515
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
=for usage |
1517
|
|
|
|
|
|
|
|
1518
|
|
|
|
|
|
|
$t = t_xyz2lab(); |
1519
|
|
|
|
|
|
|
|
1520
|
|
|
|
|
|
|
=for ref |
1521
|
|
|
|
|
|
|
|
1522
|
|
|
|
|
|
|
Converts CIE XYZ to CIE Lab. |
1523
|
|
|
|
|
|
|
|
1524
|
|
|
|
|
|
|
=cut |
1525
|
|
|
|
|
|
|
|
1526
|
|
|
|
|
|
|
sub t_xyz2lab { |
1527
|
|
|
|
|
|
|
|
1528
|
0
|
|
|
0
|
1
|
0
|
my ($me) = _new(@_,'XYZ->Lab', |
1529
|
|
|
|
|
|
|
{ |
1530
|
|
|
|
|
|
|
white=>"D65", |
1531
|
|
|
|
|
|
|
} |
1532
|
|
|
|
|
|
|
); |
1533
|
|
|
|
|
|
|
|
1534
|
|
|
|
|
|
|
# get and store illuminant XYZ |
1535
|
0
|
|
|
|
|
0
|
my $wp_xyy = xyy_from_illuminant($me->{params}->{white}); |
1536
|
0
|
|
|
|
|
0
|
$me->{params}->{wp_xyz} = $wp_xyy->copy; |
1537
|
0
|
|
|
|
|
0
|
$me->{params}->{wp_xyz}->(2) .= 1 - $wp_xyy->(0) - $wp_xyy->(1); |
1538
|
0
|
|
|
|
|
0
|
$me->{params}->{wp_xyz} *= $wp_xyy->(2); |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
# input is XYZ by the time it gets here |
1542
|
|
|
|
|
|
|
$me->{func} = sub { |
1543
|
0
|
|
|
0
|
|
0
|
my($in,$opt) = @_; |
1544
|
0
|
|
|
|
|
0
|
my($out) = zeroes($in); |
1545
|
|
|
|
|
|
|
|
1546
|
0
|
|
|
|
|
0
|
my $wp = $opt->{wp_xyz} + ($opt->{wp_xyz}==0); |
1547
|
|
|
|
|
|
|
|
1548
|
0
|
|
|
|
|
0
|
my $FYp = f_lab( $in->((1)) / $wp->((1)) ); |
1549
|
|
|
|
|
|
|
|
1550
|
0
|
|
|
|
|
0
|
$out->((0)) .= 116 * $FYp - 16; |
1551
|
0
|
|
|
|
|
0
|
$out->((1)) .= 500 * ( f_lab( $in->((0)) / $wp->((0)) ) - $FYp ); |
1552
|
0
|
|
|
|
|
0
|
$out->((2)) .= 200 * ( $FYp - f_lab( $in->((2)) / $wp->((2)) ) ); |
1553
|
|
|
|
|
|
|
|
1554
|
0
|
0
|
|
|
|
0
|
if($in->is_inplace) { |
1555
|
0
|
|
|
|
|
0
|
$in .= $out; |
1556
|
0
|
|
|
|
|
0
|
$out = $in; |
1557
|
|
|
|
|
|
|
} |
1558
|
0
|
|
|
|
|
0
|
return $out; |
1559
|
0
|
|
|
|
|
0
|
}; |
1560
|
|
|
|
|
|
|
|
1561
|
|
|
|
|
|
|
$me->{inv} = sub { |
1562
|
0
|
|
|
0
|
|
0
|
my($in,$opt) = @_; |
1563
|
0
|
|
|
|
|
0
|
my($out) = zeroes($in); |
1564
|
|
|
|
|
|
|
|
1565
|
0
|
|
|
|
|
0
|
my $Lterm = ($in->((0))+16)/116; |
1566
|
|
|
|
|
|
|
|
1567
|
0
|
|
|
|
|
0
|
$out->((0)) .= $opt->{wp_xyz}->((0)) * f_lab_inv( $Lterm + $in->((1))/500 ); |
1568
|
0
|
|
|
|
|
0
|
$out->((1)) .= $opt->{wp_xyz}->((1)) * f_lab_inv( $Lterm ); |
1569
|
0
|
|
|
|
|
0
|
$out->((2)) .= $opt->{wp_xyz}->((2)) * f_lab_inv( $Lterm - $in->((2))/200 ); |
1570
|
|
|
|
|
|
|
|
1571
|
0
|
0
|
|
|
|
0
|
if($in->is_inplace) { |
1572
|
0
|
|
|
|
|
0
|
$in .= $out; |
1573
|
0
|
|
|
|
|
0
|
$out = $in; |
1574
|
|
|
|
|
|
|
} |
1575
|
0
|
|
|
|
|
0
|
return $out; |
1576
|
0
|
|
|
|
|
0
|
}; |
1577
|
|
|
|
|
|
|
|
1578
|
0
|
|
|
|
|
0
|
return $me; |
1579
|
|
|
|
|
|
|
} |
1580
|
|
|
|
|
|
|
|
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
|
1583
|
|
|
|
|
|
|
sub t_lab { |
1584
|
0
|
|
|
0
|
1
|
0
|
my ($me) = _new(@_, 'Lab', |
1585
|
|
|
|
|
|
|
{ |
1586
|
|
|
|
|
|
|
gamma => 1.0, |
1587
|
|
|
|
|
|
|
white=>'D65', |
1588
|
|
|
|
|
|
|
} |
1589
|
|
|
|
|
|
|
); |
1590
|
|
|
|
|
|
|
return ( |
1591
|
|
|
|
|
|
|
t_xyz2lab(white=>$me->{params}->{white} ) x |
1592
|
|
|
|
|
|
|
t_xyz( gamma=>$me->{params}->{gamma}) |
1593
|
0
|
|
|
|
|
0
|
); |
1594
|
|
|
|
|
|
|
} |
1595
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
|
1597
|
|
|
|
|
|
|
=head2 t_cmyk |
1598
|
|
|
|
|
|
|
|
1599
|
|
|
|
|
|
|
converts rgb to cmyk in the most straightforward way (by subtracting |
1600
|
|
|
|
|
|
|
RGB values from unity). |
1601
|
|
|
|
|
|
|
|
1602
|
|
|
|
|
|
|
CMYK and other process spaces are very complicated; this transform |
1603
|
|
|
|
|
|
|
presents only a relatively simple conversion that does not take into |
1604
|
|
|
|
|
|
|
account ink gamut variation or many other effects. |
1605
|
|
|
|
|
|
|
|
1606
|
|
|
|
|
|
|
There *is* a provision for halftone gamma correction: "htgamma", which |
1607
|
|
|
|
|
|
|
works exactly like the rgb gamma correction but is applied to the CMYK |
1608
|
|
|
|
|
|
|
output. |
1609
|
|
|
|
|
|
|
|
1610
|
|
|
|
|
|
|
Options: |
1611
|
|
|
|
|
|
|
|
1612
|
|
|
|
|
|
|
=over 3 |
1613
|
|
|
|
|
|
|
|
1614
|
|
|
|
|
|
|
=item gamma (default 1) |
1615
|
|
|
|
|
|
|
|
1616
|
|
|
|
|
|
|
The standard gamma affecting the RGB cube |
1617
|
|
|
|
|
|
|
|
1618
|
|
|
|
|
|
|
=item htgamma (default 1) |
1619
|
|
|
|
|
|
|
|
1620
|
|
|
|
|
|
|
A "halftone gamma" that is suitable for non-wash output processes |
1621
|
|
|
|
|
|
|
such as halftoning. it acts on the CMYK values themselves. |
1622
|
|
|
|
|
|
|
|
1623
|
|
|
|
|
|
|
=item byte (default 0) |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
If present, the CMYK side is scaled to 0-255 and converted to a byte type. |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
=back |
1628
|
|
|
|
|
|
|
|
1629
|
|
|
|
|
|
|
=cut |
1630
|
|
|
|
|
|
|
; |
1631
|
|
|
|
|
|
|
sub t_cmyk { |
1632
|
1
|
|
|
1
|
1
|
239
|
my($me) = _new(@_, "CMYK", |
1633
|
|
|
|
|
|
|
{gamma=>1, |
1634
|
|
|
|
|
|
|
pigment=>0, |
1635
|
|
|
|
|
|
|
density=>2, |
1636
|
|
|
|
|
|
|
htgamma=>1, |
1637
|
|
|
|
|
|
|
clip=>0, |
1638
|
|
|
|
|
|
|
byte=>0 |
1639
|
|
|
|
|
|
|
} |
1640
|
|
|
|
|
|
|
); |
1641
|
1
|
|
|
|
|
3
|
$me->{idim} = 3; |
1642
|
1
|
|
|
|
|
2
|
$me->{odim} = 4; |
1643
|
|
|
|
|
|
|
|
1644
|
|
|
|
|
|
|
$me->{func} = sub { |
1645
|
2
|
|
|
2
|
|
780
|
my($in,$opt) = @_; |
1646
|
2
|
|
|
|
|
6
|
my $out = zeroes( 4, $in->((0))->dims ); |
1647
|
|
|
|
|
|
|
|
1648
|
2
|
|
|
|
|
151
|
my $Kp = $in->maximum->(*1); |
1649
|
2
|
|
|
|
|
22
|
(my $K = $out->(3)) .= 1 - $Kp; |
1650
|
2
|
|
|
|
|
56
|
$out->(0:2) .= ($Kp - $in->(0:2)) / $Kp; |
1651
|
2
|
|
|
|
|
71
|
$out->((3))->where($Kp==0) .= 1; |
1652
|
2
|
|
|
|
|
171
|
$out->(0:2)->mv(0,-1)->where($Kp==0) .= 0; |
1653
|
|
|
|
|
|
|
|
1654
|
2
|
50
|
33
|
|
|
123
|
if(defined($opt->{htgamma}) && $opt->{htgamma} != 1) { |
1655
|
0
|
|
|
|
|
0
|
$out *= ($out->abs) ** ($opt->{htgamma} - 1); |
1656
|
|
|
|
|
|
|
} |
1657
|
|
|
|
|
|
|
|
1658
|
2
|
50
|
|
|
|
6
|
if($opt->{clip}) { |
1659
|
0
|
|
|
|
|
0
|
$out->inplace->clip(0,1); |
1660
|
|
|
|
|
|
|
} |
1661
|
|
|
|
|
|
|
|
1662
|
2
|
50
|
|
|
|
3
|
if($opt->{byte}) { |
1663
|
0
|
|
|
|
|
0
|
$out = (256*$out)->clip(0,255.99999); |
1664
|
|
|
|
|
|
|
} |
1665
|
2
|
|
|
|
|
9
|
return $out; |
1666
|
1
|
|
|
|
|
4
|
}; |
1667
|
|
|
|
|
|
|
|
1668
|
|
|
|
|
|
|
$me->{inv} = sub { |
1669
|
2
|
|
|
2
|
|
1019
|
my($in,$opt) = @_; |
1670
|
2
|
|
|
|
|
9
|
my $out = zeroes( 3, $in->((0))->dims ); |
1671
|
|
|
|
|
|
|
|
1672
|
2
|
|
|
|
|
107
|
$in = $in->new_or_inplace; |
1673
|
|
|
|
|
|
|
|
1674
|
2
|
50
|
|
|
|
39
|
if($opt->{byte}) { |
1675
|
0
|
|
|
|
|
0
|
$in = $in / pdl(256); # makes copy |
1676
|
|
|
|
|
|
|
} |
1677
|
|
|
|
|
|
|
|
1678
|
2
|
50
|
33
|
|
|
10
|
if(defined($opt->{htgamma}) && $opt->{htgamma} != 1) { |
1679
|
0
|
|
|
|
|
0
|
$in *= ($in->abs) ** (1.0/$opt->{htgamma} - 1); |
1680
|
|
|
|
|
|
|
} |
1681
|
2
|
|
|
|
|
11
|
my $Kp = 1.0 - $in->(3); |
1682
|
2
|
|
|
|
|
40
|
$out .= $Kp * ( 1 - $in->(0:2) ); |
1683
|
2
|
|
|
|
|
63
|
return $out; |
1684
|
1
|
|
|
|
|
5
|
}; |
1685
|
|
|
|
|
|
|
|
1686
|
1
|
|
|
|
|
2
|
return gammify($me); |
1687
|
|
|
|
|
|
|
|
1688
|
|
|
|
|
|
|
} |
1689
|
|
|
|
|
|
|
|
1690
|
|
|
|
|
|
|
=head2 t_hsl and t_hsv |
1691
|
|
|
|
|
|
|
|
1692
|
|
|
|
|
|
|
=for usage |
1693
|
|
|
|
|
|
|
|
1694
|
|
|
|
|
|
|
$rgb = $hsl->invert($t_hsl()); |
1695
|
|
|
|
|
|
|
|
1696
|
|
|
|
|
|
|
=for ref |
1697
|
|
|
|
|
|
|
|
1698
|
|
|
|
|
|
|
HSL stands for Hue, Saturation, Lightness. It's not an absolute |
1699
|
|
|
|
|
|
|
color space, simply derived from each RGB (by default, linearized |
1700
|
|
|
|
|
|
|
sRGB). it has the same gamut as the host RGB system. The coordinates |
1701
|
|
|
|
|
|
|
are hexagonal on the (RYGCBM) hexagon, following the nearest face of |
1702
|
|
|
|
|
|
|
the (diagonally sliced) RGB cube. |
1703
|
|
|
|
|
|
|
|
1704
|
|
|
|
|
|
|
HSL is a double-cone system, so iso-L surfaces are close to the plane |
1705
|
|
|
|
|
|
|
perpendicular to the double-diagonal white/illuminant line R=G=B. |
1706
|
|
|
|
|
|
|
This has the effect of reducing saturation at high lightness levels, |
1707
|
|
|
|
|
|
|
but maintains luminosity independent of saturation. Maximum |
1708
|
|
|
|
|
|
|
saturation occurs when S=1 and L=0.5; at higher values of L, colors |
1709
|
|
|
|
|
|
|
grow less saturated and more pastel, so that L follows total |
1710
|
|
|
|
|
|
|
luminosity of the output. |
1711
|
|
|
|
|
|
|
|
1712
|
|
|
|
|
|
|
HSV is a stacked-cone system: iso-V surfaces are parallel to the |
1713
|
|
|
|
|
|
|
bright faces of the RGB cube, so maximal bright saturation occurs when |
1714
|
|
|
|
|
|
|
S=1 and V=1. This means that output luminosity drops with saturation, |
1715
|
|
|
|
|
|
|
but due to Helmholtz-Kolrausch effect (linking saturation to apparent |
1716
|
|
|
|
|
|
|
brightness) the *perceived* brightness is less S-dependent: V follows |
1717
|
|
|
|
|
|
|
total *apparent brightness* of the output, though output luminosity |
1718
|
|
|
|
|
|
|
drops with S. |
1719
|
|
|
|
|
|
|
|
1720
|
|
|
|
|
|
|
You can represent out-of-gamut values in either system, by using |
1721
|
|
|
|
|
|
|
S values greater than unity, or "illegal" V or L values. |
1722
|
|
|
|
|
|
|
|
1723
|
|
|
|
|
|
|
Hue, Saturation, and (Lightness or Value) each run from 0 to 1. |
1724
|
|
|
|
|
|
|
|
1725
|
|
|
|
|
|
|
By default, the hue value follows a sin**4 scaling along each side of |
1726
|
|
|
|
|
|
|
the RYGCBM hexagon. This softens the boundaries near the edges of the |
1727
|
|
|
|
|
|
|
RGB cube, giving a better peceptual "color-wheel" transition between |
1728
|
|
|
|
|
|
|
hues. There is a flag to switch to the linear behavior described in, |
1729
|
|
|
|
|
|
|
e.g., the Wikipedia article on the HSV system. |
1730
|
|
|
|
|
|
|
|
1731
|
|
|
|
|
|
|
You can encode the Lightness or Value with a gamma value ("lgamma") if |
1732
|
|
|
|
|
|
|
desired. |
1733
|
|
|
|
|
|
|
|
1734
|
|
|
|
|
|
|
Options: |
1735
|
|
|
|
|
|
|
|
1736
|
|
|
|
|
|
|
=over 3 |
1737
|
|
|
|
|
|
|
|
1738
|
|
|
|
|
|
|
=item gamma (default 1) |
1739
|
|
|
|
|
|
|
|
1740
|
|
|
|
|
|
|
Treat the base RGB as gamma-encoded (default 1 is linear) |
1741
|
|
|
|
|
|
|
|
1742
|
|
|
|
|
|
|
=item lgamma (default 1) |
1743
|
|
|
|
|
|
|
|
1744
|
|
|
|
|
|
|
Treat the L coordinate as gamma-encoded (default 1 is linear). |
1745
|
|
|
|
|
|
|
|
1746
|
|
|
|
|
|
|
=item hsv (default 0 if called as "t_hsl", 1 if called as "t_hsv") |
1747
|
|
|
|
|
|
|
|
1748
|
|
|
|
|
|
|
Sets which of the HSL/HSV transform is to be used. |
1749
|
|
|
|
|
|
|
|
1750
|
|
|
|
|
|
|
=item hue_linear (default 0) |
1751
|
|
|
|
|
|
|
|
1752
|
|
|
|
|
|
|
This flag determines how the hue ("angle") is calculated. By default, |
1753
|
|
|
|
|
|
|
a sin**4 scaling is used along each branch of the RYGCBM hexagon, |
1754
|
|
|
|
|
|
|
to soften the perceptual effects at the corners. If you set this flag, |
1755
|
|
|
|
|
|
|
then the calculated "hue" is linear along each branch of the hexagon, |
1756
|
|
|
|
|
|
|
to match (e.g.) the Wikipedia definition. |
1757
|
|
|
|
|
|
|
|
1758
|
|
|
|
|
|
|
=back |
1759
|
|
|
|
|
|
|
|
1760
|
|
|
|
|
|
|
=cut |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
sub t_hsl { |
1763
|
2
|
|
|
2
|
1
|
433
|
my($me) = _new(@_,"HSL", |
1764
|
|
|
|
|
|
|
{gamma=>1, |
1765
|
|
|
|
|
|
|
lgamma=>1, |
1766
|
|
|
|
|
|
|
hue_linear=>0, |
1767
|
|
|
|
|
|
|
hsv=>0 |
1768
|
|
|
|
|
|
|
} |
1769
|
|
|
|
|
|
|
); |
1770
|
|
|
|
|
|
|
|
1771
|
2
|
100
|
|
|
|
7
|
$me->{name} = "HSV" if($me->{params}->{hsv}); |
1772
|
|
|
|
|
|
|
|
1773
|
|
|
|
|
|
|
$me->{func} = sub { |
1774
|
2
|
|
|
2
|
|
391
|
my($in, $opt) = @_; |
1775
|
2
|
|
|
|
|
7
|
my $out = zeroes($in); |
1776
|
|
|
|
|
|
|
|
1777
|
2
|
|
|
|
|
94
|
my $Cmax = $in->maximum; |
1778
|
2
|
|
|
|
|
26
|
my $Cmin = $in->minimum; |
1779
|
2
|
|
|
|
|
32
|
my $maxdex = $in->qsorti->((2))->sever; |
1780
|
2
|
|
|
|
|
37
|
my $Delta = ( $Cmax - $Cmin ); |
1781
|
|
|
|
|
|
|
|
1782
|
2
|
|
|
|
|
7
|
my $dexes = ($maxdex->(*1) + pdl(0,1,2)) % 3; |
1783
|
|
|
|
|
|
|
|
1784
|
2
|
|
|
|
|
99
|
my $H = $out->((0)); |
1785
|
|
|
|
|
|
|
|
1786
|
2
|
50
|
|
|
|
18
|
if($opt->{hue_linear}) { |
1787
|
|
|
|
|
|
|
## Old linear method |
1788
|
0
|
|
|
|
|
0
|
$H .= ( |
1789
|
|
|
|
|
|
|
(($in->index1d($dexes->(1)) - $in->index1d($dexes->(2)))->((0))/($Delta+($Delta==0))) |
1790
|
|
|
|
|
|
|
+ 2 * $dexes->((0)) ) ; |
1791
|
|
|
|
|
|
|
|
1792
|
0
|
|
|
|
|
0
|
$H += 6*($H<0); |
1793
|
0
|
|
|
|
|
0
|
$H /= 6; |
1794
|
|
|
|
|
|
|
} else { |
1795
|
|
|
|
|
|
|
## New hotness: smooth transitions at corners |
1796
|
2
|
|
|
|
|
6
|
my $Hint = 2*$dexes->((0)); |
1797
|
2
|
|
|
|
|
34
|
my $Hfrac = (($in->index1d($dexes->(1)) - $in->index1d($dexes->(2)))->((0))/($Delta+($Delta==0))); |
1798
|
2
|
|
|
|
|
186
|
my $Hfs = -1*($Hfrac<0) + ($Hfrac >= 0); |
1799
|
2
|
|
|
|
|
82
|
$Hfrac .= $Hfs * ( asin( ($Hfrac->abs) ** 0.25 ) * 2/$PI ); |
1800
|
2
|
|
|
|
|
39
|
$H .= $Hint + $Hfrac; |
1801
|
2
|
|
|
|
|
18
|
$H /= 6; |
1802
|
|
|
|
|
|
|
} |
1803
|
|
|
|
|
|
|
|
1804
|
2
|
|
|
|
|
35
|
$H += ($H<0); |
1805
|
|
|
|
|
|
|
|
1806
|
|
|
|
|
|
|
# Lightness and Saturation |
1807
|
2
|
|
|
|
|
21
|
my $L = $out->((2)); |
1808
|
2
|
100
|
|
|
|
18
|
if($opt->{hsv}) { |
1809
|
1
|
|
|
|
|
2
|
$L .= $Cmax; |
1810
|
1
|
|
|
|
|
10
|
$out->((1)) .= $Delta / ($L + ($L==0)); |
1811
|
|
|
|
|
|
|
} else { |
1812
|
1
|
|
|
|
|
10
|
$L .= ($Cmax + $Cmin)/2; |
1813
|
1
|
|
|
|
|
13
|
$out->((1)) .= $Delta / (1 - (2*$L-1)->abs + ($L==0 | $L==1)); |
1814
|
|
|
|
|
|
|
} |
1815
|
|
|
|
|
|
|
|
1816
|
|
|
|
|
|
|
|
1817
|
2
|
50
|
|
|
|
124
|
if( $opt->{lgamma} != 1 ){ |
1818
|
0
|
|
|
|
|
0
|
$L .= $L * (($L->abs + ($L==0)) ** (1.0/$opt->{lgamma} - 1)); |
1819
|
|
|
|
|
|
|
} |
1820
|
|
|
|
|
|
|
|
1821
|
2
|
50
|
|
|
|
8
|
if($in->is_inplace) { |
1822
|
0
|
|
|
|
|
0
|
$in .= $out; |
1823
|
0
|
|
|
|
|
0
|
$out = $in; |
1824
|
|
|
|
|
|
|
} |
1825
|
2
|
|
|
|
|
12
|
return $out; |
1826
|
2
|
|
|
|
|
45
|
}; |
1827
|
|
|
|
|
|
|
|
1828
|
|
|
|
|
|
|
$me->{inv} = sub { |
1829
|
2
|
|
|
2
|
|
902
|
my($in,$opt) = @_; |
1830
|
|
|
|
|
|
|
|
1831
|
2
|
|
|
|
|
8
|
my $H = $in->((0))*6; |
1832
|
2
|
|
|
|
|
47
|
my $S = $in->((1)); |
1833
|
2
|
|
|
|
|
17
|
my $L = $in->((2)); |
1834
|
|
|
|
|
|
|
|
1835
|
2
|
50
|
|
|
|
17
|
if($opt->{lgamma} != 1) { |
1836
|
0
|
|
|
|
|
0
|
$L = $L * (($L->abs + ($L==0)) ** ($opt->{lgamma}-1)); |
1837
|
|
|
|
|
|
|
} |
1838
|
|
|
|
|
|
|
|
1839
|
2
|
|
|
|
|
4
|
my $ZCX = zeroes($in); |
1840
|
2
|
|
|
|
|
77
|
my $C = $ZCX->((1)); |
1841
|
2
|
|
|
|
|
14
|
my $m; |
1842
|
2
|
100
|
|
|
|
4
|
if($opt->{hsv}) { |
1843
|
1
|
|
|
|
|
8
|
$C .= $L * $S; |
1844
|
1
|
|
|
|
|
13
|
$m = $L - $C; |
1845
|
|
|
|
|
|
|
} else { |
1846
|
1
|
|
|
|
|
26
|
$C .= (1 - (2*$L - 1)->abs) * $S; |
1847
|
1
|
|
|
|
|
23
|
$m = $L - $C/2; |
1848
|
|
|
|
|
|
|
} |
1849
|
|
|
|
|
|
|
|
1850
|
2
|
50
|
|
|
|
7
|
if($opt->{hue_linear}){ |
1851
|
|
|
|
|
|
|
## Old linear method |
1852
|
0
|
|
|
|
|
0
|
$ZCX->((2)) .= $C * (1 - ($H % 2 - 1)->abs); |
1853
|
|
|
|
|
|
|
} else { |
1854
|
|
|
|
|
|
|
## New hotness: smooth transitions at corners. |
1855
|
2
|
|
|
|
|
5
|
$ZCX->((2)) .= $C * sin($PI/2 * (1 - ($H % 2 - 1)->abs))**4; |
1856
|
|
|
|
|
|
|
} |
1857
|
|
|
|
|
|
|
|
1858
|
2
|
|
|
|
|
158
|
my $dexes = pdl( [1,2,0], [2,1,0], [0,1,2], [0,2,1], [2,0,1], [1,0,2] )->mv(1,0)->sever; |
1859
|
2
|
|
|
|
|
68
|
my $dex = $dexes->index1d($H->floor->(*1,*1) % 6)->((0))->sever; # 3x(threads) |
1860
|
2
|
|
|
|
|
91
|
my $out = $ZCX->index1d($dex)->sever + $m->(*1); |
1861
|
|
|
|
|
|
|
|
1862
|
2
|
50
|
|
|
|
35
|
if($in->is_inplace) { |
1863
|
0
|
|
|
|
|
0
|
$in .= $out; |
1864
|
0
|
|
|
|
|
0
|
$out = $in; |
1865
|
|
|
|
|
|
|
} |
1866
|
|
|
|
|
|
|
|
1867
|
2
|
|
|
|
|
13
|
return $out; |
1868
|
2
|
|
|
|
|
11
|
}; |
1869
|
|
|
|
|
|
|
|
1870
|
2
|
|
|
|
|
3
|
return gammify($me); |
1871
|
|
|
|
|
|
|
} |
1872
|
|
|
|
|
|
|
|
1873
|
|
|
|
|
|
|
|
1874
|
|
|
|
|
|
|
sub t_hsv { |
1875
|
1
|
|
|
1
|
1
|
424
|
my($me) = _new(@_,"HSL", |
1876
|
|
|
|
|
|
|
{gamma=>1, |
1877
|
|
|
|
|
|
|
lgamma=>1, |
1878
|
|
|
|
|
|
|
hsv=>1 |
1879
|
|
|
|
|
|
|
} |
1880
|
|
|
|
|
|
|
); |
1881
|
1
|
|
|
|
|
2
|
return t_hsl(%{$me->{params}}); |
|
1
|
|
|
|
|
4
|
|
1882
|
|
|
|
|
|
|
} |
1883
|
|
|
|
|
|
|
|
1884
|
|
|
|
|
|
|
|
1885
|
|
|
|
|
|
|
|
1886
|
|
|
|
|
|
|
=head2 t_shift_illuminant |
1887
|
|
|
|
|
|
|
|
1888
|
|
|
|
|
|
|
=for ref |
1889
|
|
|
|
|
|
|
|
1890
|
|
|
|
|
|
|
C shifts a color from an old RGB system to a new one |
1891
|
|
|
|
|
|
|
with a different white point. It accepts either a PDL containing a |
1892
|
|
|
|
|
|
|
CIE xyY representation of the new illuminant, or a name of the new illuminant, |
1893
|
|
|
|
|
|
|
and some options. |
1894
|
|
|
|
|
|
|
|
1895
|
|
|
|
|
|
|
Because this is shifting RGB to RGB in the same representation, gamma |
1896
|
|
|
|
|
|
|
transformations get re-encoded afterward: if you use, for example, |
1897
|
|
|
|
|
|
|
C2>, then the RGB values are squared, then transformed, then |
1898
|
|
|
|
|
|
|
square-rooted. |
1899
|
|
|
|
|
|
|
|
1900
|
|
|
|
|
|
|
Options are: |
1901
|
|
|
|
|
|
|
|
1902
|
|
|
|
|
|
|
=over 3 |
1903
|
|
|
|
|
|
|
|
1904
|
|
|
|
|
|
|
=item gamma (default=1) |
1905
|
|
|
|
|
|
|
|
1906
|
|
|
|
|
|
|
If present, this is the gamma coefficient for the representation of |
1907
|
|
|
|
|
|
|
both the source and destination RGB spaces. |
1908
|
|
|
|
|
|
|
|
1909
|
|
|
|
|
|
|
=item from (default="D65") |
1910
|
|
|
|
|
|
|
|
1911
|
|
|
|
|
|
|
If present, this is the xyY or name of the OLD illuminant. The default |
1912
|
|
|
|
|
|
|
is D65, the illuminant for sRGB (and therefore lsRGB as well). |
1913
|
|
|
|
|
|
|
|
1914
|
|
|
|
|
|
|
=item basis (default="sRGB") |
1915
|
|
|
|
|
|
|
|
1916
|
|
|
|
|
|
|
If present, this needs to be either "sRGB" or "XYZ" (case insensitive). |
1917
|
|
|
|
|
|
|
If it's sRGB, the input and output are treated as standard lsRGB coordinates. |
1918
|
|
|
|
|
|
|
If it's XYZ, then the input and output are in CIE XYZ coordinates. |
1919
|
|
|
|
|
|
|
|
1920
|
|
|
|
|
|
|
=item method (default="Bradford") |
1921
|
|
|
|
|
|
|
|
1922
|
|
|
|
|
|
|
This can be "Bradford", "Von Kries", "XYZ", or a 3x3 matrix Ma (see |
1923
|
|
|
|
|
|
|
C) |
1924
|
|
|
|
|
|
|
|
1925
|
|
|
|
|
|
|
=back |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
=cut |
1928
|
|
|
|
|
|
|
|
1929
|
|
|
|
|
|
|
sub t_shift_illuminant { |
1930
|
0
|
|
|
0
|
1
|
0
|
my $new_illuminant = shift; |
1931
|
0
|
|
|
|
|
0
|
my($me) = _new(@_, 'New illuminant', |
1932
|
|
|
|
|
|
|
{gamma =>1, |
1933
|
|
|
|
|
|
|
from => "D65", |
1934
|
|
|
|
|
|
|
basis => 'rgb', |
1935
|
|
|
|
|
|
|
method=>"Bradford" |
1936
|
|
|
|
|
|
|
} |
1937
|
|
|
|
|
|
|
); |
1938
|
|
|
|
|
|
|
|
1939
|
0
|
0
|
|
|
|
0
|
unless(UNIVERSAL::isa($new_illuminant, 'PDL')) { |
1940
|
0
|
|
|
|
|
0
|
$new_illuminant = xyy_from_illuminant($new_illuminant); |
1941
|
|
|
|
|
|
|
} |
1942
|
0
|
0
|
|
|
|
0
|
unless(UNIVERSAL::isa($me->{params}->{from}, 'PDL')) { |
1943
|
0
|
|
|
|
|
0
|
$me->{params}->{from} = xyy_from_illuminant($me->{params}->{from}); |
1944
|
|
|
|
|
|
|
} |
1945
|
0
|
|
|
|
|
0
|
$me->{params}->{to} = $new_illuminant; |
1946
|
|
|
|
|
|
|
|
1947
|
0
|
0
|
0
|
|
|
0
|
if(UNIVERSAL::isa($me->{params}->{method},"PDL")) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1948
|
0
|
0
|
0
|
|
|
0
|
if($me->{params}->{method}->ndims==2 && |
|
|
|
0
|
|
|
|
|
1949
|
|
|
|
|
|
|
$me->{params}->{method}->dim(0)==3 && |
1950
|
|
|
|
|
|
|
$me->{params}->{method}->dim(1)==3) { |
1951
|
0
|
|
|
|
|
0
|
$me->{params}->{Ma} = $me->{params}->{method}->copy; |
1952
|
|
|
|
|
|
|
} else { |
1953
|
0
|
|
|
|
|
0
|
die "t_new_illuminant: method must be a 3x3 matrix or {Bradford|Von Kries|XYZ}"; |
1954
|
|
|
|
|
|
|
} |
1955
|
|
|
|
|
|
|
} elsif( $me->{params}->{method} =~ m/^B/i || length($me->{params}->{method})==0) { |
1956
|
|
|
|
|
|
|
# Bradford |
1957
|
0
|
|
|
|
|
0
|
$me->{params}->{Ma} = pdl( [ 0.8951000, 0.2664000, -0.1614000 ], |
1958
|
|
|
|
|
|
|
[ -0.7502000, 1.7135000, 0.0367000 ], |
1959
|
|
|
|
|
|
|
[ 0.0389000, -0.0685000, 1.0296000 ] |
1960
|
|
|
|
|
|
|
); |
1961
|
|
|
|
|
|
|
} elsif($me->{params}->{method} =~ m/^[VK]/i) { |
1962
|
|
|
|
|
|
|
# von Kries or Kries |
1963
|
0
|
|
|
|
|
0
|
$me->{params}->{Ma} = pdl( [ 0.4002400, 0.7076000, -0.0808100 ], |
1964
|
|
|
|
|
|
|
[ -0.2263000, 1.1653200, 0.0457000 ], |
1965
|
|
|
|
|
|
|
[ 0.0000000, 0.0000000, 0.9182200 ] |
1966
|
|
|
|
|
|
|
); |
1967
|
|
|
|
|
|
|
} elsif($me->{params}->{method} =~ m/^[XC]/i) { |
1968
|
|
|
|
|
|
|
# XYZ or CIE |
1969
|
0
|
|
|
|
|
0
|
$me->{params}->{Ma} = pdl( [1, 0, 0], [0, 1, 0], [0, 0, 1] ); |
1970
|
|
|
|
|
|
|
} else { |
1971
|
0
|
|
|
|
|
0
|
print "Unknown method '$me->{params}->{method}'\n"; |
1972
|
|
|
|
|
|
|
} |
1973
|
|
|
|
|
|
|
|
1974
|
0
|
|
|
|
|
0
|
$me->{params}->{Ma_inv} = $me->{params}->{Ma}->inv; |
1975
|
|
|
|
|
|
|
|
1976
|
|
|
|
|
|
|
$me->{func} = sub { |
1977
|
0
|
|
|
0
|
|
0
|
my($in, $opt) = @_; |
1978
|
0
|
|
|
|
|
0
|
my $rhgabe_fr = ( $opt->{Ma} x $opt->{from}->(*1) )->((0))->sever; |
1979
|
0
|
|
|
|
|
0
|
my $rhgabe_to = ( $opt->{Ma} x $opt->{to} ->(*1) )->((0))->sever; |
1980
|
0
|
|
|
|
|
0
|
my $M = $opt->{Ma_inv} x ( ( $rhgabe_to / $rhgabe_fr )->(*1) * $opt->{Ma} ); |
1981
|
|
|
|
|
|
|
|
1982
|
0
|
0
|
|
|
|
0
|
if($opt->{basis} =~ m/^X/i) { |
1983
|
0
|
|
|
|
|
0
|
return (( $M x $in->(*1) )->((0))->sever); |
1984
|
|
|
|
|
|
|
} else { |
1985
|
0
|
|
|
|
|
0
|
return (( ( $srgb2cxyz_inv x $M x $srgb2cxyz_mat ) x $in->(*1) )->((0))->sever); |
1986
|
|
|
|
|
|
|
} |
1987
|
|
|
|
|
|
|
|
1988
|
0
|
|
|
|
|
0
|
}; |
1989
|
|
|
|
|
|
|
|
1990
|
|
|
|
|
|
|
$me->{inv} = sub { |
1991
|
0
|
|
|
0
|
|
0
|
my($in, $opt) = @_; |
1992
|
0
|
|
|
|
|
0
|
my $rhgabe_fr = ( $opt->{Ma} x $opt->{from}->(*1) )->((0))->sever; |
1993
|
0
|
|
|
|
|
0
|
my $rhgabe_to = ( $opt->{Ma} x $opt->{to} ->(*1) )->((0))->sever; |
1994
|
0
|
|
|
|
|
0
|
my $M = $opt->{Ma_inv} x ( ( $rhgabe_fr / $rhgabe_to )->(*1) * $opt->{Ma} ); |
1995
|
|
|
|
|
|
|
|
1996
|
0
|
0
|
|
|
|
0
|
if($opt->{basis} =~ m/^X/i) { |
1997
|
0
|
|
|
|
|
0
|
return (( $M x $in->(*1) )->((0))->sever); |
1998
|
|
|
|
|
|
|
} else { |
1999
|
0
|
|
|
|
|
0
|
return (( ( $srgb2cxyz_inv x $M x $srgb2cxyz_mat ) x $in->(*1) )->((0))->sever); |
2000
|
|
|
|
|
|
|
} |
2001
|
0
|
|
|
|
|
0
|
}; |
2002
|
|
|
|
|
|
|
|
2003
|
0
|
0
|
0
|
|
|
0
|
if(exists($me->{params}->{gamma}) && |
|
|
|
0
|
|
|
|
|
2004
|
|
|
|
|
|
|
defined($me->{params}->{gamma}) && |
2005
|
|
|
|
|
|
|
$me->{params}->{gamma} != 1) { |
2006
|
0
|
|
|
|
|
0
|
return ( t_gamma(1.0/$me->{params}->{gamma}) x $me x t_gamma($me->{params}->{gamma}) ); |
2007
|
|
|
|
|
|
|
} else { |
2008
|
0
|
|
|
|
|
0
|
return $me; |
2009
|
|
|
|
|
|
|
} |
2010
|
|
|
|
|
|
|
} |
2011
|
|
|
|
|
|
|
|
2012
|
|
|
|
|
|
|
=head2 t_shift_rgb |
2013
|
|
|
|
|
|
|
|
2014
|
|
|
|
|
|
|
=for usage |
2015
|
|
|
|
|
|
|
|
2016
|
|
|
|
|
|
|
$t = t_shift_rgb("NTSC",{from=>"sRGB"}); |
2017
|
|
|
|
|
|
|
|
2018
|
|
|
|
|
|
|
=for ref |
2019
|
|
|
|
|
|
|
|
2020
|
|
|
|
|
|
|
Shifts the primary color basis of the lsrgb TO the destination system. |
2021
|
|
|
|
|
|
|
Most named RGB systems have an associated preferred gamma, but that is |
2022
|
|
|
|
|
|
|
ignored by default: the RGB values are treated as if they are all |
2023
|
|
|
|
|
|
|
linear representations. You can specify EITHER the name of the system |
2024
|
|
|
|
|
|
|
OR the specific RGB parameters for that system. |
2025
|
|
|
|
|
|
|
|
2026
|
|
|
|
|
|
|
The RGB parameters, if you specify them, need to be in the form of a |
2027
|
|
|
|
|
|
|
hash ref. The hash keys should be the same as would be returned by |
2028
|
|
|
|
|
|
|
C. All the keys must be present, |
2029
|
|
|
|
|
|
|
except for gamma (which is ignored). |
2030
|
|
|
|
|
|
|
|
2031
|
|
|
|
|
|
|
Alternatively, you can use the name of a known system. These are listed in the |
2032
|
|
|
|
|
|
|
documentation for C. |
2033
|
|
|
|
|
|
|
|
2034
|
|
|
|
|
|
|
C takes several options. |
2035
|
|
|
|
|
|
|
|
2036
|
|
|
|
|
|
|
=over 3 |
2037
|
|
|
|
|
|
|
|
2038
|
|
|
|
|
|
|
=item gamma (default 1) |
2039
|
|
|
|
|
|
|
|
2040
|
|
|
|
|
|
|
The input triplets are assumed to be encoded with this gamma function. |
2041
|
|
|
|
|
|
|
The default assumes linear representation. |
2042
|
|
|
|
|
|
|
|
2043
|
|
|
|
|
|
|
=item ogamma (default gamma) |
2044
|
|
|
|
|
|
|
|
2045
|
|
|
|
|
|
|
The output triplets are assumed to need encoding with this gamma function. |
2046
|
|
|
|
|
|
|
|
2047
|
|
|
|
|
|
|
=item use_system_gammas (default 0) |
2048
|
|
|
|
|
|
|
|
2049
|
|
|
|
|
|
|
This overrides the settings of "gamma" and "ogamma", and |
2050
|
|
|
|
|
|
|
encodes/decodes according to the original system. |
2051
|
|
|
|
|
|
|
|
2052
|
|
|
|
|
|
|
=item wp_method (default undef) |
2053
|
|
|
|
|
|
|
|
2054
|
|
|
|
|
|
|
This is the whitepoint shift method used to change illuminant value between |
2055
|
|
|
|
|
|
|
systems with different whitepoints. See C for an |
2056
|
|
|
|
|
|
|
explanation. |
2057
|
|
|
|
|
|
|
|
2058
|
|
|
|
|
|
|
=item from (default "sRGB") |
2059
|
|
|
|
|
|
|
|
2060
|
|
|
|
|
|
|
This is the RGB system to convert from, in the same format as the |
2061
|
|
|
|
|
|
|
system to convert to (names or a hash ref as described). |
2062
|
|
|
|
|
|
|
|
2063
|
|
|
|
|
|
|
=back |
2064
|
|
|
|
|
|
|
|
2065
|
|
|
|
|
|
|
=cut |
2066
|
|
|
|
|
|
|
|
2067
|
|
|
|
|
|
|
sub t_shift_rgb { |
2068
|
0
|
|
|
0
|
1
|
0
|
my $new_rgb = shift; |
2069
|
0
|
|
|
|
|
0
|
my($me) = _new(@_, 'New RGB system', |
2070
|
|
|
|
|
|
|
{gamma =>1, |
2071
|
|
|
|
|
|
|
ogamma=>undef, |
2072
|
|
|
|
|
|
|
use_system_gammas=>0, |
2073
|
|
|
|
|
|
|
wp_method=>undef, |
2074
|
|
|
|
|
|
|
from=>"sRGB" |
2075
|
|
|
|
|
|
|
} |
2076
|
|
|
|
|
|
|
); |
2077
|
|
|
|
|
|
|
|
2078
|
|
|
|
|
|
|
|
2079
|
0
|
|
|
|
|
0
|
my $to_rgb = get_rgb($new_rgb); |
2080
|
0
|
|
|
|
|
0
|
my $from_rgb = get_rgb($me->{params}->{from}); |
2081
|
|
|
|
|
|
|
|
2082
|
0
|
|
|
|
|
0
|
my ($from_gamma, $to_gamma); |
2083
|
0
|
0
|
|
|
|
0
|
if($me->{params}->{use_system_gammas}) { |
2084
|
0
|
|
|
|
|
0
|
$from_gamma = $me->{params}->{from_rgb}->{gamma}; |
2085
|
0
|
|
|
|
|
0
|
$to_gamma = $me->{params}->{to_rgb}->{gamma}; |
2086
|
|
|
|
|
|
|
} else { |
2087
|
0
|
|
|
|
|
0
|
$from_gamma = $me->{params}->{gamma}; |
2088
|
0
|
|
0
|
|
|
0
|
$to_gamma = $me->{params}->{ogamma} // $me->{params}->{gamma}; |
2089
|
|
|
|
|
|
|
} |
2090
|
|
|
|
|
|
|
|
2091
|
|
|
|
|
|
|
my $out = |
2092
|
|
|
|
|
|
|
!t_xyz(rgb_system=>$to_rgb, gamma=>$me->{params}->{gamma}, use_system_gamma=>$me->{params}->{use_system_gamma}) x |
2093
|
|
|
|
|
|
|
t_shift_illuminant($to_rgb->{w},basis=>"XYZ",from=>$from_rgb->{w},method=>$me->{params}->{wp_method}) x |
2094
|
0
|
|
|
|
|
0
|
t_xyz(rgb_system=>$from_rgb, gamma=>$me->{params}->{gamma}, use_system_gamma=>$me->{params}->{use_system_gamma}); |
2095
|
|
|
|
|
|
|
|
2096
|
0
|
|
|
|
|
0
|
return $out; |
2097
|
|
|
|
|
|
|
|
2098
|
|
|
|
|
|
|
} |
2099
|
|
|
|
|
|
|
|
2100
|
|
|
|
|
|
|
############################## |
2101
|
|
|
|
|
|
|
# Reference illuminants |
2102
|
|
|
|
|
|
|
# (aka "white points") |
2103
|
|
|
|
|
|
|
|
2104
|
|
|
|
|
|
|
=head2 PDL::Transform::Color::xyy_from_D |
2105
|
|
|
|
|
|
|
|
2106
|
|
|
|
|
|
|
=for usage |
2107
|
|
|
|
|
|
|
|
2108
|
|
|
|
|
|
|
$xyy = PDL::Transform::Color::xyy_from_D($D_value) |
2109
|
|
|
|
|
|
|
|
2110
|
|
|
|
|
|
|
=for ref |
2111
|
|
|
|
|
|
|
|
2112
|
|
|
|
|
|
|
This utility routine generates CIE xyY system colorimetric values for |
2113
|
|
|
|
|
|
|
standard CIE D-class illuminants (e.g., D50 or D65). The illuminants are |
2114
|
|
|
|
|
|
|
calculated from a standard formula and correspond to black body |
2115
|
|
|
|
|
|
|
temperatures between 4,000K and 250,000K. The D value is the |
2116
|
|
|
|
|
|
|
temperature in K divided by 100, e.g. broad daylight is D65, |
2117
|
|
|
|
|
|
|
corresponding to 6500 Kelvin. |
2118
|
|
|
|
|
|
|
|
2119
|
|
|
|
|
|
|
This is used for calculating standard reference illuminants, to convert |
2120
|
|
|
|
|
|
|
RGB values between illuminants. |
2121
|
|
|
|
|
|
|
|
2122
|
|
|
|
|
|
|
For example, sRGB uses a D65 illuminant, but many other color standards |
2123
|
|
|
|
|
|
|
refer to a D50 illuminant. |
2124
|
|
|
|
|
|
|
|
2125
|
|
|
|
|
|
|
The colorimetric values are xy only; the Y coordinate can be specified via |
2126
|
|
|
|
|
|
|
an option, or defaults to 0.5. |
2127
|
|
|
|
|
|
|
|
2128
|
|
|
|
|
|
|
This routine is mainly used by C, which handles most |
2129
|
|
|
|
|
|
|
of the CIE-recognized standard illuminant sources including the D's. |
2130
|
|
|
|
|
|
|
|
2131
|
|
|
|
|
|
|
See C for a description of the CIE xyY absolute colorimetric system. |
2132
|
|
|
|
|
|
|
|
2133
|
|
|
|
|
|
|
C accepts the following options: |
2134
|
|
|
|
|
|
|
|
2135
|
|
|
|
|
|
|
=over 3 |
2136
|
|
|
|
|
|
|
|
2137
|
|
|
|
|
|
|
=item Y - the Y value of the output xyY coordinate |
2138
|
|
|
|
|
|
|
|
2139
|
|
|
|
|
|
|
=back |
2140
|
|
|
|
|
|
|
|
2141
|
|
|
|
|
|
|
=cut |
2142
|
|
|
|
|
|
|
|
2143
|
|
|
|
|
|
|
sub xyy_from_D { |
2144
|
18
|
|
|
18
|
1
|
32
|
my $D = pdl(shift); |
2145
|
18
|
|
50
|
|
|
567
|
my $u_opt = shift || {}; |
2146
|
18
|
|
|
|
|
42
|
my %opt = parse({ |
2147
|
|
|
|
|
|
|
Y=>1 |
2148
|
|
|
|
|
|
|
}, |
2149
|
|
|
|
|
|
|
$u_opt); |
2150
|
|
|
|
|
|
|
|
2151
|
18
|
50
|
33
|
|
|
1716
|
die "cie_xy_from_D: D must be between 40 and 250" if(any($D< 40) || any($D > 250)); |
2152
|
18
|
|
|
|
|
1138
|
my $T = $D*100; |
2153
|
|
|
|
|
|
|
|
2154
|
18
|
|
|
|
|
42
|
my $Xd; |
2155
|
18
|
|
|
|
|
1274
|
$Xd = ($D<=70) * ( 0.244063 + 0.09911e3/$T + 2.9678e6/$T/$T - 4.6070e9/$T/$T/$T ) + |
2156
|
|
|
|
|
|
|
($D> 70) * ( 0.237040 + 0.24748e3/$T + 1.9018e6/$T/$T - 2.0064e9/$T/$T/$T ); |
2157
|
|
|
|
|
|
|
|
2158
|
18
|
|
|
|
|
606
|
return pdl( $Xd, -3*$Xd*$Xd + 2.870*$Xd - 0.275, $opt{Y} )->mv(-1,0)->sever; |
2159
|
|
|
|
|
|
|
} |
2160
|
|
|
|
|
|
|
|
2161
|
|
|
|
|
|
|
# xy data for FL3.x standards, from CIE "Colorimetry" 3rd edition Table T.8.2 |
2162
|
|
|
|
|
|
|
my $fl3tab = [ |
2163
|
|
|
|
|
|
|
[], |
2164
|
|
|
|
|
|
|
[0.4407, 0.4033], |
2165
|
|
|
|
|
|
|
[0.3808, 0.3734], |
2166
|
|
|
|
|
|
|
[0.3153, 0.3439], |
2167
|
|
|
|
|
|
|
[0.4429, 0.4043], |
2168
|
|
|
|
|
|
|
[0.3749, 0.3672], |
2169
|
|
|
|
|
|
|
[0.3488, 0.3600], |
2170
|
|
|
|
|
|
|
[0.4384, 0.4045], |
2171
|
|
|
|
|
|
|
[0.3820, 0.3832], |
2172
|
|
|
|
|
|
|
[0.3499, 0.3591], |
2173
|
|
|
|
|
|
|
[0.3455, 0.3460], |
2174
|
|
|
|
|
|
|
[0.3245, 0.3434], |
2175
|
|
|
|
|
|
|
[0.4377, 0.4037], |
2176
|
|
|
|
|
|
|
[0.3830, 0.3724], |
2177
|
|
|
|
|
|
|
[0.3447, 0.3609], |
2178
|
|
|
|
|
|
|
[0.3127, 0.3288] |
2179
|
|
|
|
|
|
|
]; |
2180
|
|
|
|
|
|
|
# xy data for FLx standards, from CIE "Colorimetry" 3rd edition Table T.7 |
2181
|
|
|
|
|
|
|
my $fltab = [ |
2182
|
|
|
|
|
|
|
[], |
2183
|
|
|
|
|
|
|
[0.3131, 0.3371], |
2184
|
|
|
|
|
|
|
[0.3721, 0.3751], |
2185
|
|
|
|
|
|
|
[0.4091, 0.3941], |
2186
|
|
|
|
|
|
|
[0.4402, 0.4031], |
2187
|
|
|
|
|
|
|
[0.3138, 0.3452], |
2188
|
|
|
|
|
|
|
[0.3779, 0.3882], |
2189
|
|
|
|
|
|
|
[0.3129, 0.3292], |
2190
|
|
|
|
|
|
|
[0.3458, 0.3586], |
2191
|
|
|
|
|
|
|
[0.3741, 0.3727], |
2192
|
|
|
|
|
|
|
[0.3458, 0.3588], |
2193
|
|
|
|
|
|
|
[0.3805, 0.3769], |
2194
|
|
|
|
|
|
|
[0.4370, 0.4042] |
2195
|
|
|
|
|
|
|
]; |
2196
|
|
|
|
|
|
|
# xy data for HPx standards, from CIE "Colorimetry" 3rd edition table T.9 |
2197
|
|
|
|
|
|
|
my $hptab = [ |
2198
|
|
|
|
|
|
|
[], |
2199
|
|
|
|
|
|
|
[0.5330, 0.4150], |
2200
|
|
|
|
|
|
|
[0.4778, 0.4158], |
2201
|
|
|
|
|
|
|
[0.4302, 0.4075], |
2202
|
|
|
|
|
|
|
[0.3812, 0.3797], |
2203
|
|
|
|
|
|
|
[0.3776, 0.3713] |
2204
|
|
|
|
|
|
|
]; |
2205
|
|
|
|
|
|
|
|
2206
|
|
|
|
|
|
|
|
2207
|
|
|
|
|
|
|
|
2208
|
|
|
|
|
|
|
=head2 PDL::Transform::Color::xyy_from_illuminant |
2209
|
|
|
|
|
|
|
|
2210
|
|
|
|
|
|
|
=for usage |
2211
|
|
|
|
|
|
|
|
2212
|
|
|
|
|
|
|
$xyy = PDL::Transform::Color::xyy_from_illuminant($name) |
2213
|
|
|
|
|
|
|
|
2214
|
|
|
|
|
|
|
=for ref |
2215
|
|
|
|
|
|
|
|
2216
|
|
|
|
|
|
|
This utility routine generates CIE xyY system colorimetric values for |
2217
|
|
|
|
|
|
|
all of the standard CIE illuminants. The illuminants are looked up in |
2218
|
|
|
|
|
|
|
a table populated from the CIE publication I, 3rd |
2219
|
|
|
|
|
|
|
edition. |
2220
|
|
|
|
|
|
|
|
2221
|
|
|
|
|
|
|
The illuminant of a system is equivalent to its white point -- it is |
2222
|
|
|
|
|
|
|
the location in xyY absolute colorimetric space that corresponds to |
2223
|
|
|
|
|
|
|
"white". |
2224
|
|
|
|
|
|
|
|
2225
|
|
|
|
|
|
|
CIE recognizes many standard illuminants, and (as of 2017) is in the |
2226
|
|
|
|
|
|
|
process of creating a new set -- the "L" series illuminants -- that is |
2227
|
|
|
|
|
|
|
meant to represent LED lighting. |
2228
|
|
|
|
|
|
|
|
2229
|
|
|
|
|
|
|
Proper treatment of an illuminant requires a full spectral representation, |
2230
|
|
|
|
|
|
|
which the CIE specifies for each illuminant. Analysis of that spectrum is |
2231
|
|
|
|
|
|
|
a major part of what CIE calls "Color rendering index (CRI)" for a particular |
2232
|
|
|
|
|
|
|
light source. PDL::Transform::Color is a strictly tri-coordinate system |
2233
|
|
|
|
|
|
|
and does not handle the nuances of spectral effects on CRI. In effect, |
2234
|
|
|
|
|
|
|
all illuminants are treated as having a CRI of unity (perfect). |
2235
|
|
|
|
|
|
|
|
2236
|
|
|
|
|
|
|
Illuminants that are understood are: |
2237
|
|
|
|
|
|
|
|
2238
|
|
|
|
|
|
|
=over 3 |
2239
|
|
|
|
|
|
|
|
2240
|
|
|
|
|
|
|
=item * a 3-PDL in CIE xyY coordinates |
2241
|
|
|
|
|
|
|
|
2242
|
|
|
|
|
|
|
=item * a CIE standard name |
2243
|
|
|
|
|
|
|
|
2244
|
|
|
|
|
|
|
=back |
2245
|
|
|
|
|
|
|
|
2246
|
|
|
|
|
|
|
The CIE names are: |
2247
|
|
|
|
|
|
|
|
2248
|
|
|
|
|
|
|
=over 3 |
2249
|
|
|
|
|
|
|
|
2250
|
|
|
|
|
|
|
=item A - a gas-filled tungsten filament lamp at 2856K |
2251
|
|
|
|
|
|
|
|
2252
|
|
|
|
|
|
|
=item B - not supported (deprecated by CIE) |
2253
|
|
|
|
|
|
|
|
2254
|
|
|
|
|
|
|
=item C - early daylight simulant, replaced by the D[n] sources |
2255
|
|
|
|
|
|
|
|
2256
|
|
|
|
|
|
|
=item D[n] - Blackbody radiation at 100[n] Kelvin (e.g. D65) |
2257
|
|
|
|
|
|
|
|
2258
|
|
|
|
|
|
|
=item F[n] - Fluorescent lights of various types (n=1-12 or 3.1-3.15) |
2259
|
|
|
|
|
|
|
|
2260
|
|
|
|
|
|
|
=item HP[n] - High Pressure discharge lamps (n=1-5) |
2261
|
|
|
|
|
|
|
|
2262
|
|
|
|
|
|
|
=item L[n] - LED lighting (not yet supported) |
2263
|
|
|
|
|
|
|
|
2264
|
|
|
|
|
|
|
=back |
2265
|
|
|
|
|
|
|
|
2266
|
|
|
|
|
|
|
=cut |
2267
|
|
|
|
|
|
|
|
2268
|
|
|
|
|
|
|
sub xyy_from_illuminant { |
2269
|
20
|
|
|
20
|
1
|
17
|
my $name = shift; |
2270
|
20
|
50
|
|
|
|
62
|
if(UNIVERSAL::isa($name,"PDL")) { |
2271
|
0
|
0
|
0
|
|
|
0
|
if(($name->nelem==2 || $name->nelem==3) && $name->dim(0)==$name->nelem) { |
|
|
|
0
|
|
|
|
|
2272
|
0
|
|
|
|
|
0
|
return $name; |
2273
|
|
|
|
|
|
|
} else { |
2274
|
0
|
|
|
|
|
0
|
die "xyy_from_illuminant: PDL must be a 2-PDL or a 3-PDL"; |
2275
|
|
|
|
|
|
|
} |
2276
|
|
|
|
|
|
|
} |
2277
|
20
|
|
50
|
|
|
54
|
my $u_opt = shift || {}; |
2278
|
20
|
|
|
|
|
47
|
my %opt = parse({ |
2279
|
|
|
|
|
|
|
Y=>1 |
2280
|
|
|
|
|
|
|
}, $u_opt); |
2281
|
20
|
50
|
|
|
|
2015
|
if($name =~ m/^A/i) { |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2282
|
0
|
|
|
|
|
0
|
return pdl(0.44758, 0.40745, $opt{Y}); |
2283
|
|
|
|
|
|
|
} elsif($name =~ m/^B/) { |
2284
|
0
|
|
|
|
|
0
|
die "Illuminant B is not supported (deprecated by CIE)"; |
2285
|
|
|
|
|
|
|
} elsif($name =~ m/^C/) { |
2286
|
1
|
|
|
|
|
3
|
return pdl(0.31006, 0.31616, $opt{Y}); |
2287
|
|
|
|
|
|
|
} elsif( $name =~ m/^D(.*)$/i) { |
2288
|
18
|
|
|
|
|
31
|
return xyy_from_D($1,$u_opt); |
2289
|
|
|
|
|
|
|
} elsif( $name =~ m/^E/i) { |
2290
|
1
|
|
|
|
|
3
|
return pdl(0.33333,0.33333,$opt{Y}); |
2291
|
|
|
|
|
|
|
} elsif( $name =~ m/^FL?([\d+])(\.[\d])?$/i) { |
2292
|
0
|
|
|
|
|
|
my $flno = $1+0; |
2293
|
0
|
|
|
|
|
|
my $flsubno = $2+0; |
2294
|
0
|
0
|
0
|
|
|
|
die "Illuminant $name not recognized (FL1-FL12, or FL3.1-FL3.15)" |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2295
|
|
|
|
|
|
|
if($flno < 1 || $flno > 12 || |
2296
|
|
|
|
|
|
|
($flsubno && $flno != 3) || |
2297
|
|
|
|
|
|
|
($flsubno > 15) |
2298
|
|
|
|
|
|
|
); |
2299
|
|
|
|
|
|
|
|
2300
|
0
|
0
|
0
|
|
|
|
if($flno==3 && $flsubno) { |
2301
|
0
|
|
|
|
|
|
return pdl(@{$fl3tab->[$flsubno]},$opt{Y}); |
|
0
|
|
|
|
|
|
|
2302
|
|
|
|
|
|
|
} else { |
2303
|
0
|
|
|
|
|
|
return pdl(@{$fltab->[$flno]},$opt{Y}); |
|
0
|
|
|
|
|
|
|
2304
|
|
|
|
|
|
|
} |
2305
|
|
|
|
|
|
|
} elsif( $name =~ m/^HP?(\d)/i ) { |
2306
|
0
|
|
|
|
|
|
my $hpno = $1+0; |
2307
|
0
|
0
|
0
|
|
|
|
die "Unknown HP illuminant no. $hpno" if($hpno<1 || $hpno > 5); |
2308
|
0
|
|
|
|
|
|
return pdl(@{$hptab->[$hpno]}, $opt{Y}); |
|
0
|
|
|
|
|
|
|
2309
|
|
|
|
|
|
|
} elsif( $name =~ m/^L/i) { |
2310
|
0
|
|
|
|
|
|
die "Illuminant L is not (yet) supported"; |
2311
|
|
|
|
|
|
|
} else { |
2312
|
0
|
|
|
|
|
|
die "Unknown illuminant $name"; |
2313
|
|
|
|
|
|
|
} |
2314
|
|
|
|
|
|
|
} |
2315
|
|
|
|
|
|
|
|
2316
|
|
|
|
|
|
|
|
2317
|
|
|
|
|
|
|
############################## |
2318
|
|
|
|
|
|
|
# Database of standard RGB color systems from Bruce Lindbloom |
2319
|
|
|
|
|
|
|
# Make a database of xyY values of primaries, illuminants, and standard gammas for common RGB systems |
2320
|
|
|
|
|
|
|
# Also stash matrices for converting those systems to lsRGB. |
2321
|
|
|
|
|
|
|
# |
2322
|
|
|
|
|
|
|
# Columns: gamma, illuminant, xyY for R (3 cols), xyY for G (3 cols), xyY for B (3 cols), abbrev char count |
2323
|
|
|
|
|
|
|
our $rgbtab_src = { |
2324
|
|
|
|
|
|
|
"Adobe" => [2.2, "D65", 0.6400, 0.3300, 0.297361, 0.2100, 0.7100, 0.627355, 0.1500, 0.0600, 0.075285, 2], |
2325
|
|
|
|
|
|
|
"Apple" => [1.8, "D65", 0.6250, 0.3400, 0.244634, 0.2800, 0.5950, 0.672034, 0.1550, 0.0700, 0.083332, 2], |
2326
|
|
|
|
|
|
|
"Best" => [2.2, "D50", 0.7347, 0.2653, 0.228457, 0.2150, 0.7750, 0.737352, 0.1300, 0.0350, 0.034191, 3], |
2327
|
|
|
|
|
|
|
"Beta" => [2.2, "D50", 0.6888, 0.3112, 0.303273, 0.1986, 0.7551, 0.663786, 0.1265, 0.0352, 0.032941, 3], |
2328
|
|
|
|
|
|
|
"Bruce" => [2.2, "D65", 0.6400, 0.3300, 0.240995, 0.2800, 0.6500, 0.683554, 0.1500, 0.0600, 0.075452, 2], |
2329
|
|
|
|
|
|
|
"BT 601" => [2.2, "D65", 0.6300, 0.3400, 0.299000, 0.3100, 0.5950, 0.587000, 0.1550, 0.0700, 0.114000, 3], |
2330
|
|
|
|
|
|
|
"BT 709" => [2.2, "D65", 0.6300, 0.3400, 0.212600, 0.3100, 0.5950, 0.715200, 0.1550, 0.0700, 0.072200, 3], |
2331
|
|
|
|
|
|
|
"CIE" => [2.2, "E", 0.7350, 0.2650, 0.176204, 0.2740, 0.7170, 0.812985, 0.1670, 0.0090, 0.010811, 2], |
2332
|
|
|
|
|
|
|
"ColorMatch" => [1.8, "D50", 0.6300, 0.3400, 0.274884, 0.2950, 0.6050, 0.658132, 0.1500, 0.0750, 0.066985, 2], |
2333
|
|
|
|
|
|
|
"Don 4" => [2.2, "D50", 0.6960, 0.3000, 0.278350, 0.2150, 0.7650, 0.687970, 0.1300, 0.0350, 0.033680, 1], |
2334
|
|
|
|
|
|
|
"ECI v2" => [1.0, "D50", 0.6700, 0.3300, 0.320250, 0.2100, 0.7100, 0.602071, 0.1400, 0.0800, 0.077679, 2], |
2335
|
|
|
|
|
|
|
"Ekta PS5" => [2.2, "D50", 0.6950, 0.3050, 0.260629, 0.2600, 0.7000, 0.734946, 0.1100, 0.0050, 0.004425, 2], |
2336
|
|
|
|
|
|
|
"NTSC" => [2.2, "C", 0.6700, 0.3300, 0.298839, 0.2100, 0.7100, 0.586811, 0.1400, 0.0800, 0.114350, 1], |
2337
|
|
|
|
|
|
|
"PAL" => [2.2, "D65", 0.6400, 0.3300, 0.222021, 0.2900, 0.6000, 0.706645, 0.1500, 0.0600, 0.071334, 2], |
2338
|
|
|
|
|
|
|
"ProPhoto" => [1.8, "D50", 0.7347, 0.2653, 0.288040, 0.1596, 0.8404, 0.711874, 0.0366, 0.0001, 0.000086, 2], |
2339
|
|
|
|
|
|
|
"ROMM" => [1.8, "D50", 0.7347, 0.2653, 0.288040, 0.1596, 0.8404, 0.711874, 0.0366, 0.0001, 0.000086, 2], |
2340
|
|
|
|
|
|
|
"SECAM" => [2.2, "D65", 0.6400, 0.3300, 0.222021, 0.2900, 0.6000, 0.706645, 0.1500, 0.0600, 0.071334, 2], |
2341
|
|
|
|
|
|
|
"SMPTE-C" => [2.2, "D65", 0.6300, 0.3400, 0.212395, 0.3100, 0.5950, 0.701049, 0.1550, 0.0700, 0.086556, 2], |
2342
|
|
|
|
|
|
|
"sRGB" => [2.2, "D65", 0.6300, 0.3400, 0.212395, 0.3100, 0.5950, 0.701049, 0.1550, 0.0700, 0.086556, 2], |
2343
|
|
|
|
|
|
|
"wgRGB" => [2.2, "D50", 0.7350, 0.2650, 0.258187, 0.1150, 0.8260, 0.724938, 0.1570, 0.0180, 0.016875, 1] |
2344
|
|
|
|
|
|
|
}; |
2345
|
|
|
|
|
|
|
|
2346
|
|
|
|
|
|
|
############################## |
2347
|
|
|
|
|
|
|
# RGB color systems in more code-approachable form. Parse the table to create hash refs by name, and an |
2348
|
|
|
|
|
|
|
# abbrev table that allows abbreviated naming |
2349
|
|
|
|
|
|
|
# |
2350
|
|
|
|
|
|
|
our $rgbtab = {}; |
2351
|
|
|
|
|
|
|
our $rgb_abbrevs = {}; |
2352
|
|
|
|
|
|
|
for my $k(keys %$rgbtab_src) { |
2353
|
|
|
|
|
|
|
my $v = $rgbtab_src->{$k}; |
2354
|
|
|
|
|
|
|
$rgbtab->{$k} = { |
2355
|
|
|
|
|
|
|
gamma => $v->[0], |
2356
|
|
|
|
|
|
|
w_name => $v->[1], |
2357
|
|
|
|
|
|
|
w => xyy_from_illuminant($v->[1]), |
2358
|
|
|
|
|
|
|
r => pdl($v->[2],$v->[3],$v->[4]), |
2359
|
|
|
|
|
|
|
g => pdl($v->[5],$v->[6],$v->[7]), |
2360
|
|
|
|
|
|
|
b => pdl($v->[8],$v->[9],$v->[10]) |
2361
|
|
|
|
|
|
|
}; |
2362
|
|
|
|
|
|
|
my $str = $k; |
2363
|
|
|
|
|
|
|
$str =~ tr/A-Z/a-z/; |
2364
|
|
|
|
|
|
|
$str =~ s/\s\-//g; |
2365
|
|
|
|
|
|
|
for my $i($v->[11]..length($str)){ |
2366
|
|
|
|
|
|
|
$rgb_abbrevs->{substr($str,0,$i)} = $k; |
2367
|
|
|
|
|
|
|
} |
2368
|
|
|
|
|
|
|
} |
2369
|
|
|
|
|
|
|
|
2370
|
|
|
|
|
|
|
# Gets an rgb descriptor hash from an input that might be a hash or a name. |
2371
|
|
|
|
|
|
|
# If it's a hash, check to make sure it's copacetic. |
2372
|
|
|
|
|
|
|
|
2373
|
|
|
|
|
|
|
=head2 PDL::Transform::Color::get_rgb |
2374
|
|
|
|
|
|
|
|
2375
|
|
|
|
|
|
|
=for usage |
2376
|
|
|
|
|
|
|
|
2377
|
|
|
|
|
|
|
my $rgb_hash = get_rgb( $name ); |
2378
|
|
|
|
|
|
|
|
2379
|
|
|
|
|
|
|
=for ref |
2380
|
|
|
|
|
|
|
|
2381
|
|
|
|
|
|
|
C is an internal routine that retrieves a set of |
2382
|
|
|
|
|
|
|
RGB primary colors from an internal database. There are several named RGB systems, |
2383
|
|
|
|
|
|
|
with different primary colors for each. The primary colors are represented as |
2384
|
|
|
|
|
|
|
CIE xyY values in a returned hash ref. |
2385
|
|
|
|
|
|
|
|
2386
|
|
|
|
|
|
|
The return value is a hash ref with the following fields: |
2387
|
|
|
|
|
|
|
|
2388
|
|
|
|
|
|
|
=over 3 |
2389
|
|
|
|
|
|
|
|
2390
|
|
|
|
|
|
|
=item gamma - the specified gamma of that RGB system (or 2.2, for sRGB) |
2391
|
|
|
|
|
|
|
|
2392
|
|
|
|
|
|
|
=item w_name - the name of the illuminant / white-point for that system |
2393
|
|
|
|
|
|
|
|
2394
|
|
|
|
|
|
|
=item w - the xyY value of the illuminant / white-point for that system |
2395
|
|
|
|
|
|
|
|
2396
|
|
|
|
|
|
|
=item r - the xyY value of the red primary color at unit intensity |
2397
|
|
|
|
|
|
|
|
2398
|
|
|
|
|
|
|
=item g - the xyY value of the green primary color at unit intensity |
2399
|
|
|
|
|
|
|
|
2400
|
|
|
|
|
|
|
=item b - the xyY value of the blue primary color at unit intensity |
2401
|
|
|
|
|
|
|
|
2402
|
|
|
|
|
|
|
=back |
2403
|
|
|
|
|
|
|
|
2404
|
|
|
|
|
|
|
Recognized RGB system names are: |
2405
|
|
|
|
|
|
|
|
2406
|
|
|
|
|
|
|
=over 3 |
2407
|
|
|
|
|
|
|
|
2408
|
|
|
|
|
|
|
=item Adobe - Adobe's 1998 RGB, intended to encompass nearly all of the CMYK gamut (gamma=2.2, white=D65) |
2409
|
|
|
|
|
|
|
|
2410
|
|
|
|
|
|
|
=item Apple - Apple's display standard from c. 1990 - c. 2010 (gamma=1.8, white=D65) |
2411
|
|
|
|
|
|
|
|
2412
|
|
|
|
|
|
|
=item Best - Wide-gamut RGB developed by Don Hutcheson (L) (gamma=2.2, white=D50) |
2413
|
|
|
|
|
|
|
|
2414
|
|
|
|
|
|
|
=item Beta - Bruce Lindbloom's optimized ultra-wide-gamut RGB (gamma=2.2, white=D50) |
2415
|
|
|
|
|
|
|
|
2416
|
|
|
|
|
|
|
=item Bruce - Bruce Fraser's conservative-gamut RGB space for 8-bit editing (gamma=2.2, white=D65) |
2417
|
|
|
|
|
|
|
|
2418
|
|
|
|
|
|
|
=item BT 601 - ITU-R standard BT.601 (used for MPEG & SDTV) (gamma=2.2, white=D65) |
2419
|
|
|
|
|
|
|
|
2420
|
|
|
|
|
|
|
=item BT 709 - ITU-R standard BT.709 (used for HDTV) (gamma=2.2, white=D65) |
2421
|
|
|
|
|
|
|
|
2422
|
|
|
|
|
|
|
=item CIE - CIE 1931 calibrated color space (based on physical emission lines) (gamma=2.2, white=E) |
2423
|
|
|
|
|
|
|
|
2424
|
|
|
|
|
|
|
=item ColorMatch - quasi-standard from c.1990 -- matches Radius Pressview CRT monitors. (gamma=1.8, white=D50) |
2425
|
|
|
|
|
|
|
|
2426
|
|
|
|
|
|
|
=item Don 4 - wide-gamut D50 working space gets the Ektachrome color gamut (gamma=2.2, white=D50) |
2427
|
|
|
|
|
|
|
|
2428
|
|
|
|
|
|
|
=item ECI v2 - RGB standard from the European Color Initiative (gamma=1, white=D50) |
2429
|
|
|
|
|
|
|
|
2430
|
|
|
|
|
|
|
=item Ekta PS5 - developed by Joseph Holms (L) for scanned Ektachrome slides (gamma=2.2, white=D50) |
2431
|
|
|
|
|
|
|
|
2432
|
|
|
|
|
|
|
=item NTSC - Never The Same Color (U.S. analog TV standard) (gamma=2.2, white=C) |
2433
|
|
|
|
|
|
|
|
2434
|
|
|
|
|
|
|
=item PAL - Pictures Always Lovely (U.K. analog TV standard) (gamma = 2.2, white=D65) |
2435
|
|
|
|
|
|
|
|
2436
|
|
|
|
|
|
|
=item ProPhoto - Wide gamut from Kodak, designed for photo output. (gamma=1.8, white=D60) |
2437
|
|
|
|
|
|
|
|
2438
|
|
|
|
|
|
|
=item ROMM - Synonym for ProPhoto (gamma=1.8, white=D60) |
2439
|
|
|
|
|
|
|
|
2440
|
|
|
|
|
|
|
=item SECAM - Systeme Electronique Contre les AMericains (French analog TV standard) (gamma=2.2, white=D65) |
2441
|
|
|
|
|
|
|
|
2442
|
|
|
|
|
|
|
=item SMPTE-C - Soc. Motion Pict. & TV Engineers (current U.S. TV standard) (gamma=2.2, white=D65) |
2443
|
|
|
|
|
|
|
|
2444
|
|
|
|
|
|
|
=item sRGB - Standard for consumer computer monitors (gamma~2.2, white=D65) |
2445
|
|
|
|
|
|
|
|
2446
|
|
|
|
|
|
|
=item wgRGB - Wide Gamut RGB (gamma=2.2, white=D50) |
2447
|
|
|
|
|
|
|
|
2448
|
|
|
|
|
|
|
=back |
2449
|
|
|
|
|
|
|
|
2450
|
|
|
|
|
|
|
=cut |
2451
|
|
|
|
|
|
|
|
2452
|
|
|
|
|
|
|
sub get_rgb { |
2453
|
0
|
|
|
0
|
1
|
|
my $new_rgb = shift; |
2454
|
0
|
0
|
|
|
|
|
unless(ref $new_rgb) { |
|
|
0
|
|
|
|
|
|
2455
|
0
|
|
|
|
|
|
$new_rgb=~tr/A-Z/a-z/; $new_rgb =~ s/\s\-//g; |
|
0
|
|
|
|
|
|
|
2456
|
0
|
|
|
|
|
|
my $new_rgb_name = $rgb_abbrevs->{$new_rgb}; |
2457
|
0
|
0
|
|
|
|
|
if($rgbtab->{$new_rgb_name}) { |
2458
|
0
|
|
|
|
|
|
$new_rgb = $rgbtab->{$new_rgb_name}; |
2459
|
|
|
|
|
|
|
} else { |
2460
|
0
|
|
|
|
|
|
die "Unknown RGB system '$new_rgb'\nKnown ones are:\n\t".join("\n\t",((sort keys %$rgbtab),"")); |
2461
|
|
|
|
|
|
|
} |
2462
|
0
|
|
|
|
|
|
} elsif(ref $new_rgb eq 'HASH') { |
2463
|
0
|
|
|
|
|
|
my $bad = 0; |
2464
|
0
|
|
|
|
|
|
for my $k(qw/w r g b/) { |
2465
|
0
|
0
|
0
|
|
|
|
$bad = 1 unless( exists($new_rgb->{$k}) and defined($new_rgb->{$k}) and UNIVERSAL::isa($new_rgb->{$k},"PDL") and $new_rgb->{$k}->nelem==3 and $new_rgb->{$k}->dim(0)==3); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2466
|
|
|
|
|
|
|
} |
2467
|
0
|
0
|
|
|
|
|
$new_rgb->{gamma} = 1 unless defined($new_rgb->{gamma}); |
2468
|
0
|
0
|
|
|
|
|
die "Incorrect RGB primaries hash -- see docs" if($bad); |
2469
|
|
|
|
|
|
|
} else { |
2470
|
0
|
|
|
|
|
|
die "bad RGB specification -- see docs"; |
2471
|
|
|
|
|
|
|
} |
2472
|
0
|
|
|
|
|
|
return $new_rgb; |
2473
|
|
|
|
|
|
|
} |
2474
|
|
|
|
|
|
|
|
2475
|
|
|
|
|
|
|
|
2476
|
|
|
|
|
|
|
|
2477
|
|
|
|
|
|
|
|
2478
|
|
|
|
|
|
|
1; |