File Coverage

lib/ICC/Profile/mft2.pm
Criterion Covered Total %
statement 147 371 39.6
branch 32 184 17.3
condition 6 87 6.9
subroutine 12 34 35.2
pod 2 20 10.0
total 199 696 28.5


line stmt bran cond sub pod time code
1             package ICC::Profile::mft2;
2              
3 2     2   89630 use strict;
  2         6  
  2         54  
4 2     2   12 use Carp;
  2         4  
  2         135  
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         3  
  2         13  
14              
15             # inherit from Shared
16 2     2   263 use parent qw(ICC::Shared);
  2         4  
  2         25  
17              
18             # use POSIX math
19 2     2   138 use POSIX ();
  2         3  
  2         8514  
20              
21             # create new mft2 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 1090 my $class = shift();
31              
32             # create empty mft2 object
33 1         3 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       5 if (@_) {
42            
43             # if one parameter, a hash reference
44 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
45            
46             # make new mft2 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         3 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 mft2 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 666 my $class = shift();
331              
332             # create empty mft2 object
333 1         4 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       4 (@_ == 3) or croak('wrong number of parameters');
342              
343             # read mft2 data from profile
344 1         5 _readICCmft2($self, @_);
345              
346             # bless object
347 1         4 bless($self, $class);
348              
349             # return object reference
350 1         22 return($self);
351              
352             }
353              
354             # writes mft2 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 1244 (@_ == 4) or croak('wrong number of parameters');
360              
361             # write mft2 data to profile
362 1         6 goto &_writeICCmft2;
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 771 my ($self) = @_;
372              
373             # set header size
374 3         5 my $size = 52;
375              
376             # add size of input tables (assumes 'curv' objects)
377 3         22 $size += 2 * $self->[1][1]->cin() * @{$self->[1][1]->array->[0]->array()};
  3         13  
378              
379             # add size of clut
380 3         17 $size += $self->[1][2]->_clut_size(2);
381              
382             # add size of output tables (assumes 'curv' objects)
383 3         9 $size += 2 * $self->[1][3]->cin() * @{$self->[1][3]->array->[0]->array()};
  3         9  
