File Coverage

lib/ICC/Support/PCS.pm
Criterion Covered Total %
statement 17 357 4.7
branch 2 270 0.7
condition 1 60 1.6
subroutine 5 32 15.6
pod 2 10 20.0
total 27 729 3.7


line stmt bran cond sub pod time code
1             package ICC::Support::PCS;
2              
3 2     2   102348 use strict;
  2         12  
  2         58  
4 2     2   9 use Carp;
  2         4  
  2         135  
5              
6             our $VERSION = 0.75;
7              
8             # revised 2019-01-07
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 2     2   517 use lib 'lib';
  2         704  
  2         13  
14              
15             # inherit from Shared
16 2     2   690 use parent qw(ICC::Shared);
  2         304  
  2         12  
17              
18             =encoding utf8
19              
20             list of supported PCS connection spaces
21              
22             0 - 8-bit ICC CIELAB (100, 0, 0 => 255/255, 128/255, 128/255 = 1, 0.50196, 0.50196)
23             0 - 16-bit ICC CIELAB (100, 0, 0 => 65535/65535, 32896/65535, 32896/65535 = 1, 0.50196, 0.50196)
24             1 - 16-bit ICC legacy L*a*b* (100, 0, 0 => 65280/65535, 32768/65535, 32768/65535 = 0.99611, 0.50001, 0.50001)
25             2 - 16-bit EFI/Monaco L*a*b* (100, 0, 0 => 65535/65535, 32768/65535, 32768/65535 = 1, 0.50001, 0.50001)
26             3 - L*a*b* (100, 0, 0 => 100, 0, 0)
27             4 - LxLyLz (100, 0, 0 => 100, 100, 100)
28             5 - unit LxLyLz (100, 0, 0 => 1, 1, 1)
29             6 - xyY (100, 0, 0 => 0.34570, 0.35854, 100)
30             7 - 16-bit ICC XYZ (100, 0, 0 => 0.9642 * 32768/65535, 32768/65535, 0.8249 * 32768/65535 = 0.48211, 0.50001, 0.41246)
31             8 - 32-bit ICC XYZNumber (100, 0, 0 => 0.9642, 1.0, 0.8249)
32             9 - xyz (100, 0, 0 => 1, 1, 1)
33             10 - XYZ (100, 0, 0 => 96.42, 100, 82.49)
34              
35             explanation and application
36              
37             option 0 is for both 8-bit and 16-bit CIELAB encoding. it is listed twice to show the equivalence.
38             option 1 is the 16-bit L*a*b* encoding from the v2 specification. option 1 also applies to mft2 and ncl2 tags within v4 profiles.
39             option 2 is a non-standard L*a*b* encoding used by EFI and Monaco.
40             option 3 is standard L*a*b* encoding, used in measurement files and floating point tags (e.g. D2Bx, B2Dx).
41             option 4 is L* encoding of the xyz channels.
42             option 5 is unit L* encoding of the xyz channels.
43             option 6 is chromaticity plus Y.
44             option 7 is the 16-bit XYZ encoding used by v2 and v4. 8-bit XYZ encoding is undefined by the ICC specification.
45             option 8 is the 32-bit format used by XYZ tags, and the format used to set absolute colorimetry when creating PCS objects.
46             option 9 is X/Xn, Y/Yn, Z/Zn, as defined in ISO 13655.
47             option 10 is standard XYZ encoding, used in measurement files.
48              
49             =cut
50              
51             # make PCS connection object
52             # structure of the input/output parameter arrays is: (pcs_connection_space, [white_point, [black_point]])
53             # white point and black point values are optional. default values are D50 for white point and (0, 0, 0) for black point.
54             # white point and black point are encoded as ICC XYZNumbers, which is how they are stored within ICC profiles.
55             # for explanation of tone compression linearity, see 'tone_compression_notes.txt'.
56             # default tone compression linearity = 0 (linear tone compression).
57             # parameters: ()
58             # parameters: (ref_to_input_parameter_array, ref_to_output_parameter_array, [tone_compression_linearity])
59             sub new {
60              
61             # get object class
62 1     1 0 855 my $class = shift();
63              
64             # create empty PCS object
65 1         4 my $self = [
66             {}, # object header
67             [], # parameter array
68             [], # tone compression array
69             0 # clipping flag
70             ];
71              
72             # if 2 or 3 parameters
73 1 50 33     11 if (@_ == 2 || @_ == 3) {
    50          
74            
75             # create new object from parameters
76 0         0 _new_pcs($self, @_);
77            
78             # if any parameters
79             } elsif (@_) {
80            
81             # error
82 0         0 croak('wrong number of parameters');
83            
84             }
85              
86             # bless object
87 1         3 bless($self, $class);
88              
89             # return object reference
90 1         3 return($self);
91              
92             }
93              
94             # get/set reference to header hash
95             # parameters: ([ref_to_new_hash])
96             # returns: (ref_to_hash)
97             sub header {
98              
99             # get object reference
100 0     0 0   my $self = shift();
101              
102             # if there are parameters
103 0 0         if (@_) {
104            
105             # if one parameter, a hash reference
106 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
107            
108             # set header to new hash
109 0           $self->[0] = {%{shift()}};
  0            
110            
111             } else {
112            
113             # error
114 0           croak('parameter must be a hash reference');
115            
116             }
117            
118             }
119              
120             # return reference
121 0           return($self->[0]);
122              
123             }
124              
125             # get/set clipping flag
126             # if flag is true, values are clipped
127             # parameters: ([new_flag_value])
128             # returns: (flag_value)
129             sub clip {
130            
131             # get object reference
132 0     0 1   my $self = shift();
133            
134             # if there are parameters
135 0 0         if (@_) {
136            
137             # if one parameter
138 0 0         if (@_ == 1) {
139            
140             # set object clipping mask value
141 0           $self->[3] = shift();
142            
143             } else {
144            
145             # error
146 0           croak('more than one parameter');
147            
148             }
149            
150             }
151            
152             # return clipping mask value
153 0           return($self->[3]);
154            
155             }
156              
157             # get/set tone compression linearity
158             # parameters: ([new_linearity_value])
159             # returns: (linearity_value)
160             sub linearity {
161            
162             # get object reference
163 0     0 0   my $self = shift();
164            
165             # if there are parameters
166 0 0         if (@_) {
167            
168             # if one parameter
169 0 0         if (@_ == 1) {
170            
171             # set linearity value
172 0           $self->[2][1] = shift();
173            
174             # update tone compression coefficients
175 0           tc_pars($self);
176            
177             } else {
178            
179             # error
180 0           croak('more than one parameter');
181            
182             }
183            
184             }
185            
186             # return linearity value
187 0           return($self->[2][1]);
188            
189             }
190              
191             # set input/output gamut scale
192             # sets input/output black point vector to the white point vector x (1 - scale)
193             # a zero value leaves the corresponding black point unchanged
194             # parameters: ([input_gamut_scale_factor, output_gamut_scale_factor])
195             # returns: (input_gamut_scale_factor, output_gamut_scale_factor)
196             sub scale {
197              
198             # get object reference
199 0     0 0   my $self = shift();
200              
201             # local variable
202 0           my (@scale);
203              
204             # if no parameters
205 0 0 0       if (@_ == 0) {
    0          
206            
207             # for each scale factor
208 0           for my $i (0 .. 1) {
209            
210             # if white point and black point are defined
211 0 0 0       if (defined($self->[1][$i][1]) && defined($self->[1][$i][2]) && $self->[1][$i][1][1] != 0) {
      0        
212            
213             # set scale value
214 0           $scale[$i] = 1 - ($self->[1][$i][2][1]/$self->[1][$i][1][1]);
215            
216             } else {
217            
218             # set scale value
219 0           $scale[$i] = 0;
220            
221             }
222            
223             }
224            
225             # return
226 0           return(@scale);
227            
228             # if two numeric parameters
229 0           } elsif (@_ == 2 && 2 == grep {Scalar::Util::looks_like_number($_)} @_) {
230            
231             # for each scale factor
232 0           for my $i (0 .. 1) {
233            
234             # if gamut scale factor not 0, and white point defined
235 0 0 0       if ($_[$i] && defined($self->[1][$i][1])) {
236            
237             # for XYZ
238 0           for my $j (0 .. 2) {
239            
240             # set black point to scaled white point value
241 0           $self->[1][$i][2][$j] = (1 - $_[$i]) * $self->[1][$i][1][$j];
242            
243             }
244            
245             }
246            
247             }
248            
249             # update tone compression coefficients
250 0           tc_pars($self);
251            
252             # return
253 0           return(@_);
254            
255             } else {
256            
257             # error
258 0           croak('invalid scale inputs');
259            
260             }
261            
262             }
263              
264             # transform data
265             # supported input types:
266             # parameters: (list, [hash])
267             # parameters: (vector, [hash])
268             # parameters: (matrix, [hash])
269             # parameters: (Math::Matrix_object, [hash])
270             # parameters: (structure, [hash])
271             # returns: (same_type_as_input)
272             sub transform {
273              
274             # set hash value (0 or 1)
275 0 0   0 0   my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
276              
277             # if input a 'Math::Matrix' object
278 0 0 0       if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
279            
280             # call matrix transform
281 0           &_trans2;
282            
283             # if input an array reference
284             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
285            
286             # if array contains numbers (vector)
287 0 0 0       if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0        
  0            
  0            
288            
289             # call vector transform
290 0           &_trans1;
291            
292             # if array contains vectors (2-D array)
293 0 0         } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0            
  0            
