File Coverage

lib/ICC/Profile/mft1.pm
Criterion Covered Total %
statement 133 354 37.5
branch 33 178 18.5
condition 4 81 4.9
subroutine 12 34 35.2
pod 2 20 10.0
total 184 667 27.5


line stmt bran cond sub pod time code
1             package ICC::Profile::mft1;
2              
3 2     2   90182 use strict;
  2         5  
  2         55  
4 2     2   9 use Carp;
  2         2  
  2         140  
5              
6             our $VERSION = 2.51;
7              
8             # revised 2018-08-07
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 2     2   11 use lib 'lib';
  2         2  
  2         15  
14              
15             # inherit from Shared
16 2     2   264 use parent qw(ICC::Shared);
  2         3  
  2         14  
17              
18             # use POSIX math
19 2     2   135 use POSIX ();
  2         3  
  2         8258  
20              
21             # create new mft1 object
22             # hash may contain pointers to matrix, input curves, CLUT, or output curves
23             # keys are: ('matrix', 'input', 'clut', 'output')
24             # tag elements not specified in the hash are left empty
25             # parameters: ([ref_to_attribute_hash])
26             # returns: (ref_to_object)
27             sub new {
28              
29             # get object class
30 1     1 0 1135 my $class = shift();
31              
32             # create empty mft1 object
33 1         4 my $self = [
34             {}, # object header
35             [], # processing elements
36             0x00, # transform mask
37             0x00 # clipping mask
38             ];
39              
40             # if there are parameters
41 1 50       4 if (@_) {
42            
43             # if one parameter, a hash reference
44 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
45            
46             # make new mft1 tag from attribute hash
47 0         0 _new_from_hash($self, @_);
48            
49             } else {
50            
51             # error
52 0         0 croak('parameter must be a hash reference');
53            
54             }
55            
56             }
57              
58             # bless object
59 1         2 bless($self, $class);
60              
61             # return object reference
62 1         2 return($self);
63              
64             }
65              
66             # get/set reference to header hash
67             # parameters: ([ref_to_new_hash])
68             # returns: (ref_to_hash)
69             sub header {
70              
71             # get object reference
72 0     0 0 0 my $self = shift();
73              
74             # if there are parameters
75 0 0       0 if (@_) {
76            
77             # if one parameter, a hash reference
78 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
79            
80             # set header to new hash
81 0         0 $self->[0] = shift();
82            
83             } else {
84            
85             # error
86 0         0 croak('parameter must be a hash reference');
87            
88             }
89            
90             }
91              
92             # return reference
93 0         0 return($self->[0]);
94              
95             }
96              
97             # get/set processing element array reference
98             # parameters: ([ref_to_array])
99             # returns: (ref_to_array)
100             sub array {
101              
102             # get object reference
103 0     0 0 0 my $self = shift();
104              
105             # if parameter
106 0 0       0 if (@_) {
107            
108             # verify array reference
109 0 0       0 (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
110            
111             # set array reference
112 0         0 $self->[1] = [@{shift()}];
  0         0  
113            
114             }
115              
116             # return array reference
117 0         0 return($self->[1]);
118              
119             }
120              
121             # get/set reference to matrix 'matf' object
122             # parameters: ([ref_to_new_object])
123             # returns: (ref_to_object)
124             sub matrix {
125              
126             # get object reference
127 0     0 0 0 my $self = shift();
128              
129             # if there are parameters
130 0 0       0 if (@_) {
131            
132             # if one parameter, an 'matf' object
133 0 0 0     0 if (@_ == 1 && UNIVERSAL::isa($_[0], 'ICC::Profile::matf')) {
134            
135             # warn if 'matf' object has offset values
136 0 0       0 (@{$_[0]->offset()}) or carp('offset values in matrix object are not supported');
  0         0  
137            
138             # set matrix to new object
139 0         0 $self->[1][0] = shift();
140            
141             # set transform mask bit
142 0         0 $self->[2] |= 0x01;
143            
144             } else {
145            
146             # error
147 0         0 croak('parameter must be an \'matf\' object');
148            
149             }
150            
151             }
152              
153             # return object reference
154 0         0 return($self->[1][0]);
155              
156             }
157              
158             # get/set reference to input curves 'cvst' object
159             # parameters: ([ref_to_new_object])
160             # returns: (ref_to_object)
161             sub input {
162              
163             # get object reference
164 0     0 0 0 my $self = shift();
165              
166             # if there are parameters
167 0 0       0 if (@_) {
168            
169             # if one parameter, a 'cvst' object
170 0 0 0     0 if (@_ == 1 && UNIVERSAL::isa($_[0], 'ICC::Profile::cvst')) {
171            
172             # set input curves to new object
173 0         0 $self->[1][1] = shift();
174            
175             # set transform mask bit
176 0         0 $self->[2] |= 0x02;
177            
178             } else {
179            
180             # error
181 0         0 croak('parameter must be a \'cvst\' object');
182            
183             }
184            
185             }
186              
187             # return object reference
188 0         0 return($self->[1][1]);
189              
190             }
191              
192             # get/set reference to CLUT 'clut' object
193             # parameters: ([ref_to_new_object])
194             # returns: (ref_to_object)
195             sub clut {
196              
197             # get object reference
198 0     0 0 0 my $self = shift();
199              
200             # if there are parameters
201 0 0       0 if (@_) {
202            
203             # if one parameter, a 'clut' object
204 0 0 0     0 if (@_ == 1 && UNIVERSAL::isa($_[0], 'ICC::Profile::clut')) {
205            
206             # set CLUT to new object
207 0         0 $self->[1][2] = shift();
208            
209             # set transform mask bit
210 0         0 $self->[2] |= 0x04;
211            
212             } else {
213            
214             # error
215 0         0 croak('parameter must be a \'clut\' object');
216            
217             }
218            
219             }
220              
221             # return object reference
222 0         0 return($self->[1][2]);
223              
224             }
225              
226             # get/set reference to output curves 'cvst' object
227             # parameters: ([ref_to_new_object])
228             # returns: (ref_to_object)
229             sub output {
230              
231             # get object reference
232 0     0 0 0 my $self = shift();
233              
234             # if there are parameters
235 0 0       0 if (@_) {
236            
237             # if one parameter, a 'cvst' object
238 0 0 0     0 if (@_ == 1 && UNIVERSAL::isa($_[0], 'ICC::Profile::cvst')) {
239            
240             # set output curves to new object
241 0         0 $self->[1][3] = shift();
242            
243             # set transform mask bit
244 0         0 $self->[2] |= 0x08;
245            
246             } else {
247            
248             # error
249 0         0 croak('parameter must be a \'cvst\' object');
250            
251             }
252            
253             }
254              
255             # return object reference
256 0         0 return($self->[1][3]);
257              
258             }
259              
260             # get/set transform mask
261             # bits 3-2-1-0 correpsond to OUTPUT-CLUT-INPUT-MATRIX
262             # parameters: ([new_mask_value])
263             # returns: (mask_value)
264             sub mask {
265              
266             # get object reference
267 0     0 0 0 my $self = shift();
268              
269             # if there are parameters
270 0 0       0 if (@_) {
271            
272             # if one parameter
273 0 0       0 if (@_ == 1) {
274            
275             # set object transform mask value
276 0         0 $self->[2] = shift();
277            
278             } else {
279            
280             # error
281 0         0 croak('more than one parameter');
282            
283             }
284            
285             }
286              
287             # return transform mask value
288 0         0 return($self->[2]);
289              
290             }
291              
292             # get/set clipping mask
293             # bits 3-2-1-0 correpsond to OUTPUT-CLUT-INPUT-MATRIX
294             # parameters: ([new_mask_value])
295             # returns: (mask_value)
296             sub clip {
297            
298             # get object reference
299 0     0 1 0 my $self = shift();
300            
301             # if there are parameters
302 0 0       0 if (@_) {
303            
304             # if one parameter
305 0 0       0 if (@_ == 1) {
306            
307             # set object clipping mask value
308 0         0 $self->[3] = shift();
309            
310             } else {
311            
312             # error
313 0         0 croak('more than one parameter');
314            
315             }
316            
317             }
318            
319             # return clipping mask value
320 0         0 return($self->[3]);
321            
322             }
323              
324             # create mft1 tag object from ICC profile
325             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
326             # returns: (ref_to_object)
327             sub new_fh {
328              
329             # get object class
330 1     1 0 667 my $class = shift();
331              
332             # create empty mft1 object
333 1         3 my $self = [
334             {}, # object header
335             [], # processing elements
336             0x00, # transform mask
337             0x00 # clipping mask
338             ];
339              
340             # verify 3 parameters
341 1 50       5 (@_ == 3) or croak('wrong number of parameters');
342              
343             # read mft1 data from profile
344 1         5 _readICCmft1($self, @_);
345              
346             # bless object
347 1         3 bless($self, $class);
348              
349             # return object reference
350 1         23 return($self);
351              
352             }
353              
354             # writes mft1 tag object to ICC profile
355             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
356             sub write_fh {
357              
358             # verify 4 parameters
359 1 50   1 0 1211 (@_ == 4) or croak('wrong number of parameters');
360              
361             # write mft1 data to profile
362 1         8 goto &_writeICCmft1;
363              
364             }
365              
366             # get tag size (for writing to profile)
367             # returns: (tag_clut_size)
368             sub size {
369              
370             # get parameters
371 3     3 0 727 my ($self) = @_;
372              
373             # set header size
374 3         7 my $size = 48;
375              
376             # add size of input tables (assumes 'curv' objects)
377 3         20 $size += $self->[1][1]->cin() * 256;
378              
379             # add size of clut
380 3         16 $size += $self->[1][2]->_clut_size(1);
381              
382             # add size of output tables (assumes 'curv' objects)
383 3         8 $size += $self->[1][3]->cin() * 256;
384              
385             # return size
386 3         155 return($size);
387              
388             }
389              
390             # get number of input channels
391             # returns: (number)
392             sub cin {
393              
394             # get object reference
395 0     0 0 0 my $self = shift();
396              
397             # return
398 0         0 return($self->[1][1]->cin());
399              
400             }
401              
402             # get number of output channels
403             # returns: (number)
404             sub cout {
405              
406             # get object reference
407 0     0 0 0 my $self = shift();
408              
409             # return
410 0         0 return($self->[1][3]->cout());
411              
412             }
413              
414             # transform data
415             # transform mask enables/disables individual tag elements
416             # clipping mask enables/disables individual tag output clipping
417             # supported input types:
418             # parameters: (list, [hash])
419             # parameters: (vector, [hash])
420             # parameters: (matrix, [hash])
421             # parameters: (Math::Matrix_object, [hash])
422             # parameters: (structure, [hash])
423             # returns: (same_type_as_input)
424             sub transform {
425              
426             # set hash value (0 or 1)
427 0 0   0 0 0 my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
428              
429             # if input a 'Math::Matrix' object
430 0 0 0     0 if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
431            
432             # call matrix transform
433 0         0 &_trans2;
434            
435             # if input an array reference
436             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
437            
438             # if array contains numbers (vector)
439 0 0 0     0 if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0     0  
  0         0  
  0         0  
440            
441             # call vector transform
442 0         0 &_trans1;
443            
444             # if array contains vectors (2-D array)
445 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  
446            
447             # call matrix transform
448 0         0 &_trans2;
449            
450             } else {
451            
452             # call structure transform
453 0         0 &_trans3;
454            
455             }
456            
457             # if input a list (of numbers)
458 0         0 } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
459            
460             # call list transform
461 0         0 &_trans0;
462            
463             } else {
464            
465             # error
466 0         0 croak('invalid transform input');
467            
468             }
469              
470             }
471              
472             # inverse transform
473             # note: number of undefined output values must equal number of defined input values
474             # note: the input and output vectors contain the final solution on return
475             # hash key 'init' specifies initial value vector
476             # hash key 'ubox' enables unit box extrapolation
477             # parameters: (input_vector, output_vector, [hash])
478             # returns: (RMS_error_value)
479             sub inverse {
480              
481             # get parameters
482 0     0 0 0 my ($self, $in, $out, $hash) = @_;
483              
484             # local variables
485 0         0 my ($i, $j, @si, @so, $init);
486 0         0 my ($int, $jac, $mat, $delta);
487 0         0 my ($max, $elim, $dlim, $accum, $error);
488              
489             # initialize indices
490 0         0 $i = $j = -1;
491              
492             # build slice arrays while validating input and output arrays
493 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  
494              
495             # get init array
496 0         0 $init = $hash->{'init'};
497              
498             # for each undefined output value
499 0         0 for my $i (@so) {
500            
501             # set to supplied initial value or 0.5
502 0 0       0 $out->[$i] = defined($init->[$i]) ? $init->[$i] : 0.5;
503            
504             }
505              
506             # set maximum loop count
507 0   0     0 $max = $hash->{'inv_max'} || 10;
508              
509             # loop error limit
510 0   0     0 $elim = $hash->{'inv_elim'} || 1E-6;
511              
512             # set delta limit
513 0   0     0 $dlim = $hash->{'inv_dlim'} || 0.5;
514              
515             # create empty solution matrix
516 0         0 $mat = Math::Matrix->new([]);
517              
518             # compute initial transform values
519 0         0 ($jac, $int) = jacobian($self, $out, $hash);
520              
521             # solution loop
522 0         0 for (1 .. $max) {
523            
524             # for each input
525 0         0 for my $i (0 .. $#si) {
526            
527             # for each output
528 0         0 for my $j (0 .. $#so) {
529            
530             # copy Jacobian value to solution matrix
531 0         0 $mat->[$i][$j] = $jac->[$si[$i]][$so[$j]];
532            
533             }
534            
535             # save residual value to solution matrix
536 0         0 $mat->[$i][$#si + 1] = $in->[$si[$i]] - $int->[$si[$i]];
537            
538             }
539            
540             # solve for delta values
541 0         0 $delta = $mat->solve;
542            
543             # for each output value
544 0         0 for my $i (0 .. $#so) {
545            
546             # add delta (limited using hyperbolic tangent)
547 0         0 $out->[$so[$i]] += POSIX::tanh($delta->[$i][0]/$dlim) * $dlim;
548            
549             }
550            
551             # compute updated transform values
552 0         0 ($jac, $int) = jacobian($self, $out, $hash);
553            
554             # initialize error accumulator
555 0         0 $accum = 0;
556            
557             # for each input
558 0         0 for my $i (0 .. $#si) {
559            
560             # accumulate delta squared
561 0         0 $accum += ($in->[$si[$i]] - $int->[$si[$i]])**2;
562            
563             }
564            
565             # compute RMS error
566 0         0 $error = sqrt($accum/@si);
567            
568             # if error less than limit
569 0 0       0 last if ($error < $elim);
570            
571             }
572              
573             # update input vector with final values
574 0         0 @{$in} = @{$int};
  0         0  
  0         0  
575              
576             # return
577 0         0 return($error);
578              
579             }
580              
581             # compute Jacobian matrix
582             # transform mask enables/disables individual tag elements
583             # parameters: (input_vector, [hash])
584             # returns: (Jacobian_matrix, [output_vector])
585             sub jacobian {
586              
587             # get parameters
588 0     0 0 0 my ($self, $data, $hash) = @_;
589              
590             # local variables
591 0         0 my ($jac, $jaci);
592              
593             # for each processing element
594 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
595            
596             # if processing element defined, and transform mask bit set
597 0 0 0     0 if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
598            
599             # compute Jacobian matrix and transform data
600 0         0 ($jaci, $data) = $self->[1][$i]->jacobian($data, $hash);
601            
602             # multiply Jacobian matrices
603 0 0       0 $jac = defined($jac) ? $jaci * $jac : $jaci;
604            
605             }
606            
607             }
608              
609             # if Jacobian matrix is undefined, use identity matrix
610 0 0       0 $jac = Math::Matrix->diagonal((1) x @{$data}) if (! defined($jac));
  0         0  
