File Coverage

blib/lib/PDL/Transform/Color.pm
Criterion Covered Total %
statement 292 550 53.0
branch 70 204 34.3
condition 15 93 16.1
subroutine 40 59 67.8
pod 18 22 81.8
total 435 928 46.8


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   72087 use PDL::Transform;
  1         247970  
  1         8  
334              
335             package PDL::Transform::Color;
336              
337 1     1   216 use PDL::Core ':Internal'; # load "topdl" (internal routine)
  1         2  
  1         5  
338              
339             @ISA = ( 'Exporter', 'PDL::Transform' );
340             our $VERSION = '1.005';
341             $VERSION = eval $VERSION;
342              
343             BEGIN {
344             package PDL::Transform::Color;
345 1     1   137 use base 'Exporter';
  1         2  
  1         223  
346 1     1   7 @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         5 @EXPORT = @EXPORT_OK;
348 1         21 %EXPORT_TAGS = (Func=>[@EXPORT_OK]);
349             };
350              
351 1     1   8 use strict;
  1         2  
  1         33  
352 1     1   7 use PDL;
  1         2  
  1         6  
353 1     1   2917 use PDL::Transform;
  1         2  
  1         6  
354 1     1   138 use PDL::MatrixOps;
  1         2  
  1         4  
355 1     1   118 use PDL::Options;
  1         3  
  1         54  
356 1     1   18 use PDL::NiceSlice;
  1         3  
  1         10  
