File Coverage

blib/lib/Image/ExifTool/SigmaRaw.pm
Criterion Covered Total %
statement 195 222 87.8
branch 87 148 58.7
condition 13 33 39.3
subroutine 10 10 100.0
pod 0 6 0.0
total 305 419 72.7


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: SigmaRaw.pm
3             #
4             # Description: Read Sigma/Foveon RAW (X3F) meta information
5             #
6             # Revisions: 2005/10/16 - P. Harvey Created
7             # 2009/11/30 - P. Harvey Support X3F v2.3 written by Sigma DP2
8             #
9             # References: 1) http://www.x3f.info/technotes/FileDocs/X3F_Format.pdf
10             #------------------------------------------------------------------------------
11              
12             package Image::ExifTool::SigmaRaw;
13              
14 1     1   10 use strict;
  1         2  
  1         40  
15 1     1   5 use vars qw($VERSION);
  1         3  
  1         53  
16 1     1   7 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         307  
17 1     1   8 use Image::ExifTool::Sigma;
  1         4  
  1         3166  
18              
19             $VERSION = '1.30';
20              
21             sub ProcessX3FHeader($$$);
22             sub ProcessX3FDirectory($$$);
23             sub ProcessX3FProperties($$$);
24              
25             # main X3F sections (plus header stuff)
26             %Image::ExifTool::SigmaRaw::Main = (
27             PROCESS_PROC => \&ProcessX3FDirectory,
28             NOTES => q{
29             These tags are used in Sigma and Foveon RAW (.X3F) images. Metadata is also
30             extracted from the JpgFromRaw image if it exists (all models but the SD9 and
31             SD10). Currently, metadata may only be written to the embedded JpgFromRaw.
32             },
33             Header => {
34             SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Header' },
35             },
36             Header4 => {
37             SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Header4' },
38             },
39             HeaderExt => {
40             SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::HeaderExt' },
41             },
42             PROP => {
43             Name => 'Properties',
44             SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Properties' },
45             },
46             IMAG => {
47             Name => 'PreviewImage',
48             Groups => { 2 => 'Preview' },
49             Binary => 1,
50             },
51             IMA2 => [
52             {
53             Name => 'PreviewImage',
54             Condition => 'not $$self{IsJpgFromRaw}',
55             Groups => { 2 => 'Preview' },
56             Binary => 1,
57             },
58             {
59             Name => 'JpgFromRaw',
60             Groups => { 2 => 'Preview' },
61             Binary => 1,
62             },
63             ]
64             );
65              
66             # common X3F header structure
67             %Image::ExifTool::SigmaRaw::Header = (
68             PROCESS_PROC => \&ProcessX3FHeader,
69             FORMAT => 'int32u',
70             NOTES => 'Information extracted from the header of an X3F file.',
71             1 => {
72             Name => 'FileVersion',
73             ValueConv => '($val >> 16) . "." . ($val & 0xffff)',
74             },
75             2 => {
76             Name => 'ImageUniqueID',
77             # the serial number (with an extra leading "0") makes up
78             # the first 8 digits of this UID,
79             Format => 'undef[16]',
80             ValueConv => 'unpack("H*", $val)',
81             },
82             6 => {
83             Name => 'MarkBits',
84             PrintConv => { BITMASK => { } },
85             },
86             7 => 'ImageWidth',
87             8 => 'ImageHeight',
88             9 => 'Rotation',
89             10 => {
90             Name => 'WhiteBalance',
91             Format => 'string[32]',
92             },
93             18 => { #PH (DP2, FileVersion 2.3)
94             Name => 'SceneCaptureType',
95             Format => 'string[32]',
96             },
97             );
98              
99             # X3F version 4 header structure (ref PH)
100             %Image::ExifTool::SigmaRaw::Header4 = (
101             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
102             FORMAT => 'int32u',
103             NOTES => 'Header information for version 4.0 or greater X3F.',
104             1 => {
105             Name => 'FileVersion',
106             ValueConv => '($val >> 16) . "." . ($val & 0xffff)',
107             },
108             # 8 - undef[4]: 4 random ASCII characters
109             10 => 'ImageWidth',
110             11 => 'ImageHeight',
111             12 => 'Rotation',
112             # don't know what the rest of the header contains, but none of
113             # these values change in any of my samples...
114             );
115              
116             # extended header tags
117             %Image::ExifTool::SigmaRaw::HeaderExt = (
118             GROUPS => { 2 => 'Camera' },
119             FORMAT => 'float',
120             NOTES => 'Extended header data found in version 2.1 and 2.2 files',
121             0 => 'Unused',
122             1 => { Name => 'ExposureAdjust',PrintConv => 'sprintf("%.1f",$val)' },
123             2 => { Name => 'Contrast', PrintConv => 'sprintf("%.1f",$val)' },
124             3 => { Name => 'Shadow', PrintConv => 'sprintf("%.1f",$val)' },
125             4 => { Name => 'Highlight', PrintConv => 'sprintf("%.1f",$val)' },
126             5 => { Name => 'Saturation', PrintConv => 'sprintf("%.1f",$val)' },
127             6 => { Name => 'Sharpness', PrintConv => 'sprintf("%.1f",$val)' },
128             7 => { Name => 'RedAdjust', PrintConv => 'sprintf("%.1f",$val)' },
129             8 => { Name => 'GreenAdjust', PrintConv => 'sprintf("%.1f",$val)' },
130             9 => { Name => 'BlueAdjust', PrintConv => 'sprintf("%.1f",$val)' },
131             10 => { Name => 'X3FillLight', PrintConv => 'sprintf("%.1f",$val)' },
132             );
133              
134             # PROP tags
135             %Image::ExifTool::SigmaRaw::Properties = (
136             PROCESS_PROC => \&ProcessX3FProperties,
137             GROUPS => { 2 => 'Camera' },
138             PRIORITY => 0, # (because these aren't writable like the EXIF ones)
139             AEMODE => {
140             Name => 'MeteringMode',
141             PrintConv => {
142             8 => '8-segment',
143             C => 'Center-weighted average',
144             A => 'Average',
145             },
146             },
147             AFAREA => 'AFArea', # observed: CENTER_V
148             AFINFOCUS => 'AFInFocus', # observed: H
149             AFMODE => 'FocusMode',
150             AP_DESC => 'ApertureDisplayed',
151             APERTURE => {
152             Name => 'FNumber',
153             Groups => { 2 => 'Image' },
154             PrintConv => 'sprintf("%.1f",$val)',
155             },
156             BRACKET => 'BracketShot',
157             BURST => 'BurstShot',
158             CAMMANUF => 'Make',
159             CAMMODEL => 'Model',
160             CAMNAME => 'CameraName',
161             CAMSERIAL => 'SerialNumber',
162             CM_DESC => 'SceneCaptureType', #PH (DP2)
163             COLORSPACE => 'ColorSpace', # observed: sRGB
164             DRIVE => {
165             Name => 'DriveMode',
166             PrintConv => {
167             SINGLE => 'Single Shot',
168             MULTI => 'Multi Shot',
169             '2S' => '2 s Timer',
170             '10S' => '10 s Timer',
171             UP => 'Mirror Up',
172             AB => 'Auto Bracket',
173             OFF => 'Off',
174             },
175             },
176             EVAL_STATE => 'EvalState', # observed: POST-EXPOSURE
177             EXPCOMP => {
178             Name => 'ExposureCompensation',
179             Groups => { 2 => 'Image' },
180             PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
181             },
182             EXPNET => {
183             Name => 'NetExposureCompensation',
184             Groups => { 2 => 'Image' },
185             PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
186             },
187             EXPTIME => {
188             Name => 'IntegrationTime',
189             Groups => { 2 => 'Image' },
190             ValueConv => '$val * 1e-6', # convert from usec
191             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
192             },
193             FIRMVERS => 'FirmwareVersion',
194             FLASH => {
195             Name => 'FlashMode',
196             PrintConv => 'ucfirst(lc($val))',
197             },
198             FLASHEXPCOMP=> 'FlashExpComp',
199             FLASHPOWER => 'FlashPower',
200             FLASHTTLMODE=> 'FlashTTLMode', # observed: ON
201             FLASHTYPE => 'FlashType', # observed: NONE
202             FLENGTH => {
203             Name => 'FocalLength',
204             PrintConv => 'sprintf("%.1f mm",$val)',
205             },
206             FLEQ35MM => {
207             Name => 'FocalLengthIn35mmFormat',
208             PrintConv => 'sprintf("%.1f mm",$val)',
209             },
210             FOCUS => {
211             Name => 'Focus',
212             PrintConv => {
213             AF => 'Auto-focus Locked',
214             'NO LOCK' => "Auto-focus Didn't Lock",
215             M => 'Manual',
216             },
217             },
218             IMAGERBOARDID => 'ImagerBoardID',
219             IMAGERTEMP => {
220             Name => 'SensorTemperature',
221             PrintConv => '"$val C"',
222             },
223             IMAGEBOARDID=> 'ImageBoardID', #PH (DP2)
224             ISO => 'ISO',
225             LENSARANGE => 'LensApertureRange',
226             LENSFRANGE => 'LensFocalRange',
227             LENSMODEL => {
228             Name => 'LensType',
229             ValueConv => '$val =~ /^[0-9a-f]+$/i ? hex($val) : $val',
230             ValueConvInv => '$val=~s/\.\d+$//; IsInt($val) ? sprintf("%x",$val) : $val', # (truncate decimal part)
231             SeparateTable => 'Sigma LensType',
232             PrintHex => 1,
233             PrintConv => \%Image::ExifTool::Sigma::sigmaLensTypes,
234             },
235             PMODE => {
236             Name => 'ExposureProgram',
237             PrintConv => {
238             P => 'Program',
239             A => 'Aperture Priority',
240             S => 'Shutter Priority',
241             M => 'Manual',
242             },
243             },
244             RESOLUTION => {
245             Name => 'Quality',
246             PrintConv => {
247             LOW => 'Low',
248             MED => 'Medium',
249             HI => 'High',
250             },
251             },
252             SENSORID => 'SensorID',
253             SH_DESC => 'ShutterSpeedDisplayed',
254             SHUTTER => {
255             Name => 'ExposureTime',
256             Groups => { 2 => 'Image' },
257             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
258             },
259             TIME => {
260             Name => 'DateTimeOriginal',
261             Groups => { 2 => 'Time' },
262             Description => 'Date/Time Original',
263             ValueConv => 'ConvertUnixTime($val)',
264             PrintConv => '$self->ConvertDateTime($val)',
265             },
266             WB_DESC => 'WhiteBalance',
267             VERSION_BF => 'VersionBF',
268             );
269              
270             #------------------------------------------------------------------------------
271             # Extract null-terminated unicode string from list of characters
272             # Inputs: 0) ExifTool ref, 1) list ref, 2) position in list
273             # Returns: Converted string
274             sub ExtractUnicodeString($$$)
275             {
276 118     118 0 196 my ($et, $chars, $pos) = @_;
277 118         177 my $i;
278 118         267 for ($i=$pos; $i<@$chars; ++$i) {
279 808 100       1665 last unless $$chars[$i];
280             }
281 118         371 my $buff = pack('v*', @$chars[$pos..$i-1]);
282 118         391 return $et->Decode($buff, 'UCS2', 'II');
283             }
284              
285             #------------------------------------------------------------------------------
286             # Process an X3F header
287             # Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
288             # Returns: 1 on success
289             sub ProcessX3FHeader($$$)
290             {
291 2     2 0 6 my ($et, $dirInfo, $tagTablePtr) = @_;
292 2         6 my $dataPt = $$dirInfo{DataPt};
293 2         4 my $hdrLen = $$dirInfo{DirLen};
294              
295             # process the static header structure first
296 2         14 $et->ProcessBinaryData($dirInfo, $tagTablePtr);
297              
298             # process extended data if available
299 2 50       9 if (length($$dataPt) - $hdrLen >= 160) {
300 2         8 my $verbose = $et->Options('Verbose');
301 2 50       5 if ($verbose) {
302 0         0 $et->VerboseDir('X3F HeaderExt', 32);
303 0         0 $et->VerboseDump($dataPt, Start => $hdrLen);
304             }
305 2         10 $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::HeaderExt');
306 2         13 my @tags = unpack("x${hdrLen}C32", $$dataPt);
307 2         5 my $i;
308 2         4 my $unused = 0;
309 2         8 for ($i=0; $i<32; ++$i) {
310 64 100       140 $tags[$i] or ++$unused, next;
311 20         84 $et->HandleTag($tagTablePtr, $tags[$i], undef,
312             Index => $i,
313             DataPt => $dataPt,
314             Start => $hdrLen + 32 + $i * 4,
315             Size => 4,
316             );
317             }
318 2         20 $et->VPrint(0, "$$et{INDENT}($unused entries unused)\n");
319             }
320 2         6 return 1;
321             }
322              
323             #------------------------------------------------------------------------------
324             # Process an X3F properties
325             # Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
326             # Returns: 1 on success
327             sub ProcessX3FProperties($$$)
328             {
329 2     2 0 12 my ($et, $dirInfo, $tagTablePtr) = @_;
330 2         4 my $dataPt = $$dirInfo{DataPt};
331 2         6 my $size = length($$dataPt);
332 2         18 my $verbose = $et->Options('Verbose');
333 2         14 my $unknown = $et->Options('Unknown');
334              
335 2 50 33     21 unless ($size >= 24 and $$dataPt =~ /^SECp/) {
336 0         0 $et->Warn('Bad properties header');
337 0         0 return 0;
338             }
339 2         26 my ($entries, $fmt, $len) = unpack('x8V2x4V', $$dataPt);
340 2 50       9 unless ($size >= 24 + 8 * $entries + $len) {
341 0         0 $et->Warn('Truncated Property directory');
342 0         0 return 0;
343             }
344 2 50       8 $verbose and $et->VerboseDir('Properties', $entries);
345 2 50       14 $fmt == 0 or $et->Warn("Unsupported character format $fmt"), return 0;
346 2         6 my $charPos = 24 + 8 * $entries;
347 2         82 my @chars = unpack('v*',substr($$dataPt, $charPos, $len * 2));
348 2         9 my $index;
349 2         10 for ($index=0; $index<$entries; ++$index) {
350 59         212 my ($namePos, $valPos) = unpack('V2',substr($$dataPt, $index*8 + 24, 8));
351 59 50 33     237 if ($namePos >= @chars or $valPos >= @chars) {
352 0         0 $et->Warn('Bad Property pointer');
353 0         0 return 0;
354             }
355 59         129 my $tag = ExtractUnicodeString($et, \@chars, $namePos);
356 59         133 my $val = ExtractUnicodeString($et, \@chars, $valPos);
357 59 0 33     219 if (not $$tagTablePtr{$tag} and $unknown and $tag =~ /^\w+$/) {
      33        
358 0         0 my $tagInfo = {
359             Name => "SigmaRaw_$tag",
360             Description => Image::ExifTool::MakeDescription('SigmaRaw', $tag),
361             Unknown => 1,
362             Writable => 0, # can't write unknown tags
363             };
364             # add tag information to table
365 0         0 AddTagToTable($tagTablePtr, $tag, $tagInfo);
366             }
367              
368 59         201 $et->HandleTag($tagTablePtr, $tag, $val,
369             Index => $index,
370             DataPt => $dataPt,
371             Start => $charPos + 2 * $valPos,
372             Size => 2 * (length($val) + 1),
373             );
374             }
375 2         20 return 1;
376             }
377              
378             #------------------------------------------------------------------------------
379             # Write an X3F file
380             # Inputs: 0) ExifTool ref, 1) DirInfo ref (DirStart = directory offset)
381             # Returns: error string, undef on success, or -1 on write error
382             # Notes: Writes metadata to embedded JpgFromRaw image
383             sub WriteX3F($$)
384             {
385 1     1 0 3 my ($et, $dirInfo) = @_;
386 1         3 my $raf = $$dirInfo{RAF};
387 1         3 my $outfile = $$dirInfo{OutFile};
388 1         4 my ($hdr, $buff, $ver, $entries, $dir, $outPos, $index, $didContain, %order, @order);
389              
390 1 50       13 $raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start';
391              
392             # read the X3F directory header (will be copied directly to output)
393 1 50       7 $raf->Read($hdr, 12) == 12 or return 'Truncated X3F image';
394 1 50       7 $hdr =~ /^SECd/ or return 'Bad section header';
395 1         5 ($ver, $entries) = unpack('x4V2', $hdr);
396              
397             # do sanity check on number of entries in directory
398 1 50 33     9 return 'Invalid X3F directory count' unless $entries > 2 and $entries < 20;
399             # read the directory entries
400 1 50       6 unless ($raf->Read($dir, $entries * 12) == $entries * 12) {
401 0         0 return 'Truncated X3F directory';
402             }
403             # do a quick scan to determine the offset of the first data subsection,
404             # and the order in which the actual data is stored in the file
405 1         5 for ($index=0; $index<$entries; ++$index) {
406 5         9 my $pos = $index * 12;
407 5         17 my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
408             # remember position of first data subsection
409 5 100 66     21 $outPos = $offset if not defined $outPos or $outPos > $offset;
410             # save the order of the data
411 5 50       13 $order{BAD} = 1 if defined $order{$offset};
412 5         18 $order{$offset} = $index;
413             }
414             # copy the file header up to the start of the first data subsection
415 1 50 33     8 unless ($raf->Seek(0,0) and $raf->Read($buff, $outPos) == $outPos) {
416 0         0 return 'Error reading X3F header';
417             }
418 1 50       8 Write($outfile, $buff) or return -1;
419              
420             # this is a bit tricky/unfortunate: the current version of Sigma Photo Pro
421             # (2022-10-18) is sensitive to the order of the data sections, and these may
422             # differ from the order of their respective entries in the footer. To patch
423             # this, instead of looping through the footer sections in order, we process
424             # them in the order of the offsets they contain, writing their referenced data
425             # sequentially as we go. This preserves both the order of the data sections
426             # and the order of the footer entries. (Note that the upcoming release of
427             # Sigma Photo Pro will fix this issue at their end, but this patch will remain
428             # to maintain backward compatibilty with older SPP versions.)
429 1 50       6 if ($order{BAD}) {
430             # (this could perhaps happen if any of the sections is ever zero-length)
431 0         0 $et->Error('Double-referenced data in footer directory!', 1);
432 0         0 @order = ( 0 .. $entries-1 );
433             } else {
434 1         7 @order = map $order{$_}, sort { $a <=> $b } keys %order;
  8         31  
435             }
436              
437             # loop through footer directory, rewriting each section
438 1         4 foreach $index (@order) {
439              
440 5         12 my $pos = $index * 12;
441 5         25 my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
442 5 50       18 $raf->Seek($offset, 0) or return 'Bad data offset';
443              
444 5 100 66     37 if ($tag eq 'IMA2' and $len > 28) {
445             # check subsection header (28 bytes) to see if this is a JPEG preview image
446 3 50       11 $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
447 3 50       14 Write($outfile, $buff) or return -1;
448 3         7 $len -= 28;
449              
450             # only rewrite full-sized JpgFromRaw (version 2.0, type 2, format 18)
451 3 100       16 if ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/) {
452 2 50       9 $raf->Read($buff, $len) == $len or return 'Error reading JpgFromRaw';
453 2 100       11 if ($buff =~ /^\xff\xd8\xff\xe1/) { # does this preview contain EXIF?
454             # use same write directories as JPEG
455 1         7 $et->InitWriteDirs('JPEG');
456             # make sure we don't add APP0 JFIF because it would mess up our preview identification
457 1         4 delete $$et{ADD_DIRS}{APP0};
458 1         4 delete $$et{ADD_DIRS}{JFIF};
459             # rewrite the embedded JPEG in memory
460 1         2 my $newData;
461 1         15 my %jpegInfo = (
462             Parent => 'X3F',
463             RAF => new File::RandomAccess(\$buff),
464             OutFile => \$newData,
465             );
466 1         4 $$et{FILE_TYPE} = 'JPEG';
467 1         7 my $success = $et->WriteJPEG(\%jpegInfo);
468 1         4 $$et{FILE_TYPE} = 'X3F';
469 1         5 SetByteOrder('II');
470 1 50 33     7 return 'Error writing X3F JpgFromRaw' unless $success and $newData;
471 1 50       5 return -1 if $success < 0;
472             # (this shouldn't happen unless someone tries to delete the EXIF...)
473 1 50       7 return 'EXIF segment must come first in X3F JpgFromRaw' unless $newData =~ /^\xff\xd8\xff\xe1/;
474             # trim off any extra null bytes (since section length includes padding -- silly Sigma)
475 1         82 $newData =~ s/\0+$//;
476             # write new data if anything changed, otherwise copy old image
477 1 50       8 my $outPt = $$et{CHANGED} ? \$newData : \$buff;
478 1 50       7 Write($outfile, $$outPt) or return -1;
479             # set $len to the total subsection data length
480 1         11 $len = length($$outPt);
481 1         7 $didContain = 1;
482             } else {
483 1 50       4 Write($outfile, $buff) or return -1;
484             }
485             } else {
486             # copy original image data
487 1 50       6 Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F image';
488             }
489 3         8 $len += 28; # add back header length
490             } else {
491             # copy data for this subsection
492 2 50       11 Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F directory';
493             }
494             # pad data to an even 4-byte boundary
495             # (stored length includes padding! ref Sigma engineer Yuki Miyahara)
496 5 100       14 if ($len & 0x03) {
497 1         5 my $pad = 4 - ($len & 0x03);
498 1 50       6 Write($outfile, "\0" x $pad) or return -1;
499 1         3 $len += $pad;
500             }
501             # update footer entry with new offset/size
502 5         18 substr($dir, $pos, 8) = pack('V2', $outPos, $len);
503 5         13 $outPos += $len;
504             }
505             # warn if we couldn't add metadata to this image (should only be SD9 or SD10)
506 1 50       4 $didContain or $et->Warn("Can't yet write SD9 or SD10 X3F images");
507             # write out the directory and the directory pointer, and we are done
508 1 50       7 Write($outfile, $hdr, $dir, pack('V', $outPos)) or return -1;
509 1         8 return undef;
510             }
511              
512             #------------------------------------------------------------------------------
513             # Process an X3F directory
514             # Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
515             # Returns: error string or undef on success
516             sub ProcessX3FDirectory($$$)
517             {
518 2     2 0 11 my ($et, $dirInfo, $tagTablePtr) = @_;
519 2         5 my $raf = $$dirInfo{RAF};
520 2         6 my $verbose = $et->Options('Verbose');
521              
522 2 50       8 $raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start';
523              
524             # parse the X3F directory structure
525 2         6 my ($buff, $ver, $entries, $index, $dir);
526 2 50       8 $raf->Read($buff, 12) == 12 or return 'Truncated X3F image';
527 2 50       22 $buff =~ /^SECd/ or return 'Bad section header';
528 2         9 ($ver, $entries) = unpack('x4V2', $buff);
529 2 50       9 $verbose and $et->VerboseDir('X3F Subsection', $entries);
530 2 50       9 $raf->Read($dir, $entries * 12) == $entries * 12 or return 'Truncated X3F directory';
531 2         10 for ($index=0; $index<$entries; ++$index) {
532 10         20 my $pos = $index * 12;
533 10         49 my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
534 10         38 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
535 10 50       29 if ($verbose) {
536 0         0 $et->VPrint(0, "$$et{INDENT}$index) $tag Subsection ($len bytes):\n");
537 0 0       0 if ($verbose > 2) {
538 0 0       0 $raf->Seek($offset, 0) or return 'Error seeking';
539 0 0       0 $raf->Read($buff, $len) == $len or return 'Truncated image';
540 0         0 $et->VerboseDump(\$buff);
541             }
542             }
543 10 100       25 next unless $tagInfo;
544 8 50       28 $raf->Seek($offset, 0) or return "Error seeking for $$tagInfo{Name}";
545 8 100       36 if ($$tagInfo{Name} eq 'PreviewImage') {
546             # check image header to see if this is a JPEG preview image
547 6 50       24 $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
548 6         20 $offset += 28;
549 6         14 $len -= 28;
550             # ignore all image data but JPEG compressed (version 2.0, type 2, format 18)
551 6 100       31 unless ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/) {
552             # do MD5 on non-preview data if requested
553 4 50 33     28 if ($$et{ImageDataMD5} and substr($buff,8,1) ne "\x02") {
554 0         0 $et->ImageDataMD5($raf, $len, 'SigmaRaw IMAG');
555             }
556 4         16 next;
557             }
558 2 50       12 $raf->Read($buff, $len) == $len or return "Error reading PreviewImage data";
559             # check fore EXIF segment, and extract this image as the JpgFromRaw
560 2 100       20 if ($buff =~ /^\xff\xd8\xff\xe1/) {
561 1         4 $$et{IsJpgFromRaw} = 1;
562 1         5 $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
563 1         3 delete $$et{IsJpgFromRaw};
564             }
565             } else {
566 2 50       11 $raf->Read($buff, $len) == $len or return "Error reading $$tagInfo{Name} data";
567             }
568 4         11 my $subdir = $$tagInfo{SubDirectory};
569 4 100       26 if ($subdir) {
570 2         11 my %dirInfo = ( DataPt => \$buff );
571 2         10 my $subTable = GetTagTable($$subdir{TagTable});
572 2         13 $et->ProcessDirectory(\%dirInfo, $subTable);
573             } else {
574             # extract metadata from JpgFromRaw
575 2 100       10 if ($$tagInfo{Name} eq 'JpgFromRaw') {
576 1         6 my %dirInfo = (
577             Parent => 'X3F',
578             RAF => new File::RandomAccess(\$buff),
579             );
580 1         3 $$et{BASE} += $offset;
581 1         8 $et->ProcessJPEG(\%dirInfo);
582 1         4 $$et{BASE} -= $offset;
583 1         5 SetByteOrder('II');
584             }
585 2         9 $et->FoundTag($tagInfo, $buff);
586             }
587             }
588 2         18 return undef;
589             }
590              
591             #------------------------------------------------------------------------------
592             # Read/write information from a Sigma raw (X3F) image
593             # Inputs: 0) ExifTool ref, 1) DirInfo ref
594             # Returns: 1 on success, 0 if this wasn't a valid X3F image, or -1 on write error
595             sub ProcessX3F($$)
596             {
597 3     3 0 13 my ($et, $dirInfo) = @_;
598 3         9 my $outfile = $$dirInfo{OutFile};
599 3         9 my $raf = $$dirInfo{RAF};
600 3 100       13 my $warn = $outfile ? \&Image::ExifTool::Error : \&Image::ExifTool::Warn;
601 3         7 my ($buff, $err, $hdrLen);
602              
603 3 50       13 return 0 unless $raf->Read($buff, 40) == 40;
604 3 50       20 return 0 unless $buff =~ /^FOVb/;
605              
606 3         13 SetByteOrder('II');
607 3         23 $et->SetFileType();
608              
609             # check version number
610 3         14 my $ver = unpack('x4V',$buff);
611 3         20 $ver = ($ver >> 16) . '.' . ($ver & 0xffff);
612 3 50       21 if ($ver > 5) {
613 0         0 &$warn($et, "Untested X3F version ($ver). Please submit sample for testing", 1);
614             }
615             # read version 2.1/2.2/2.3 extended header
616 3 50       12 if ($ver > 2) {
617 3         6 my ($extra, $buf2);
618 3 50       12 if ($ver >= 4) {
619 0         0 $hdrLen = 0x300;
620 0         0 $extra = 0;
621             } else {
622 3 100       14 $hdrLen = $ver > 2.2 ? 104 : 72; # SceneCaptureType string added in 2.3
623 3         9 $extra = 160; # (extended header is 160 bytes)
624             }
625 3         9 my $more = $hdrLen - length($buff) + $extra;
626 3 50       12 unless ($raf->Read($buf2, $more) == $more) {
627 0         0 &$warn($et, 'Error reading X3F header');
628 0         0 return 1;
629             }
630 3         14 $buff .= $buf2;
631             }
632 3 50       33 my ($widPos, $hdrType) = $ver < 4 ? (28, 'Header') : (40, 'Header4');
633             # process header information
634 3         13 my $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::Main');
635 3 100       18 unless ($outfile) {
636 2         14 $et->HandleTag($tagTablePtr, $hdrType, $buff,
637             DataPt => \$buff,
638             Size => $hdrLen,
639             );
640             }
641             # read the directory pointer
642 3 50       13 $raf->Seek(-4, 2) or &$warn($et, 'Seek error'), return 1;
643 3 50       15 unless ($raf->Read($buff, 4) == 4) {
644 0         0 &$warn($et, 'Error reading X3F dir pointer');
645 0         0 return 1;
646             }
647 3         13 my $offset = unpack('V', $buff);
648 3         13 my %dirInfo = (
649             RAF => $raf,
650             DirStart => $offset,
651             );
652 3 100       12 if ($outfile) {
653 1         4 $dirInfo{OutFile} = $outfile;
654 1         7 $err = WriteX3F($et, \%dirInfo);
655 1 50 33     6 return -1 if $err and $err eq '-1';
656             } else {
657             # process the X3F subsections
658 2         11 $err = $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
659             }
660 3 50       20 $err and &$warn($et, $err);
661 3         23 return 1;
662             }
663              
664             1; # end
665              
666             __END__