File Coverage

blib/lib/ICC/Profile/mpet.pm
Criterion Covered Total %
statement 17 278 6.1
branch 1 126 0.7
condition 0 43 0.0
subroutine 5 28 17.8
pod 1 15 6.6
total 24 490 4.9


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