File Coverage

blib/lib/ICC/Support/PCS.pm
Criterion Covered Total %
statement 14 354 3.9
branch 2 270 0.7
condition 1 60 1.6
subroutine 4 31 12.9
pod 2 10 20.0
total 23 725 3.1


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