File Coverage

blib/lib/Image/ExifTool/Fixup.pm
Criterion Covered Total %
statement 118 160 73.7
branch 38 70 54.2
condition 9 12 75.0
subroutine 10 12 83.3
pod 0 9 0.0
total 175 263 66.5


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Fixup.pm
3             #
4             # Description: Utility to handle pointer fixups
5             #
6             # Revisions: 01/19/2005 - P. Harvey Created
7             # 04/11/2005 - P. Harvey Allow fixups to be tagged with a marker,
8             # and add new marker-related routines
9             # 06/21/2006 - P. Harvey Patch to work with negative offsets
10             # 07/07/2006 - P. Harvey Added support for 16-bit pointers
11             # 02/19/2013 - P. Harvey Added IsEmpty()
12             #
13             # Data Members:
14             #
15             # Start - Position in data where a zero pointer points to.
16             # Shift - Amount to shift offsets (relative to Start).
17             # Fixups - List of Fixup object references to to shift relative to this Fixup.
18             # Pointers - Hash of references to fixup pointer arrays, keyed by ByteOrder
19             # string (with "2" added if pointer is 16-bit [default is 32-bit],
20             # plus "_$marker" suffix if tagged with a marker name).
21             #
22             # Procedure:
23             #
24             # 1. Create a Fixup object for each data block containing pointers
25             # 2. Call AddFixup with the offset of each pointer in the block
26             # - pointer is assumed int32u with the current byte order
27             # - may also be called with a fixup reference for contained blocks
28             # 3. Add the necessary pointer offset to $$fixup{Shift}
29             # 4. Add data size to $$fixup{Start} if data is added before the block
30             # - automatically also shifts pointers by this amount
31             # 5. Call ApplyFixup to apply the fixup to all pointers
32             # - resets Shift and Start to 0 after applying fixup
33             #------------------------------------------------------------------------------
34              
35             package Image::ExifTool::Fixup;
36              
37 61     61   345 use strict;
  61         414  
  61         2736  
38 61         4328 use Image::ExifTool qw(GetByteOrder SetByteOrder Get32u Get32s Set32u
39 61     61   264 Get16u Get16s Set16u);
  61         87  
40 61     61   269 use vars qw($VERSION);
  61         95  
  61         90553  