357              
358 1     1   87831 use Carp;
  1         2  
  1         10615  
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   42 sub _new { new('PDL::Transform::Color',@_) }
390              
391             sub new {
392 15     15 0 31 my($class) = shift;
393 15         24 my($parse) = pop;
394 15         25 my($name) = pop;
395 15         44 my($me) = PDL::Transform::new($class);
396 15         228 $me->{name} = $name;
397 15         37 $me->{u_opt} = {@_};
398 15         26 $me->{idim} = 3;
399 15         22 $me->{odim} = 3;
400            
401 15         49 my %opt = parse($parse, $me->{u_opt});
402 15         4460 $me->{params} = \%opt;
403            
404 15         39 return $me;
405             }
406              
407              
408             ## Compose with gamma correction if necessary
409             sub gammify {
410 10     10 0 19 my $me = shift;
411              
412 10 100 33     77 if( exists($me->{params}->{gamma}) &&
      66        
413             defined($me->{params}->{gamma}) &&
414             $me->{params}->{gamma} != 1 ) {
415              
416             # Decode gamma from source
417 1         4 return ( $me x t_gamma($me->{params}->{gamma}) );
418            
419             } else {
420              
421 9         135 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 728 my $gamma = shift;
454 2         7 my ($me) = _new("gamma",{});
455              
456 2         6 $me->{params} = {gamma=>$gamma};
457 2         24 $me->{name} .= sprintf("=%g",$gamma);
458 2         5 $me->{idim} = 3;
459 2         4 $me->{odim} = 3;
460              
461             $me->{func} = sub {
462 3     3   1894 my ($in, $opt) = @_;
463 3         13 my $out = $in->new_or_inplace;
464 3 50       154 if($opt->{gamma} != 1) {
465 3         174 $out *= ($in->abs + ($in==0)) ** ($opt->{gamma}-1);
466             }
467 3         67 $out;
468 2         11 };
469              
470             $me->{inv} = sub {
471 2     2   2451 my ($in, $opt) = @_;
472 2         8 my $out = $in->new_or_inplace;
473 2 50       82 if($opt->{gamma} != 1) {
474 2         73 $out *= ($in->abs + ($in==0)) ** (1.0/$opt->{gamma} - 1);
475             }
476 2         39 $out;
477 2         7 };
478            
479 2         9 $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 4454 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   653 my($in, $opt) = @_;
545 4         12 my $out = $in->new_or_inplace;
546              
547 4 100       152 if($opt->{display_gamma} != 1) {
548 1         60 $out *= ($out->abs)**(1.0/$opt->{display_gamma} - 1);
549             }
550            
551 4         31 $out *= 255.0;
552              
553 4 50       70 if($opt->{byte}) {
    0          
554 4         43 $out = byte($out->rint->clip(0,255));
555             } elsif($opt->{clip}) {
556 0         0 $out->inplace->clip(0,255.49999);
557             }
558              
559 4         571 $out;
560 4         25 };
561              
562             $me->{inv} = sub {
563 2     2   2465 my($in,$opt) = @_;
564              
565 2         37 my $out = $in / 255.0;
566              
567 2 50       69 if($opt->{display_gamma} != 1) {
568 0         0 $out *= ($out->abs)**($opt->{display_gamma}-1);
569             }
570              
571 2 50       9 if($opt->{clip}) {
572 2         9 $out->inplace->clip(0,1);
573             }
574 2         108 $out;
575 4         15 };
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   1088 my $a = shift;
622 2 100       13 my $b = ($a->is_inplace ? $a->new_or_inplace : $a->copy);
623 2         125 my $sgn = 2*(0.5-($a<0));
624 2         24 $b->inplace->abs;
625 2         400 $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         73 $b *= $sgn;
632 2         45 return $b;
633             }
634              
635             sub _srgb_decode {
636 1     1   1710 my $a = shift;
637 1 50       15 my $b = ($a->is_inplace ? $a->new_or_inplace : $a->copy);
638 1         57 my $sgn = 2*(0.5-($a<0));
639 1         12 $b->inplace->abs;
640 1         42 my $c = ($b+0.055)/1.055;
641 1         106 $b .= (
642             ($b <= 0.03928) * ( $b / 12.92 ) +
643             ($b > 0.03928) * (
644             $c * ( $c->abs ** 1.4 )
645             )
646             );
647 1         30 $b *= $sgn;
648 1         16 return $b;
649             }
650            
651             sub t_srgb {
652 1     1 1 6 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   12 my($in,$opt) = @_;
660             # Convert from CIE RGB to sRGB primaries
661 1         5 my($rgb) = $in->new_or_inplace();
662             # Slow and lame -- would work far better as a pp routine...
663 1         46 _srgb_encode($rgb->inplace);
664 1         5 my $out;
665              
666 1         4 $rgb *= 255;
667 1 50       22 if($opt->{byte}) {
    0          
668 1         17 $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         125 $out;
676 1         8 };
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         5 };
687              
688 1         4 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 1373 unless(0+@_){
1026 1         3 my $s = "Usage: 't_pc(\$colortab_name, %opt)'. Named pseudocolor mappings available:\n";
1027 1         4 $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         3 for my $k(sort keys %{$pc_tab}) {
  1         17  
1030 26 100       100 $s .= sprintf(" %8s - %s%s\n",$k,$pc_tab->{$k}->{doc},($pc_tab->{$k}->{phot}?" (phot)":""));
1031             }
1032 1         11 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       7 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       7 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         5 $lut_name = $pc_tab_abbrevs->{lc($lut_name)};
1052 1 50       3 unless($lut_name) {
1053 0         0 t_pc(); # generate usage message
1054             }
1055              
1056            
1057             # Generate the object
1058 1         14 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         7 $me->{params}->{lut_name} = $lut_name;
1072 1         4 $me->{params}->{lut} = $pc_tab->{$lut_name};
1073 1 50       6 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       5 $me->{params}->{irange} = $me->{params}->{domain} if(defined($me->{params}->{domain}));
1079              
1080             # Check that range is correct
1081 1 50       4 $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     9 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       6 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         3 $me->{params}->{subs} = [ @{$me->{params}->{lut}->{subs}}[ (@{ $_t_pc_combinatorics[$me->{params}->{combination}] }) ] ];
  1         5  
  1         4  
1126            
1127             }
1128              
1129             # Generate the forward transform
1130             $me->{func} = sub {
1131 1     1   587 my($in,$opt) = @_;
1132              
1133 1         4 my $in2 = $in->new_or_inplace;
1134              
1135 1         40 my ($min,$max) = @{$opt->{irange}};
  1         4  
1136            
1137 1 50 33     10 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     17 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         169 $in2 -= $min;
1151 1         23 $in2 /= $max;
1152              
1153 1         16 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     7 if($opt->{lut}->{phot} && $opt->{perceptual}) {
1168 0         0 _srgb_decode($in2->inplace);
1169             }
1170              
1171 1 50       5 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       58 if(defined($opt->{lut}->{igamma})) {
1180 0         0 $in2 *= ($in2->abs+1e-10) ** ($opt->{lut}->{igamma} - 1);
1181             }
1182              
1183 1 50       3 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         89 $out->((0)) .= &{$opt->{subs}->[0]}($in2)->clip(0,1);
  1         19  
1204 1         96 $out->((1)) .= &{$opt->{subs}->[1]}($in2)->clip(0,1);
  1         18  
1205 1         61 $out->((2)) .= &{$opt->{subs}->[2]}($in2)->clip(0,1);
  1         16  
1206              
1207 1 50       72 if(defined($opt->{lut}->{ogamma})) {
1208 0         0 $out *= ($out->abs) ** ($opt->{lut}->{ogamma}-1);
1209             }
1210 1         6 return $out;
1211 1         8 };
1212              
1213 1         3 my $out = $me;
1214              
1215 1 50       5 if($me->{params}->{lut}->{type} eq 'hsv') {
1216 0         0 $out = (!t_hsv()) x $out;
1217             }
1218            
1219 1 50       6 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       5 unless($me->{params}->{lsRGB}) {
1224 1         5 $out = t_srgb(clip=>$me->{params}->{clip}, byte=>$me->{params}->{byte}) x $out;
1225             }
1226              
1227 1         94 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 1518 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         5 $me->{params}->{mat} = $srgb2cxyz_mat;
1295 2         26 $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   20 my($in, $opt) = @_;
1335              
1336 1         6 my $out = ( $opt->{mat} x $in->(*1) )->((0))->sever;
1337            
1338 1 50       135 if($in->is_inplace) {
1339 0         0 $in .= $out;
1340 0         0 $out = $in;
1341             }
1342 1         4 return $out;
1343 2         15 };
1344              
1345             $me->{inv} = sub {
1346 1     1   19 my($in, $opt) = @_;
1347 1         4 my $out = ( $opt->{inv} x $in->(*1) )->((0))->sever;
1348              
1349 1 50       87 if($in->is_inplace) {
1350 0         0 $in .= $out;
1351 0         0 $out = $in;
1352             }
1353 1         3 return $out;
1354 2         11 };
1355              
1356 2         6 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 993 my($me) = _new(@_, 'RGI',
1374             {gamma=>1,
1375             }
1376             );
1377              
1378             $me->{func} = sub {
1379 1     1   571 my($in,$opt) = @_;
1380 1         16 my $i = $in->sumover->(*1);
1381 1         22 my $out = zeroes($in);
1382 1         71 $out->(0:1) .= $in(0:1) / ($i+($i==0));
1383 1         71 $out->(2) .= $i/3;
1384 1 50       43 if($in->is_inplace) {
1385 0         0 $in .= $out;
1386 0         0 return $in;
1387             }
1388 1         5 return $out;
1389 1         7 };
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 404 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         4 $me->{idim} = 3;
1647 1         2 $me->{odim} = 4;
1648              
1649             $me->{func} = sub {
1650 2     2   1882 my($in,$opt) = @_;
1651 2         11 my $out = zeroes( 4, $in->((0))->dims );
1652            
1653 2         241 my $Kp = $in->maximum->(*1);
1654 2         36 (my $K = $out->(3)) .= 1 - $Kp;
1655 2         87 $out->(0:2) .= ($Kp - $in->(0:2)) / $Kp;
1656 2         252 $out->((3))->where($Kp==0) .= 1;
1657 2         224 $out->(0:2)->mv(0,-1)->where($Kp==0) .= 0;
1658              
1659 2 50 33     194 if(defined($opt->{htgamma}) && $opt->{htgamma} != 1) {
1660 0         0 $out *= ($out->abs) ** ($opt->{htgamma} - 1);
1661             }
1662              
1663 2 50       7 if($opt->{clip}) {
1664 0         0 $out->inplace->clip(0,1);
1665             }
1666              
1667 2 50       6 if($opt->{byte}) {
1668 0         0 $out = (256*$out)->clip(0,255.99999);
1669             }
1670 2         13 return $out;
1671 1         7 };
1672              
1673             $me->{inv} = sub {
1674 2     2   2023 my($in,$opt) = @_;
1675 2         11 my $out = zeroes( 3, $in->((0))->dims );
1676              
1677 2         229 $in = $in->new_or_inplace;
1678            
1679 2 50       72 if($opt->{byte}) {
1680 0         0 $in = $in / pdl(256); # makes copy
1681             }
1682              
1683 2 50 33     13 if(defined($opt->{htgamma}) && $opt->{htgamma} != 1) {
1684 0         0 $in *= ($in->abs) ** (1.0/$opt->{htgamma} - 1);
1685             }
1686 2         7 my $Kp = 1.0 - $in->(3);
1687 2         62 $out .= $Kp * ( 1 - $in->(0:2) );
1688 2         89 return $out;
1689 1         6 };
1690              
1691 1         7 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 1031 my($me) = _new(@_,"HSL",
1769             {gamma=>1,
1770             lgamma=>1,
1771             hue_linear=>0,
1772             hsv=>0
1773             }
1774             );
1775              
1776 2 100       11 $me->{name} = "HSV" if($me->{params}->{hsv});
1777            
1778             $me->{func} = sub {
1779 2     2   1152 my($in, $opt) = @_;
1780 2         7 my $out = zeroes($in);
1781            
1782 2         160 my $Cmax = $in->maximum;
1783 2         37 my $Cmin = $in->minimum;
1784 2         44 my $maxdex = $in->qsorti->((2))->sever;
1785 2         63 my $Delta = ( $Cmax - $Cmin );
1786              
1787 2         8 my $dexes = ($maxdex->(*1) + pdl(0,1,2)) % 3;
1788              
1789 2         151 my $H = $out->((0));
1790              
1791 2 50       35 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         7 my $Hint = 2*$dexes->((0));
1802 2         53 my $Hfrac = (($in->index1d($dexes->(1)) - $in->index1d($dexes->(2)))->((0))/($Delta+($Delta==0)));
1803 2         244 my $Hfs = -1*($Hfrac<0) + ($Hfrac >= 0);
1804 2         110 $Hfrac .= $Hfs * ( asin( ($Hfrac->abs) ** 0.25 ) * 2/$PI );
1805 2         63 $H .= $Hint + $Hfrac;
1806 2         33 $H /= 6;
1807             }
1808              
1809 2         51 $H += ($H<0);
1810            
1811             # Lightness and Saturation
1812 2         33 my $L = $out->((2));
1813 2 100       32 if($opt->{hsv}) {
1814 1         4 $L .= $Cmax;
1815 1         16 $out->((1)) .= $Delta / ($L + ($L==0));
1816             } else {
1817 1         15 $L .= ($Cmax + $Cmin)/2;
1818 1         20 $out->((1)) .= $Delta / (1 - (2*$L-1)->abs + ($L==0 | $L==1));
1819             }
1820            
1821              
1822 2 50       188 if( $opt->{lgamma} != 1 ){
1823 0         0 $L .= $L * (($L->abs + ($L==0)) ** (1.0/$opt->{lgamma} - 1));
1824             }
1825              
1826 2 50       10 if($in->is_inplace) {
1827 0         0 $in .= $out;
1828 0         0 $out = $in;
1829             }
1830 2         17 return $out;
1831 2         17 };
1832              
1833             $me->{inv} = sub {
1834 2     2   2002 my($in,$opt) = @_;
1835              
1836 2         10 my $H = $in->((0))*6;
1837 2         69 my $S = $in->((1));
1838 2         29 my $L = $in->((2));
1839              
1840 2 50       27 if($opt->{lgamma} != 1) {
1841 0         0 $L = $L * (($L->abs + ($L==0)) ** ($opt->{lgamma}-1));
1842             }
1843            
1844 2         7 my $ZCX = zeroes($in);
1845 2         132 my $C = $ZCX->((1));
1846 2         27 my $m;
1847 2 100       8 if($opt->{hsv}) {
1848 1         10 $C .= $L * $S;
1849 1         22 $m = $L - $C;
1850             } else {
1851 1         34 $C .= (1 - (2*$L - 1)->abs) * $S;
1852 1         35 $m = $L - $C/2;
1853             }
1854              
1855 2 50       12 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         8 $ZCX->((2)) .= $C * sin($PI/2 * (1 - ($H % 2 - 1)->abs))**4;
1861             }
1862            
1863 2         199 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         91 my $dex = $dexes->index1d($H->floor->(*1,*1) % 6)->((0))->sever; # 3x(threads)
1865 2         133 my $out = $ZCX->index1d($dex)->sever + $m->(*1);
1866              
1867 2 50       53 if($in->is_inplace) {
1868 0         0 $in .= $out;
1869 0         0 $out = $in;
1870             }
1871              
1872 2         21 return $out;
1873 2         10 };
1874              
1875 2         5 return gammify($me);
1876             }
1877              
1878              
1879             sub t_hsv {
1880 1     1 1 1019 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 $to_gamma = $me->{params}->{ogamma};
2094 0 0       0 $to_gamma = $me->{params}->{gamma} if !defined $to_gamma;
2095             }
2096              
2097             my $out =
2098             !t_xyz(rgb_system=>$to_rgb, gamma=>$me->{params}->{gamma}, use_system_gamma=>$me->{params}->{use_system_gamma}) x
2099             t_shift_illuminant($to_rgb->{w},basis=>"XYZ",from=>$from_rgb->{w},method=>$me->{params}->{wp_method}) x
2100 0         0 t_xyz(rgb_system=>$from_rgb, gamma=>$me->{params}->{gamma}, use_system_gamma=>$me->{params}->{use_system_gamma});
2101              
2102 0         0 return $out;
2103              
2104             }
2105              
2106             ##############################
2107             # Reference illuminants
2108             # (aka "white points")
2109              
2110             =head2 PDL::Transform::Color::xyy_from_D
2111              
2112             =for usage
2113              
2114             $xyy = PDL::Transform::Color::xyy_from_D($D_value)
2115              
2116             =for ref
2117              
2118             This utility routine generates CIE xyY system colorimetric values for
2119             standard CIE D-class illuminants (e.g., D50 or D65). The illuminants are
2120             calculated from a standard formula and correspond to black body
2121             temperatures between 4,000K and 250,000K. The D value is the
2122             temperature in K divided by 100, e.g. broad daylight is D65,
2123             corresponding to 6500 Kelvin.
2124              
2125             This is used for calculating standard reference illuminants, to convert
2126             RGB values between illuminants.
2127              
2128             For example, sRGB uses a D65 illuminant, but many other color standards
2129             refer to a D50 illuminant.
2130              
2131             The colorimetric values are xy only; the Y coordinate can be specified via
2132             an option, or defaults to 0.5.
2133              
2134             This routine is mainly used by C, which handles most
2135             of the CIE-recognized standard illuminant sources including the D's.
2136              
2137             See C for a description of the CIE xyY absolute colorimetric system.
2138              
2139             C accepts the following options:
2140              
2141             =over 3
2142              
2143             =item Y - the Y value of the output xyY coordinate
2144              
2145             =back
2146              
2147             =cut
2148            
2149             sub xyy_from_D {
2150 18     18 1 48 my $D = pdl(shift);
2151 18   50     931 my $u_opt = shift || {};
2152 18         56 my %opt = parse({
2153             Y=>1
2154             },
2155             $u_opt);
2156              
2157 18 50 33     4200 die "cie_xy_from_D: D must be between 40 and 250" if(any($D< 40) || any($D > 250));
2158 18         1945 my $T = $D*100;
2159              
2160 18         71 my $Xd;
2161 18         1863 $Xd = ($D<=70) * ( 0.244063 + 0.09911e3/$T + 2.9678e6/$T/$T - 4.6070e9/$T/$T/$T ) +
2162             ($D> 70) * ( 0.237040 + 0.24748e3/$T + 1.9018e6/$T/$T - 2.0064e9/$T/$T/$T );
2163              
2164 18         888 return pdl( $Xd, -3*$Xd*$Xd + 2.870*$Xd - 0.275, $opt{Y} )->mv(-1,0)->sever;
2165             }
2166              
2167             # xy data for FL3.x standards, from CIE "Colorimetry" 3rd edition Table T.8.2
2168             my $fl3tab = [
2169             [],
2170             [0.4407, 0.4033],
2171             [0.3808, 0.3734],
2172             [0.3153, 0.3439],
2173             [0.4429, 0.4043],
2174             [0.3749, 0.3672],
2175             [0.3488, 0.3600],
2176             [0.4384, 0.4045],
2177             [0.3820, 0.3832],
2178             [0.3499, 0.3591],
2179             [0.3455, 0.3460],
2180             [0.3245, 0.3434],
2181             [0.4377, 0.4037],
2182             [0.3830, 0.3724],
2183             [0.3447, 0.3609],
2184             [0.3127, 0.3288]
2185             ];
2186             # xy data for FLx standards, from CIE "Colorimetry" 3rd edition Table T.7
2187             my $fltab = [
2188             [],
2189             [0.3131, 0.3371],
2190             [0.3721, 0.3751],
2191             [0.4091, 0.3941],
2192             [0.4402, 0.4031],
2193             [0.3138, 0.3452],
2194             [0.3779, 0.3882],
2195             [0.3129, 0.3292],
2196             [0.3458, 0.3586],
2197             [0.3741, 0.3727],
2198             [0.3458, 0.3588],
2199             [0.3805, 0.3769],
2200             [0.4370, 0.4042]
2201             ];
2202             # xy data for HPx standards, from CIE "Colorimetry" 3rd edition table T.9
2203             my $hptab = [
2204             [],
2205             [0.5330, 0.4150],
2206             [0.4778, 0.4158],
2207             [0.4302, 0.4075],
2208             [0.3812, 0.3797],
2209             [0.3776, 0.3713]
2210             ];
2211            
2212              
2213              
2214             =head2 PDL::Transform::Color::xyy_from_illuminant
2215              
2216             =for usage
2217              
2218             $xyy = PDL::Transform::Color::xyy_from_illuminant($name)
2219              
2220             =for ref
2221              
2222             This utility routine generates CIE xyY system colorimetric values for
2223             all of the standard CIE illuminants. The illuminants are looked up in
2224             a table populated from the CIE publication I, 3rd
2225             edition.
2226              
2227             The illuminant of a system is equivalent to its white point -- it is
2228             the location in xyY absolute colorimetric space that corresponds to
2229             "white".
2230              
2231             CIE recognizes many standard illuminants, and (as of 2017) is in the
2232             process of creating a new set -- the "L" series illuminants -- that is
2233             meant to represent LED lighting.
2234              
2235             Proper treatment of an illuminant requires a full spectral representation,
2236             which the CIE specifies for each illuminant. Analysis of that spectrum is
2237             a major part of what CIE calls "Color rendering index (CRI)" for a particular
2238             light source. PDL::Transform::Color is a strictly tri-coordinate system
2239             and does not handle the nuances of spectral effects on CRI. In effect,
2240             all illuminants are treated as having a CRI of unity (perfect).
2241              
2242             Illuminants that are understood are:
2243              
2244             =over 3
2245              
2246             =item * a 3-PDL in CIE xyY coordinates
2247              
2248             =item * a CIE standard name
2249              
2250             =back
2251              
2252             The CIE names are:
2253              
2254             =over 3
2255              
2256             =item A - a gas-filled tungsten filament lamp at 2856K
2257              
2258             =item B - not supported (deprecated by CIE)
2259              
2260             =item C - early daylight simulant, replaced by the D[n] sources
2261              
2262             =item D[n] - Blackbody radiation at 100[n] Kelvin (e.g. D65)
2263              
2264             =item F[n] - Fluorescent lights of various types (n=1-12 or 3.1-3.15)
2265              
2266             =item HP[n] - High Pressure discharge lamps (n=1-5)
2267              
2268             =item L[n] - LED lighting (not yet supported)
2269              
2270             =back
2271              
2272             =cut
2273              
2274             sub xyy_from_illuminant {
2275 20     20 1 31 my $name = shift;
2276 20 50       114 if(UNIVERSAL::isa($name,"PDL")) {
2277 0 0 0     0 if(($name->nelem==2 || $name->nelem==3) && $name->dim(0)==$name->nelem) {
      0        
2278 0         0 return $name;
2279             } else {
2280 0         0 die "xyy_from_illuminant: PDL must be a 2-PDL or a 3-PDL";
2281             }
2282             }
2283 20   50     78 my $u_opt = shift || {};
2284 20         73 my %opt = parse({
2285             Y=>1
2286             }, $u_opt);
2287 20 50       4531 if($name =~ m/^A/i) {
    50          
    100          
    100          
    50          
    0          
    0          
    0          
2288 0         0 return pdl(0.44758, 0.40745, $opt{Y});
2289             } elsif($name =~ m/^B/) {
2290 0         0 die "Illuminant B is not supported (deprecated by CIE)";
2291             } elsif($name =~ m/^C/) {
2292 1         13 return pdl(0.31006, 0.31616, $opt{Y});
2293             } elsif( $name =~ m/^D(.*)$/i) {
2294 18         44 return xyy_from_D($1,$u_opt);
2295             } elsif( $name =~ m/^E/i) {
2296 1         13 return pdl(0.33333,0.33333,$opt{Y});
2297             } elsif( $name =~ m/^FL?([\d+])(\.[\d])?$/i) {
2298 0           my $flno = $1+0;
2299 0           my $flsubno = $2+0;
2300 0 0 0       die "Illuminant $name not recognized (FL1-FL12, or FL3.1-FL3.15)"
      0        
      0        
      0        
2301             if($flno < 1 || $flno > 12 ||
2302             ($flsubno && $flno != 3) ||
2303             ($flsubno > 15)
2304             );
2305              
2306 0 0 0       if($flno==3 && $flsubno) {
2307 0           return pdl(@{$fl3tab->[$flsubno]},$opt{Y});
  0            
2308             } else {
2309 0           return pdl(@{$fltab->[$flno]},$opt{Y});
  0            
2310             }
2311             } elsif( $name =~ m/^HP?(\d)/i ) {
2312 0           my $hpno = $1+0;
2313 0 0 0       die "Unknown HP illuminant no. $hpno" if($hpno<1 || $hpno > 5);
2314 0           return pdl(@{$hptab->[$hpno]}, $opt{Y});
  0            
2315             } elsif( $name =~ m/^L/i) {
2316 0           die "Illuminant L is not (yet) supported";
2317             } else {
2318 0           die "Unknown illuminant $name";
2319             }
2320             }
2321              
2322              
2323             ##############################
2324             # Database of standard RGB color systems from Bruce Lindbloom
2325             # Make a database of xyY values of primaries, illuminants, and standard gammas for common RGB systems
2326             # Also stash matrices for converting those systems to lsRGB.
2327             #
2328             # Columns: gamma, illuminant, xyY for R (3 cols), xyY for G (3 cols), xyY for B (3 cols), abbrev char count
2329             our $rgbtab_src = {
2330             "Adobe" => [2.2, "D65", 0.6400, 0.3300, 0.297361, 0.2100, 0.7100, 0.627355, 0.1500, 0.0600, 0.075285, 2],
2331             "Apple" => [1.8, "D65", 0.6250, 0.3400, 0.244634, 0.2800, 0.5950, 0.672034, 0.1550, 0.0700, 0.083332, 2],
2332             "Best" => [2.2, "D50", 0.7347, 0.2653, 0.228457, 0.2150, 0.7750, 0.737352, 0.1300, 0.0350, 0.034191, 3],
2333             "Beta" => [2.2, "D50", 0.6888, 0.3112, 0.303273, 0.1986, 0.7551, 0.663786, 0.1265, 0.0352, 0.032941, 3],
2334             "Bruce" => [2.2, "D65", 0.6400, 0.3300, 0.240995, 0.2800, 0.6500, 0.683554, 0.1500, 0.0600, 0.075452, 2],
2335             "BT 601" => [2.2, "D65", 0.6300, 0.3400, 0.299000, 0.3100, 0.5950, 0.587000, 0.1550, 0.0700, 0.114000, 3],
2336             "BT 709" => [2.2, "D65", 0.6300, 0.3400, 0.212600, 0.3100, 0.5950, 0.715200, 0.1550, 0.0700, 0.072200, 3],
2337             "CIE" => [2.2, "E", 0.7350, 0.2650, 0.176204, 0.2740, 0.7170, 0.812985, 0.1670, 0.0090, 0.010811, 2],
2338             "ColorMatch" => [1.8, "D50", 0.6300, 0.3400, 0.274884, 0.2950, 0.6050, 0.658132, 0.1500, 0.0750, 0.066985, 2],
2339             "Don 4" => [2.2, "D50", 0.6960, 0.3000, 0.278350, 0.2150, 0.7650, 0.687970, 0.1300, 0.0350, 0.033680, 1],
2340             "ECI v2" => [1.0, "D50", 0.6700, 0.3300, 0.320250, 0.2100, 0.7100, 0.602071, 0.1400, 0.0800, 0.077679, 2],
2341             "Ekta PS5" => [2.2, "D50", 0.6950, 0.3050, 0.260629, 0.2600, 0.7000, 0.734946, 0.1100, 0.0050, 0.004425, 2],
2342             "NTSC" => [2.2, "C", 0.6700, 0.3300, 0.298839, 0.2100, 0.7100, 0.586811, 0.1400, 0.0800, 0.114350, 1],
2343             "PAL" => [2.2, "D65", 0.6400, 0.3300, 0.222021, 0.2900, 0.6000, 0.706645, 0.1500, 0.0600, 0.071334, 2],
2344             "ProPhoto" => [1.8, "D50", 0.7347, 0.2653, 0.288040, 0.1596, 0.8404, 0.711874, 0.0366, 0.0001, 0.000086, 2],
2345             "ROMM" => [1.8, "D50", 0.7347, 0.2653, 0.288040, 0.1596, 0.8404, 0.711874, 0.0366, 0.0001, 0.000086, 2],
2346             "SECAM" => [2.2, "D65", 0.6400, 0.3300, 0.222021, 0.2900, 0.6000, 0.706645, 0.1500, 0.0600, 0.071334, 2],
2347             "SMPTE-C" => [2.2, "D65", 0.6300, 0.3400, 0.212395, 0.3100, 0.5950, 0.701049, 0.1550, 0.0700, 0.086556, 2],
2348             "sRGB" => [2.2, "D65", 0.6300, 0.3400, 0.212395, 0.3100, 0.5950, 0.701049, 0.1550, 0.0700, 0.086556, 2],
2349             "wgRGB" => [2.2, "D50", 0.7350, 0.2650, 0.258187, 0.1150, 0.8260, 0.724938, 0.1570, 0.0180, 0.016875, 1]
2350             };
2351              
2352             ##############################
2353             # RGB color systems in more code-approachable form. Parse the table to create hash refs by name, and an
2354             # abbrev table that allows abbreviated naming
2355             #
2356             our $rgbtab = {};
2357             our $rgb_abbrevs = {};
2358             for my $k(keys %$rgbtab_src) {
2359             my $v = $rgbtab_src->{$k};
2360             $rgbtab->{$k} = {
2361             gamma => $v->[0],
2362             w_name => $v->[1],
2363             w => xyy_from_illuminant($v->[1]),
2364             r => pdl($v->[2],$v->[3],$v->[4]),
2365             g => pdl($v->[5],$v->[6],$v->[7]),
2366             b => pdl($v->[8],$v->[9],$v->[10])
2367             };
2368             my $str = $k;
2369             $str =~ tr/A-Z/a-z/;
2370             $str =~ s/\s\-//g;
2371             for my $i($v->[11]..length($str)){
2372             $rgb_abbrevs->{substr($str,0,$i)} = $k;
2373             }
2374             }
2375              
2376             # Gets an rgb descriptor hash from an input that might be a hash or a name.
2377             # If it's a hash, check to make sure it's copacetic.
2378              
2379             =head2 PDL::Transform::Color::get_rgb
2380              
2381             =for usage
2382              
2383             my $rgb_hash = get_rgb( $name );
2384              
2385             =for ref
2386              
2387             C is an internal routine that retrieves a set of
2388             RGB primary colors from an internal database. There are several named RGB systems,
2389             with different primary colors for each. The primary colors are represented as
2390             CIE xyY values in a returned hash ref.
2391              
2392             The return value is a hash ref with the following fields:
2393              
2394             =over 3
2395              
2396             =item gamma - the specified gamma of that RGB system (or 2.2, for sRGB)
2397              
2398             =item w_name - the name of the illuminant / white-point for that system
2399              
2400             =item w - the xyY value of the illuminant / white-point for that system
2401              
2402             =item r - the xyY value of the red primary color at unit intensity
2403              
2404             =item g - the xyY value of the green primary color at unit intensity
2405              
2406             =item b - the xyY value of the blue primary color at unit intensity
2407              
2408             =back
2409              
2410             Recognized RGB system names are:
2411              
2412             =over 3
2413              
2414             =item Adobe - Adobe's 1998 RGB, intended to encompass nearly all of the CMYK gamut (gamma=2.2, white=D65)
2415              
2416             =item Apple - Apple's display standard from c. 1990 - c. 2010 (gamma=1.8, white=D65)
2417              
2418             =item Best - Wide-gamut RGB developed by Don Hutcheson (L) (gamma=2.2, white=D50)
2419              
2420             =item Beta - Bruce Lindbloom's optimized ultra-wide-gamut RGB (gamma=2.2, white=D50)
2421              
2422             =item Bruce - Bruce Fraser's conservative-gamut RGB space for 8-bit editing (gamma=2.2, white=D65)
2423              
2424             =item BT 601 - ITU-R standard BT.601 (used for MPEG & SDTV) (gamma=2.2, white=D65)
2425              
2426             =item BT 709 - ITU-R standard BT.709 (used for HDTV) (gamma=2.2, white=D65)
2427              
2428             =item CIE - CIE 1931 calibrated color space (based on physical emission lines) (gamma=2.2, white=E)
2429              
2430             =item ColorMatch - quasi-standard from c.1990 -- matches Radius Pressview CRT monitors. (gamma=1.8, white=D50)
2431              
2432             =item Don 4 - wide-gamut D50 working space gets the Ektachrome color gamut (gamma=2.2, white=D50)
2433              
2434             =item ECI v2 - RGB standard from the European Color Initiative (gamma=1, white=D50)
2435              
2436             =item Ekta PS5 - developed by Joseph Holms (L) for scanned Ektachrome slides (gamma=2.2, white=D50)
2437              
2438             =item NTSC - Never The Same Color (U.S. analog TV standard) (gamma=2.2, white=C)
2439              
2440             =item PAL - Pictures Always Lovely (U.K. analog TV standard) (gamma = 2.2, white=D65)
2441              
2442             =item ProPhoto - Wide gamut from Kodak, designed for photo output. (gamma=1.8, white=D60)
2443              
2444             =item ROMM - Synonym for ProPhoto (gamma=1.8, white=D60)
2445              
2446             =item SECAM - Systeme Electronique Contre les AMericains (French analog TV standard) (gamma=2.2, white=D65)
2447              
2448             =item SMPTE-C - Soc. Motion Pict. & TV Engineers (current U.S. TV standard) (gamma=2.2, white=D65)
2449              
2450             =item sRGB - Standard for consumer computer monitors (gamma~2.2, white=D65)
2451              
2452             =item wgRGB - Wide Gamut RGB (gamma=2.2, white=D50)
2453              
2454             =back
2455            
2456             =cut
2457            
2458             sub get_rgb {
2459 0     0 1   my $new_rgb = shift;
2460 0 0         unless(ref $new_rgb) {
    0          
2461 0           $new_rgb=~tr/A-Z/a-z/; $new_rgb =~ s/\s\-//g;
  0            
2462 0           my $new_rgb_name = $rgb_abbrevs->{$new_rgb};
2463 0 0         if($rgbtab->{$new_rgb_name}) {
2464 0           $new_rgb = $rgbtab->{$new_rgb_name};
2465             } else {
2466 0           die "Unknown RGB system '$new_rgb'\nKnown ones are:\n\t".join("\n\t",((sort keys %$rgbtab),""));
2467             }
2468 0           } elsif(ref $new_rgb eq 'HASH') {
2469 0           my $bad = 0;
2470 0           for my $k(qw/w r g b/) {
2471 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        
2472             }
2473 0 0         $new_rgb->{gamma} = 1 unless defined($new_rgb->{gamma});
2474 0 0         die "Incorrect RGB primaries hash -- see docs" if($bad);
2475             } else {
2476 0           die "bad RGB specification -- see docs";
2477             }
2478 0           return $new_rgb;
2479             }
2480              
2481              
2482              
2483              
2484             1;