File Coverage

blib/lib/ICC/Profile/clut.pm
Criterion Covered Total %
statement 71 573 12.3
branch 13 238 5.4
condition 0 110 0.0
subroutine 13 40 32.5
pod 1 16 6.2
total 98 977 10.0


line stmt bran cond sub pod time code
1             package ICC::Profile::clut;
2              
3 7     7   105277 use strict;
  7         26  
  7         211  
4 7     7   35 use Carp;
  7         15  
  7         477  
5              
6             our $VERSION = 0.21;
7              
8             # revised 2018-08-07
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 7     7   524 use lib 'lib';
  7         657  
  7         62  
14              
15             # inherit from Shared
16 7     7   1416 use parent qw(ICC::Shared);
  7         304  
  7         48  
17              
18             # use POSIX math
19 7     7   428 use POSIX ();
  7         13  
  7         123  
20              
21             # enable static variables
22 7     7   31 use feature 'state';
  7         14  
  7         51865  
23              
24             # create new clut object
25             # hash may contain pointers to clut, grid size array or user-defined functions
26             # hash keys are: ('array', 'clut_bytes', 'gsa', 'udf')
27             # parameters: ([ref_to_attribute_hash])
28             # returns: (ref_to_object)
29             sub new {
30              
31             # get object class
32 5     5 0 1111 my $class = shift();
33              
34             # create empty clut object
35 5         20 my $self = [
36             {}, # object header
37             [], # clut
38             [], # grid size array
39             [], # user-defined functions
40             undef, # clut cache (for Lapack)
41             undef, # corner point cache
42             ];
43              
44             # if there are parameters
45 5 50       19 if (@_) {
46            
47             # if one parameter, a hash reference
48 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
49            
50             # make new clut object from attribute hash
51 0         0 _new_from_hash($self, @_);
52            
53             } else {
54            
55             # error
56 0         0 croak('parameter must be a hash reference');
57            
58             }
59            
60             }
61              
62             # bless object
63 5         11 bless($self, $class);
64              
65             # return object reference
66 5         12 return($self);
67              
68             }
69              
70             # get/set reference to header hash
71             # parameters: ([ref_to_new_hash])
72             # returns: (ref_to_hash)
73             sub header {
74            
75             # get object reference
76 0     0 0 0 my $self = shift();
77            
78             # if there are parameters
79 0 0       0 if (@_) {
80            
81             # if one parameter, a hash reference
82 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
83            
84             # set header to new hash
85 0         0 $self->[0] = {%{shift()}};
  0         0  
86            
87             } else {
88            
89             # error
90 0         0 croak('parameter must be a hash reference');
91            
92             }
93            
94             }
95            
96             # return reference
97 0         0 return($self->[0]);
98            
99             }
100              
101             # get/set reference to clut array
102             # parameters: ([ref_to_new_array])
103             # returns: (ref_to_array)
104             sub array {
105              
106             # get object reference
107 0     0 0 0 my $self = shift();
108              
109             # if there are parameters
110 0 0       0 if (@_) {
111            
112             # if one parameter, a 2-D array reference
113 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) {
  0 0 0     0  
  0   0     0  
  0         0  
114            
115             # set clut to clone of array
116 0         0 $self->[1] = Storable::dclone($_[0]);
117            
118             # update caches
119 0 0       0 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
120 0         0 undef($self->[5]);
121            
122             # if one parameter, a Math::Matrix object
123             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
124            
125             # set clut to object
126 0         0 $self->[1] = $_[0];
127            
128             # update caches
129 0 0       0 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
130 0         0 undef($self->[5]);
131            
132             } else {
133            
134             # error
135 0         0 croak('clut array must be a 2-D array reference or Math::Matrix object');
136            
137             }
138            
139             }
140              
141             # return reference
142 0         0 return($self->[1]);
143              
144             }
145              
146             # get/set reference to grid size array
147             # parameters: ([ref_to_new_array])
148             # returns: (ref_to_array)
149             sub gsa {
150              
151             # get object reference
152 38     38 0 42 my $self = shift();
153              
154             # if there are parameters
155 38 50       66 if (@_) {
156            
157             # if one parameter, an array reference
158 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {! ref()} @{$_[0]}) {
  0   0     0  
  0         0  
  0         0  
159            
160             # set gsa to copy of array
161 0         0 $self->[2] = [@{shift()}];
  0         0  
162            
163             } else {
164            
165             # error
166 0         0 croak('clut gsa must be an array reference');
167            
168             }
169            
170             }
171              
172             # return reference
173 38         120 return($self->[2]);
174              
175             }
176              
177             # get/set reference to user-defined functions array
178             # parameters: ([ref_to_new_array])
179             # returns: (ref_to_array)
180             sub udf {
181            
182             # get object reference
183 0     0 0 0 my $self = shift();
184            
185             # if there are parameters
186 0 0       0 if (@_) {
187            
188             # if one parameter, an array reference
189 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'CODE'} @{$_[0]}) {
  0   0     0  
  0         0  
  0         0  
190            
191             # set udf to copy of array
192 0         0 $self->[3] = [@{shift()}];
  0         0  
193            
194             } else {
195            
196             # error
197 0         0 croak('parameter must be an array reference');
198            
199             }
200            
201             }
202            
203             # return reference
204 0         0 return($self->[3]);
205            
206             }
207              
208             # get/set reference to clut array element
209             # array element is an array of output values
210             # parameters: (index_array, [ref_to_new_array])
211             # returns: (ref_to_array)
212             sub clut {
213              
214             # get object reference
215 0     0 0 0 my $self = shift();
216              
217             # local variables
218 0         0 my ($lx, $ref, $gsa);
219              
220             # get reference to new array (if present)
221 0 0       0 $ref = pop() if (ref($_[-1]) eq 'ARRAY');
222              
223             # get grid size array
224 0         0 $gsa = $self->[2];
225              
226             # validate indices
227 0 0       0 (@_ == @{$gsa}) or croak('wrong number of clut indices');
  0         0  
228 0 0       0 (@_ == grep {! ref() && $_ == int($_)} @_) or croak('clut index not an integer');
  0 0       0  
229 0 0       0 (@_ == grep {$_[$_] >= 0 && $_[$_] < $gsa->[$_]} 0 .. $#_) or croak('clut index out of range');
  0 0       0  
230              
231             # initialize clut pointer
232 0         0 $lx = $_[0];
233              
234             # for each remaining index
235 0         0 for my $i (1 .. $#_) {
236            
237             # multiply by grid size
238 0         0 $lx *= $gsa->[$i];
239            
240             # add index
241 0         0 $lx += $_[$i];
242            
243             }
244              
245             # if replacement data provided
246 0 0       0 if (defined($ref)) {
247            
248             # update CLUT
249 0         0 $self->[1][$lx] = [@{$ref}];
  0         0  
250            
251             # update caches
252 0 0       0 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
253 0         0 undef($self->[5]);
254            
255             }
256              
257             # return array reference
258 0         0 return($self->[1][$lx]);
259              
260             }
261              
262             # create clut object from ICC profile
263             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
264             # returns: (ref_to_object)
265             sub new_fh {
266              
267             # get object class
268 0     0 0 0 my $class = shift();
269              
270             # create empty clut object
271 0         0 my $self = [
272             {}, # object header
273             [], # clut
274             [], # grid size array
275             [] # user-defined functions
276             ];
277              
278             # verify 3 parameters
279 0 0       0 (@_ == 3) or croak('wrong number of parameters');
280              
281             # read clut data from profile
282 0         0 _readICCclut($self, @_);
283              
284             # bless object
285 0         0 bless($self, $class);
286              
287             # return object reference
288 0         0 return($self);
289              
290             }
291              
292             # writes clut object to ICC profile
293             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
294             sub write_fh {
295              
296             # verify 4 parameters
297 0 0   0 0 0 (@_ == 4) or croak('wrong number of parameters');
298              
299             # write clut data to profile
300 0         0 goto &_writeICCclut;
301              
302             }
303              
304             # get tag size (for writing to profile)
305             # returns: (clut_size)
306             sub size {
307              
308             # get parameter
309 0     0 0 0 my $self = shift();
310              
311             # return size
312 0         0 return(_clut_size($self, 4) + 28);
313              
314             }
315              
316             # get number of input channels
317             # returns: (number)
318             sub cin {
319              
320             # get object reference
321 4     4 0 8 my $self = shift();
322              
323             # return
324 4         8 return(scalar(@{$self->[2]}));
  4         24  
325              
326             }
327              
328             # get number of output channels
329             # returns: (number)
330             sub cout {
331              
332             # get object reference
333 4     4 0 9 my $self = shift();
334              
335             # return
336 4         7 return(scalar(@{$self->[1][0]}));
  4         12  
337              
338             }
339              
340             # build clut array from user-defined transform function
341             # parameters may be set with an optional hash
342             # keys are: ('clut_bytes', 'gsa', 'udf', 'slice')
343             # parameters: ([ref_to_attribute_hash])
344             # returns: (ref_to_object)
345             sub build {
346              
347             # get parameters
348 0     0 0 0 my ($self, $hash) = @_;
349              
350             # local variables
351 0         0 my ($gsa, $ci, $co, @out);
352 0         0 my ($size, @slice);
353              
354             # for each attribute
355 0         0 for my $attr (keys(%{$hash})) {
  0         0  
356            
357             # if 'clut_bytes'
358 0 0       0 if ($attr eq 'clut_bytes') {
    0          
    0          
    0          
359            
360             # if a scalar, 1 or 2
361 0 0 0     0 if (! ref($hash->{$attr}) && ($hash->{$attr} == 1 || $hash->{$attr} == 2)) {
      0        
362            
363             # add to header hash
364 0         0 $self->[0]{'clut_bytes'} = $hash->{$attr};
365            
366             } else {
367            
368             # wrong data type
369 0         0 croak('clut \'clut_bytes\' attribute must be a scalar, 1 or 2');
370            
371             }
372            
373             # if 'gsa'
374             } elsif ($attr eq 'gsa') {
375            
376             # if reference to an array of scalars
377 0 0 0     0 if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {! ref()} @{$hash->{$attr}}) {
  0         0  
  0         0  
  0         0  
378            
379             # set object element
380 0         0 $self->[2] = [@{$hash->{$attr}}];
  0         0  
381            
382             } else {
383            
384             # wrong data type
385 0         0 croak('clut \'gsa\' attribute must be an array reference');
386            
387             }
388            
389             # if 'udf'
390             } elsif ($attr eq 'udf') {
391            
392             # if reference to an array of CODE references
393 0 0 0     0 if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {ref() eq 'CODE'} @{$hash->{$attr}}) {
  0         0  
  0         0  
  0         0  
394            
395             # set object element
396 0         0 $self->[3] = [@{$hash->{$attr}}];
  0         0  
397            
398             } else {
399            
400             # wrong data type
401 0         0 croak('clut \'udf\' attribute must be an array reference');
402            
403             }
404            
405             # if 'slice'
406             } elsif ($attr eq 'slice') {
407            
408             # if reference to an array of scalars
409 0 0 0     0 if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {! ref()} @{$hash->{$attr}}) {
  0 0       0  
  0         0  
  0         0  
410            
411             # set slice array
412 0         0 @slice = @{$hash->{$attr}};
  0         0  
413            
414             # if 'log'
415             } elsif ($hash->{$attr} eq 'log') {
416            
417             # if 'log' hash is defined
418 0 0 0     0 if (defined($self->[0]{'log'}) && ref($self->[0]{'log'}) eq 'HASH') {
419            
420             # set slice to hash keys
421 0         0 @slice = keys(%{$self->[0]{'log'}});
  0         0  
422            
423             }
424            
425             } else {
426            
427             # wrong data type
428 0         0 croak('clut \'slice\' attribute must be an array reference or \'log\'');
429            
430             }
431            
432             } else {
433            
434             # invalid attribute
435 0         0 croak('invalid clut attribute');
436            
437             }
438            
439             }
440              
441             # get grid size array
442 0         0 $gsa = $self->[2];
443              
444             # get number of input channels
445 0         0 $self->[0]{'input_channels'} = $ci = @{$gsa};
  0         0  
