File Coverage

blib/lib/Image/ExifTool/OpenEXR.pm
Criterion Covered Total %
statement 62 101 61.3
branch 27 66 40.9
condition 10 24 41.6
subroutine 5 5 100.0
pod 0 1 0.0
total 104 197 52.7


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: OpenEXR.pm
3             #
4             # Description: Read OpenEXR meta information
5             #
6             # Revisions: 2011/12/10 - P. Harvey Created
7             # 2023/01/31 - PH Added support for multipart images
8             #
9             # References: 1) http://www.openexr.com/
10             #------------------------------------------------------------------------------
11              
12             package Image::ExifTool::OpenEXR;
13              
14 1     1   4380 use strict;
  1         2  
  1         32  
15 1     1   5 use vars qw($VERSION);
  1         2  
  1         40  
16 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         224  
17 1     1   505 use Image::ExifTool::GPS;
  1         9  
  1         1481  
18              
19             $VERSION = '1.04';
20              
21             # supported EXR value format types (other types are extracted as undef binary data)
22             my %formatType = (
23             box2f => 'float[4]',
24             box2i => 'int32s[4]',
25             chlist => 1,
26             chromaticities => 'float[8]',
27             compression => 'int8u',
28             double => 'double',
29             envmap => 'int8u',
30             float => 'float',
31             'int' => 'int32s',
32             keycode => 'int32s[7]',
33             lineOrder => 'int8u',
34             m33f => 'float[9]',
35             m44f => 'float[16]',
36             rational => 'rational64s',
37             string => 'string', # incorrect in specification! (no leading int)
38             stringvector => 1,
39             tiledesc => 1,
40             timecode => 'int32u[2]',
41             v2f => 'float[2]',
42             v2i => 'int32s[2]',
43             v3f => 'float[3]',
44             v3i => 'int32s[3]',
45             );
46              
47             # OpenEXR tags
48             %Image::ExifTool::OpenEXR::Main = (
49             GROUPS => { 2 => 'Image' },
50             NOTES => q{
51             Information extracted from EXR images. Use the ExtractEmbedded option to
52             extract information from all frames of a multipart image. See
53             L for the official specification.
54             },
55             _ver => { Name => 'EXRVersion', Notes => 'low byte of Flags word' },
56             _flags => { Name => 'Flags',
57             PrintConv => { BITMASK => {
58             9 => 'Tiled',
59             10 => 'Long names',
60             11 => 'Deep data',
61             12 => 'Multipart',
62             }},
63             },
64             adoptedNeutral => { },
65             altitude => {
66             Name => 'GPSAltitude',
67             Groups => { 2 => 'Location' },
68             PrintConv => q{
69             $val = int($val * 10) / 10;
70             return(($val =~ s/^-// ? "$val m Below" : "$val m Above") . " Sea Level");
71             },
72             },
73             aperture => { PrintConv => 'sprintf("%.1f",$val)' },
74             channels => { },
75             chromaticities => { },
76             capDate => {
77             Name => 'DateTimeOriginal',
78             Description => 'Date/Time Original',
79             Groups => { 2 => 'Time' },
80             PrintConv => '$self->ConvertDateTime($val)',
81             },
82             comments => { },
83             compression => {
84             PrintConvColumns => 2,
85             PrintConv => {
86             0 => 'None',
87             1 => 'RLE',
88             2 => 'ZIPS',
89             3 => 'ZIP',
90             4 => 'PIZ',
91             5 => 'PXR24',
92             6 => 'B44',
93             7 => 'B44A',
94             },
95             },
96             dataWindow => { },
97             displayWindow => { },
98             envmap => {
99             Name => 'EnvironmentMap',
100             PrintConv => {
101             0 => 'Latitude/Longitude',
102             1 => 'Cube',
103             },
104             },
105             expTime => {
106             Name => 'ExposureTime',
107             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
108             },
109             focus => {
110             Name => 'FocusDistance',
111             PrintConv => '"$val m"',
112             },
113             framesPerSecond => { },
114             keyCode => { },
115             isoSpeed => { Name => 'ISO' },
116             latitude => {
117             Name => 'GPSLatitude',
118             Groups => { 2 => 'Location' },
119             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
120             },
121             lineOrder => {
122             PrintConv => {
123             0 => 'Increasing Y',
124             1 => 'Decreasing Y',
125             2 => 'Random Y',
126             },
127             },
128             longitude => {
129             Name => 'GPSLongitude',
130             Groups => { 2 => 'Location' },
131             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
132             },
133             lookModTransform => { },
134             multiView => { },
135             owner => { Groups => { 2 => 'Author' } },
136             pixelAspectRatio => { },
137             preview => { },
138             renderingTransform => { },
139             screenWindowCenter => { },
140             screenWindowWidth => { },
141             tiles => { },
142             timeCode => { },
143             utcOffset => {
144             Name => 'TimeZone',
145             Groups => { 2 => 'Time' },
146             PrintConv => 'TimeZoneString($val / 60)',
147             },
148             whiteLuminance => { },
149             worldToCamera => { },
150             worldToNDC => { },
151             wrapmodes => { Name => 'WrapModes' },
152             xDensity => { Name => 'XResolution' },
153             name => { },
154             type => { },
155             version => { },
156             chunkCount => { },
157             # also observed:
158             # ilut
159             );
160              
161             #------------------------------------------------------------------------------
162             # Extract information from an OpenEXR file
163             # Inputs: 0) ExifTool object reference, 1) DirInfo reference
164             # Returns: 1 on success, 0 if this wasn't a valid OpenEXR file
165             sub ProcessEXR($$)
166             {
167 1     1 0 3 my ($et, $dirInfo) = @_;
168 1         2 my $raf = $$dirInfo{RAF};
169 1         4 my $verbose = $et->Options('Verbose');
170 1   33     3 my $binary = $et->Options('Binary') || $verbose;
171 1         3 my ($buff, $dim);
172              
173             # verify this is a valid RIFF file
174 1 50       5 return 0 unless $raf->Read($buff, 8) == 8;
175 1 50       6 return 0 unless $buff =~ /^\x76\x2f\x31\x01/s;
176 1         5 $et->SetFileType();
177 1         5 SetByteOrder('II');
178 1         3 my $tagTablePtr = GetTagTable('Image::ExifTool::OpenEXR::Main');
179              
180             # extract information from header
181 1         15 my $flags = unpack('x4V', $buff);
182 1         6 $et->HandleTag($tagTablePtr, '_ver', $flags & 0xff);
183 1         11 $et->HandleTag($tagTablePtr, '_flags', $flags & 0xffffff00);
184 1 50       5 my $maxLen = ($flags & 0x400) ? 255 : 31;
185 1         2 my $multi = $flags & 0x1000;
186              
187             # extract attributes
188 1         2 for (;;) {
189 9 50       30 $raf->Read($buff, ($maxLen + 1) * 2 + 5) or last;
190 9 100       30 if ($buff =~ /^\0/) {
191 1 50 33     15 last unless $multi and $et->Options('ExtractEmbedded');
192             # remove null and process the next frame header as a sub-document
193             # (second null is end of all headers)
194 0 0 0     0 last if $buff =~ s/^(\0+)// and length($1) > 1;
195 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
196             }
197 8 50       110 unless ($buff =~ /^([^\0]{1,$maxLen})\0([^\0]{1,$maxLen})\0(.{4})/sg) {
198 0         0 $et->Warn('EXR format error');
199 0         0 last;
200             }
201 8         40 my ($tag, $type, $size) = ($1, $2, unpack('V', $3));
202 8 50       27 unless ($raf->Seek(pos($buff) - length($buff), 1)) {
203 0         0 $et->Warn('Seek error');
204 0         0 last;
205             }
206 8         33 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
207 8 50       19 unless ($tagInfo) {
208 0         0 my $name = ucfirst $tag;
209 0         0 $name =~ s/([^a-zA-Z])([a-z])/$1\U$2/g; # capitalize first letter of each word
210 0         0 $name =~ tr/-_a-zA-Z0-9//dc;
211 0 0       0 if (length $name <= 1) {
212 0 0       0 if (length $name) {
213 0         0 $name = "Tag$name";
214             } else {
215 0         0 $name = 'Invalid';
216             }
217             }
218 0         0 $tagInfo = { Name => $name };
219 0         0 AddTagToTable($tagTablePtr, $tag, $tagInfo);
220 0         0 $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n");
221             }
222 8         903 my ($val, $success);
223 8         31 my $format = $formatType{$type};
224 8 50 33     27 if ($format or $binary) {
225 8 50       21 $raf->Read($buff, $size) == $size and $success = 1;
226 8 50       27 if (not $format) {
    100          
    50          
    50          
    0          
227 0         0 $val = \$buff; # treat as undef binary data
228             } elsif ($format ne '1') {
229             # handle formats which map nicely into ExifTool format codes
230 7 50       37 if ($format =~ /^(\w+)\[?(\d*)/) {
231 7         26 my ($fmt, $cnt) = ($1, $2);
232 7 50       19 $cnt = $fmt eq 'string' ? $size : 1 unless $cnt;
    100          
233 7         21 $val = ReadValue(\$buff, 0, $fmt, $cnt, $size);
234             }
235             # handle other format types
236             } elsif ($type eq 'tiledesc') {
237 0 0       0 if ($size >= 9) {
238 0         0 my $x = Get32u(\$buff, 0);
239 0         0 my $y = Get32u(\$buff, 4);
240 0         0 my $mode = Get8u(\$buff, 8);
241 0         0 my $lvl = { 0 => 'One Level', 1 => 'MIMAP Levels', 2 => 'RIPMAP Levels' }->{$mode & 0x0f};
242 0 0       0 $lvl or $lvl = 'Unknown Levels (' . ($mode & 0xf) . ')';
243 0         0 my $rnd = { 0 => 'Round Down', 1 => 'Round Up' }->{$mode >> 4};
244 0 0       0 $rnd or $rnd = 'Unknown Rounding (' . ($mode >> 4) . ')';
245 0         0 $val = "${x}x$y; $lvl; $rnd";
246             }
247             } elsif ($type eq 'chlist') {
248 1         3 $val = [ ];
249 1         8 while ($buff =~ /\G([^\0]{1,31})\0(.{16})/sg) {
250 4         13 my ($str, $dat) = ($1, $2);
251 4         11 my ($pix,$lin,$x,$y) = unpack('VCx3VV', $dat);
252 4   33     17 $pix = { 0 => 'int8u', 1 => 'half', 2 => 'float' }->{$pix} || "unknown($pix)";
253 4 50       34 push @$val, "$str $pix" . ($lin ? ' linear' : '') . " $x $y";
254             }
255             } elsif ($type eq 'stringvector') {
256 0         0 $val = [ ];
257 0         0 my $pos = 0;
258 0         0 while ($pos + 4 <= length($buff)) {
259 0         0 my $len = Get32u(\$buff, $pos);
260 0 0       0 last if $pos + 4 + $len > length($buff);
261 0         0 push @$val, substr($buff, $pos + 4, $len);
262 0         0 $pos += 4 + $len;
263             }
264             } else {
265 0         0 $val = \$buff; # (shouldn't happen)
266             }
267             } else {
268             # avoid loading binary data
269 0         0 $val = \ "Binary data $size bytes";
270 0         0 $success = $raf->Seek($size, 1);
271             }
272 8 50       20 unless ($success) {
273 0         0 $et->Warn('Truncated or corrupted EXR file');
274 0         0 last;
275             }
276 8 50       16 $val = '' unless defined $val;
277              
278             # take image dimensions from dataWindow (with displayWindow as backup)
279 8 50 66     53 if (($tag eq 'dataWindow' or (not $dim and $tag eq 'displayWindow')) and
      66        
      66        
280             $val =~ /^(-?\d+) (-?\d+) (-?\d+) (-?\d+)$/ and not $$et{DOC_NUM})
281             {
282 1         7 $dim = [$3 - $1 + 1, $4 - $2 + 1];
283             }
284 8 50       18 if ($verbose) {
285 0 0       0 my $dataPt = ref $val ? $val : \$val,
286             $et->VerboseInfo($tag, $tagInfo,
287             Table => $tagTablePtr,
288             Value => $val,
289             Size => $size,
290             Format => $type,
291             DataPt => \$buff,
292             Addr => $raf->Tell() - $size,
293             );
294             }
295 8         25 $et->FoundTag($tagInfo, $val);
296             }
297 1         6 delete $$et{DOC_NUM};
298 1 50       3 if ($dim) {
299 1         5 $et->FoundTag('ImageWidth', $$dim[0]);
300 1         29 $et->FoundTag('ImageHeight', $$dim[1]);
301             }
302 1         5 return 1;
303             }
304              
305             1; # end
306              
307             __END__