611              
612             # if output values wanted
613 0 0       0 if (wantarray) {
614            
615             # return Jacobian and output values
616 0         0 return($jac, $data);
617            
618             } else {
619            
620             # return Jacobian only
621 0         0 return($jac);
622            
623             }
624            
625             }
626              
627             # get/set PCS encoding
628             # for use with ICC::Support::PCS objects
629             # parameters: ([PCS_encoding])
630             # returns: (PCS_encoding)
631             sub pcs {
632              
633             # get parameters
634 0     0 0 0 my ($self, $pcs) = @_;
635              
636             # if PCS parameter is supplied
637 0 0       0 if (defined($pcs)) {
638            
639             # if a valid PCS encoding
640 0 0       0 if ($pcs == 0) {
641            
642             # copy to tag header hash
643 0         0 $self->[0]{'pcs_encoding'} = $pcs;
644            
645             # return PCS encoding
646 0         0 return($pcs);
647            
648             } else {
649            
650             # error
651 0         0 croak('invalid PCS encoding');
652            
653             }
654            
655             } else {
656            
657             # if tag PCS is L*a*b*
658 0 0 0     0 if (($self->[0]{'input_cs'} eq 'Lab ' && $self->[0]{'output_cs'} ne 'XYZ ') || ($self->[0]{'input_cs'} ne 'XYZ ' && $self->[0]{'output_cs'} eq 'Lab ')) {
    0 0        
      0        
      0        
      0        
      0        
659            
660             # return PCS type (8-bit CIELab)
661 0         0 return(0);
662            
663             # if tag PCS is XYZ
664             } elsif (($self->[0]{'input_cs'} eq 'XYZ ' && $self->[0]{'output_cs'} ne 'Lab ') || ($self->[0]{'input_cs'} ne 'Lab ' && $self->[0]{'output_cs'} eq 'XYZ ')) {
665            
666             # error
667 0         0 croak('invalid PCS encoding');
668            
669             } else {
670            
671             # error
672 0         0 croak('can\'t determine PCS encoding');
673            
674             }
675            
676             }
677            
678             }
679              
680             # get/set white point
681             # parameters: ([white_point])
682             # returns: (white_point)
683             sub wtpt {
684              
685             # get parameters
686 0     0 0 0 my ($self, $wtpt) = @_;
687              
688             # if white point parameter is supplied
689 0 0       0 if (defined($wtpt)) {
690            
691             # if an array of three scalars
692 0 0 0     0 if (@{$wtpt} == 3 && 3 == grep {! ref()} @{$wtpt}) {
  0         0  
  0         0  
  0         0  
693            
694             # copy to tag header hash
695 0         0 $self->[0]{'wtpt'} = $wtpt;
696            
697             # return white point
698 0         0 return($wtpt);
699            
700             } else {
701            
702             # error
703 0         0 croak('invalid white point');
704            
705             }
706            
707             } else {
708            
709             # if white point is defined in tag header
710 0 0       0 if (defined($self->[0]{'wtpt'})) {
711            
712             # return return white point
713 0         0 return($self->[0]{'wtpt'});
714            
715             } else {
716            
717             # error
718 0         0 croak('can\'t determine white point');
719            
720             }
721            
722             }
723            
724             }
725              
726             # print object contents to string
727             # format is an array structure
728             # parameter: ([format])
729             # returns: (string)
730             sub sdump {
731              
732             # get parameters
733 0     0 1 0 my ($self, $p) = @_;
734              
735             # local variables
736 0         0 my ($element, $fmt, $s, $pt, $st);
737              
738             # resolve parameter to an array reference
739 0 0       0 $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
740              
741             # get format string
742 0 0 0     0 $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 's';
743              
744             # set string to object ID
745 0         0 $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
746              
747             # if format contains 'o'
748 0 0       0 if ($fmt =~ m/s/) {
749            
750             # get default parameter
751 0         0 $pt = $p->[-1];
752            
753             # for each processing element
754 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
755            
756             # get element reference
757 0         0 $element = $self->[1][$i];
758            
759             # if processing element is undefined
760 0 0       0 if (! defined($element)) {
    0          
    0          
761            
762             # append message
763 0         0 $s .= "\tprocessing element is undefined\n";
764            
765             # if processing element is not a blessed object
766             } elsif (! Scalar::Util::blessed($element)) {
767            
768             # append message
769 0         0 $s .= "\tprocessing element is not a blessed object\n";
770            
771             # if processing element has an 'sdump' method
772             } elsif ($element->can('sdump')) {
773            
774             # get 'sdump' string
775 0 0       0 $st = $element->sdump(defined($p->[$i + 1]) ? $p->[$i + 1] : $pt);
776            
777             # prepend tabs to each line
778 0         0 $st =~ s/^/\t/mg;
779            
780             # append 'sdump' string
781 0         0 $s .= $st;
782            
783             # processing element is object without an 'sdump' method
784             } else {
785            
786             # append object info
787 0         0 $s .= sprintf("\t'%s' object, (0x%x)\n", ref($element), $element);
788            
789             }
790            
791             }
792            
793             }
794              
795             # return
796 0         0 return($s);
797              
798             }
799              
800             # transform list
801             # parameters: (object_reference, list, [hash])
802             # returns: (list)
803             sub _trans0 {
804              
805             # local variables
806 0     0   0 my ($self, $hash, $data);
807              
808             # get object reference
809 0         0 $self = shift();
810              
811             # get optional hash
812 0 0       0 $hash = pop() if (ref($_[-1]) eq 'HASH');
813              
814             # process data
815 0         0 $data = _trans1($self, [@_], $hash);
816              
817             # return list
818 0         0 return(@{$data});
  0         0  
819              
820             }
821              
822             # transform vector
823             # parameters: (object_reference, vector, [hash])
824             # returns: (vector)
825             sub _trans1 {
826              
827             # get parameters
828 0     0   0 my ($self, $data, $hash) = @_;
829              
830             # for each processing element
831 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
832            
833             # if processing element defined, and transform mask bit set
834 0 0 0     0 if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
835            
836             # transform data
837 0         0 $data = $self->[1][$i]->_trans1($data, $hash);
838            
839             # clip output values if clipping mask bit set
840 0 0       0 ICC::Shared::clip_struct($data) if ($self->[3] & 0x01 << $i);
841            
842             }
843            
844             }
845            
846             # return
847 0         0 return($data);
848            
849             }
850              
851             # transform matrix (2-D array -or- Math::Matrix object)
852             # parameters: (object_reference, matrix, [hash])
853             # returns: (matrix)
854             sub _trans2 {
855              
856             # get parameters
857 0     0   0 my ($self, $data, $hash) = @_;
858              
859             # for each processing element
860 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
861            
862             # if processing element defined, and transform mask bit set
863 0 0 0     0 if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
864            
865             # transform data
866 0         0 $data = $self->[1][$i]->_trans2($data, $hash);
867            
868             # clip output values if clipping mask bit set
869 0 0       0 ICC::Shared::clip_struct($data) if ($self->[3] & 0x01 << $i);
870            
871             }
872            
873             }
874              
875             # return
876 0         0 return($data);
877              
878             }
879              
880             # transform structure
881             # parameters: (object_reference, structure, [hash])
882             # returns: (structure)
883             sub _trans3 {
884              
885             # get parameters
886 0     0   0 my ($self, $in, $hash) = @_;
887              
888             # transform the array structure
889 0         0 _crawl($self, $in, my $out = [], $hash);
890              
891             # return
892 0         0 return($out);
893              
894             }
895              
896             # recursive transform
897             # array structure is traversed until scalar arrays are found and transformed
898             # parameters: (ref_to_object, input_array_reference, output_array_reference, hash)
899             sub _crawl {
900              
901             # get parameters
902 0     0   0 my ($self, $in, $out, $hash) = @_;
903              
904             # if input is a vector (reference to a scalar array)
905 0 0       0 if (@{$in} == grep {! ref()} @{$in}) {
  0         0  
  0         0  
  0         0  
906            
907             # transform input vector and copy to output
908 0         0 @{$out} = @{_trans1($self, $in, $hash)};
  0         0  
  0         0  
909            
910             } else {
911            
912             # for each input element
913 0         0 for my $i (0 .. $#{$in}) {
  0         0  
914            
915             # if an array reference
916 0 0       0 if (ref($in->[$i]) eq 'ARRAY') {
917            
918             # transform next level
919 0         0 _crawl($self, $in->[$i], $out->[$i] = [], $hash);
920            
921             } else {
922            
923             # error
924 0         0 croak('invalid transform input');
925            
926             }
927            
928             }
929            
930             }
931            
932             }
933              
934             # check object structure
935             # parameter: (ref_to_object)
936             # returns: (number_input_channels, number_output_channels, grid_size)
937             sub _check {
938              
939             # get object reference
940 1     1   3 my $self = shift();
941              
942             # local variables
943 1         13 my (@class, $ci, $co, $gsa);
944              
945             # make object class array
946 1         6 @class = qw(ICC::Profile::matf ICC::Profile::cvst ICC::Profile::clut ICC::Profile::cvst);
947              
948             # verify number of processing elements
949 1 50       1 ($#{$self->[1]} == 3) or croak('\'mft1\' object has wrong number of processing elements');
  1         6  
950              
951             # for each processing element
952 1         4 for my $i (0 .. 3) {
953            
954             # if element is defined (matrix may be undefined)
955 4 50       9 if (defined($self->[1][$i])) {
    0          
956            
957             # verify element has correct class
958 4 50       13 (ref($self->[1][$i]) eq $class[$i]) or croak("'mft1' processing element $i is wrong object type");
959            
960             # if not matrix processing element
961 4 100       7 if ($i) {
962            
963             # if number of input channels is undefined
964 3 100       7 if (! defined($ci)) {
965            
966             # set number of input channels
967 1         4 $ci = $self->[1][$i]->cin();
968            
969             }
970            
971             # if number of output channels is defined
972 3 100       7 if (defined($co)) {
973            
974             # verify input channels of this element match output channels of previous element
975 2 50       8 ($self->[1][$i]->cin() == $co) or croak("'mft1' processing element $i has wrong number of input channels");
976            
977             }
978            
979             # set number of output channels
980 3         8 $co = $self->[1][$i]->cout();
981            
982             } else {
983            
984             # verify matrix has 3 input and 3 output channels
985 1 50 33     7 ($self->[1][0]->cin() == 3 && $self->[1][0]->cout() == 3) or croak("'mft1' matrix processing element wrong size");
986            
987             # warn if matrix has non-zero offset values
988 1 50 33     15 (defined($self->[1][0]->offset) && grep {$_} @{$self->[1][0]->offset}) && carp("'mft1' matrix processing element has non-zero offset values")
  0         0  
  1         3  
989            
990             }
991            
992             # if not matrix processing element
993             } elsif ($i) {
994            
995             # error
996 0         0 croak("'mft1' processing element $i is missing");
997            
998             }
999            
1000             }
1001              
1002             # get 'clut' grid size array
1003 1         6 $gsa = $self->[1][2]->gsa();
1004              
1005             # verify 'clut' grid points are same for each channel
1006 1 50       3 (@{$gsa} == grep {$_ == $gsa->[0]} @{$gsa}) or croak("'mft1' clut processing element grid points vary by channel");
  1         3  
  3         9  
  1         2  
1007              
1008             # verify input 'cvst' elements are 'curv' objects
1009 1 50       2 (@{$self->[1][1]->array} == map {UNIVERSAL::isa($_, 'ICC::Profile::curv')} @{$self->[1][1]->array}) or croak("'mft1' input processing element has wrong curve type");
  1         10  
  3         14  
  1         3  
1010              
1011             # verify input 'curv' objects have 256 entries
1012 1 50       1 (@{$self->[1][1]->array} == grep {@{$_->array} == 256} @{$self->[1][1]->array}) or croak("'mft1' input processing element has wrong number of curve entries");
  1         4  
  3         4  
  3         8  
  1         3  
1013              
1014             # verify output 'cvst' elements are 'curv' objects
1015 1 50       10 (@{$self->[1][3]->array} == map {UNIVERSAL::isa($_, 'ICC::Profile::curv')} @{$self->[1][3]->array}) or croak("'mft1' output processing element has wrong curve type");
  1         6  
  1         5  
  1         3  
1016              
1017             # verify output 'curv' objects have 256 entries
1018 1 50       2 (@{$self->[1][3]->array} == grep {@{$_->array} == 256} @{$self->[1][3]->array}) or croak("'mft1' output processing element has wrong number of curve entries");
  1         3  
  1         2  
  1         3  
  1         4  
1019              
1020             # return
1021 1         5 return($ci, $co, $gsa->[0]);
1022              
1023             }
1024              
1025             # make new mft1 tag from attribute hash
1026             # hash may contain pointers to matrix, input curves, CLUT, or output curves
1027             # keys are: ('matrix', 'input', 'clut', 'output')
1028             # tag elements not specified in the hash are left empty
1029             # parameters: (ref_to_object, ref_to_attribute_hash)
1030             sub _new_from_hash {
1031              
1032             # get parameters
1033 0     0   0 my ($self, $hash) = @_;
1034              
1035             # set attribute list (key => [reference_type, array_index])
1036 0         0 my %list = ('matrix' => ['ICC::Profile::matf', 0], 'input' => ['ICC::Profile::cvst', 1], 'clut' => ['ICC::Profile::clut', 2], 'output' => ['ICC::Profile::cvst', 3]);
1037              
1038             # for each attribute
1039 0         0 for my $attr (keys(%{$hash})) {
  0         0  
1040            
1041             # if value defined
1042 0 0       0 if (defined($hash->{$attr})) {
1043            
1044             # if correct reference type
1045 0 0       0 if (ref($hash->{$attr}) eq $list{$attr}[0]) {
1046            
1047             # set tag element
1048 0         0 $self->[1][$list{$attr}[1]] = $hash->{$attr};
1049            
1050             # set transform mask bit
1051 0         0 $self->[2] |= (0x01 << $list{$attr}[1]);
1052            
1053             } else {
1054            
1055             # error
1056 0         0 croak("wrong object type for $attr key");
1057            
1058             }
1059            
1060             }
1061            
1062             }
1063            
1064             }
1065              
1066             # read mft1 tag from ICC profile
1067             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1068             sub _readICCmft1 {
1069              
1070             # get parameters
1071 1     1   5 my ($self, $parent, $fh, $tag) = @_;
1072              
1073             # local variables
1074 1         2 my ($buf, @mft, $input, $gsa, $output);
1075              
1076             # set tag signature
1077 1         3 $self->[0]{'signature'} = $tag->[0];
1078              
1079             # if 'A2Bx' tag
1080 1 50       10 if ($tag->[0] =~ m|^A2B[0-2]$|) {
    50          
    50          
    50          
1081            
1082             # set input colorspace
1083 0         0 $self->[0]{'input_cs'} = $parent->[1][4];
1084            
1085             # set output colorspace
1086 0         0 $self->[0]{'output_cs'} = $parent->[1][5];
1087            
1088             # if 'B2Ax' tag
1089             } elsif ($tag->[0] =~ m|^B2A[0-2]$|) {
1090            
1091             # set input colorspace
1092 0         0 $self->[0]{'input_cs'} = $parent->[1][5];
1093            
1094             # set output colorspace
1095 0         0 $self->[0]{'output_cs'} = $parent->[1][4];
1096            
1097             # if 'prex' tag
1098             } elsif ($tag->[0] =~ m|^pre[0-2]$|) {
1099            
1100             # set input colorspace
1101 0         0 $self->[0]{'input_cs'} = $parent->[1][5];
1102            
1103             # set output colorspace
1104 0         0 $self->[0]{'output_cs'} = $parent->[1][5];
1105            
1106             # if 'gamt' tag
1107             } elsif ($tag->[0] eq 'gamt') {
1108            
1109             # set input colorspace
1110 1         3 $self->[0]{'input_cs'} = $parent->[1][5];
1111            
1112             # set output colorspace
1113 1         2 $self->[0]{'output_cs'} = 'gamt';
1114            
1115             }
1116              
1117             # seek start of tag
1118 1         12 seek($fh, $tag->[1], 0);
1119              
1120             # read tag header
1121 1         13 read($fh, $buf, 12);
1122              
1123             # unpack header
1124 1         6 @mft = unpack('a4 x4 C3', $buf);
1125              
1126             # verify tag signature
1127 1 50       5 ($mft[0] eq 'mft1') or croak('wrong tag type');
1128              
1129             # verify number input channels (1 to 15)
1130 1 50 33     8 ($mft[1] > 0 && $mft[1] < 16) or croak('unsupported number of input channels');
1131              
1132             # verify number output channels (1 to 15)
1133 1 50 33     8 ($mft[2] > 0 && $mft[2] < 16) or croak('unsupported number of output channels');
1134              
1135             # make 'matf' object for matrix
1136 1         7 $self->[1][0] = ICC::Profile::matf->new();
1137              
1138             # read matrix
1139 1         5 $self->[1][0]->_read_matf($fh, 3, 3, 0, 2);
1140              
1141             # set signature
1142 1         3 $self->[1][0][0]{'signature'} = 'mft1';
1143              
1144             # for each input curve
1145 1         4 for my $i (0 .. $mft[1] - 1) {
1146            
1147             # read curve values
1148 3         9 read($fh, $buf, 256);
1149            
1150             # make 'curv' object
1151 3         25 $input->[$i] = ICC::Profile::curv->new([map {$_/255} unpack('C*', $buf)]);
  768         894  
1152            
1153             }
1154              
1155             # make 'cvst' object for input curves
1156 1         8 $self->[1][1] = ICC::Profile::cvst->new($input);
1157              
1158             # set signature
1159 1         5 $self->[1][1][0]{'signature'} = 'mft1';
1160              
1161             # make gsa array
1162 1         5 $gsa = [($mft[3]) x $mft[1]];
1163              
1164             # make 'clut' object for CLUT
1165 1         7 $self->[1][2] = ICC::Profile::clut->new();
1166              
1167             # read 'clut' data
1168 1         6 $self->[1][2]->_read_clut($fh, $mft[2], $gsa, 1);
1169            
1170             # save 'clut' gsa
1171 1         5 $self->[1][2][2] = $gsa;
1172              
1173             # save CLUT byte width
1174 1         12 $self->[1][2][0]{'clut_bytes'} = 1;
1175              
1176             # set number of input channels
1177 1         5 $self->[1][2][0]{'input_channels'} = $mft[1];
1178              
1179             # set number of output channels
1180 1         3 $self->[1][2][0]{'output_channels'} = $mft[2];
1181              
1182             # set signature
1183 1         5 $self->[1][2][0]{'signature'} = 'mft1';
1184              
1185             # for each output curve
1186 1         5 for my $i (0 .. $mft[2] - 1) {
1187            
1188             # read curve values
1189 1         5 read($fh, $buf, 256);
1190            
1191             # make 'curv' object
1192 1         27 $output->[$i] = ICC::Profile::curv->new([map {$_/255} unpack('C*', $buf)]);
  256         319  
1193            
1194             }
1195              
1196             # make 'cvst' object for output curves
1197 1         10 $self->[1][3] = ICC::Profile::cvst->new($output);
1198              
1199             # set signature
1200 1         5 $self->[1][3][0]{'signature'} = 'mft1';
1201              
1202             # set transform mask (enabling matrix if input colorspace is XYZ)
1203 1 50       11 $self->[2] = $self->[0]{'input_cs'} eq 'XYZ ' ? 0x0F : 0x0E;
1204              
1205             }
1206              
1207             # write mft1 tag to ICC profile
1208             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1209             sub _writeICCmft1 {
1210              
1211             # get parameters
1212 1     1   4 my ($self, $parent, $fh, $tag) = @_;
1213              
1214             # local variables
1215 1         4 my (@mft, $offset, $mat, $bytes, $size);
1216 1         0 my (@mat);
1217              
1218             # set tag type
1219 1         3 $mft[0] = 'mft1';
1220              
1221             # check object structure
1222 1         4 @mft[1, 2, 3] = _check($self);
1223              
1224             # if 'matf' object is defined
1225 1 50       4 if (defined($self->[1][0])) {
1226            
1227             # get matrix
1228 1         7 $mat = $self->[1][0]->matrix();
1229            
1230             # copy matrix values
1231 1         3 @mft[4 .. 12] = ICC::Shared::v2s15f16(@{$mat->[0]}, @{$mat->[1]}, @{$mat->[2]});
  1         3  
  1         3  
  1         6  
1232            
1233             } else {
1234            
1235             # copy identity matrix
1236 0         0 @mft[4 .. 12] = (65536, 0, 0, 0, 65536, 0, 0, 0, 65536);
1237            
1238             }
1239              
1240             # seek start of tag
1241 1         12 seek($fh, $tag->[1], 0);
1242              
1243             # write mft tag header
1244 1         32 print $fh pack('a4 x4 C3 x N9', @mft);
1245              
1246             # for each input channel
1247 1         6 for my $i (0 .. $mft[1] - 1) {
1248            
1249             # write table values
1250 3 50       8 print $fh pack('C*', map {$_ < 0 ? 0 : ($_ > 1 ? 255 : $_ * 255 + 0.5)} @{$self->[1][1]->array->[$i]->array});
  768 50       1823  
  3         12  
1251            
1252             }
1253              
1254             # write clut
1255 1         7 $self->[1][2]->_write_clut($fh, $self->[1][2]->gsa(), 1);
1256              
1257             # for each output channel
1258 1         8 for my $i (0 .. $mft[2] - 1) {
1259            
1260             # write table values
1261 1 50       2 print $fh pack('C*', map {$_ < 0 ? 0 : ($_ > 1 ? 255 : $_ * 255 + 0.5)} @{$self->[1][3]->array->[$i]->array});
  256 50       477  
  1         13  
1262            
1263             }
1264            
1265             }
1266              
1267             1;