446              
447             # validate user-defined function
448 0 0       0 (ref($self->[3][0]) eq 'CODE') or croak('invalid user-defined function');
449              
450             # test user-defined function
451 0         0 @out = &{$self->[3][0]}((0) x $ci);
  0         0  
452              
453             # determine number of output channels
454 0         0 $self->[0]{'output_channels'} = $co = @out;
455              
456             # validate parameters
457 0 0       0 (@{$gsa} == grep {! ref() && $_ == int($_)} @{$gsa}) or croak('grid size not an integer');
  0 0       0  
  0         0  
  0         0  
458 0 0       0 (0 == grep {$_ < 2} @{$gsa}) or croak('grid size less than 2');
  0         0  
  0         0  
459 0 0 0     0 ($ci > 0 && $ci < 16) or croak('invalid number of input channels');
460 0 0 0     0 ($co > 0 && $co < 16) or croak('invalid number of output channels');
461              
462             # initialize clut entries
463 0         0 $size = 1;
464              
465             # for each input channel
466 0         0 for (@{$gsa}) {
  0         0  
467            
468             # multiply by grid size
469 0         0 $size *= $_;
470            
471             }
472              
473             # set slice to entire clut, if empty
474 0 0       0 @slice = (0 .. $size - 1) if (! @slice);
475              
476             # for each clut entry
477 0         0 for my $i (@slice) {
478            
479             # compute transform value
480 0         0 $self->[1][$i] = [&{$self->[3][0]}(_lin2ix($gsa, $i))];
  0         0  
481            
482             }
483            
484             # update caches
485 0 0       0 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
486 0         0 undef($self->[5]);
487              
488             # return object reference
489 0         0 return($self);
490              
491             }
492              
493             # transform data
494             # input range is (0 - 1)
495             # hash key 'ubox' enables unit box extrapolation
496             # supported input types:
497             # parameters: (list, [hash])
498             # parameters: (vector, [hash])
499             # parameters: (matrix, [hash])
500             # parameters: (Math::Matrix_object, [hash])
501             # parameters: (structure, [hash])
502             # returns: (same_type_as_input)
503             sub transform {
504              
505             # set hash value (0 or 1)
506 0 0   0 0 0 my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
507              
508             # if input a 'Math::Matrix' object
509 0 0 0     0 if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
510            
511             # call matrix transform
512 0         0 &_trans2;
513            
514             # if input an array reference
515             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
516            
517             # if array contains numbers (vector)
518 0 0 0     0 if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0     0  
  0         0  
  0         0  
519            
520             # call vector transform
521 0         0 &_trans1;
522            
523             # if array contains vectors (2-D array)
524 0 0       0 } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0         0  
  0         0  
