File Coverage

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