41              
42             $VERSION = '1.06';
43              
44             sub AddFixup($$;$$);
45             sub ApplyFixup($$);
46             sub Dump($;$);
47              
48             #------------------------------------------------------------------------------
49             # New - create new Fixup object
50             # Inputs: 0) reference to Fixup object or Fixup class name
51             sub new
52             {
53 1168     1168 0 1843 local $_;
54 1168         1613 my $that = shift;
55 1168   50     4051 my $class = ref($that) || $that || 'Image::ExifTool::Fixup';
56 1168         2360 my $self = bless {}, $class;
57              
58             # initialize required members
59 1168         2829 $self->{Start} = 0;
60 1168         2125 $self->{Shift} = 0;
61              
62 1168         4089 return $self;
63             }
64              
65             #------------------------------------------------------------------------------
66             # Clone this object
67             # Inputs: 0) reference to Fixup object or Fixup class name
68             # Returns: reference to new Fixup object
69             sub Clone($)
70             {
71 6     6 0 13 my $self = shift;
72 6         30 my $clone = Image::ExifTool::Fixup->new;
73 6         43 $clone->{Start} = $self->{Start};
74 6         16 $clone->{Shift} = $self->{Shift};
75 6         20 my $phash = $self->{Pointers};
76 6 50       20 if ($phash) {
77 6         15 $clone->{Pointers} = { };
78 6         12 my $byteOrder;
79 6         24 foreach $byteOrder (keys %$phash) {
80 7         14 my @pointers = @{$phash->{$byteOrder}};
  7         49  
81 7         23 $clone->{Pointers}->{$byteOrder} = \@pointers;
82             }
83             }
84 6 50       23 if ($self->{Fixups}) {
85 0         0 $clone->{Fixups} = [ ];
86 0         0 my $subFixup;
87 0         0 foreach $subFixup (@{$self->{Fixups}}) {
  0         0  
88 0         0 push @{$clone->{Fixups}}, $subFixup->Clone();
  0         0  
89             }
90             }
91 6         12 return $clone;
92             }
93              
94             #------------------------------------------------------------------------------
95             # Add fixup pointer or another fixup object below this one
96             # Inputs: 0) Fixup object reference
97             # 1) Scalar for pointer offset, or reference to Fixup object
98             # 2) Optional marker name for the pointer
99             # 3) Optional pointer format ('int16u' or 'int32u', defaults to 'int32u')
100             # Notes: Byte ordering must be set properly for the pointer being added (must keep
101             # track of the byte order of each offset since MakerNotes may have different byte order!)
102             sub AddFixup($$;$$)
103             {
104 3417     3417 0 5927 my ($self, $pointer, $marker, $format) = @_;
105 3417 100       5055 if (ref $pointer) {
106 559 100       1511 $self->{Fixups} or $self->{Fixups} = [ ];
107 559         736 push @{$self->{Fixups}}, $pointer;
  559         1435  
108             } else {
109 2858         4641 my $byteOrder = GetByteOrder();
110 2858 50       4732 if (defined $format) {
111 0 0       0 if ($format eq 'int16u') {
    0          
112 0         0 $byteOrder .= '2';
113             } elsif ($format ne 'int32u') {
114 0         0 warn "Bad Fixup pointer format $format\n";
115             }
116             }
117 2858 100       4351 $byteOrder .= "_$marker" if defined $marker;
118 2858         4220 my $phash = $self->{Pointers};
119 2858 100       5020 $phash or $phash = $self->{Pointers} = { };
120 2858 100       6007 $phash->{$byteOrder} or $phash->{$byteOrder} = [ ];
121 2858         3183 push @{$phash->{$byteOrder}}, $pointer;
  2858         7531  
122             }
123             }
124              
125             #------------------------------------------------------------------------------
126             # fix up pointer offsets
127             # Inputs: 0) Fixup object reference, 1) data reference
128             # Outputs: Collapses fixup hierarchy into linear lists of fixup pointers
129             sub ApplyFixup($$)
130             {
131 1402     1402 0 2208 my ($self, $dataPt) = @_;
132              
133 1402         2045 my $start = $self->{Start};
134 1402         1887 my $shift = $self->{Shift} + $start; # make shift relative to start
135 1402         1794 my $phash = $self->{Pointers};
136              
137             # fix up pointers in this fixup
138 1402 100 100     3833 if ($phash and ($start or $shift)) {
      100        
139 652         1135 my $saveOrder = GetByteOrder(); # save original byte ordering
140 652         870 my ($byteOrder, $ptr);
141 652         1495 foreach $byteOrder (keys %$phash) {
142 729         2080 SetByteOrder(substr($byteOrder,0,2));
143             # apply the fixup offset shift (must get as signed integer
144             # to avoid overflow in case it was negative before)
145 729 50       2486 my ($get, $set) = ($byteOrder =~ /^(II2|MM2)/) ?
146             (\&Get16s, \&Set16u) : (\&Get32s, \&Set32u);
147 729         961 foreach $ptr (@{$phash->{$byteOrder}}) {
  729         1252  
148 7328         7227 $ptr += $start; # update pointer to new start location
149 7328 50       9117 next unless $shift;
150 7328         8927 &$set(&$get($dataPt, $ptr) + $shift, $dataPt, $ptr);
151             }
152             }
153 652         1215 SetByteOrder($saveOrder); # restore original byte ordering
154             }
155             # recurse into contained fixups
156 1402 100       2602 if ($self->{Fixups}) {
157             # create our pointer hash if it doesn't exist
158 329 100       927 $phash or $phash = $self->{Pointers} = { };
159             # loop through all contained fixups
160 329         456 my $subFixup;
161 329         422 foreach $subFixup (@{$self->{Fixups}}) {
  329         648  
162             # adjust the subfixup start and shift
163 559         1005 $subFixup->{Start} += $start;
164 559         905 $subFixup->{Shift} += $shift - $start;
165             # recursively apply contained fixups
166 559         1295 ApplyFixup($subFixup, $dataPt);
167 559 100       1213 my $shash = $subFixup->{Pointers} or next;
168             # add all pointers to our collapsed lists
169 479         574 my $byteOrder;
170 479         938 foreach $byteOrder (keys %$shash) {
171 467 100       1195 $phash->{$byteOrder} or $phash->{$byteOrder} = [ ];
172 467         600 push @{$phash->{$byteOrder}}, @{$shash->{$byteOrder}};
  467         666  
  467         1758  
173 467         1103 delete $shash->{$byteOrder};
174             }
175 479         1493 delete $subFixup->{Pointers};
176             }
177 329         1252 delete $self->{Fixups}; # remove our contained fixups
178             }
179             # reset our Start/Shift for the collapsed fixup
180 1402         4088 $self->{Start} = $self->{Shift} = 0;
181             }
182              
183             #------------------------------------------------------------------------------
184             # Is this Fixup empty?
185             # Inputs: 0) Fixup object ref
186             # Returns: True if there are no offsets to fix
187             sub IsEmpty($)
188             {
189 13     13 0 27 my $self = shift;
190 13         33 my $phash = $self->{Pointers};
191 13 50       59 if ($phash) {
192 13         24 my $key;
193 13         29 foreach $key (keys %$phash) {
194 12 50       48 next unless ref $$phash{$key} eq 'ARRAY';
195 12 50       23 return 0 if @{$$phash{$key}};
  12         104  
196             }
197             }
198 1         7 return 1;
199             }
200              
201             #------------------------------------------------------------------------------
202             # Does specified marker exist?
203             # Inputs: 0) Fixup object reference, 1) marker name
204             # Returns: True if fixup contains specified marker name
205             sub HasMarker($$)
206             {
207 0     0 0 0 my ($self, $marker) = @_;
208 0         0 my $phash = $self->{Pointers};
209 0 0       0 return 0 unless $phash;
210 0 0       0 return 1 if grep /_$marker$/, keys %$phash;
211 0 0       0 return 0 unless $self->{Fixups};
212 0         0 my $subFixup;
213 0         0 foreach $subFixup (@{$self->{Fixups}}) {
  0         0  
214 0 0       0 return 1 if $subFixup->HasMarker($marker);
215             }
216 0         0 return 0;
217             }
218              
219             #------------------------------------------------------------------------------
220             # Set all marker pointers to specified value
221             # Inputs: 0) Fixup object reference, 1) data reference
222             # 2) marker name, 3) pointer value, 4) offset to start of data
223             sub SetMarkerPointers($$$$;$)
224             {
225 131     131 0 413 my ($self, $dataPt, $marker, $value, $startOffset) = @_;
226 131   50     704 my $start = $self->{Start} + ($startOffset || 0);
227 131         280 my $phash = $self->{Pointers};
228              
229 131 100       372 if ($phash) {
230 125         318 my $saveOrder = GetByteOrder(); # save original byte ordering
231 125         222 my ($byteOrder, $ptr);
232 125         376 foreach $byteOrder (keys %$phash) {
233 222 100       2483 next unless $byteOrder =~ /^(II|MM)(2?)_$marker$/;
234 6         21 SetByteOrder($1);
235 6 50       29 my $set = $2 ? \&Set16u : \&Set32u;
236 6         11 foreach $ptr (@{$phash->{$byteOrder}}) {
  6         19  
237 7         20 &$set($value, $dataPt, $ptr + $start);
238             }
239             }
240 125         312 SetByteOrder($saveOrder); # restore original byte ordering
241             }
242 131 50       471 if ($self->{Fixups}) {
243 0         0 my $subFixup;
244 0         0 foreach $subFixup (@{$self->{Fixups}}) {
  0         0  
245 0         0 $subFixup->SetMarkerPointers($dataPt, $marker, $value, $start);
246             }
247             }
248             }
249              
250             #------------------------------------------------------------------------------
251             # Get pointer values for specified marker
252             # Inputs: 0) Fixup object reference, 1) data reference,
253             # 2) marker name, 3) offset to start of data
254             # Returns: List of marker pointers in list context, or first marker pointer otherwise
255             sub GetMarkerPointers($$$;$)
256             {
257 6     6 0 19 my ($self, $dataPt, $marker, $startOffset) = @_;
258 6   50     32 my $start = $self->{Start} + ($startOffset || 0);
259 6         12 my $phash = $self->{Pointers};
260 6         23 my @pointers;
261              
262 6 50       21 if ($phash) {
263 6         17 my $saveOrder = GetByteOrder();
264 6         9 my ($byteOrder, $ptr);
265 6         107 foreach $byteOrder (grep /_$marker$/, keys %$phash) {
266 3         10 SetByteOrder(substr($byteOrder,0,2));
267 3 50       19 my $get = ($byteOrder =~ /^(II2|MM2)/) ? \&Get16u : \&Get32u;
268 3         5 foreach $ptr (@{$phash->{$byteOrder}}) {
  3         30  
269 9         24 push @pointers, &$get($dataPt, $ptr + $start);
270             }
271             }
272 6         19 SetByteOrder($saveOrder); # restore original byte ordering
273             }
274 6 50       20 if ($self->{Fixups}) {
275 0         0 my $subFixup;
276 0         0 foreach $subFixup (@{$self->{Fixups}}) {
  0         0  
277 0         0 push @pointers, $subFixup->GetMarkerPointers($dataPt, $marker, $start);
278             }
279             }
280 6 50       31 return @pointers if wantarray;
281 0           return $pointers[0];
282             }
283              
284             #------------------------------------------------------------------------------
285             # Dump fixup to console for debugging
286             # Inputs: 0) Fixup object reference, 1) optional initial indent string
287             sub Dump($;$)
288             {
289 0     0 0   my ($self, $indent) = @_;
290 0 0         $indent or $indent = '';
291 0           printf "${indent}Fixup start=0x%x shift=0x%x\n", $self->{Start}, $self->{Shift};
292 0           my $phash = $self->{Pointers};
293 0 0         if ($phash) {
294 0           my $byteOrder;
295 0           foreach $byteOrder (sort keys %$phash) {
296 0           print "$indent $byteOrder: ", join(' ',@{$phash->{$byteOrder}}),"\n";
  0            
297             }
298             }
299 0 0         if ($self->{Fixups}) {
300 0           my $subFixup;
301 0           foreach $subFixup (@{$self->{Fixups}}) {
  0            
302 0           Dump($subFixup, $indent . ' ');
303             }
304             }
305             }
306              
307              
308             1; # end
309              
310             __END__