525            
526             # call matrix transform
527 0         0 &_trans2;
528            
529             } else {
530            
531             # call structure transform
532 0         0 &_trans3;
533            
534             }
535            
536             # if input a list (of numbers)
537 0         0 } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
538            
539             # call list transform
540 0         0 &_trans0;
541            
542             } else {
543            
544             # error
545 0         0 croak('invalid transform input');
546            
547             }
548              
549             }
550              
551             # inverse transform
552             # note: number of undefined output values must equal number of defined input values
553             # note: the input and output vectors contain the final solution on return
554             # hash key 'init' specifies initial value vector
555             # hash key 'ubox' enables unit box extrapolation
556             # parameters: (input_vector, output_vector, [hash])
557             # returns: (RMS_error_value)
558             sub inverse {
559              
560             # get parameters
561 0     0 0 0 my ($self, $in, $out, $hash) = @_;
562              
563             # local variables
564 0         0 my ($i, $j, @si, @so, $init);
565 0         0 my ($int, $jac, $mat, $delta);
566 0         0 my ($max, $elim, $dlim, $accum, $error);
567              
568             # initialize indices
569 0         0 $i = $j = -1;
570              
571             # build slice arrays while validating input and output arrays
572 0 0       0 ((grep {$i++; defined() && push(@si, $i)} @{$in}) == (grep {$j++; ! defined() && push(@so, $j)} @{$out})) or croak('wrong number of undefined values');
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
573              
574             # get init array
575 0         0 $init = $hash->{'init'};
576              
577             # for each undefined output value
578 0         0 for my $i (@so) {
579            
580             # set to supplied initial value or 0.5
581 0 0       0 $out->[$i] = defined($init->[$i]) ? $init->[$i] : 0.5;
582            
583             }
584              
585             # set maximum loop count
586 0   0     0 $max = $hash->{'inv_max'} || 10;
587              
588             # loop error limit
589 0   0     0 $elim = $hash->{'inv_elim'} || 1E-6;
590              
591             # set delta limit
592 0   0     0 $dlim = $hash->{'inv_dlim'} || 0.5;
593              
594             # create empty solution matrix
595 0         0 $mat = Math::Matrix->new([]);
596              
597             # compute initial transform values
598 0         0 ($jac, $int) = jacobian($self, $out, $hash);
599              
600             # solution loop
601 0         0 for (1 .. $max) {
602            
603             # for each input
604 0         0 for my $i (0 .. $#si) {
605            
606             # for each output
607 0         0 for my $j (0 .. $#so) {
608            
609             # copy Jacobian value to solution matrix
610 0         0 $mat->[$i][$j] = $jac->[$si[$i]][$so[$j]];
611            
612             }
613            
614             # save residual value to solution matrix
615 0         0 $mat->[$i][$#si + 1] = $in->[$si[$i]] - $int->[$si[$i]];
616            
617             }
618            
619             # solve for delta values
620 0         0 $delta = $mat->solve;
621            
622             # for each output value
623 0         0 for my $i (0 .. $#so) {
624            
625             # add delta (limited using hyperbolic tangent)
626 0         0 $out->[$so[$i]] += POSIX::tanh($delta->[$i][0]/$dlim) * $dlim;
627            
628             }
629            
630             # compute updated transform values
631 0         0 ($jac, $int) = jacobian($self, $out, $hash);
632            
633             # initialize error accumulator
634 0         0 $accum = 0;
635            
636             # for each input
637 0         0 for my $i (0 .. $#si) {
638            
639             # accumulate delta squared
640 0         0 $accum += ($in->[$si[$i]] - $int->[$si[$i]])**2;
641            
642             }
643            
644             # compute RMS error
645 0         0 $error = sqrt($accum/@si);
646            
647             # if error less than limit
648 0 0       0 last if ($error < $elim);
649            
650             }
651              
652             # update input vector with final values
653 0         0 @{$in} = @{$int};
  0         0  
  0         0  
654              
655             # return
656 0         0 return($error);
657              
658             }
659              
660             # compute Jacobian matrix
661             # nominal input range is (0 - 1)
662             # hash key 'ubox' enables unit box extrapolation
663             # clipped outputs are extrapolated using Jacobian
664             # parameters: (input_vector, [hash])
665             # returns: (Jacobian_matrix, [output_vector])
666             sub jacobian {
667              
668             # get parameters
669 0     0 0 0 my ($self, $in, $hash) = @_;
670              
671             # local variables
672 0         0 my ($ext, $out, $jac, $rel, $cp, $jac_bc, $sf);
673              
674             # check if ICC::Support::Lapack module is loaded
675 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
676              
677             # if user-defined transform and user-defined Jacobian functions
678 0 0 0     0 if (defined($self->[3][0]) && defined($self->[3][1])) {
    0          
679            
680             # if output values wanted
681 0 0       0 if (wantarray) {
682            
683             # return Jacobian and output values
684 0         0 return(&{$self->[3][1]}(@{$in}), [&{$self->[3][0]}(@{$in})]);
  0         0  
  0         0  
  0         0  
  0         0  
685            
686             } else {
687            
688             # return Jacobian only
689 0         0 return(&{$self->[3][1]}(@{$in}));
  0         0  
  0         0  
690            
691             }
692            
693             # if user-defined transform xor user-defined Jacobian functions
694             } elsif (defined($self->[3][0]) ^ defined($self->[3][1])) {
695            
696             # die with message
697 0         0 croak('transform and Jacobian must both be user-defined functions, or not');
698            
699             }
700              
701             # if unit box extrapolation
702 0 0 0     0 if ($hash->{'ubox'} && grep {$_ < 0.0 || $_ > 1.0} @{$in}) {
  0 0       0  
  0         0  
703            
704             # compute intersection with unit box
705 0         0 ($ext, $in) = _intersect($in);
706            
707             }
708              
709             # if ICC::Support::Lapack module is loaded
710 0 0       0 if ($lapack) {
711            
712             # if extrapolating
713 0 0       0 if (defined($ext)) {
714            
715             # compute Jacobian matrix using Lapack module
716 0         0 $jac = ICC::Support::Lapack::clut_jacobian_ext($self->[2], $in, $self->[4]);
717            
718             } else {
719            
720             # compute Jacobian matrix using Lapack module
721 0         0 $jac = ICC::Support::Lapack::clut_jacobian($self->[2], $in, $self->[4]);
722            
723             }
724            
725             # bless Jacobian as Math::Matrix object
726 0         0 bless($jac, 'Math::Matrix');
727            
728             } else {
729            
730             # if extrapolating
731 0 0       0 if (defined($ext)) {
732            
733             # compute outer corner points
734 0         0 $cp = _locate_ext($self);
735            
736             # compute the barycentric jacobian
737 0         0 $jac_bc = _barycentric_jacobian($in);
738            
739             # compute Jacobian matrix
740 0         0 $jac = bless($cp, 'Math::Matrix') * $jac_bc;
741            
742             } else {
743            
744             # compute relative input vector and corner points
745 0         0 ($rel, $cp) = _locate($self, $in);
746            
747             # compute the barycentric jacobian
748 0         0 $jac_bc = _barycentric_jacobian($rel);
749            
750             # compute Jacobian matrix
751 0         0 $jac = bless($cp, 'Math::Matrix') * $jac_bc;
752            
753             # for each input channel
754 0         0 for my $i (0 .. $#{$jac->[0]}) {
  0         0  
755            
756             # compute scale factor for grid size
757 0         0 $sf = $self->[2][$i] - 1;
758            
759             # for each output channel
760 0         0 for my $j (0 .. $#{$jac}) {
  0         0  
761            
762             # scale matrix element
763 0         0 $jac->[$j][$i] *= $sf;
764            
765             }
766            
767             }
768            
769             }
770            
771             }
772              
773             # if output values wanted
774 0 0       0 if (wantarray) {
775            
776             # compute output values
777 0         0 $out = _trans1($self, $in);
778            
779             # if extrapolating
780 0 0       0 if (defined($ext)) {
781            
782             # for each output
783 0         0 for my $i (0 .. $#{$self->[1][0]}) {
  0         0  
784            
785             # add delta value
786 0         0 $out->[$i] += ICC::Shared::dotProduct($jac->[$i], $ext);
787            
788             }
789            
790             }
791            
792             # return Jacobian and output vector
793 0         0 return($jac, $out);
794            
795             } else {
796            
797             # return Jacobian only
798 0         0 return($jac);
799            
800             }
801            
802             }
803              
804             # print object contents to string
805             # format is an array structure
806             # parameter: ([format])
807             # returns: (string)
808             sub sdump {
809              
810             # get parameters
811 0     0 1 0 my ($self, $p) = @_;
812              
813             # local variables
814 0         0 my ($s, $fmt);
815              
816             # resolve parameter to an array reference
817 0 0       0 $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
818              
819             # get format string
820 0 0 0     0 $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
821              
822             # set string to object ID
823 0         0 $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
824              
825             # return
826 0         0 return($s);
827              
828             }
829              
830             # transform list
831             # parameters: (object_reference, list, [hash])
832             # returns: (list)
833             sub _trans0 {
834              
835             # local variables
836 0     0   0 my ($self, $hash, @out);
837              
838             # get object reference
839 0         0 $self = shift();
840              
841             # get optional hash
842 0 0       0 $hash = pop() if (ref($_[-1]) eq 'HASH');
843              
844             # compute output using '_trans1'
845 0         0 @out = @{_trans1($self, \@_, $hash)};
  0         0  
846              
847             # return
848 0         0 return(@out);
849              
850             }
851              
852             # transform vector
853             # parameters: (object_reference, vector, [hash])
854             # returns: (vector)
855             sub _trans1 {
856              
857             # get parameters
858 0     0   0 my ($self, $in, $hash) = @_;
859              
860             # local variables
861 0         0 my ($ext, $out, $rel, $cp, $coef, $jac_bc, $jac);
862              
863             # check if ICC::Support::Lapack module is loaded
864 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
865              
866             # if user-defined transform function
867 0 0       0 if (defined($self->[3][0])) {
868            
869             # call it and return
870 0         0 return([&{$self->[3][0]}(@{$in})]);
  0         0  
  0         0  
871            
872             }
873              
874             # if unit box extrapolation
875 0 0 0     0 if ($hash->{'ubox'} && grep {$_ < 0.0 || $_ > 1.0} @{$in}) {
  0 0       0  
  0         0  
876            
877             # compute intersection with unit box
878 0         0 ($ext, $in) = _intersect($in);
879            
880             }
881            
882             # if ICC::Support::Lapack module is loaded
883 0 0       0 if ($lapack) {
884            
885             # compute output using Lapack module
886 0         0 $out = ICC::Support::Lapack::clut_vec_trans($self->[2], $in, $self->[4]);
887            
888             # if extrapolating
889 0 0       0 if (defined($ext)) {
890            
891             # compute Jacobian matrix using Lapack module
892 0         0 $jac = ICC::Support::Lapack::clut_jacobian_ext($self->[2], $in, $self->[4]);
893            
894             # for each output
895 0         0 for my $i (0 .. $#{$self->[1][0]}) {
  0         0  
896            
897             # add delta value
898 0         0 $out->[$i] += ICC::Shared::dotProduct($jac->[$i], $ext);
899            
900             }
901            
902             }
903            
904            
905             } else {
906            
907             # compute relative input vector and corner points
908 0         0 ($rel, $cp) = _locate($self, $in);
909            
910             # compute barycentric coefficients
911 0         0 $coef = _barycentric($rel);
912            
913             # for each output value
914 0         0 for my $i (0 .. $#{$self->[1][0]}) {
  0         0  
915            
916             # compute output value
917 0         0 $out->[$i] = ICC::Shared::dotProduct($cp->[$i], $coef);
918            
919             }
920            
921             # if extrapolating
922 0 0       0 if (defined($ext)) {
923            
924             # compute outer corner points
925 0         0 $cp = _locate_ext($self);
926            
927             # compute the barycentric Jacobian
928 0         0 $jac_bc = _barycentric_jacobian($in);
929            
930             # compute Jacobian matrix
931 0         0 $jac = bless($cp, 'Math::Matrix') * $jac_bc;
932            
933             # for each output
934 0         0 for my $i (0 .. $#{$self->[1][0]}) {
  0         0  
935            
936             # add delta value
937 0         0 $out->[$i] += ICC::Shared::dotProduct($jac->[$i], $ext);
938            
939             }
940            
941             }
942            
943             }
944              
945             # return
946 0         0 return($out);
947              
948             }
949              
950             # transform matrix (2-D array -or- Math::Matrix object)
951             # parameters: (object_reference, matrix, [hash])
952             # returns: (matrix)
953             sub _trans2 {
954              
955             # get parameters
956 0     0   0 my ($self, $in, $hash) = @_;
957              
958             # local variables
959 0         0 my ($out, $ext, $ink, $rel, $cp, $coef, $jac_bc, $jac);
960              
961             # check if ICC::Support::Lapack module is loaded
962 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
963              
964             # if user-defined transform function
965 0 0       0 if (defined($self->[3][0])) {
    0          
966            
967             # for each input vector
968 0         0 for my $i (0 .. $#{$in}) {
  0         0  
969            
970             # call udf to compute transformed value
971 0         0 $out->[$i] = [&{$self->[3][0]}(@{$in->[$i]})];
  0         0  
  0         0  
972            
973             }
974            
975             # if ICC::Support::Lapack module is loaded
976             } elsif ($lapack) {
977            
978             # for each input vector
979 0         0 for my $i (0 .. $#{$in}) {
  0         0  
980            
981             # if unit box extrapolation
982 0 0 0     0 if ($hash->{'ubox'} && grep {$_ < 0.0 || $_ > 1.0} @{$in->[$i]}) {
  0 0       0  
  0         0  
983            
984             # compute intersection with unit box
985 0         0 ($ext, $ink) = _intersect($in->[$i]);
986            
987             } else {
988            
989             # no extrapolation, copy input
990 0         0 ($ext, $ink) = (undef, $in->[$i]);
991            
992             }
993            
994             # compute output using Lapack module
995 0         0 $out->[$i] = ICC::Support::Lapack::clut_vec_trans($self->[2], $ink, $self->[4]);
996            
997             # if extrapolating
998 0 0       0 if (defined($ext)) {
999            
1000             # compute Jacobian matrix using Lapack module
1001 0         0 $jac = ICC::Support::Lapack::clut_jacobian_ext($self->[2], $ink, $self->[4]);
1002            
1003             # for each output value
1004 0         0 for my $j (0 .. $#{$self->[1][0]}) {
  0         0  
1005            
1006             # add delta value
1007 0         0 $out->[$i][$j] += ICC::Shared::dotProduct($jac->[$j], $ext);
1008            
1009             }
1010            
1011             }
1012            
1013             }
1014            
1015             } else {
1016            
1017             # for each input vector
1018 0         0 for my $i (0 .. $#{$in}) {
  0         0  
1019            
1020             # if unit box extrapolation
1021 0 0 0     0 if ($hash->{'ubox'} && grep {$_ < 0.0 || $_ > 1.0} @{$in->[$i]}) {
  0 0       0  
  0         0  
1022            
1023             # compute intersection with unit box
1024 0         0 ($ext, $ink) = _intersect($in->[$i]);
1025            
1026             } else {
1027            
1028             # no extrapolation, copy input
1029 0         0 ($ext, $ink) = (undef, $in->[$i]);
1030            
1031             }
1032            
1033             # compute relative input vector and corner points
1034 0         0 ($rel, $cp) = _locate($self, $ink);
1035            
1036             # compute barycentric coefficients
1037 0         0 $coef = _barycentric($rel);
1038            
1039             # for each output value
1040 0         0 for my $j (0 .. $#{$self->[1][0]}) {
  0         0  
1041            
1042             # compute output value
1043 0         0 $out->[$i][$j] = ICC::Shared::dotProduct($cp->[$j], $coef);
1044            
1045             }
1046            
1047             # if extrapolating
1048 0 0       0 if (defined($ext)) {
1049            
1050             # compute outer corner points
1051 0         0 $cp = _locate_ext($self);
1052            
1053             # compute the barycentric Jacobian
1054 0         0 $jac_bc = _barycentric_jacobian($ink);
1055            
1056             # compute Jacobian matrix
1057 0         0 $jac = bless($cp, 'Math::Matrix') * $jac_bc;
1058            
1059             # for each output value
1060 0         0 for my $j (0 .. $#{$self->[1][0]}) {
  0         0  
1061            
1062             # add delta value
1063 0         0 $out->[$i][$j] += ICC::Shared::dotProduct($jac->[$j], $ext);
1064            
1065             }
1066            
1067             }
1068            
1069             }
1070            
1071             }
1072              
1073             # return
1074 0 0       0 return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
1075              
1076             }
1077              
1078             # transform structure
1079             # parameters: (object_reference, structure, [hash])
1080             # returns: (structure)
1081             sub _trans3 {
1082              
1083             # get parameters
1084 0     0   0 my ($self, $in, $hash) = @_;
1085              
1086             # transform the array structure
1087 0         0 _crawl($self, $in, my $out = [], $hash);
1088              
1089             # return
1090 0         0 return($out);
1091              
1092             }
1093              
1094             # recursive transform
1095             # array structure is traversed until scalar arrays are found and transformed
1096             # parameters: (ref_to_object, input_array_reference, output_array_reference, hash)
1097             sub _crawl {
1098              
1099             # get parameters
1100 0     0   0 my ($self, $in, $out, $hash) = @_;
1101              
1102             # if input is a vector (reference to a scalar array)
1103 0 0       0 if (@{$in} == grep {! ref()} @{$in}) {
  0         0  
  0         0  
  0         0  
1104            
1105             # transform input vector and copy to output
1106 0         0 @{$out} = @{_trans1($self, $in, $hash)};
  0         0  
  0         0  
1107            
1108             } else {
1109            
1110             # for each input element
1111 0         0 for my $i (0 .. $#{$in}) {
  0         0  
1112            
1113             # if an array reference
1114 0 0       0 if (ref($in->[$i]) eq 'ARRAY') {
1115            
1116             # transform next level
1117 0         0 _crawl($self, $in->[$i], $out->[$i] = [], $hash);
1118            
1119             } else {
1120            
1121             # error
1122 0         0 croak('invalid transform input');
1123            
1124             }
1125            
1126             }
1127            
1128             }
1129            
1130             }
1131              
1132             # compute relative input vector and corner points
1133             # parameter: (object_ref, input_vector)
1134             # returns: (relative_input_vector, corner_point_array)
1135             sub _locate {
1136              
1137             # get parameter
1138 0     0   0 my ($self, $in) = @_;
1139              
1140             # local variables
1141 0         0 my (@rel, @ox, $ux, $key, @ix, $gp, $cp);
1142              
1143             # for each input value
1144 0         0 for my $i (0 .. $#{$in}) {
  0         0  
1145            
1146             # split clut span into fractional and integer parts
1147 0         0 ($rel[$i], $ox[$i]) = POSIX::modf($in->[$i] * ($self->[2][$i] - 1));
1148            
1149             # compute upper grid index
1150 0         0 $ux = $self->[2][$i] - 2;
1151            
1152             # if grid index < 0
1153 0 0       0 if ($ox[$i] < 0) {
    0          
1154            
1155             # adjust
1156 0         0 $rel[$i] += $ox[$i];
1157 0         0 $ox[$i] = 0;
1158            
1159             # if grid index > upper index
1160             } elsif ($ox[$i] > $ux) {
1161            
1162             #adjust
1163 0         0 $rel[$i] += $ox[$i] - $ux;
1164 0         0 $ox[$i] = $ux;
1165            
1166             }
1167            
1168             }
1169              
1170             # compute hash key
1171 0         0 $key = join(':', @ox);
1172            
1173             # if corner points are not cached
1174 0 0       0 if (! ($cp = $self->[5]{$key})) {
1175            
1176             # for each corner point
1177 0         0 for my $i (0 .. 2**@{$in} - 1) {
  0         0  
1178            
1179             # copy origin
1180 0         0 @ix = @ox;
1181            
1182             # for each input
1183 0         0 for my $j (0 .. $#{$in}) {
  0         0  
1184            
1185             # increment index if bit set
1186 0 0       0 $ix[$j]++ if ($i >> $j & 1);
1187            
1188             }
1189            
1190             # get clut grid point array
1191 0         0 $gp = $self->[1][_ix2lin($self->[2], @ix)];
1192            
1193             # for each output
1194 0         0 for my $j (0 .. $#{$gp}) {
  0         0  
1195            
1196             # copy array value
1197 0         0 $cp->[$j][$i] = $gp->[$j];
1198            
1199             }
1200            
1201             }
1202            
1203             # save in cache
1204 0         0 $self->[5]{$key} = $cp;
1205            
1206             }
1207              
1208             # return
1209 0         0 return(\@rel, $cp);
1210              
1211             }
1212              
1213             # compute outer corner points
1214             # parameter: (object_ref)
1215             # returns: (corner_point_array)
1216             sub _locate_ext {
1217              
1218             # get parameter
1219 0     0   0 my ($self) = @_;
1220              
1221             # local variables
1222 0         0 my ($cp, @ix, $gp);
1223              
1224             # if ext corner points are not cached
1225 0 0       0 if (! ($cp = $self->[5]{'ext'})) {
1226            
1227             # for each corner point
1228 0         0 for my $i (0 .. 2**@{$self->[2]} - 1) {
  0         0  
1229            
1230             # for each input
1231 0         0 for my $j (0 .. $#{$self->[2]}) {
  0         0  
1232            
1233             # increment index if bit set
1234 0 0       0 $ix[$j] = ($i >> $j & 1) ? $self->[2][$j] - 1 : 0;
1235            
1236             }
1237            
1238             # get clut grid point array
1239 0         0 $gp = $self->[1][_ix2lin($self->[2], @ix)];
1240            
1241             # for each output
1242 0         0 for my $j (0 .. $#{$gp}) {
  0         0  
1243            
1244             # copy array value
1245 0         0 $cp->[$j][$i] = $gp->[$j];
1246            
1247             }
1248            
1249             }
1250            
1251             # save in cache
1252 0         0 $self->[5]{'ext'} = $cp;
1253            
1254             }
1255              
1256             # return
1257 0         0 return($cp);
1258              
1259             }
1260              
1261             # compute barycentric coefficients
1262             # parameter: (input_vector)
1263             # returns: (coefficient_array)
1264             sub _barycentric {
1265              
1266             # get parameter
1267 0     0   0 my $in = shift();
1268              
1269             # local variables
1270 0         0 my ($inc, $coef);
1271              
1272             # compute complement values
1273 0         0 $inc = [map {1 - $_} @{$in}];
  0         0  
  0         0  
1274              
1275             # initialize coefficient array
1276 0         0 $coef = [(1.0) x 2**@{$in}];
  0         0  
1277              
1278             # for each coefficient
1279 0         0 for my $i (0 .. $#{$coef}) {
  0         0  
1280            
1281             # for each device value
1282 0         0 for my $j (0 .. $#{$in}) {
  0         0  
1283            
1284             # if $j-th bit set
1285 0 0       0 if ($i >> $j & 1) {
1286            
1287             # multiply by device value
1288 0         0 $coef->[$i] *= $in->[$j];
1289            
1290             } else {
1291            
1292             # multiply by (1 - device value)
1293 0         0 $coef->[$i] *= $inc->[$j];
1294            
1295             }
1296            
1297             }
1298            
1299             }
1300              
1301             # return
1302 0         0 return($coef);
1303              
1304             }
1305              
1306             # compute barycentric Jacobian matrix
1307             # parameter: (input_vector)
1308             # returns: (Jacobian_matrix)
1309             sub _barycentric_jacobian {
1310              
1311             # get parameter
1312 0     0   0 my $in = shift();
1313              
1314             # local variables
1315 0         0 my ($inc, $rows, $jac);
1316              
1317             # compute complement values
1318 0         0 $inc = [map {1 - $_} @{$in}];
  0         0  
  0         0  
1319              
1320             # compute matrix rows
1321 0         0 $rows = 2**@{$in};
  0         0  
1322              
1323             # for each matrix row
1324 0         0 for my $i (0 .. $rows - 1) {
1325            
1326             # initialize row
1327 0         0 $jac->[$i] = [(1.0) x @{$in}];
  0         0  
1328            
1329             # for each matrix column
1330 0         0 for my $j (0 .. $#{$in}) {
  0         0  
1331            
1332             # for each device value
1333 0         0 for my $k (0 .. $#{$in}) {
  0         0  
1334            
1335             # if $k-th bit set
1336 0 0       0 if ($i >> $k & 1) {
1337            
1338             # multiply by device value -or- 1 (skip)
1339 0 0       0 $jac->[$i][$j] *= $in->[$k] if ($j != $k);
1340            
1341             } else {
1342            
1343             # multiply by (1 - device value) -or- -1
1344 0 0       0 $jac->[$i][$j] *= ($j != $k) ? $inc->[$k] : -1;
1345            
1346             }
1347            
1348             }
1349            
1350             }
1351            
1352             }
1353              
1354             # return
1355 0         0 return(bless($jac, 'Math::Matrix'));
1356              
1357             }
1358              
1359             # find unit box intersection
1360             # with line from input to box-center
1361             # parameters: (input_vector)
1362             # returns: (extrapolation_vector, intersection_vector)
1363             sub _intersect {
1364              
1365             # get input values
1366 0     0   0 my ($in) = shift();
1367              
1368             # local variables
1369 0         0 my (@cin, $dmax, $ubox, $ext);
1370              
1371             # compute input to box-center difference
1372 0         0 @cin = map {$_ - 0.5} @{$in};
  0         0  
  0         0  
1373              
1374             # initialize
1375 0         0 $dmax = 0;
1376              
1377             # for each difference
1378 0         0 for (@cin) {
1379            
1380             # if larger absolute value
1381 0 0       0 if (abs($_) > $dmax) {
1382            
1383             # new max difference
1384 0         0 $dmax = abs($_);
1385            
1386             }
1387            
1388             }
1389              
1390             # multiply max difference by 2
1391 0         0 $dmax *= 2;
1392              
1393             # compute intersection vector (on surface of unit box)
1394 0         0 $ubox = [map {$_/$dmax + 0.5} @cin];
  0         0  
1395              
1396             # compute extrapolation vector (as Math::Matrix object)
1397 0         0 $ext = [map {$in->[$_] - $ubox->[$_]} (0 .. $#{$in})];
  0         0  
  0         0  
1398              
1399             # return
1400 0         0 return($ext, $ubox);
1401              
1402             }
1403              
1404             # compute clut linear index from index array
1405             # parameters: (ref_to_grid_size_array, index_array)
1406             # returns: (linear_index)
1407             sub _ix2lin {
1408              
1409             # get parameters
1410 0     0   0 my ($gsa, @ix) = @_;
1411              
1412             # initialize linear_index
1413 0         0 my $lx = $ix[0];
1414              
1415             # for each remaining array value
1416 0         0 for my $i (1 .. $#ix) {
1417            
1418             # multiply by grid size
1419 0         0 $lx *= $gsa->[$i];
1420            
1421             # add index value
1422 0         0 $lx += $ix[$i];
1423            
1424             }
1425              
1426             # return linear index
1427 0         0 return($lx);
1428              
1429             }
1430              
1431             # compute clut index array from linear index
1432             # parameters: (ref_to_grid_size_array, linear_index)
1433             # returns: (index_array)
1434             sub _lin2ix {
1435              
1436             # get parameters
1437 0     0   0 my ($gsa, $lx) = @_;
1438              
1439             # local variables
1440 0         0 my ($mod, @ix);
1441              
1442             # for each input channel
1443 0         0 for my $gs (reverse(@{$gsa})) {
  0         0  
1444            
1445             # compute modulus
1446 0         0 $mod = $lx % $gs;
1447            
1448             # adjust linear index
1449 0         0 $lx = ($lx - $mod)/$gs;
1450            
1451             # save input value
1452 0         0 unshift(@ix, $mod/($gs - 1));
1453            
1454             }
1455              
1456             # return index array
1457 0         0 return(@ix);
1458              
1459             }
1460              
1461             # get clut size
1462             # parameter: (clut_bytes)
1463             # returns: (clut_size)
1464             sub _clut_size {
1465              
1466             # get parameter
1467 14     14   38 my ($self, $bytes) = @_;
1468              
1469             # get size of clut entry
1470 14         23 my $size = $bytes * @{$self->[1][0]};
  14         35  
1471              
1472             # for each grid size value
1473 14         19 for (@{$self->[2]}) {
  14         37  
1474            
1475             # multiply by grid size
1476 45         57 $size *= $_;
1477            
1478             }
1479              
1480             # return size
1481 14         31 return($size);
1482              
1483             }
1484              
1485             # make new clut object from attribute hash
1486             # hash may contain pointers to clut, clut size, grid size array, and user-defined functions
1487             # hash keys are: ('array', 'clut_bytes', 'gsa', 'udf')
1488             # object elements not specified in the hash are unchanged
1489             # parameters: (ref_to_object, ref_to_attribute_hash)
1490             sub _new_from_hash {
1491              
1492             # get parameters
1493 0     0   0 my ($self, $hash) = @_;
1494              
1495             # for each attribute
1496 0         0 for my $attr (keys(%{$hash})) {
  0         0  
1497            
1498             # if 'array'
1499 0 0       0 if ($attr eq 'array') {
    0          
    0          
    0          
1500            
1501             # if reference to a 2-D array
1502 0 0 0     0 if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {ref() eq 'ARRAY'} @{$hash->{$attr}}) {
  0 0       0  
  0         0  
  0         0  
1503            
1504             # set clut to clone of array
1505 0         0 $self->[1] = Storable::dclone($hash->{$attr});
1506            
1507             # update caches
1508 0 0       0 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
1509 0         0 undef($self->[5]);
1510            
1511             # if reference to a Math::Matrix object
1512             } elsif (UNIVERSAL::isa($hash->{$attr}, 'Math::Matrix')) {
1513            
1514             # set clut to object
1515 0         0 $self->[1] = $hash->{$attr};
1516            
1517             # update caches
1518 0 0       0 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
1519 0         0 undef($self->[5]);
1520            
1521             } else {
1522            
1523             # wrong data type
1524 0         0 croak('clut \'array\' attribute must be a 2-D array reference or Math::Matrix object');
1525            
1526             }
1527            
1528             # if 'clut_bytes'
1529             } elsif ($attr eq 'clut_bytes') {
1530            
1531             # if a scalar, 1 or 2
1532 0 0 0     0 if (! ref($hash->{$attr}) && ($hash->{$attr} == 1 || $hash->{$attr} == 2)) {
      0        
1533            
1534             # add to header hash
1535 0         0 $self->[0]{'clut_bytes'} = $hash->{$attr};
1536            
1537             } else {
1538            
1539             # wrong data type
1540 0         0 croak('clut \'clut_bytes\' attribute must be a scalar, 1 or 2');
1541            
1542             }
1543            
1544             # if 'gsa'
1545             } elsif ($attr eq 'gsa') {
1546            
1547             # if reference to a 1-D array (vector)
1548 0 0 0     0 if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {Scalar::Util::looks_like_number($_)} @{$hash->{$attr}}) {
  0         0  
  0         0  
  0         0  
1549            
1550             # set object element
1551 0         0 $self->[2] = [@{$hash->{$attr}}];
  0         0  
1552            
1553             } else {
1554            
1555             # wrong data type
1556 0         0 croak('clut \'gsa\' attribute must be an array reference');
1557            
1558             }
1559            
1560             # if 'udf'
1561             } elsif ($attr eq 'udf') {
1562            
1563             # if reference to an array of CODE references
1564 0 0 0     0 if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {ref() eq 'CODE'} @{$hash->{$attr}}) {
  0         0  
  0         0  
  0         0  
1565            
1566             # set object element
1567 0         0 $self->[3] = [@{$hash->{$attr}}];
  0         0  
1568            
1569             } else {
1570            
1571             # wrong data type
1572 0         0 croak('clut \'udf\' attribute must be an array reference');
1573            
1574             }
1575            
1576             } else {
1577            
1578             # invalid attribute
1579 0         0 croak('invalid clut attribute');
1580            
1581             }
1582            
1583             }
1584            
1585             }
1586              
1587             # read clut data
1588             # note: assumes file handle is positioned at start of clut data
1589             # header information must be read separately by the calling function
1590             # precision is number of bytes per clut element, 1 (8-bit), 2 (16-bit) or 4 (floating point)
1591             # parameters: (ref_to_object, file_handle, output_channels, ref_to_grid_size_array, precision)
1592             sub _read_clut {
1593              
1594             # get parameters
1595 4     4   17 my ($self, $fh, $co, $gsa, $bytes) = @_;
1596              
1597             # local variables
1598 4         8 my ($rbs, $size, $buf);
1599              
1600             # set read block size
1601 4         9 $rbs = $bytes * $co;
1602              
1603             # initialize clut entries
1604 4         6 $size = 1;
1605              
1606             # for each input channel
1607 4         14 for (@{$gsa}) {
  4         10  
1608            
1609             # multiply by grid size
1610 13         20 $size *= $_;
1611            
1612             }
1613            
1614             # if 8-bit table
1615 4 100       21 if ($bytes == 1) {
    50          
    0          
1616            
1617             # for each clut entry
1618 1         3 for my $i (0 .. $size - 1) {
1619            
1620             # read into buffer
1621 35937         46500 read($fh, $buf, $rbs);
1622            
1623             # unpack buffer and save
1624 35937         44424 $self->[1][$i] = [map {$_/255} unpack('C*', $buf)];
  35937         74158  
1625            
1626             }
1627            
1628             # if 16-bit table
1629             } elsif ($bytes == 2) {
1630            
1631             # for each clut entry
1632 3         10 for my $i (0 .. $size - 1) {
1633            
1634             # read into buffer
1635 93347         120610 read($fh, $buf, $rbs);
1636            
1637             # unpack buffer and save
1638 93347         118133 $self->[1][$i] = [map {$_/65535} unpack('n*', $buf)];
  280041         411917  
1639            
1640             }
1641            
1642             # if floating point table
1643             } elsif ($bytes == 4) {
1644            
1645             # for each clut entry
1646 0         0 for my $i (0 .. $size - 1) {
1647            
1648             # read into buffer
1649 0         0 read($fh, $buf, $rbs);
1650            
1651             # unpack buffer and save
1652 0         0 $self->[1][$i] = [unpack('f>*', $buf)];
1653            
1654             }
1655            
1656             } else {
1657            
1658             # error
1659 0         0 croak('unsupported data size, must be 1, 2 or 4 bytes');
1660            
1661             }
1662              
1663             # cache clut for Lapack functions, if defined
1664 4 50       39 $self->[4] = ICC::Support::Lapack::cache_2D($self->[1]) if (defined($INC{'ICC/Support/Lapack.pm'}));
1665              
1666             }
1667              
1668             # read clut tag from ICC profile
1669             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1670             sub _readICCclut {
1671              
1672             # get parameters
1673 0     0   0 my ($self, $parent, $fh, $tag) = @_;
1674              
1675             # local variables
1676 0         0 my ($buf, $ci, $co, $gsa);
1677              
1678             # save tag signature
1679 0         0 $self->[0]{'signature'} = $tag->[0];
1680              
1681             # seek start of tag
1682 0         0 seek($fh, $tag->[1], 0);
1683              
1684             # read tag header
1685 0         0 read($fh, $buf, 12);
1686              
1687             # unpack header
1688 0         0 ($ci, $co) = unpack('x8 n2', $buf);
1689              
1690             # set number of input channels
1691 0         0 $self->[0]{'input_channels'} = $ci;
1692              
1693             # set number of output channels
1694 0         0 $self->[0]{'output_channels'} = $co;
1695              
1696             # read grid size array
1697 0         0 read($fh, $buf, 16);
1698              
1699             # make grid size array
1700 0         0 $gsa = [grep {$_} unpack('C16', $buf)];
  0         0  
1701              
1702             # save grid size array
1703 0         0 $self->[2] = [@{$gsa}];
  0         0  
1704              
1705             # read clut
1706 0         0 _read_clut($self, $fh, $co, $gsa, 4);
1707              
1708             }
1709              
1710             # write clut data
1711             # note: assumes file handle is positioned at start of clut data
1712             # header information must be written separately by the calling function
1713             # precision is number of bytes per clut element, 1 (8-bit), 2 (16-bit) or 4 (floating point)
1714             # parameters: (ref_to_object, file_handle, ref_to_grid_size_array, precision)
1715             sub _write_clut {
1716              
1717             # get parameters
1718 4     4   14 my ($self, $fh, $gsa, $bytes) = @_;
1719              
1720             # local variables
1721 4         9 my ($size, $buf);
1722              
1723             # initialize clut size
1724 4         7 $size = 1;
1725              
1726             # for each input channel
1727 4         8 for (@{$gsa}) {
  4         12  
1728            
1729             # multiply by grid size
1730 13         19 $size *= $_;
1731            
1732             }
1733              
1734             # if 8-bit table
1735 4 100       34 if ($bytes == 1) {
    50          
    0          
1736            
1737             # for each clut entry
1738 1         5 for my $i (0 .. $size - 1) {
1739            
1740             # write clut values, limiting and adding 0.5 to round
1741 35937 50       41326 print $fh pack('C*', map {$_ < 0 ? 0 : ($_ > 1 ? 255 : $_ * 255 + 0.5)} @{$self->[1][$i]});
  35937 50       90036  
  35937         45663  
1742            
1743             }
1744            
1745             # if 16-bit table
1746             } elsif ($bytes == 2) {
1747            
1748             # for each clut entry
1749 3         13 for my $i (0 .. $size - 1) {
1750            
1751             # write clut values, limiting and adding 0.5 to round
1752 93347 50       103550 print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @{$self->[1][$i]});
  280041 50       561098  
  93347         119664  
1753            
1754             }
1755            
1756             # if floating point table
1757             } elsif ($bytes == 4) {
1758            
1759             # for each clut entry
1760 0           for my $i (0 .. $size - 1) {
1761            
1762             # write clut values
1763 0           print $fh pack('f>*', @{$self->[1][$i]});
  0            
1764            
1765             }
1766            
1767             } else {
1768            
1769             # error
1770 0           croak('unsupported data size, must be 1, 2 or 4 bytes');
1771            
1772             }
1773            
1774             }
1775              
1776             # write clut tag to ICC profile
1777             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1778             sub _writeICCclut {
1779              
1780             # get parameters
1781 0     0     my ($self, $parent, $fh, $tag) = @_;
1782              
1783             # local variables
1784 0           my ($gsa, $ci, $co, @mat);
1785              
1786             # get grid size array
1787 0           $gsa = $self->[2];
1788              
1789             # get number of input channels
1790 0           $ci = @{$gsa};
  0            
1791              
1792             # get number of output channels
1793 0           $co = @{$self->[1][0]};
  0            
1794              
1795             # validate number input channels (1 to 15)
1796 0 0 0       ($ci > 0 && $ci < 16) or croak('unsupported number of input channels');
1797              
1798             # validate number output channels (1 to 15)
1799 0 0 0       ($co > 0 && $co < 16) or croak('unsupported number of output channels');
1800              
1801             # for each possible input channel
1802 0           for my $i (0 .. 15) {
1803            
1804             # set grid size
1805 0   0       $mat[$i] = $gsa->[$i] || 0;
1806            
1807             }
1808              
1809             # seek start of tag
1810 0           seek($fh, $tag->[1], 0);
1811              
1812             # write 'clut' header
1813 0           print $fh pack('a4 x4 n2 C16', 'clut', $ci, $co, @mat);
1814              
1815             # write clut
1816 0           _write_clut($self, $fh, $gsa, 4);
1817              
1818             }
1819              
1820             1;