294            
295             # call matrix transform
296 0           &_trans2;
297            
298             } else {
299            
300             # call structure transform
301 0           &_trans3;
302            
303             }
304            
305             # if input a list (of numbers)
306 0           } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
307            
308             # call list transform
309 0           &_trans0;
310            
311             } else {
312            
313             # error
314 0           croak('invalid transform input');
315            
316             }
317              
318             }
319              
320             # invert data
321             # supported input types:
322             # parameters: (list, [hash])
323             # parameters: (vector, [hash])
324             # parameters: (matrix, [hash])
325             # parameters: (Math::Matrix_object, [hash])
326             # parameters: (structure, [hash])
327             # returns: (same_type_as_input)
328             sub inverse {
329              
330             # set hash value (0 or 1)
331 0 0   0 0   my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
332              
333             # if input a 'Math::Matrix' object
334 0 0 0       if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
335            
336             # call matrix transform
337 0           &_inv2;
338            
339             # if input an array reference
340             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
341            
342             # if array contains numbers (vector)
343 0 0 0       if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0        
  0            
  0            
344            
345             # call vector transform
346 0           &_inv1;
347            
348             # if array contains vectors (2-D array)
349 0 0         } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0            
  0            
350            
351             # call matrix transform
352 0           &_inv2;
353            
354             } else {
355            
356             # call structure transform
357 0           &_inv3;
358            
359             }
360            
361             # if input a list (of numbers)
362 0           } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
363            
364             # call list transform
365 0           &_inv0;
366            
367             } else {
368            
369             # error
370 0           croak('invalid transform input');
371            
372             }
373              
374             }
375              
376             # compute Jacobian matrix
377             # parameters: (input_vector, [hash])
378             # returns: (Jacobian_matrix, [output_vector])
379             sub jacobian {
380              
381             # get parameters
382 0     0 0   my ($self, $in, $hash) = @_;
383              
384             # local variables
385 0           my ($pcsi, $pcso, @t, @d, $jac);
386              
387             # verify 3 channels
388 0 0         (@{$in} == 3) or croak('PCS object input not 3 channels');
  0            
389              
390             # get input PCS
391 0           $pcsi = $self->[1][0][0];
392              
393             # get output PCS
394 0           $pcso = $self->[1][1][0];
395              
396             # convert from input PCS
397 0           @t = _rev($pcsi, @{$in});
  0            
398              
399             # compute _rev Jacobian
400 0           $jac = _rev_jac($pcsi, @{$in});
  0            
401              
402             # if tone compression is required
403 0 0 0       if ($self->[2][0]) {
    0 0        
    0          
404            
405             # if input PCS is L*a*b*
406 0 0         if ($pcsi <= 5) {
407            
408             # apply Lab2xyz Jacobian
409 0           $jac = ICC::Shared::Lab2xyz_jac(@t) * $jac;
410            
411             # convert to xyz
412 0           @t = ICC::Shared::_Lab2xyz(@t);
413            
414             }
415            
416             # compute forward derivatives
417 0           @d = _tc_derv($self, 0, @t);
418            
419             # for each output
420 0           for my $i (0 .. 2) {
421            
422             # for each input
423 0           for my $j (0 .. 2) {
424            
425             # adjust Jacobian
426 0           $jac->[$i][$j] *= $d[$i];
427            
428             }
429            
430             }
431            
432             # compute forward tone compression
433 0           @t = _tc($self, 0, @t);
434            
435             # if output PCS is L*a*b*
436 0 0         if ($pcso <= 5) {
437            
438             # apply xyz2Lab Jacobian
439 0           $jac = ICC::Shared::xyz2Lab_jac(@t) * $jac;
440            
441             # convert to L*a*b*
442 0           @t = ICC::Shared::_xyz2Lab(@t);
443            
444             }
445            
446             # if input PCS is L*a*b* and output PCS is xyz
447             } elsif ($pcsi <= 5 && $pcso >= 6) {
448            
449             # apply Lab2xyz Jacobian
450 0           $jac = ICC::Shared::Lab2xyz_jac(@t) * $jac;
451            
452             # convert to xyz
453 0           @t = ICC::Shared::_Lab2xyz(@t);
454            
455             # if input PCS is xyz and output PCS is L*a*b*
456             } elsif ($pcsi >= 6 && $pcso <= 5) {
457            
458             # apply xyz2Lab Jacobian
459 0           $jac = ICC::Shared::xyz2Lab_jac(@t) * $jac;
460            
461             # convert to L*a*b*
462 0           @t = ICC::Shared::_xyz2Lab(@t);
463            
464             }
465              
466             # apply forward Jacobian
467 0           $jac = _fwd_jac($pcso, @t) * $jac;
468              
469             # if output values wanted
470 0 0         if (wantarray) {
471            
472             # return Jacobian and output values
473 0           return($jac, [_fwd($pcso, $self->[3], @t)]);
474            
475             } else {
476            
477             # return Jacobian only
478 0           return($jac);
479            
480             }
481            
482             }
483              
484             # compute tone compression coefficients
485             # parameters: (object_reference)
486             sub tc_pars {
487            
488             # get object reference
489 0     0 0   my $self = shift();
490            
491             # local variables
492 0           my ($lin, $elin);
493            
494             # set tc flag (true if tc required)
495 0 0         $self->[2][0] = grep {$self->[1][0][1][$_] != $self->[1][1][1][$_] || $self->[1][0][2][$_] != $self->[1][1][2][$_]} (0 .. 2);
  0            
496            
497             # if non-linear tone compression
498 0 0         if ($lin = $self->[2][1]) {
499            
500             # compute value
501 0           $elin = exp($lin);
502            
503             # for each xyz
504 0           for my $i (0 .. 2) {
505            
506             # compute a = (exp(r) - exp(r * y0/y1))/(exp(r) - exp(r * x0/x1))
507 0           $self->[2][2][$i] = ($elin - exp($lin * $self->[1][1][2][$i]/$self->[1][1][1][$i]))/($elin - exp($lin * $self->[1][0][2][$i]/$self->[1][0][1][$i]));
508            
509             # compute b = (1 - a) * exp(r)
510 0           $self->[2][3][$i] = (1 - $self->[2][2][$i]) * $elin;
511            
512             }
513            
514             # else linear tone compression
515             } else {
516            
517             # for each xyz
518 0           for my $i (0 .. 2) {
519            
520             # compute a = (y1 - y0)/(x1 - x0)
521 0           $self->[2][2][$i] = ($self->[1][1][1][$i] - $self->[1][1][2][$i])/($self->[1][0][1][$i] - $self->[1][0][2][$i]);
522            
523             # compute b = y1 - a * x1
524 0           $self->[2][3][$i] = $self->[1][1][1][$i] - $self->[2][2][$i] * $self->[1][0][1][$i];
525            
526             }
527            
528             }
529            
530             }
531              
532             # print object contents to string
533             # format is an array structure
534             # parameter: ([format])
535             # returns: (string)
536             sub sdump {
537              
538             # get parameters
539 0     0 1   my ($self, $p) = @_;
540              
541             # local variables
542 0           my ($s, $fmt);
543              
544             # resolve parameter to an array reference
545 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
546              
547             # get format string
548 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
549              
550             # set string to object ID
551 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
552              
553             # return
554 0           return($s);
555              
556             }
557              
558             # transform list
559             # parameters: (object_reference, list, [hash])
560             # returns: (list)
561             sub _trans0 {
562              
563             # local variables
564 0     0     my ($self, $hash);
565              
566             # get object reference
567 0           $self = shift();
568              
569             # get optional hash
570 0 0         $hash = pop() if (ref($_[-1]) eq 'HASH');
571              
572             # transform single value
573 0           return(_transform($self, 0, @_));
574              
575             }
576              
577             # transform vector
578             # parameters: (object_reference, vector, [hash])
579             # returns: (vector)
580             sub _trans1 {
581              
582             # get parameters
583 0     0     my ($self, $in, $hash) = @_;
584              
585             # transform vector
586 0           return([_transform($self, 0, @{$in})]);
  0            
587              
588             }
589              
590             # transform matrix (2-D array -or- Math::Matrix object)
591             # parameters: (object_reference, matrix, [hash])
592             # returns: (matrix)
593             sub _trans2 {
594              
595             # get parameters
596 0     0     my ($self, $in, $hash) = @_;
597              
598             # local variable
599 0           my ($out);
600              
601             # for each sample
602 0           for my $i (0 .. $#{$in}) {
  0            
603            
604             # transform sample
605 0           $out->[$i] = [_transform($self, 0, @{$in->[$i]})];
  0            
606            
607             }
608              
609             # return
610 0 0         return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
611              
612             }
613              
614             # transform structure
615             # parameters: (object_reference, structure, [hash])
616             # returns: (structure)
617             sub _trans3 {
618              
619             # get parameters
620 0     0     my ($self, $in, $hash) = @_;
621              
622             # transform the array structure
623 0           _crawl($self, $in, my $out = [], $hash);
624              
625             # return
626 0           return($out);
627              
628             }
629              
630             # recursive transform
631             # array structure is traversed until scalar arrays are found and transformed
632             # parameters: (ref_to_object, ref_to_input_array, ref_to_output_array, hash)
633             sub _crawl {
634              
635             # get parameters
636 0     0     my ($self, $in, $out, $hash) = @_;
637              
638             # if input is a vector (reference to a scalar array)
639 0 0         if (@{$in} == grep {! ref()} @{$in}) {
  0            
  0            
  0            
640            
641             # transform input vector and copy to output
642 0           @{$out} = @{_trans1($self, $in, $hash)};
  0            
  0            
643            
644             } else {
645            
646             # for each input element
647 0           for my $i (0 .. $#{$in}) {
  0            
648            
649             # if an array reference
650 0 0         if (ref($in->[$i]) eq 'ARRAY') {
651            
652             # transform next level
653 0           _crawl($self, $in->[$i], $out->[$i] = [], $hash);
654            
655             } else {
656            
657             # error
658 0           croak('invalid transform input');
659            
660             }
661            
662             }
663            
664             }
665            
666             }
667              
668             # invert list
669             # parameters: (object_reference, list, [hash])
670             # returns: (list)
671             sub _inv0 {
672              
673             # local variables
674 0     0     my ($self, $hash);
675              
676             # get object reference
677 0           $self = shift();
678              
679             # get optional hash
680 0 0         $hash = pop() if (ref($_[-1]) eq 'HASH');
681              
682             # invert single value
683 0           return(_transform($self, 1, @_));
684              
685             }
686              
687             # invert vector
688             # parameters: (object_reference, vector, [hash])
689             # returns: (vector)
690             sub _inv1 {
691              
692             # get parameters
693 0     0     my ($self, $in, $hash) = @_;
694              
695             # invert vector
696 0           return([_transform($self, 1, @{$in})]);
  0            
697              
698             }
699              
700             # invert matrix (2-D array -or- Math::Matrix object)
701             # parameters: (object_reference, matrix, [hash])
702             # returns: (matrix)
703             sub _inv2 {
704              
705             # get parameters
706 0     0     my ($self, $in, $hash) = @_;
707              
708             # local variable
709 0           my ($out);
710              
711             # for each sample
712 0           for my $i (0 .. $#{$in}) {
  0            
713            
714             # invert sample
715 0           $out->[$i] = [_transform($self, 1, @{$in->[$i]})];
  0            
716            
717             }
718              
719             # return
720 0 0         return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
721              
722             }
723              
724             # invert structure
725             # parameters: (object_reference, structure, [hash])
726             # returns: (structure)
727             sub _inv3 {
728              
729             # get parameters
730 0     0     my ($self, $in, $hash) = @_;
731              
732             # invert the array structure
733 0           _crawl2($self, $in, my $out = [], $hash);
734              
735             # return
736 0           return($out);
737              
738             }
739              
740             # recursive transform
741             # array structure is traversed until scalar arrays are found and inverted
742             # parameters: (ref_to_object, ref_to_input_array, ref_to_output_array, hash)
743             sub _crawl2 {
744              
745             # get parameters
746 0     0     my ($self, $in, $out, $hash) = @_;
747              
748             # if input is a vector (reference to a scalar array)
749 0 0         if (@{$in} == grep {! ref()} @{$in}) {
  0            
  0            
  0            
750            
751             # invert input vector and copy to output
752 0           @{$out} = @{_inv1($in, $hash)};
  0            
  0            
753            
754             } else {
755            
756             # for each input element
757 0           for my $i (0 .. $#{$in}) {
  0            
758            
759             # if an array reference
760 0 0         if (ref($in->[$i]) eq 'ARRAY') {
761            
762             # invert next level
763 0           _crawl($self, $in->[$i], $out->[$i] = [], $hash);
764            
765             } else {
766            
767             # error
768 0           croak('invalid inverse input');
769            
770             }
771            
772             }
773            
774             }
775            
776             }
777              
778             # transform sample data
779             # direction: 0 - normal, 1 - inverse
780             # parameters: (object_reference, direction, array_of_input_values)
781             # returns: (array_of_output_values)
782             sub _transform {
783              
784             # get parameters
785 0     0     my ($self, $dir, @in) = @_;
786              
787             # local variables
788 0           my ($i, $pcsi, $pcso, @t);
789              
790             # verify 3 input channels
791 0 0         (@in == 3) or croak('PCS object input not 3 channels');
792              
793             # get input PCS
794 0           $pcsi = $self->[1][$dir][0];
795              
796             # get output PCS
797 0           $pcso = $self->[1][1 - $dir][0];
798              
799             # convert from input PCS
800 0           @t = _rev($pcsi, @in);
801              
802             # if tone compression required
803 0 0 0       if ($self->[2][0]) {
    0 0        
    0          
804            
805             # convert to xyz if input PCS is L*a*b*
806 0 0         @t = ICC::Shared::_Lab2xyz(@t) if ($pcsi <= 5);
807            
808             # tone compression
809 0           @t = _tc($self, $dir, @t);
810            
811             # convert to L*a*b* if output PCS is L*a*b*
812 0 0         @t = ICC::Shared::_xyz2Lab(@t) if ($pcso <= 5);
813            
814             # if input PCS is L*a*b* and output PCS is xyz
815             } elsif ($pcsi <= 5 && $pcso >= 6) {
816            
817             # convert to xyz
818 0           @t = ICC::Shared::_Lab2xyz(@t);
819            
820             # if input PCS is xyz and output PCS is L*a*b*
821             } elsif ($pcsi >= 6 && $pcso <= 5) {
822            
823             # convert to L*a*b*
824 0           @t = ICC::Shared::_xyz2Lab(@t);
825            
826             }
827              
828             # convert to output PCS and return
829 0           return(_fwd($pcso, $self->[3], @t));
830              
831             }
832              
833             # convert to output PCS
834             # input values are either L*a*b* or xyz, depending on PCS
835             # parameters: (PCS, clipping_flag, array_of_input_values)
836             # returns: (array_of_output_values)
837             sub _fwd {
838              
839             # get parameters
840 0     0     my ($pcs, $clip, @in) = @_;
841              
842             # local variable
843 0           my ($denom);
844              
845             # if 8-bit ICC CIELAB or 16-bit ICC CIELAB
846 0 0         if ($pcs == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
847            
848             # if clipping flag set
849 0 0         if ($clip) {
850            
851             # return clipped ICC CIELAB values
852 0 0         return(map {$_ < 0 ? 0 : ($_ > 1 ? 1 : $_)} $in[0]/100, ($in[1] + 128)/255, ($in[2] + 128)/255);
  0 0          
853            
854             } else {
855            
856             # return ICC CIELAB values
857 0           return($in[0]/100, ($in[1] + 128)/255, ($in[2] + 128)/255);
858            
859             }
860            
861             # if 16-bit ICC legacy L*a*b*
862             } elsif ($pcs == 1) {
863            
864             # if clipping flag set
865 0 0         if ($clip) {
866            
867             # clip L* value
868 0 0         $in[0] = $in[0] > 100 ? 100 : $in[0];
869            
870             # return clipped 16-bit ICC legacy L*a*b* values
871 0 0         return(map {$_ < 0 ? 0 : ($_ > 1 ? 1 : $_)} $in[0] * 256/25700, ($in[1] + 128) * 256/65535, ($in[2] + 128) * 256/65535);
  0 0          
872            
873             } else {
874            
875             # return 16-bit ICC legacy L*a*b* values
876 0           return($in[0] * 256/25700, ($in[1] + 128) * 256/65535, ($in[2] + 128) * 256/65535);
877            
878             }
879            
880             # if 16-bit ICC EFI/Monaco L*a*b*
881             } elsif ($pcs == 2) {
882            
883             # if clipping flag set
884 0 0         if ($clip) {
885            
886             # return clipped 16-bit ICC EFI/Monaco L*a*b* values
887 0 0         return(map {$_ < 0 ? 0 : ($_ > 1 ? 1 : $_)} $in[0]/100, ($in[1] + 128) * 256/65535, ($in[2] + 128) * 256/65535);
  0 0          
888            
889             } else {
890            
891             # return 16-bit ICC EFI/Monaco L*a*b* values
892 0           return($in[0]/100, ($in[1] + 128) * 256/65535, ($in[2] + 128) * 256/65535);
893            
894             }
895            
896             # if L*a*b*
897             } elsif ($pcs == 3) {
898            
899             # return L*a*b* values
900 0           return(@in);
901            
902             # if LxLyLz
903             } elsif ($pcs == 4) {
904            
905             # return LxLyLz values
906 0           return($in[0] + 116 * $in[1]/500, $in[0], $in[0] - 116 * $in[2]/200);
907            
908             # if unit LxLyLz
909             } elsif ($pcs == 5) {
910            
911             # return unit LxLyLz values
912 0           return(map {$_/100} ($in[0] + 116 * $in[1]/500, $in[0], $in[0] - 116 * $in[2]/200));
  0            
913            
914             # if xyY
915             } elsif ($pcs == 6) {
916            
917             # compute denominator (X + Y + Z)
918 0           $denom = (96.42 * $in[0] + 100 * $in[1] + 82.49 * $in[2]);
919            
920             # return xyY values
921 0 0         return($denom ? (96.42 * $in[0]/$denom, 100 * $in[1]/$denom, 100 * $in[1]) : (0, 0, 0));
922            
923             # if 16-bit ICC XYZ
924             } elsif ($pcs == 7) {
925            
926             # if clipping flag set
927 0 0         if ($clip) {
928            
929             # return clipped 16-bit ICC XYZ values
930 0 0         return(map {$_ < 0 ? 0 : ($_ > 1 ? 1 : $_)} $in[0] * 0.482107356374456, $in[1] * 0.500007629510948, $in[2] * 0.412456293583581);
  0 0          
931            
932             } else {
933            
934             # return 16-bit ICC XYZ values
935 0           return($in[0] * 0.482107356374456, $in[1] * 0.500007629510948, $in[2] * 0.412456293583581);
936            
937             }
938            
939             # if 32-bit ICC XYZNumber
940             } elsif ($pcs == 8) {
941            
942             # return 32-bit ICC XYZNumber
943 0           return($in[0] * 0.9642, $in[1], $in[2] * 0.8249);
944            
945             # if xyz
946             } elsif ($pcs == 9) {
947            
948             # return xyz values
949 0           return(@in);
950            
951             # if XYZ
952             } elsif ($pcs == 10) {
953            
954             # return XYZ values
955 0           return($in[0] * 96.42, $in[1] * 100, $in[2] * 82.49);
956            
957             } else {
958            
959             # error
960 0           croak('unsupported PCS color space');
961            
962             }
963            
964             }
965              
966             # convert from input PCS
967             # output values are either L*a*b* or xyz, depending on PCS
968             # parameters: (PCS, array_of_input_values)
969             # returns: (array_of_output_values)
970             sub _rev {
971              
972             # get parameters
973 0     0     my ($pcs, @in) = @_;
974              
975             # local variable
976 0           my ($denom);
977              
978             # if 8-bit ICC CIELAB or 16-bit ICC CIELAB
979 0 0         if ($pcs == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
980            
981             # return L*a*b*
982 0           return($in[0] * 100, $in[1] * 255 - 128, $in[2] * 255 - 128);
983            
984             # if 16-bit ICC legacy L*a*b*
985             } elsif ($pcs == 1) {
986            
987             # return L*a*b*
988 0           return($in[0] * 25700/256, $in[1] * 65535/256 - 128, $in[2] * 65535/256 - 128);
989            
990             # if 16-bit EFI/Monaco L*a*b*
991             } elsif ($pcs == 2) {
992            
993             # return L*a*b*
994 0           return($in[0] * 100, $in[1] * 65535/256 - 128, $in[2] * 65535/256 - 128);
995            
996             # if L*a*b*
997             } elsif ($pcs == 3) {
998            
999             # return L*a*b*
1000 0           return(@in);
1001            
1002             # if LxLyLz
1003             } elsif ($pcs == 4) {
1004            
1005             # return L*a*b*
1006 0           return($in[1], 500 * ($in[0] - $in[1])/116, 200 * ($in[1] - $in[2])/116);
1007            
1008             # if unit LxLyLz
1009             } elsif ($pcs == 5) {
1010            
1011             # return L*a*b*
1012 0           return(map {$_ * 100} ($in[1], 500 * ($in[0] - $in[1])/116, 200 * ($in[1] - $in[2])/116));
  0            
1013            
1014             # if xyY
1015             } elsif ($pcs == 6) {
1016            
1017             # compute denominator (X + Y + Z)
1018 0 0         $denom = $in[1] ? $in[2]/$in[1] : 0;
1019            
1020             # return xyz
1021 0           return($in[0] * $denom/96.42, $in[1] * $denom/100, (1 - $in[0] - $in[1]) * $denom/82.49);
1022            
1023             # if 16-bit ICC XYZ
1024             } elsif ($pcs == 7) {
1025            
1026             # return xyz
1027 0           return($in[0]/0.482107356374456, $in[1]/0.500007629510948, $in[2]/0.412456293583581);
1028            
1029             # if ICC XYZNumber
1030             } elsif ($pcs == 8) {
1031            
1032             # return xyz
1033 0           return($in[0]/0.9642, $in[1], $in[2]/0.8249);
1034            
1035             # if xyz
1036             } elsif ($pcs == 9) {
1037            
1038             # return xyz
1039 0           return(@in);
1040            
1041             # if XYZ
1042             } elsif ($pcs == 10) {
1043            
1044             # return xyz
1045 0           return($in[0]/96.42, $in[1]/100, $in[2]/82.49);
1046            
1047             } else {
1048            
1049             # error
1050 0           croak('unsupported PCS color space');
1051            
1052             }
1053            
1054             }
1055              
1056             # compute Jacobian matrix for forward transform
1057             # input values are either L*a*b* or xyz, depending on PCS
1058             # parameters: (PCS, array_of_input_values)
1059             # returns: (Jacobian_matrix)
1060             sub _fwd_jac {
1061              
1062             # get parameters
1063 0     0     my ($pcs, @in) = @_;
1064              
1065             # local variables
1066 0           my ($denom, @out);
1067              
1068             # if 8-bit ICC CIELAB or 16-bit ICC CIELAB
1069 0 0         if ($pcs == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1070            
1071             # return Jacobian matrix
1072 0           return(Math::Matrix->new(
1073             [1/100, 0, 0],
1074             [0, 1/255, 0],
1075             [0, 0, 1/255]
1076             ));
1077            
1078             # if 16-bit ICC legacy L*a*b*
1079             } elsif ($pcs == 1) {
1080            
1081             # return Jacobian matrix
1082 0           return(Math::Matrix->new(
1083             [256/25700, 0, 0],
1084             [0, 256/65535, 0],
1085             [0, 0, 256/65535]
1086             ));
1087            
1088             # if 16-bit ICC EFI/Monaco L*a*b*
1089             } elsif ($pcs == 2) {
1090            
1091             # return Jacobian matrix
1092 0           return(Math::Matrix->new(
1093             [1/100, 0, 0],
1094             [0, 256/65535, 0],
1095             [0, 0, 256/65535]
1096             ));
1097            
1098             # if L*a*b*
1099             } elsif ($pcs == 3) {
1100            
1101             # return Jacobian matrix
1102 0           return(Math::Matrix->new(
1103             [1, 0, 0],
1104             [0, 1, 0],
1105             [0, 0, 1]
1106             ));
1107            
1108             # if LxLyLz
1109             } elsif ($pcs == 4) {
1110            
1111             # return Jacobian matrix
1112 0           return(Math::Matrix->new(
1113             [1, 116/500, 0],
1114             [1, 0, 0],
1115             [1, 0, -116/200]
1116             ));
1117            
1118             # if unit LxLyLz
1119             } elsif ($pcs == 5) {
1120            
1121             # return Jacobian matrix
1122 0           return(Math::Matrix->new(
1123             [1/100, 116/50000, 0],
1124             [1/100, 0, 0],
1125             [1/100, 0, -116/20000]
1126             ));
1127            
1128             # if xyY
1129             } elsif ($pcs == 6) {
1130            
1131             # if denominator (X + Y + Z) is non-zero
1132 0 0         if ($denom = (96.42 * $in[0] + 100 * $in[1] + 82.49 * $in[2])) {
1133            
1134             # compute output vector
1135 0           @out = (96.42 * $in[0]/$denom, 100 * $in[1]/$denom, 100 * $in[1]);
1136            
1137             # return Jacobian matrix
1138 0           return(Math::Matrix->new(
1139             [96.42 * (1 - $out[0])/$denom, -100 * $out[0]/$denom, -82.49 * $out[0]/$denom],
1140             [-96.42 * $out[1]/$denom, 100 * (1 - $out[1])/$denom, -82.49 * $out[1]/$denom],
1141             [0, 100, 0]
1142             ));
1143            
1144             } else {
1145            
1146             # print warning
1147 0           print "Jacobian matrix overflow!\n";
1148            
1149             # return Jacobian matrix
1150 0           return(Math::Matrix->new(
1151             ['inf', '-inf', '-inf'],
1152             ['-inf', 'inf', '-inf'],
1153             [0, 100, 0]
1154             ));
1155            
1156             }
1157            
1158             # if 16-bit ICC XYZ
1159             } elsif ($pcs == 7) {
1160            
1161             # return Jacobian matrix
1162 0           return(Math::Matrix->new(
1163             [0.482107356374456, 0, 0],
1164             [0, 0.500007629510948, 0],
1165             [0, 0, 0.412456293583581]
1166             ));
1167            
1168             # if 32-bit ICC XYZNumber
1169             } elsif ($pcs == 8) {
1170            
1171             # return Jacobian matrix
1172 0           return(Math::Matrix->new(
1173             [0.9642, 0, 0],
1174             [0, 1, 0],
1175             [0, 0, 0.8249]
1176             ));
1177            
1178             # if xyz
1179             } elsif ($pcs == 9) {
1180            
1181             # return Jacobian matrix
1182 0           return(Math::Matrix->new(
1183             [1, 0, 0],
1184             [0, 1, 0],
1185             [0, 0, 1]
1186             ));
1187            
1188             # if XYZ
1189             } elsif ($pcs == 10) {
1190            
1191             # return Jacobian matrix
1192 0           return(Math::Matrix->new(
1193             [96.42, 0, 0],
1194             [0, 100, 0],
1195             [0, 0, 82.49]
1196             ));
1197            
1198             } else {
1199            
1200             # error
1201 0           croak('unsupported PCS color space');
1202            
1203             }
1204            
1205             }
1206              
1207             # compute Jacobian matrix for reverse transform
1208             # output values are either L*a*b* or xyz, depending on PCS
1209             # parameters: (PCS, array_of_input_values)
1210             # returns: (Jacobian_matrix)
1211             sub _rev_jac {
1212              
1213             # get parameters
1214 0     0     my ($pcs, @in) = @_;
1215              
1216             # local variables
1217 0           my ($denom, $xr, $zr);
1218              
1219             # if 8-bit ICC CIELAB or 16-bit ICC CIELAB
1220 0 0         if ($pcs == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1221            
1222             # return Jacobian matrix
1223 0           return(Math::Matrix->new(
1224             [100, 0, 0],
1225             [0, 255, 0],
1226             [0, 0, 255]
1227             ));
1228            
1229             # if 16-bit ICC legacy L*a*b*
1230             } elsif ($pcs == 1) {
1231            
1232             # return Jacobian matrix
1233 0           return(Math::Matrix->new(
1234             [25700/256, 0, 0],
1235             [0, 65535/256, 0],
1236             [0, 0, 65535/256]
1237             ));
1238            
1239             # if 16-bit EFI/Monaco L*a*b*
1240             } elsif ($pcs == 2) {
1241            
1242             # return Jacobian matrix
1243 0           return(Math::Matrix->new(
1244             [100, 0, 0],
1245             [0, 65535/256, 0],
1246             [0, 0, 65535/256]
1247             ));
1248            
1249             # if L*a*b*
1250             } elsif ($pcs == 3) {
1251            
1252             # return Jacobian matrix
1253 0           return(Math::Matrix->new(
1254             [1, 0, 0],
1255             [0, 1, 0],
1256             [0, 0, 1]
1257             ));
1258            
1259             # if LxLyLz
1260             } elsif ($pcs == 4) {
1261            
1262             # return Jacobian matrix
1263 0           return(Math::Matrix->new(
1264             [0, 1, 0],
1265             [500/116, -500/116, 0],
1266             [0, 200/116, -200/116]
1267             ));
1268            
1269             # if unit LxLyLz
1270             } elsif ($pcs == 5) {
1271            
1272             # return Jacobian matrix
1273 0           return(Math::Matrix->new(
1274             [0, 100, 0],
1275             [50000/116, -50000/116, 0],
1276             [0, 20000/116, -20000/116]
1277             ));
1278            
1279             # if xyY
1280             } elsif ($pcs == 6) {
1281            
1282             # if y not zero
1283 0 0         if ($in[1]) {
1284            
1285             # compute denominator (X + Y + Z)
1286 0           $denom = $in[2]/$in[1];
1287            
1288             # compute ratios
1289 0           $xr = $in[0]/$in[1];
1290 0           $zr = (1 - $in[0])/$in[1];
1291            
1292             # return Jacobian matrix
1293 0           return(Math::Matrix->new(
1294             [$denom/96.42, -$denom * $xr/96.42, $xr/96.42],
1295             [0, 0, 1/100],
1296             [-$denom/82.49, -$denom * $zr/82.49, ($zr - 1)/82.49]
1297             ));
1298            
1299             } else {
1300            
1301             # print warning
1302 0           print "Jacobian matrix overflow!\n";
1303            
1304             # return Jacobian matrix
1305 0           return(Math::Matrix->new(
1306             ['inf', '-inf', 'inf'],
1307             [0, 0, 1/100],
1308             ['-inf', '-inf', 'inf']
1309             ));
1310            
1311             }
1312            
1313             # if 16-bit ICC XYZ
1314             } elsif ($pcs == 7) {
1315            
1316             # return Jacobian matrix
1317 0           return(Math::Matrix->new(
1318             [2.074226801931005, 0, 0],
1319             [0, 1.999969482421875, 0],
1320             [0, 0, 2.424499311943114]
1321             ));
1322            
1323             # if ICC XYZNumber
1324             } elsif ($pcs == 8) {
1325            
1326             # return Jacobian matrix
1327 0           return(Math::Matrix->new(
1328             [1/0.9642, 0, 0],
1329             [0, 1, 0],
1330             [0, 0, 1/0.8249]
1331             ));
1332            
1333             # if xyz
1334             } elsif ($pcs == 9) {
1335            
1336             # return Jacobian matrix
1337 0           return(Math::Matrix->new(
1338             [1, 0, 0],
1339             [0, 1, 0],
1340             [0, 0, 1]
1341             ));
1342            
1343             # if XYZ
1344             } elsif ($pcs == 10) {
1345            
1346             # return Jacobian matrix
1347 0           return(Math::Matrix->new(
1348             [1/96.42, 0, 0],
1349             [0, 1/100, 0],
1350             [0, 0, 1/82.49]
1351             ));
1352            
1353             } else {
1354            
1355             # error
1356 0           croak('unsupported PCS color space');
1357            
1358             }
1359            
1360             }
1361              
1362             # forward tone compression derivative
1363             # input and output values are xyz
1364             # parameters: (object_reference, direction, array_of_input_values)
1365             # returns: (array_of_output values)
1366             sub _tc_derv {
1367            
1368             # get parameters
1369 0     0     my ($self, $dir, @in) = @_;
1370            
1371             # local variables
1372 0           my ($lin, @out, $t, $u);
1373            
1374             # if non-linear tone compression
1375 0 0         if ($lin = $self->[2][1]) {
1376            
1377             # if reverse direction
1378 0 0         if ($dir) {
1379            
1380             # for each xyz
1381 0           for my $i (0 .. 2) {
1382            
1383             # compute t = (exp(r * y/y1) - b)/a
1384 0           $t = (exp($lin * $in[$i]/$self->[1][1][1][$i]) - $self->[2][3][$i])/$self->[2][2][$i];
1385            
1386             # compute u = x1/y1
1387 0           $u = $self->[1][0][1][$i]/$self->[1][1][1][$i];
1388            
1389             # compute x = u * a * exp(r * y/y1)/exp(r * x/x1)
1390 0 0         $out[$i] = $t == 0 ? 1E99 : $u * $self->[2][2][$i] * exp($lin * $in[$i]/$self->[1][1][1][$i])/$t;
1391            
1392             }
1393            
1394             } else {
1395            
1396             # for each xyz
1397 0           for my $i (0 .. 2) {
1398            
1399             # compute t = exp(r * x/x1) * a + b
1400 0           $t = exp($lin * $in[$i]/$self->[1][0][1][$i]) * $self->[2][2][$i] + $self->[2][3][$i];
1401            
1402             # compute u = y1/x1
1403 0           $u = $self->[1][1][1][$i]/$self->[1][0][1][$i];
1404            
1405             # compute x = u * a * (exp(r * x/x1)/exp(r * y/y1))
1406 0 0         $out[$i] = $t == 0 ? 1E99 : $u * $self->[2][2][$i] * exp($lin * $in[$i]/$self->[1][0][1][$i])/$t;
1407            
1408             }
1409            
1410             }
1411            
1412             # if linear tone compression
1413             } else {
1414            
1415             # if reverse direction
1416 0 0         if ($dir) {
1417            
1418             # for each xyz
1419 0           for my $i (0 .. 2) {
1420            
1421             # compute y = 1/a
1422 0 0         $out[$i] = $self->[2][2][$i] == 0 ? 1E99 : 1/$self->[2][2][$i];
1423            
1424             }
1425            
1426             } else {
1427            
1428             # for each xyz
1429 0           for my $i (0 .. 2) {
1430            
1431             # compute y = a
1432 0           $out[$i] = $self->[2][2][$i];
1433            
1434             }
1435            
1436             }
1437            
1438             }
1439              
1440             # return
1441 0           return(@out);
1442            
1443             }
1444              
1445             # tone compression transform
1446             # input and output values are xyz
1447             # parameters: (object_reference, direction, array_of_input_values)
1448             # returns: (array_of_output values)
1449             sub _tc {
1450            
1451             # get parameters
1452 0     0     my ($self, $dir, @in) = @_;
1453            
1454             # local variables
1455 0           my ($lin, @out, $t);
1456            
1457             # if non-linear tone compression
1458 0 0         if ($lin = $self->[2][1]) {
1459            
1460             # if reverse direction
1461 0 0         if ($dir) {
1462            
1463             # for each xyz
1464 0           for my $i (0 .. 2) {
1465            
1466             # compute t = (exp(r * y/y1) - b)/a
1467 0           $t = (exp($lin * $in[$i]/$self->[1][1][1][$i]) - $self->[2][3][$i])/$self->[2][2][$i];
1468            
1469             # compute x = ln(t) * x1/r
1470 0 0         $out[$i] = $t > 0 ? log($t) * $self->[1][0][1][$i]/$lin : -1E99;
1471            
1472             }
1473            
1474             } else {
1475            
1476             # for each xyz
1477 0           for my $i (0 .. 2) {
1478            
1479             # compute t = exp(r * x/x1) * a + b
1480 0           $t = exp($lin * $in[$i]/$self->[1][0][1][$i]) * $self->[2][2][$i] + $self->[2][3][$i];
1481            
1482             # compute y = ln(t) * y1/r
1483 0 0         $out[$i] = $t > 0 ? log($t) * $self->[1][1][1][$i]/$lin : -1E99;
1484            
1485             }
1486            
1487             }
1488            
1489             # else linear tone compression
1490             } else {
1491            
1492             # if reverse direction
1493 0 0         if ($dir) {
1494            
1495             # for each xyz
1496 0           for my $i (0 .. 2) {
1497            
1498             # compute y = (x - b)/a
1499 0 0         $out[$i] = $self->[2][2][$i] == 0 ? 1E99 : ($in[$i] - $self->[2][3][$i])/$self->[2][2][$i];
1500            
1501             }
1502            
1503             } else {
1504            
1505             # for each xyz
1506 0           for my $i (0 .. 2) {
1507            
1508             # compute y = ax + b
1509 0           $out[$i] = $in[$i] * $self->[2][2][$i] + $self->[2][3][$i];
1510            
1511             }
1512            
1513             }
1514            
1515             }
1516            
1517             # return
1518 0           return(@out);
1519            
1520             }
1521              
1522             # make PCS connection object from parameters
1523             # structure of the input/output parameter arrays is: (pcs_connection_space, [white_point, [black_point]])
1524             # parameters: (object_reference, ref_to_input_parameter_array, ref_to_output_parameter_array, [tone_compression_linearity])
1525             sub _new_pcs {
1526              
1527             # get object reference
1528 0     0     my ($self) = shift();
1529              
1530             # local variables
1531 0           my (@cs, @io);
1532              
1533             # list of supported connection spaces
1534 0           @cs = (0 .. 10);
1535              
1536             # message labels
1537 0           @io = qw(input output);
1538              
1539             # for input and output parameters
1540 0           for my $i (0 .. 1) {
1541            
1542             # verify parameter is an array reference
1543 0 0         (ref($_[$i]) eq 'ARRAY') or croak("$io[$i] parameter not an array reference");
1544            
1545             # verify number of array parameters
1546 0 0 0       (@{$_[$i]} >= 1 || @{$_[$i]} <= 3) or croak("$io[$i] array has wrong number parameters");
  0            
  0            
1547            
1548             # verify color space
1549 0 0         (grep {$_[$i][0] == $_} @cs) or croak("$io[$i] color space not supported");
  0            
1550            
1551             # copy color space
1552 0           $self->[1][$i][0] = $_[$i][0];
1553            
1554             # if white point is defined
1555 0 0         if (defined($_[$i][1])) {
1556            
1557             # verify white point is an array reference
1558 0 0         (ref($_[$i][1]) eq 'ARRAY') or croak("$io[$i] white point not an array reference");
1559            
1560             # verify array structure
1561 0 0         (3 == grep {! ref()} @{$_[$i][1]}) or croak("$io[$i] white point array has wrong structure");
  0            
  0            
1562            
1563             # copy white point (converting XYZNumber to xyz)
1564 0           $self->[1][$i][1] = ICC::Shared::XYZ2xyz($_[$i][1], ICC::Shared::d50);
1565            
1566             } else {
1567            
1568             # set white point to perfect white
1569 0           $self->[1][$i][1] = [1, 1, 1];
1570            
1571             }
1572            
1573             # if black point is defined
1574 0 0         if (defined($_[$i][2])) {
1575            
1576             # verify black point is an array reference
1577 0 0         (ref($_[$i][2]) eq 'ARRAY') or croak("$io[$i] black point not an array reference");
1578            
1579             # verify array structure
1580 0 0         (3 == grep {! ref()} @{$_[$i][2]}) or croak("$io[$i] black point array has wrong structure");
  0            
  0            
1581            
1582             # copy black point (converting XYZNumber to xyz)
1583 0           $self->[1][$i][2] = ICC::Shared::XYZ2xyz($_[$i][2], ICC::Shared::d50);
1584            
1585             } else {
1586            
1587             # set black point to perfect black
1588 0           $self->[1][$i][2] = [0, 0, 0];
1589            
1590             }
1591            
1592             }
1593              
1594             # set tone compression linearity (default = 0)
1595 0 0         $self->[2][1] = defined($_[2]) ? $_[2] : 0;
1596              
1597             # compute tone compression coefficients
1598 0           tc_pars($self);
1599              
1600             }
1601              
1602             1;
1603