File Coverage

blib/lib/ICC/Profile/mft1.pm
Criterion Covered Total %
statement 130 351 37.0
branch 33 178 18.5
condition 4 81 4.9
subroutine 11 33 33.3
pod 2 20 10.0
total 180 663 27.1


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