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