File Coverage

blib/lib/Image/ExifTool/OpenEXR.pm
Criterion Covered Total %
statement 63 105 60.0
branch 28 70 40.0
condition 11 27 40.7
subroutine 5 5 100.0
pod 0 1 0.0
total 107 208 51.4


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   8674 use strict;
  1         2  
  1         54  
15 1     1   6 use vars qw($VERSION);
  1         3  
  1         58  
16 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         313  
17 1     1   693 use Image::ExifTool::GPS;
  1         7  
  1         2536  
18              
19             $VERSION = '1.07';
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             8 => 'DWAA', #github276
95             9 => 'DWAB', #github276
96             },
97             },
98             dataWindow => { },
99             displayWindow => { },
100             envmap => {
101             Name => 'EnvironmentMap',
102             PrintConv => {
103             0 => 'Latitude/Longitude',
104             1 => 'Cube',
105             },
106             },
107             expTime => {
108             Name => 'ExposureTime',
109             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
110             },
111             focus => {
112             Name => 'FocusDistance',
113             PrintConv => '"$val m"',
114             },
115             framesPerSecond => { },
116             keyCode => { },
117             isoSpeed => { Name => 'ISO' },
118             latitude => {
119             Name => 'GPSLatitude',
120             Groups => { 2 => 'Location' },
121             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
122             },
123             lineOrder => {
124             PrintConv => {
125             0 => 'Increasing Y',
126             1 => 'Decreasing Y',
127             2 => 'Random Y',
128             },
129             },
130             longitude => {
131             Name => 'GPSLongitude',
132             Groups => { 2 => 'Location' },
133             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
134             },
135             lookModTransform => { },
136             multiView => { },
137             owner => { Groups => { 2 => 'Author' } },
138             pixelAspectRatio => { },
139             preview => { Groups => { 2 => 'Preview' } },
140             renderingTransform => { },
141             screenWindowCenter => { },
142             screenWindowWidth => { },
143             tiles => { },
144             timeCode => { },
145             utcOffset => {
146             Name => 'TimeZone',
147             Groups => { 2 => 'Time' },
148             PrintConv => 'TimeZoneString($val / 60)',
149             },
150             whiteLuminance => { },
151             worldToCamera => { },
152             worldToNDC => { },
153             wrapmodes => { Name => 'WrapModes' },
154             xDensity => { Name => 'XResolution' },
155             name => { },
156             type => { },
157             version => { },
158             chunkCount => { },
159             # exif and xmp written by PanoramaStudio4.0.2Pro
160             exif => {
161             Name => 'EXIF',
162             SubDirectory => {
163             TagTable => 'Image::ExifTool::Exif::Main',
164             ProcessProc => \&Image::ExifTool::ProcessTIFF,
165             Start => 4, # (skip leading 4 bytes with data length)
166             },
167             },
168             xmp => {
169             Name => 'XMP',
170             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
171             },
172             # also observed:
173             # ilut
174             );
175              
176             #------------------------------------------------------------------------------
177             # Extract information from an OpenEXR file
178             # Inputs: 0) ExifTool object reference, 1) DirInfo reference
179             # Returns: 1 on success, 0 if this wasn't a valid OpenEXR file
180             sub ProcessEXR($$)
181             {
182 1     1 0 3 my ($et, $dirInfo) = @_;
183 1         3 my $raf = $$dirInfo{RAF};
184 1         5 my $verbose = $et->Options('Verbose');
185 1   33     4 my $binary = $et->Options('Binary') || $verbose;
186 1         2 my ($buff, $dim);
187              
188             # verify this is a valid RIFF file
189 1 50       5 return 0 unless $raf->Read($buff, 8) == 8;
190 1 50       6 return 0 unless $buff =~ /^\x76\x2f\x31\x01/s;
191 1         28 $et->SetFileType();
192 1         7 SetByteOrder('II');
193 1         4 my $tagTablePtr = GetTagTable('Image::ExifTool::OpenEXR::Main');
194              
195             # extract information from header
196 1         17 my $flags = unpack('x4V', $buff);
197 1         11 $et->HandleTag($tagTablePtr, '_ver', $flags & 0xff);
198 1         6 $et->HandleTag($tagTablePtr, '_flags', $flags & 0xffffff00);
199 1 50       5 my $maxLen = ($flags & 0x400) ? 255 : 31;
200 1         3 my $multi = $flags & 0x1000;
201              
202             # extract attributes
203 1         2 for (;;) {
204 9 50       43 $raf->Read($buff, ($maxLen + 1) * 2 + 5) or last;
205 9 100       32 if ($buff =~ /^\0/) {
206 1 50 33     23 last unless $multi and $et->Options('ExtractEmbedded');
207             # remove null and process the next frame header as a sub-document
208             # (second null is end of all headers)
209 0 0 0     0 last if $buff =~ s/^(\0+)// and length($1) > 1;
210 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
211             }
212 8 50       244 unless ($buff =~ /^([^\0]{1,$maxLen})\0([^\0]{1,$maxLen})\0(.{4})/sg) {
213 0         0 $et->Warn('EXR format error');
214 0         0 last;
215             }
216 8         89 my ($tag, $type, $size) = ($1, $2, unpack('V', $3));
217 8 50       44 unless ($raf->Seek(pos($buff) - length($buff), 1)) {
218 0         0 $et->Warn('Seek error');
219 0         0 last;
220             }
221 8         40 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
222 8 50       25 unless ($tagInfo) {
223 0         0 my $name = ucfirst $tag;
224 0         0 $name =~ s/([^a-zA-Z])([a-z])/$1\U$2/g; # capitalize first letter of each word
225 0         0 $name =~ tr/-_a-zA-Z0-9//dc;
226 0 0       0 if (length $name <= 1) {
227 0 0       0 if (length $name) {
228 0         0 $name = "Tag$name";
229             } else {
230 0         0 $name = 'Invalid';
231             }
232             }
233 0         0 $tagInfo = { Name => $name };
234 0         0 AddTagToTable($tagTablePtr, $tag, $tagInfo);
235 0         0 $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n");
236             }
237 8         14 my ($val, $success, $buf2);
238 8         24 my $format = $formatType{$type};
239 8         16 my $subdir = $$tagInfo{SubDirectory};
240 8 50 33     27 if ($format or $binary or $subdir) {
      33        
241 8 50       59 $raf->Read($buf2, $size) == $size and $success = 1;
242 8 50       51 if ($subdir) {
    50          
    100          
    50          
    50          
    0          
243 0         0 $et->HandleTag($tagTablePtr, $tag, undef,
244             DataPt => \$buf2, DataPos => $raf->Tell() - length($buf2));
245 0 0       0 next if $success;
246             } elsif (not $format) {
247 0         0 $val = \$buf2; # treat as undef binary data
248             } elsif ($format ne '1') {
249             # handle formats which map nicely into ExifTool format codes
250 7 50       62 if ($format =~ /^(\w+)\[?(\d*)/) {
251 7         29 my ($fmt, $cnt) = ($1, $2);
252 7 50       24 $cnt = $fmt eq 'string' ? $size : 1 unless $cnt;
    100          
253 7         27 $val = ReadValue(\$buf2, 0, $fmt, $cnt, $size);
254             }
255             # handle other format types
256             } elsif ($type eq 'tiledesc') {
257 0 0       0 if ($size >= 9) {
258 0         0 my $x = Get32u(\$buf2, 0);
259 0         0 my $y = Get32u(\$buf2, 4);
260 0         0 my $mode = Get8u(\$buf2, 8);
261 0         0 my $lvl = { 0 => 'One Level', 1 => 'MIMAP Levels', 2 => 'RIPMAP Levels' }->{$mode & 0x0f};
262 0 0       0 $lvl or $lvl = 'Unknown Levels (' . ($mode & 0xf) . ')';
263 0         0 my $rnd = { 0 => 'Round Down', 1 => 'Round Up' }->{$mode >> 4};
264 0 0       0 $rnd or $rnd = 'Unknown Rounding (' . ($mode >> 4) . ')';
265 0         0 $val = "${x}x$y; $lvl; $rnd";
266             }
267             } elsif ($type eq 'chlist') {
268 1         3 $val = [ ];
269 1         11 while ($buf2 =~ /\G([^\0]{1,31})\0(.{16})/sg) {
270 4         13 my ($str, $dat) = ($1, $2);
271 4         14 my ($pix,$lin,$x,$y) = unpack('VCx3VV', $dat);
272 4   33     22 $pix = { 0 => 'int8u', 1 => 'half', 2 => 'float' }->{$pix} || "unknown($pix)";
273 4 50       40 push @$val, "$str $pix" . ($lin ? ' linear' : '') . " $x $y";
274             }
275             } elsif ($type eq 'stringvector') {
276 0         0 $val = [ ];
277 0         0 my $pos = 0;
278 0         0 while ($pos + 4 <= length($buf2)) {
279 0         0 my $len = Get32u(\$buf2, $pos);
280 0 0       0 last if $pos + 4 + $len > length($buf2);
281 0         0 push @$val, substr($buf2, $pos + 4, $len);
282 0         0 $pos += 4 + $len;
283             }
284             } else {
285 0         0 $val = \$buf2; # (shouldn't happen)
286             }
287             } else {
288             # avoid loading binary data
289 0         0 $val = \ "Binary data $size bytes";
290 0         0 $success = $raf->Seek($size, 1);
291             }
292 8 50       26 unless ($success) {
293 0         0 $et->Warn('Truncated or corrupted EXR file');
294 0         0 last;
295             }
296 8 50       18 $val = '' unless defined $val;
297              
298             # take image dimensions from dataWindow (with displayWindow as backup)
299 8 50 66     61 if (($tag eq 'dataWindow' or (not $dim and $tag eq 'displayWindow')) and
      66        
      66        
300             $val =~ /^(-?\d+) (-?\d+) (-?\d+) (-?\d+)$/ and not $$et{DOC_NUM})
301             {
302 1         19 $dim = [$3 - $1 + 1, $4 - $2 + 1];
303             }
304 8 50       32 if ($verbose) {
305 0 0       0 my $dataPt = ref $val eq 'SCALAR' ? $val : \$buf2;
306 0         0 $et->VerboseInfo($tag, $tagInfo,
307             Table => $tagTablePtr,
308             Value => $val,
309             Size => $size,
310             Format => $type,
311             DataPt => $dataPt,
312             Addr => $raf->Tell() - $size,
313             );
314             }
315 8         37 $et->FoundTag($tagInfo, $val);
316             }
317 1         5 delete $$et{DOC_NUM};
318 1 50       4 if ($dim) {
319 1         6 $et->FoundTag('ImageWidth', $$dim[0]);
320 1         6 $et->FoundTag('ImageHeight', $$dim[1]);
321             }
322 1         7 return 1;
323             }
324              
325             1; # end
326              
327             __END__