File Coverage

blib/lib/ICC/Profile/clut.pm
Criterion Covered Total %
statement 68 570 11.9
branch 13 238 5.4
condition 0 110 0.0
subroutine 12 39 30.7
pod 1 16 6.2
total 94 973 9.6


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