File Coverage

blib/lib/ICC/Profile/mft2.pm
Criterion Covered Total %
statement 144 368 39.1
branch 32 184 17.3
condition 6 87 6.9
subroutine 11 33 33.3
pod 2 20 10.0
total 195 692 28.1


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