File Coverage

lib/ICC/Profile/mpet.pm
Criterion Covered Total %
statement 20 281 7.1
branch 1 126 0.7
condition 0 43 0.0
subroutine 6 29 20.6
pod 1 15 6.6
total 28 494 5.6


line stmt bran cond sub pod time code
1             package ICC::Profile::mpet;
2              
3 2     2   93279 use strict;
  2         4  
  2         55  
4 2     2   9 use Carp;
  2         4  
  2         153  
5              
6             our $VERSION = 0.51;
7              
8             # revised 2018-08-07
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 2     2   15 use lib 'lib';
  2         3  
  2         12  
14              
15             # inherit from Shared
16 2     2   242 use parent qw(ICC::Shared);
  2         4  
  2         10  
17              
18             # use POSIX math
19 2     2   127 use POSIX ();
  2         4  
  2         6475  
20              
21             # create new mpet object
22             # array contains processing element objects
23             # objects must have '_transform' and 'jacobian' methods
24             # parameters: ([array_ref])
25             # returns: (ref_to_object)
26             sub new {
27              
28             # get object class
29 1     1 0 1088 my $class = shift();
30              
31             # create empty mpet object
32 1         3 my $self = [
33             {}, # object header
34             [], # processing elements
35             0x00 # transform mask
36             ];
37              
38             # if there are parameters
39 1 50       4 if (@_) {
40            
41             # if one parameter, an array reference
42 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY') {
43            
44             # make new mpet tag
45 0         0 _new_from_array($self, @_);
46            
47             } else {
48            
49             # error
50 0         0 croak('parameter must be an array reference');
51            
52             }
53            
54             }
55              
56             # bless object
57 1         2 bless($self, $class);
58              
59             # return object reference
60 1         3 return($self);
61              
62             }
63              
64             # get/set reference to header hash
65             # parameters: ([ref_to_new_hash])
66             # returns: (ref_to_hash)
67             sub header {
68              
69             # get object reference
70 0     0 0   my $self = shift();
71              
72             # if there are parameters
73 0 0         if (@_) {
74            
75             # if one parameter, a hash reference
76 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
77            
78             # set header to new hash
79 0           $self->[0] = shift();
80            
81             } else {
82            
83             # error
84 0           croak('parameter must be a hash reference');
85            
86             }
87            
88             }
89              
90             # return reference
91 0           return($self->[0]);
92              
93             }
94              
95             # get/set processing element array reference
96             # parameters: ([ref_to_array])
97             # returns: (ref_to_array)
98             sub array {
99              
100             # get object reference
101 0     0 0   my $self = shift();
102              
103             # if parameter
104 0 0         if (@_) {
105            
106             # verify array reference
107 0 0         (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
108            
109             # for each processing element
110 0           for my $i (0 .. $#{$_[0]}) {
  0            
111            
112             # verify object has processing methods
113 0 0 0       ($_[0][$i]->can('_transform') && $_[0][$i]->can('jacobian')) or croak('processing element lacks \'transform\' or \'jacobian\' method');
114            
115             # add processing element
116 0           $self->[1][$i] = $_[0][$i];
117            
118             }
119            
120             }
121              
122             # return array reference
123 0           return($self->[1]);
124              
125             }
126              
127             # get/set transform mask
128             # bits ... 3-2-1-0 correpsond to ... PE3-PE2-PE1-PE0
129             # parameters: ([new_mask_value])
130             # returns: (mask_value)
131             sub mask {
132              
133             # get object reference
134 0     0 0   my $self = shift();
135              
136             # if there are parameters
137 0 0         if (@_) {
138            
139             # if one parameter
140 0 0         if (@_ == 1) {
141            
142             # set object transform mask value
143 0           $self->[2] = shift();
144            
145             } else {
146            
147             # error
148 0           croak('more than one parameter');
149            
150             }
151            
152             }
153              
154             # return transform mask value
155 0           return($self->[2]);
156              
157             }
158              
159             # create mpet tag object from ICC profile
160             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
161             # returns: (ref_to_object)
162             sub new_fh {
163              
164             # get object class
165 0     0 0   my $class = shift();
166              
167             # create empty mpet object
168 0           my $self = [
169             {}, # object header
170             [], # processing elements
171             0x00 # transform mask
172             ];
173              
174             # verify 3 parameters
175 0 0         (@_ == 3) or croak('wrong number of parameters');
176              
177             # read mpet data from profile
178 0           _readICCmpet($self, @_);
179              
180             # bless object
181 0           bless($self, $class);
182              
183             # return object reference
184 0           return($self);
185              
186             }
187              
188             # writes mpet tag object to ICC profile
189             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
190             sub write_fh {
191              
192             # verify 4 parameters
193 0 0   0 0   (@_ == 4) or croak('wrong number of parameters');
194              
195             # write mpet data to profile
196 0           goto &_writeICCmpet;
197              
198             }
199              
200             # get tag size (for writing to profile)
201             # returns: (tag_size)
202             sub size {
203              
204             # get parameters
205 0     0 0   my ($self) = @_;
206              
207             # local variables
208 0           my ($size);
209              
210             # set header size
211 0           $size = 16 + 8 * @{$self->[1]};
  0            
212              
213             # for each processing element
214 0           for my $pel (@{$self->[1]}) {
  0            
215            
216             # add size
217 0           $size += $pel->size();
218            
219             # adjust to 4-byte boundary
220 0           $size += -$size % 4;
221            
222             }
223              
224             # return size
225 0           return($size);
226              
227             }
228              
229             # get number of input channels
230             # returns: (number)
231             sub cin {
232              
233             # get object reference
234 0     0 0   my $self = shift();
235              
236             # return
237 0           return($self->[1][0]->cin());
238              
239             }
240              
241             # get number of output channels
242             # returns: (number)
243             sub cout {
244              
245             # get object reference
246 0     0 0   my $self = shift();
247              
248             # return
249 0           return($self->[1][-1]->cout());
250              
251             }
252              
253             # transform data
254             # transform mask enables/disables individual tag elements
255             # clipping mask enables/disables individual tag output clipping
256             # supported input types:
257             # parameters: (list, [hash])
258             # parameters: (vector, [hash])
259             # parameters: (matrix, [hash])
260             # parameters: (Math::Matrix_object, [hash])
261             # parameters: (structure, [hash])
262             # returns: (same_type_as_input)
263             sub transform {
264              
265             # set hash value (0 or 1)
266 0 0   0 0   my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
267              
268             # if input a 'Math::Matrix' object
269 0 0 0       if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
270            
271             # call matrix transform
272 0           &_trans2;
273            
274             # if input an array reference
275             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
276            
277             # if array contains numbers (vector)
278 0 0 0       if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0        
  0            
  0            
279            
280             # call vector transform
281 0           &_trans1;
282            
283             # if array contains vectors (2-D array)
284 0 0         } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0            
  0            
285            
286             # call matrix transform
287 0           &_trans2;
288            
289             } else {
290            
291             # call structure transform
292 0           &_trans3;
293            
294             }
295            
296             # if input a list (of numbers)
297 0           } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
298            
299             # call list transform
300 0           &_trans0;
301            
302             } else {
303            
304             # error
305 0           croak('invalid transform input');
306            
307             }
308              
309             }
310              
311             # inverse transform
312             # note: number of undefined output values must equal number of defined input values
313             # note: the input and output vectors contain the final solution on return
314             # hash key 'init' specifies initial value vector
315             # hash key 'ubox' enables unit box extrapolation
316             # parameters: (input_vector, output_vector, [hash])
317             # returns: (RMS_error_value)
318             sub inverse {
319              
320             # get parameters
321 0     0 0   my ($self, $in, $out, $hash) = @_;
322              
323             # local variables
324 0           my ($i, $j, @si, @so, $init);
325 0           my ($int, $jac, $mat, $delta);
326 0           my ($max, $elim, $dlim, $accum, $error);
327              
328             # initialize indices
329 0           $i = $j = -1;
330              
331             # build slice arrays while validating input and output arrays
332 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            
333              
334             # get init array
335 0           $init = $hash->{'init'};
336              
337             # for each undefined output value
338 0           for my $i (@so) {
339            
340             # set to supplied initial value or 0.5
341 0 0         $out->[$i] = defined($init->[$i]) ? $init->[$i] : 0.5;
342            
343             }
344              
345             # set maximum loop count
346 0   0       $max = $hash->{'inv_max'} || 10;
347              
348             # loop error limit
349 0   0       $elim = $hash->{'inv_elim'} || 1E-6;
350              
351             # set delta limit ('mpet' tags use floating point PCS, so L*a*b* values need greater limit)
352 0 0 0       $dlim = $hash->{'inv_dlim'} || ($self->[0]{'input_cs'} eq 'Lab ') ? 50.0 : 0.5;
353              
354             # create empty solution matrix
355 0           $mat = Math::Matrix->new([]);
356              
357             # compute initial transform values
358 0           ($jac, $int) = jacobian($self, $out, $hash);
359              
360             # solution loop
361 0           for (1 .. $max) {
362            
363             # for each input
364 0           for my $i (0 .. $#si) {
365            
366             # for each output
367 0           for my $j (0 .. $#so) {
368            
369             # copy Jacobian value to solution matrix
370 0           $mat->[$i][$j] = $jac->[$si[$i]][$so[$j]];
371            
372             }
373            
374             # save residual value to solution matrix
375 0           $mat->[$i][$#si + 1] = $in->[$si[$i]] - $int->[$si[$i]];
376            
377             }
378            
379             # solve for delta values
380 0           $delta = $mat->solve;
381            
382             # for each output value
383 0           for my $i (0 .. $#so) {
384            
385             # add delta (limited using hyperbolic tangent)
386 0           $out->[$so[$i]] += POSIX::tanh($delta->[$i][0]/$dlim) * $dlim;
387            
388             }
389            
390             # compute updated transform values
391 0           ($jac, $int) = jacobian($self, $out, $hash);
392            
393             # initialize error accumulator
394 0           $accum = 0;
395            
396             # for each input
397 0           for my $i (0 .. $#si) {
398            
399             # accumulate delta squared
400 0           $accum += ($in->[$si[$i]] - $int->[$si[$i]])**2;
401            
402             }
403            
404             # compute RMS error
405 0           $error = sqrt($accum/@si);
406            
407             # if error less than limit
408 0 0         last if ($error < $elim);
409            
410             }
411              
412             # update input vector with final values
413 0           @{$in} = @{$int};
  0            
  0            
414              
415             # return
416 0           return($error);
417              
418             }
419              
420             # compute Jacobian matrix
421             # transform mask enables/disables individual tag elements
422             # parameters: (input_vector, [hash])
423             # returns: (Jacobian_matrix, [output_vector])
424             sub jacobian {
425              
426             # get parameters
427 0     0 0   my ($self, $data, $hash) = @_;
428              
429             # local variables
430 0           my ($jac, $jaci);
431              
432             # for each processing element
433 0           for my $i (0 .. $#{$self->[1]}) {
  0            
434            
435             # if processing element defined, and transform mask bit set
436 0 0 0       if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
437            
438             # compute Jacobian matrix and transform data
439 0           ($jaci, $data) = $self->[1][$i]->jacobian($data, $hash);
440            
441             # multiply Jacobian matrices
442 0 0         $jac = defined($jac) ? $jaci * $jac : $jaci;
443            
444             }
445            
446             }
447              
448             # if Jacobian matrix is undefined, use identity matrix
449 0 0         $jac = Math::Matrix->diagonal((1) x @{$data}) if (! defined($jac));
  0            
450              
451             # if output values wanted
452 0 0         if (wantarray) {
453            
454             # return Jacobian and output values
455 0           return($jac, $data);
456            
457             } else {
458            
459             # return Jacobian only
460 0           return($jac);
461            
462             }
463            
464             }
465              
466             # get/set PCS encoding
467             # for use with ICC::Support::PCS objects
468             # parameters: ([PCS_encoding])
469             # returns: (PCS_encoding)
470             sub pcs {
471              
472             # get parameters
473 0     0 0   my ($self, $pcs) = @_;
474              
475             # if PCS parameter is supplied
476 0 0         if (defined($pcs)) {
477            
478             # if a valid PCS encoding
479 0 0         if (grep {$pcs == $_} (3, 8)) {
  0            
480            
481             # copy to tag header hash
482 0           $self->[0]{'pcs_encoding'} = $pcs;
483            
484             # return PCS encoding
485 0           return($pcs);
486            
487             } else {
488            
489             # error
490 0           croak('invalid PCS encoding');
491            
492             }
493            
494             } else {
495            
496             # if PCS is defined in tag header
497 0 0         if (defined($self->[0]{'pcs_encoding'})) {
498            
499             # return PCS encoding
500 0           return($self->[0]{'pcs_encoding'});
501            
502             } else {
503            
504             # error
505 0           croak('can\'t determine PCS encoding');
506            
507             }
508            
509             }
510            
511             }
512              
513             # get/set white point
514             # parameters: ([white_point])
515             # returns: (white_point)
516             sub wtpt {
517              
518             # get parameters
519 0     0 0   my ($self, $wtpt) = @_;
520              
521             # if white point parameter is supplied
522 0 0         if (defined($wtpt)) {
523            
524             # if an array of three scalars
525 0 0 0       if (@{$wtpt} == 3 && 3 == grep {! ref()} @{$wtpt}) {
  0            
  0            
  0            
526            
527             # copy to tag header hash
528 0           $self->[0]{'wtpt'} = $wtpt;
529            
530             # return white point
531 0           return($wtpt);
532            
533             } else {
534            
535             # error
536 0           croak('invalid white point');
537            
538             }
539            
540             } else {
541            
542             # if white point is defined in tag header
543 0 0         if (defined($self->[0]{'wtpt'})) {
544            
545             # return return white point
546 0           return($self->[0]{'wtpt'});
547            
548             } else {
549            
550             # error
551 0           croak('can\'t determine white point');
552            
553             }
554            
555             }
556            
557             }
558              
559             # print object contents to string
560             # format is an array structure
561             # parameter: ([format])
562             # returns: (string)
563             sub sdump {
564              
565             # get parameters
566 0     0 1   my ($self, $p) = @_;
567              
568             # local variables
569 0           my ($element, $fmt, $s, $pt, $st);
570              
571             # resolve parameter to an array reference
572 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
573              
574             # get format string
575 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 's';
576              
577             # set string to object ID
578 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
579              
580             # if format contains 'o'
581 0 0         if ($fmt =~ m/s/) {
582            
583             # get default parameter
584 0           $pt = $p->[-1];
585            
586             # for each processing element
587 0           for my $i (0 .. $#{$self->[1]}) {
  0            
588            
589             # get element reference
590 0           $element = $self->[1][$i];
591            
592             # if processing element is undefined
593 0 0         if (! defined($element)) {
    0          
    0          
594            
595             # append message
596 0           $s .= "\tprocessing element is undefined\n";
597            
598             # if processing element is not a blessed object
599             } elsif (! Scalar::Util::blessed($element)) {
600            
601             # append message
602 0           $s .= "\tprocessing element is not a blessed object\n";
603            
604             # if processing element has an 'sdump' method
605             } elsif ($element->can('sdump')) {
606            
607             # get 'sdump' string
608 0 0         $st = $element->sdump(defined($p->[$i + 1]) ? $p->[$i + 1] : $pt);
609            
610             # prepend tabs to each line
611 0           $st =~ s/^/\t/mg;
612            
613             # append 'sdump' string
614 0           $s .= $st;
615            
616             # processing element is object without an 'sdump' method
617             } else {
618            
619             # append object info
620 0           $s .= sprintf("\t'%s' object, (0x%x)\n", ref($element), $element);
621            
622             }
623            
624             }
625            
626             }
627              
628             # return
629 0           return($s);
630              
631             }
632              
633             # transform list
634             # parameters: (object_reference, list, [hash])
635             # returns: (list)
636             sub _trans0 {
637              
638             # local variables
639 0     0     my ($self, $hash, $data);
640              
641             # get object reference
642 0           $self = shift();
643              
644             # get optional hash
645 0 0         $hash = pop() if (ref($_[-1]) eq 'HASH');
646              
647             # process data
648 0           $data = _trans1($self, [@_], $hash);
649              
650             # return list
651 0           return(@{$data});
  0            
652              
653             }
654              
655             # transform vector
656             # parameters: (object_reference, vector, [hash])
657             # returns: (vector)
658             sub _trans1 {
659              
660             # get parameters
661 0     0     my ($self, $data, $hash) = @_;
662              
663             # for each processing element
664 0           for my $i (0 .. $#{$self->[1]}) {
  0            
665            
666             # if processing element defined, and transform mask bit set
667 0 0 0       if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
668            
669             # transform data
670 0           $data = $self->[1][$i]->_trans1($data, $hash);
671            
672             }
673            
674             }
675            
676             # return
677 0           return($data);
678            
679             }
680              
681             # transform matrix (2-D array -or- Math::Matrix object)
682             # parameters: (object_reference, matrix, [hash])
683             # returns: (matrix)
684             sub _trans2 {
685              
686             # get parameters
687 0     0     my ($self, $data, $hash) = @_;
688              
689             # for each processing element
690 0           for my $i (0 .. $#{$self->[1]}) {
  0            
691            
692             # if processing element defined, and transform mask bit set
693 0 0 0       if (defined($self->[1][$i]) && $self->[2] & 0x01 << $i) {
694            
695             # transform data
696 0           $data = $self->[1][$i]->_trans2($data, $hash);
697            
698             }
699            
700             }
701              
702             # return
703 0           return($data);
704              
705             }
706              
707             # transform structure
708             # parameters: (object_reference, structure, [hash])
709             # returns: (structure)
710             sub _trans3 {
711              
712             # get parameters
713 0     0     my ($self, $in, $hash) = @_;
714              
715             # transform the array structure
716 0           _crawl($self, $in, my $out = [], $hash);
717              
718             # return
719 0           return($out);
720              
721             }
722              
723             # recursive transform
724             # array structure is traversed until scalar arrays are found and transformed
725             # parameters: (ref_to_object, input_array_reference, output_array_reference, hash)
726             sub _crawl {
727              
728             # get parameters
729 0     0     my ($self, $in, $out, $hash) = @_;
730              
731             # if input is a vector (reference to a scalar array)
732 0 0         if (@{$in} == grep {! ref()} @{$in}) {
  0            
  0            
  0            
733            
734             # transform input vector and copy to output
735 0           @{$out} = @{_trans1($self, $in, $hash)};
  0            
  0            
736            
737             } else {
738            
739             # for each input element
740 0           for my $i (0 .. $#{$in}) {
  0            
741            
742             # if an array reference
743 0 0         if (ref($in->[$i]) eq 'ARRAY') {
744            
745             # transform next level
746 0           _crawl($self, $in->[$i], $out->[$i] = [], $hash);
747            
748             } else {
749            
750             # error
751 0           croak('invalid transform input');
752            
753             }
754            
755             }
756            
757             }
758            
759             }
760              
761             # check object structure
762             # parameter: (ref_to_object)
763             # returns: (number_input_channels, number_output_channels)
764             sub _check {
765              
766             # get object reference
767 0     0     my $self = shift();
768              
769             # local variables
770 0           my ($ci, $co);
771              
772             # for each processing element
773 0           for my $i (0 .. $#{$self->[1]}) {
  0            
774            
775             # if element has 'cin' method
776 0 0         if ($self->[1][$i]->can('cin')) {
777            
778             # if number of input channels is undefined
779 0 0         if (! defined($ci)) {
780            
781             # set number of input channels
782 0           $ci = $self->[1][$i]->cin();
783            
784             }
785            
786             # if number of output channels is defined
787 0 0         if (defined($co)) {
788            
789             # verify input channels of this element match output channels of previous element
790 0 0         ($self->[1][$i]->cin() == $co) or croak('\'mpet\' processing element has wrong number of channels');
791            
792             }
793            
794             }
795            
796             # if element has 'cout' method
797 0 0         if ($self->[1][$i]->can('cout')) {
798            
799             # set number of output channels
800 0           $co = $self->[1][$i]->cout();
801            
802             }
803            
804             }
805              
806             # return
807 0           return($ci, $co);
808              
809             }
810              
811             # make new mpet tag from array
812             # array contains processing element objects
813             # objects must have '_trans1', '_trans2', and 'jacobian' methods
814             # parameters: (ref_to_object, ref_to_array)
815             sub _new_from_array {
816              
817             # get parameters
818 0     0     my ($self, $array) = @_;
819              
820             # for each processing element
821 0           for my $i (0 .. $#{$array}) {
  0            
822            
823             # verify object has required processing methods
824 0 0         ($array->[$i]->can('_trans1')) or croak('processing element lacks \'_trans1\'method');
825 0 0         ($array->[$i]->can('_trans2')) or croak('processing element lacks \'_trans2\'method');
826 0 0         ($array->[$i]->can('jacobian')) or croak('processing element lacks\'jacobian\' method');
827            
828             # add processing element
829 0           $self->[1][$i] = $array->[$i];
830            
831             }
832              
833             # check object structure
834 0           _check($self);
835              
836             }
837              
838             # read mpet tag from ICC profile
839             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
840             sub _readICCmpet {
841              
842             # get parameters
843 0     0     my ($self, $parent, $fh, $tag) = @_;
844              
845             # local variables
846 0           my ($buf, @mft, $table, $tag2, $type, $class, %hash);
847              
848             # set tag signature
849 0           $self->[0]{'signature'} = $tag->[0];
850              
851             # if 'D2Bx' tag
852 0 0         if ($tag->[0] =~ m|^D2B[0-2]$|) {
    0          
853            
854             # set input colorspace
855 0           $self->[0]{'input_cs'} = $parent->[1][4];
856            
857             # set output colorspace
858 0           $self->[0]{'output_cs'} = $parent->[1][5];
859            
860             # if 'B2Dx' tag
861             } elsif ($tag->[0] =~ m|^B2D[0-2]$|) {
862            
863             # set input colorspace
864 0           $self->[0]{'input_cs'} = $parent->[1][5];
865            
866             # set output colorspace
867 0           $self->[0]{'output_cs'} = $parent->[1][4];
868            
869             }
870              
871             # seek start of tag
872 0           seek($fh, $tag->[1], 0);
873              
874             # read tag header
875 0           read($fh, $buf, 16);
876              
877             # unpack header
878 0           @mft = unpack('a4 x4 n2 N', $buf);
879              
880             # verify tag signature
881 0 0         ($mft[0] eq 'mpet') or croak('wrong tag type');
882              
883             # for each processing element
884 0           for my $i (0 .. $mft[3] - 1) {
885            
886             # read positionNumber
887 0           read($fh, $buf, 8);
888            
889             # unpack to processing element tag table
890 0           $table->[$i] = ['mpet', unpack('N2', $buf)];
891            
892             }
893              
894             # clear transform mask
895 0           $self->[2] = 0;
896              
897             # for each processing element
898 0           for my $i (0 .. $mft[3] - 1) {
899            
900             # get tag table entry
901 0           $tag2 = $table->[$i];
902            
903             # make offset absolute
904 0           $tag2->[1] += $tag->[1];
905            
906             # if a duplicate tag
907 0 0         if (exists($hash{$tag2->[1]})) {
908            
909             # use original tag
910 0           $self->[1][$i] = $hash{$tag2->[1]};
911            
912             } else {
913            
914             # seek to start of tag
915 0           seek($fh, $tag2->[1], 0);
916            
917             # read tag type signature
918 0           read($fh, $type, 4);
919            
920             # convert non-word characters to underscores
921 0           $type =~ s|\W|_|g;
922            
923             # form class specifier
924 0           $class = "ICC::Profile::$type";
925            
926             # if 'class->new_fh' method exists
927 0 0         if ($class->can('new_fh')) {
928            
929             # create specific tag object
930 0           $self->[1][$i] = $class->new_fh($self, $fh, $tag2);
931            
932             } else {
933            
934             # create generic tag object
935 0           $self->[1][$i] = ICC::Profile::Generic->new_fh($self, $fh, $tag2);
936            
937             # print warning
938 0           print "processing element $type opened as generic\n";
939            
940             }
941            
942             # save tag in hash
943 0           $hash{$tag2->[1]} = $self->[1][$i];
944            
945             }
946            
947             # set mask bit
948 0           $self->[2] |= 0x01 << $i;
949            
950             }
951              
952             }
953              
954             # write mpet tag to ICC profile
955             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
956             sub _writeICCmpet {
957              
958             # get parameters
959 0     0     my ($self, $parent, $fh, $tag) = @_;
960              
961             # local variables
962 0           my ($ci, $co, $n, $offset, $size, @pept, %hash);
963              
964             # check object structure
965 0           ($ci, $co) = _check($self);
966              
967             # set number of processing elements
968 0           $n = @{$self->[1]};
  0            
969              
970             # seek start of tag
971 0           seek($fh, $tag->[1], 0);
972              
973             # write 'mpet' header
974 0           print $fh pack('a4 x4 n2 N', 'mpet', $ci, $co, $n);
975              
976             # set tag offset
977 0           $offset = 16 + 8 * $n;
978              
979             # for each processing element
980 0           for my $i (0 .. $#{$self->[1]}) {
  0            
981            
982             # verify processing element allowed in 'mpet' tag
983 0 0         (ref($self->[1][$i]) =~ m/^ICC::Profile::(cvst|matf|clut|Generic)$/) or croak('processing element not allowed in \'mpet\' tag');
984            
985             # if tag not in hash
986 0 0         if (! exists($hash{$self->[1][$i]})) {
987            
988             # get size
989 0           $size = $self->[1][$i]->size();
990            
991             # set table entry and add to hash
992 0           $pept[$i] = $hash{$self->[1][$i]} = [$offset, $size];
993            
994             # update offset
995 0           $offset += $size;
996            
997             # adjust to 4-byte boundary
998 0           $offset += -$offset % 4;
999            
1000             } else {
1001            
1002             # set table entry
1003 0           $pept[$i] = $hash{$self->[1][$i]};
1004            
1005             }
1006            
1007             # write processing element position entry
1008 0           print $fh pack('N2', @{$pept[$i]});
  0            
1009            
1010             }
1011              
1012             # initialize hash
1013 0           %hash = ();
1014              
1015             # for each processing element
1016 0           for my $i (0 .. $#{$self->[1]}) {
  0            
1017            
1018             # if tag not in hash
1019 0 0         if (! exists($hash{$self->[1][$i]})) {
1020            
1021             # make offset absolute
1022 0           $pept[$i][0] += $tag->[1];
1023            
1024             # write tag
1025 0           $self->[1][$i]->write_fh($self, $fh, ['mpet', $pept[$i][0], $pept[$i][1]]);
1026            
1027             # add key to hash
1028 0           $hash{$self->[1][$i]}++;
1029            
1030             }
1031            
1032             }
1033            
1034             }
1035              
1036             1;