384              
385             # return size
386 3         1920 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 (grep {$pcs == $_} (1, 2, 7)) {
  0         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 PCS is defined in tag header
658 0 0       0 if (defined($self->[0]{'pcs_encoding'})) {
659            
660             # return PCS encoding
661 0         0 return($self->[0]{'pcs_encoding'});
662            
663             } else {
664            
665             # if tag PCS is L*a*b*
666 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        
667            
668             # return PCS type (16-bit ICC legacy, but could be Monaco)
669 0         0 return(1);
670            
671             # if tag PCS is XYZ
672             } 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 ')) {
673            
674             # return PCS type (XYZ)
675 0         0 return(7);
676            
677             } else {
678            
679             # error
680 0         0 croak('can\'t determine PCS encoding');
681            
682             }
683            
684             }
685            
686             }
687            
688             }
689              
690             # get/set white point
691             # parameters: ([white_point])
692             # returns: (white_point)
693             sub wtpt {
694              
695             # get parameters
696 0     0 0 0 my ($self, $wtpt) = @_;
697              
698             # if white point parameter is supplied
699 0 0       0 if (defined($wtpt)) {
700            
701             # if an array of three scalars
702 0 0 0     0 if (@{$wtpt} == 3 && 3 == grep {! ref()} @{$wtpt}) {
  0         0  
  0         0  
  0         0  
703            
704             # copy to tag header hash
705 0         0 $self->[0]{'wtpt'} = $wtpt;
706            
707             # return white point
708 0         0 return($wtpt);
709            
710             } else {
711            
712             # error
713 0         0 croak('invalid white point');
714            
715             }
716            
717             } else {
718            
719             # if white point is defined in tag header
720 0 0       0 if (defined($self->[0]{'wtpt'})) {
721            
722             # return return white point
723 0         0 return($self->[0]{'wtpt'});
724            
725             } else {
726            
727             # error
728 0         0 croak('can\'t determine white point');
729            
730             }
731            
732             }
733            
734             }
735              
736             # print object contents to string
737             # format is an array structure
738             # parameter: ([format])
739             # returns: (string)
740             sub sdump {
741              
742             # get parameters
743 0     0 1 0 my ($self, $p) = @_;
744              
745             # local variables
746 0         0 my ($element, $fmt, $s, $pt, $st);
747              
748             # resolve parameter to an array reference
749 0 0       0 $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
750              
751             # get format string
752 0 0 0     0 $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 's';
753              
754             # set string to object ID
755 0         0 $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
756              
757             # if format contains 'o'
758 0 0       0 if ($fmt =~ m/s/) {
759            
760             # get default parameter
761 0         0 $pt = $p->[-1];
762            
763             # for each processing element
764 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
765            
766             # get element reference
767 0         0 $element = $self->[1][$i];
768            
769             # if processing element is undefined
770 0 0       0 if (! defined($element)) {
    0          
    0          
771            
772             # append message
773 0         0 $s .= "\tprocessing element is undefined\n";
774            
775             # if processing element is not a blessed object
776             } elsif (! Scalar::Util::blessed($element)) {
777            
778             # append message
779 0         0 $s .= "\tprocessing element is not a blessed object\n";
780            
781             # if processing element has an 'sdump' method
782             } elsif ($element->can('sdump')) {
783            
784             # get 'sdump' string
785 0 0       0 $st = $element->sdump(defined($p->[$i + 1]) ? $p->[$i + 1] : $pt);
786            
787             # prepend tabs to each line
788 0         0 $st =~ s/^/\t/mg;
789            
790             # append 'sdump' string
791 0         0 $s .= $st;
792            
793             # processing element is object without an 'sdump' method
794             } else {
795            
796             # append object info
797 0         0 $s .= sprintf("\t'%s' object, (0x%x)\n", ref($element), $element);
798            
799             }
800            
801             }
802            
803             }
804              
805             # return
806 0         0 return($s);
807              
808             }
809              
810             # transform list
811             # parameters: (object_reference, list, [hash])
812             # returns: (list)
813             sub _trans0 {
814              
815             # local variables
816 0     0   0 my ($self, $hash, $data);
817              
818             # get object reference
819 0         0 $self = shift();
820              
821             # get optional hash
822 0 0       0 $hash = pop() if (ref($_[-1]) eq 'HASH');
823              
824             # process data
825 0         0 $data = _trans1($self, [@_], $hash);
826              
827             # return list
828 0         0 return(@{$data});
  0         0  
829              
830             }
831              
832             # transform vector
833             # parameters: (object_reference, vector, [hash])
834             # returns: (vector)
835             sub _trans1 {
836              
837             # get parameters
838 0     0   0 my ($self, $data, $hash) = @_;
839              
840             # for each processing element
841 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
842            
843             # if processing element defined, and transform mask bit set
844 0 0 0     0 if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
845            
846             # transform data
847 0         0 $data = $self->[1][$i]->_trans1($data, $hash);
848            
849             # clip output values if clipping mask bit set
850 0 0       0 ICC::Shared::clip_struct($data) if ($self->[3] & 0x01 << $i);
851            
852             }
853            
854             }
855            
856             # return
857 0         0 return($data);
858            
859             }
860              
861             # transform matrix (2-D array -or- Math::Matrix object)
862             # parameters: (object_reference, matrix, [hash])
863             # returns: (matrix)
864             sub _trans2 {
865              
866             # get parameters
867 0     0   0 my ($self, $data, $hash) = @_;
868              
869             # for each processing element
870 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
871            
872             # if processing element defined, and transform mask bit set
873 0 0 0     0 if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
874            
875             # transform data
876 0         0 $data = $self->[1][$i]->_trans2($data, $hash);
877            
878             # clip output values if clipping mask bit set
879 0 0       0 ICC::Shared::clip_struct($data) if ($self->[3] & 0x01 << $i);
880            
881             }
882            
883             }
884              
885             # return
886 0         0 return($data);
887              
888             }
889              
890             # transform structure
891             # parameters: (object_reference, structure, [hash])
892             # returns: (structure)
893             sub _trans3 {
894              
895             # get parameters
896 0     0   0 my ($self, $in, $hash) = @_;
897              
898             # transform the array structure
899 0         0 _crawl($self, $in, my $out = [], $hash);
900              
901             # return
902 0         0 return($out);
903              
904             }
905              
906             # recursive transform
907             # array structure is traversed until scalar arrays are found and transformed
908             # parameters: (ref_to_object, input_array_reference, output_array_reference, hash)
909             sub _crawl {
910              
911             # get parameters
912 0     0   0 my ($self, $in, $out, $hash) = @_;
913              
914             # if input is a vector (reference to a scalar array)
915 0 0       0 if (@{$in} == grep {! ref()} @{$in}) {
  0         0  
  0         0  
  0         0  
916            
917             # transform input vector and copy to output
918 0         0 @{$out} = @{_trans1($self, $in, $hash)};
  0         0  
  0         0  
919            
920             } else {
921            
922             # for each input element
923 0         0 for my $i (0 .. $#{$in}) {
  0         0  
924            
925             # if an array reference
926 0 0       0 if (ref($in->[$i]) eq 'ARRAY') {
927            
928             # transform next level
929 0         0 _crawl($self, $in->[$i], $out->[$i] = [], $hash);
930            
931             } else {
932            
933             # error
934 0         0 croak('invalid transform input');
935            
936             }
937            
938             }
939            
940             }
941            
942             }
943              
944             # check object structure
945             # parameter: (ref_to_object)
946             # returns: (number_input_channels, number_output_channels, grid_size)
947             sub _check {
948              
949             # get object reference
950 1     1   1 my $self = shift();
951              
952             # local variables
953 1         3 my (@class, $ci, $co, $gsa, $n);
954              
955             # make object class array
956 1         3 @class = qw(ICC::Profile::matf ICC::Profile::cvst ICC::Profile::clut ICC::Profile::cvst);
957              
958             # verify number of processing elements
959 1 50       2 ($#{$self->[1]} == 3) or croak('\'mft2\' object has wrong number of processing elements');
  1         4  
960              
961             # for each processing element
962 1         4 for my $i (0 .. 3) {
963            
964             # if element is defined (matrix may be undefined)
965 4 50       10 if (defined($self->[1][$i])) {
    0          
966            
967             # verify element has correct class
968 4 50       11 (ref($self->[1][$i]) eq $class[$i]) or croak("'mft2' processing element $i is wrong object type");
969            
970             # if not matrix processing element
971 4 100       6 if ($i) {
972            
973             # if number of input channels is undefined
974 3 100       7 if (! defined($ci)) {
975            
976             # set number of input channels
977 1         4 $ci = $self->[1][$i]->cin();
978            
979             }
980            
981             # if number of output channels is defined
982 3 100       6 if (defined($co)) {
983            
984             # verify input channels of this element match output channels of previous element
985 2 50       10 ($self->[1][$i]->cin() == $co) or croak("'mft2' processing element $i has wrong number of input channels");
986            
987             }
988            
989             # set number of output channels
990 3         9 $co = $self->[1][$i]->cout();
991            
992             } else {
993            
994             # verify matrix has 3 input and 3 output channels
995 1 50 33     6 ($self->[1][0]->cin() == 3 && $self->[1][0]->cout() == 3) or croak("'mft2' matrix processing element wrong size");
996            
997             # warn if matrix has non-zero offset values
998 1 50 33     5 (defined($self->[1][0]->offset) && grep {$_} @{$self->[1][0]->offset}) && carp("'mft2' matrix processing element has non-zero offset values")
  0         0  
  1         4  
999            
1000             }
1001            
1002             # if not matrix processing element
1003             } elsif ($i) {
1004            
1005             # error
1006 0         0 croak("'mft2' processing element $i is missing");
1007            
1008             }
1009            
1010             }
1011              
1012             # get 'clut' grid size array
1013 1         4 $gsa = $self->[1][2]->gsa();
1014              
1015             # verify 'clut' grid points are same for each channel
1016 1 50       2 (@{$gsa} == grep {$_ == $gsa->[0]} @{$gsa}) or croak("'mft2' clut processing element grid points vary by channel");
  1         2  
  4         10  
  1         2  
1017              
1018             # verify input 'cvst' elements are 'curv' objects
1019 1 50       2 (@{$self->[1][1]->array} == map {UNIVERSAL::isa($_, 'ICC::Profile::curv')} @{$self->[1][1]->array}) or croak("'mft2' input processing element has wrong curve type");
  1         4  
  4         13  
  1         3  
1020              
1021             # get number of entries from first input 'curv' object
1022 1         2 $n = @{$self->[1][1]->array->[0]->array};
  1         3  
1023              
1024             # verify number of entries is between 2 and 4096
1025 1 50 33     8 ($n >= 2 && $n <= 4096) or croak("'mft2' input processing element has wrong number of curve entries");
1026              
1027             # verify input 'curv' objects have same number of entries
1028 1 50       2 (@{$self->[1][1]->array} == grep {@{$_->array} == $n} @{$self->[1][1]->array}) or croak("'mft2' input processing elements have different numbers of curve entries");
  1         3  
  4         6  
  4         5  
  1         4  
1029              
1030             # verify output 'cvst' elements are 'curv' objects
1031 1 50       2 (@{$self->[1][3]->array} == map {UNIVERSAL::isa($_, 'ICC::Profile::curv')} @{$self->[1][3]->array}) or croak("'mft2' output processing element has wrong curve type");
  1         3  
  3         9  
  1         3  
1032              
1033             # get number of entries from first output 'curv' object
1034 1         2 $n = @{$self->[1][3]->array->[0]->array};
  1         4  
1035              
1036             # verify number of entries is between 2 and 4096
1037 1 50 33     5 ($n >= 2 && $n <= 4096) or croak("'mft2' output processing element has wrong number of curve entries");
1038              
1039             # verify output 'curv' objects have same number of entries
1040 1 50       2 (@{$self->[1][3]->array} == grep {@{$_->array} == $n} @{$self->[1][3]->array}) or croak("'mft2' output processing elements have different numbers of curve entries");
  1         3  
  3         4  
  3         6  
  1         4  
1041              
1042             # return
1043 1         5 return($ci, $co, $gsa->[0]);
1044              
1045             }
1046              
1047             # make new mft2 tag from attribute hash
1048             # hash may contain pointers to matrix, input curves, CLUT, or output curves
1049             # keys are: ('matrix', 'input', 'clut', 'output')
1050             # tag elements not specified in the hash are left empty
1051             # parameters: (ref_to_object, ref_to_attribute_hash)
1052             sub _new_from_hash {
1053              
1054             # get parameters
1055 0     0   0 my ($self, $hash) = @_;
1056              
1057             # set attribute list (key => [reference_type, array_index])
1058 0         0 my %list = ('matrix' => ['ICC::Profile::matf', 0], 'input' => ['ICC::Profile::cvst', 1], 'clut' => ['ICC::Profile::clut', 2], 'output' => ['ICC::Profile::cvst', 3]);
1059              
1060             # for each attribute
1061 0         0 for my $attr (keys(%{$hash})) {
  0         0  
1062            
1063             # if value defined
1064 0 0       0 if (defined($hash->{$attr})) {
1065            
1066             # if correct reference type
1067 0 0       0 if (ref($hash->{$attr}) eq $list{$attr}[0]) {
1068            
1069             # set tag element
1070 0         0 $self->[1][$list{$attr}[1]] = $hash->{$attr};
1071            
1072             # set transform mask bit
1073 0         0 $self->[2] |= (0x01 << $list{$attr}[1]);
1074            
1075             } else {
1076            
1077             # error
1078 0         0 croak("wrong object type for $attr key");
1079            
1080             }
1081            
1082             }
1083            
1084             }
1085            
1086             }
1087              
1088             # read mft2 tag from ICC profile
1089             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1090             sub _readICCmft2 {
1091              
1092             # get parameters
1093 1     1   3 my ($self, $parent, $fh, $tag) = @_;
1094              
1095             # local variables
1096 1         2 my ($buf, @mft, $input, $gsa, $output);
1097              
1098             # set tag signature
1099 1         3 $self->[0]{'signature'} = $tag->[0];
1100              
1101             # if 'A2Bx' tag
1102 1 50       7 if ($tag->[0] =~ m|^A2B[0-2]$|) {
    0          
    0          
    0          
1103            
1104             # set input colorspace
1105 1         3 $self->[0]{'input_cs'} = $parent->[1][4];
1106            
1107             # set output colorspace
1108 1         2 $self->[0]{'output_cs'} = $parent->[1][5];
1109            
1110             # if 'B2Ax' tag
1111             } elsif ($tag->[0] =~ m|^B2A[0-2]$|) {
1112            
1113             # set input colorspace
1114 0         0 $self->[0]{'input_cs'} = $parent->[1][5];
1115            
1116             # set output colorspace
1117 0         0 $self->[0]{'output_cs'} = $parent->[1][4];
1118            
1119             # if 'prex' tag
1120             } elsif ($tag->[0] =~ m|^pre[0-2]$|) {
1121            
1122             # set input colorspace
1123 0         0 $self->[0]{'input_cs'} = $parent->[1][5];
1124            
1125             # set output colorspace
1126 0         0 $self->[0]{'output_cs'} = $parent->[1][5];
1127            
1128             # if 'gamt' tag
1129             } elsif ($tag->[0] eq 'gamt') {
1130            
1131             # set input colorspace
1132 0         0 $self->[0]{'input_cs'} = $parent->[1][5];
1133            
1134             # set output colorspace
1135 0         0 $self->[0]{'output_cs'} = 'gamt';
1136            
1137             }
1138              
1139             # seek start of tag
1140 1         12 seek($fh, $tag->[1], 0);
1141              
1142             # read tag header
1143 1         11 read($fh, $buf, 12);
1144              
1145             # unpack header
1146 1         6 @mft = unpack('a4 x4 C3', $buf);
1147              
1148             # verify tag signature
1149 1 50       4 ($mft[0] eq 'mft2') or croak('wrong tag type');
1150              
1151             # verify number input channels (1 to 15)
1152 1 50 33     8 ($mft[1] > 0 && $mft[1] < 16) or croak('unsupported number of input channels');
1153              
1154             # verify number output channels (1 to 15)
1155 1 50 33     7 ($mft[2] > 0 && $mft[2] < 16) or croak('unsupported number of output channels');
1156              
1157             # make 'matf' object for matrix
1158 1         8 $self->[1][0] = ICC::Profile::matf->new();
1159              
1160             # read matrix
1161 1         5 $self->[1][0]->_read_matf($fh, 3, 3, 0, 2);
1162              
1163             # set signature
1164 1         3 $self->[1][0][0]{'signature'} = 'mft2';
1165              
1166             # read size of input and output tables
1167 1         3 read($fh, $buf, 4);
1168              
1169             # unpack values
1170 1         5 @mft[4, 5] = unpack('n2', $buf);
1171              
1172             # for each input curve
1173 1         3 for my $i (0 .. $mft[1] - 1) {
1174            
1175             # read curve values
1176 4         69 read($fh, $buf, 2 * $mft[4]);
1177            
1178             # make 'curv' object
1179 4         550 $input->[$i] = ICC::Profile::curv->new([map {$_/65535} unpack('n*', $buf)]);
  16384         18440  
1180            
1181             }
1182              
1183             # make 'cvst' object for input curves
1184 1         10 $self->[1][1] = ICC::Profile::cvst->new($input);
1185              
1186             # set signature
1187 1         6 $self->[1][1][0]{'signature'} = 'mft2';
1188              
1189             # make gsa array
1190 1         5 $gsa = [($mft[3]) x $mft[1]];
1191              
1192             # make 'clut' object for CLUT
1193 1         6 $self->[1][2] = ICC::Profile::clut->new();
1194              
1195             # read 'clut' data
1196 1         6 $self->[1][2]->_read_clut($fh, $mft[2], $gsa, 2);
1197            
1198             # save 'clut' gsa
1199 1         5 $self->[1][2][2] = $gsa;
1200              
1201             # save CLUT byte width
1202 1         4 $self->[1][2][0]{'clut_bytes'} = 2;
1203              
1204             # set number of input channels
1205 1         6 $self->[1][2][0]{'input_channels'} = $mft[1];
1206              
1207             # set number of output channels
1208 1         10 $self->[1][2][0]{'output_channels'} = $mft[2];
1209              
1210             # set signature
1211 1         6 $self->[1][2][0]{'signature'} = 'mft2';
1212              
1213             # for each output curve
1214 1         6 for my $i (0 .. $mft[2] - 1) {
1215            
1216             # read curve values
1217 3         43 read($fh, $buf, 2 * $mft[5]);
1218            
1219             # make 'curv' object
1220 3         444 $output->[$i] = ICC::Profile::curv->new([map {$_/65535} unpack('n*', $buf)]);
  12288         13848  
1221            
1222             }
1223              
1224             # make 'cvst' object for output curves
1225 1         14 $self->[1][3] = ICC::Profile::cvst->new($output);
1226              
1227             # set signature
1228 1         4 $self->[1][3][0]{'signature'} = 'mft2';
1229              
1230             # set transform mask (enabling matrix if input colorspace is XYZ)
1231 1 50       10 $self->[2] = $self->[0]{'input_cs'} eq 'XYZ ' ? 0x0F : 0x0E;
1232              
1233             }
1234              
1235             # write mft2 tag to ICC profile
1236             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1237             sub _writeICCmft2 {
1238              
1239             # get parameters
1240 1     1   4 my ($self, $parent, $fh, $tag) = @_;
1241              
1242             # local variables
1243 1         4 my (@mft, $offset, $mat, $bytes, $size);
1244 1         0 my (@mat);
1245              
1246             # set tag type
1247 1         3 $mft[0] = 'mft2';
1248              
1249             # check object structure
1250 1         5 @mft[1, 2, 3] = _check($self);
1251              
1252             # if 'matf' object is defined
1253 1 50       4 if (defined($self->[1][0])) {
1254            
1255             # get matrix
1256 1         5 $mat = $self->[1][0]->matrix();
1257            
1258             # copy matrix values
1259 1         2 @mft[4 .. 12] = ICC::Shared::v2s15f16(@{$mat->[0]}, @{$mat->[1]}, @{$mat->[2]});
  1         3  
  1         2  
  1         8  
1260              
1261            
1262             } else {
1263            
1264             # copy identity matrix
1265 0         0 @mft[4 .. 12] = (65536, 0, 0, 0, 65536, 0, 0, 0, 65536);
1266            
1267             }
1268              
1269             # set size of input tables
1270 1         2 $mft[13] = @{$self->[1][1]->array->[0]->array};
  1         3  
1271              
1272             # set size of output tables
1273 1         2 $mft[14] = @{$self->[1][3]->array->[0]->array};
  1         4  
1274              
1275             # seek start of tag
1276 1         12 seek($fh, $tag->[1], 0);
1277              
1278             # write mft tag header
1279 1         27 print $fh pack('a4 x4 C3 x N9 n2', @mft);
1280              
1281             # for each input channel
1282 1         6 for my $i (0 .. $mft[1] - 1) {
1283            
1284             # write table values
1285 4 50       12 print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @{$self->[1][1]->array->[$i]->array});
  16384 50       27869  
  4         17  
1286            
1287             }
1288              
1289             # write clut
1290 1         20 $self->[1][2]->_write_clut($fh, $self->[1][2]->gsa(), 2);
1291              
1292             # for each output channel
1293 1         9 for my $i (0 .. $mft[2] - 1) {
1294            
1295             # write table values
1296 3 50       10 print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @{$self->[1][3]->array->[$i]->array});
  12288 50       21148  
  3         32  
1297            
1298             }
1299            
1300             }
1301              
1302             1;