File Coverage

blib/lib/Image/ExifTool/SigmaRaw.pm
Criterion Covered Total %
statement 187 211 88.6
branch 84 142 59.1
condition 12 30 40.0
subroutine 10 10 100.0
pod 0 6 0.0
total 293 399 73.4


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   7 use strict;
  1         2  
  1         32  
15 1     1   5 use vars qw($VERSION);
  1         1  
  1         43  
16 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         207  
17 1     1   6 use Image::ExifTool::Sigma;
  1         2  
  1         2488  
18              
19             $VERSION = '1.27';
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 178 my ($et, $chars, $pos) = @_;
277 118         125 my $i;
278 118         220 for ($i=$pos; $i<@$chars; ++$i) {
279 808 100       1416 last unless $$chars[$i];
280             }
281 118         302 my $buff = pack('v*', @$chars[$pos..$i-1]);
282 118         273 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 5 my ($et, $dirInfo, $tagTablePtr) = @_;
292 2         5 my $dataPt = $$dirInfo{DataPt};
293 2         3 my $hdrLen = $$dirInfo{DirLen};
294              
295             # process the static header structure first
296 2         10 $et->ProcessBinaryData($dirInfo, $tagTablePtr);
297              
298             # process extended data if available
299 2 50       6 if (length($$dataPt) - $hdrLen >= 160) {
300 2         6 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         6 $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::HeaderExt');
306 2         10 my @tags = unpack("x${hdrLen}C32", $$dataPt);
307 2         4 my $i;
308 2         5 my $unused = 0;
309 2         6 for ($i=0; $i<32; ++$i) {
310 64 100       113 $tags[$i] or ++$unused, next;
311 20         53 $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         15 $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 5 my ($et, $dirInfo, $tagTablePtr) = @_;
330 2         2 my $dataPt = $$dirInfo{DataPt};
331 2         5 my $size = length($$dataPt);
332 2         7 my $verbose = $et->Options('Verbose');
333 2         9 my $unknown = $et->Options('Unknown');
334              
335 2 50 33     18 unless ($size >= 24 and $$dataPt =~ /^SECp/) {
336 0         0 $et->Warn('Bad properties header');
337 0         0 return 0;
338             }
339 2         8 my ($entries, $fmt, $len) = unpack('x8V2x4V', $$dataPt);
340 2 50       6 unless ($size >= 24 + 8 * $entries + $len) {
341 0         0 $et->Warn('Truncated Property directory');
342 0         0 return 0;
343             }
344 2 50       6 $verbose and $et->VerboseDir('Properties', $entries);
345 2 50       5 $fmt == 0 or $et->Warn("Unsupported character format $fmt"), return 0;
346 2         4 my $charPos = 24 + 8 * $entries;
347 2         94 my @chars = unpack('v*',substr($$dataPt, $charPos, $len * 2));
348 2         7 my $index;
349 2         8 for ($index=0; $index<$entries; ++$index) {
350 59         176 my ($namePos, $valPos) = unpack('V2',substr($$dataPt, $index*8 + 24, 8));
351 59 50 33     179 if ($namePos >= @chars or $valPos >= @chars) {
352 0         0 $et->Warn('Bad Property pointer');
353 0         0 return 0;
354             }
355 59         106 my $tag = ExtractUnicodeString($et, \@chars, $namePos);
356 59         109 my $val = ExtractUnicodeString($et, \@chars, $valPos);
357 59 0 33     148 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         177 $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         18 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         4 my $outfile = $$dirInfo{OutFile};
388 1         2 my ($outDir, $buff, $ver, $entries, $dir, $outPos, $index, $didContain);
389              
390 1 50       4 $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       4 $raf->Read($outDir, 12) == 12 or return 'Truncated X3F image';
394 1 50       5 $outDir =~ /^SECd/ or return 'Bad section header';
395 1         5 ($ver, $entries) = unpack('x4V2', $outDir);
396              
397             # do sanity check on number of entries in directory
398 1 50 33     7 return 'Invalid X3F directory count' unless $entries > 2 and $entries < 20;
399             # read the directory entries
400 1 50       4 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 1         5 for ($index=0; $index<$entries; ++$index) {
405 5         7 my $pos = $index * 12;
406 5         13 my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
407             # remember position of first data subsection
408 5 100 66     20 $outPos = $offset if not defined $outPos or $outPos > $offset;
409             }
410             # copy the file header up to the start of the first data subsection
411 1 50 33     6 unless ($raf->Seek(0,0) and $raf->Read($buff, $outPos) == $outPos) {
412 0         0 return 'Error reading X3F header';
413             }
414 1 50       6 Write($outfile, $buff) or return -1;
415              
416             # loop through directory, rewriting each section
417 1         5 for ($index=0; $index<$entries; ++$index) {
418              
419 5         11 my $pos = $index * 12;
420 5         19 my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
421 5 50       14 $raf->Seek($offset, 0) or return 'Bad data offset';
422              
423 5 100 66     27 if ($tag eq 'IMA2' and $len > 28) {
424             # check subsection header (28 bytes) to see if this is a JPEG preview image
425 3 50       10 $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
426 3 50       26 Write($outfile, $buff) or return -1;
427 3         9 $len -= 28;
428              
429             # only rewrite full-sized JpgFromRaw (version 2.0, type 2, format 18)
430 3 100       13 if ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/) {
431 2 50       7 $raf->Read($buff, $len) == $len or return 'Error reading JpgFromRaw';
432 2 100       7 if ($buff =~ /^\xff\xd8\xff\xe1/) { # does this preview contain EXIF?
433             # use same write directories as JPEG
434 1         10 $et->InitWriteDirs('JPEG');
435             # make sure we don't add APP0 JFIF because it would mess up our preview identification
436 1         3 delete $$et{ADD_DIRS}{APP0};
437 1         3 delete $$et{ADD_DIRS}{JFIF};
438             # rewrite the embedded JPEG in memory
439 1         2 my $newData;
440 1         5 my %jpegInfo = (
441             Parent => 'X3F',
442             RAF => new File::RandomAccess(\$buff),
443             OutFile => \$newData,
444             );
445 1         3 $$et{FILE_TYPE} = 'JPEG';
446 1         5 my $success = $et->WriteJPEG(\%jpegInfo);
447 1         3 $$et{FILE_TYPE} = 'X3F';
448 1         4 SetByteOrder('II');
449 1 50 33     5 return 'Error writing X3F JpgFromRaw' unless $success and $newData;
450 1 50       3 return -1 if $success < 0;
451             # (this shouldn't happen unless someone tries to delete the EXIF...)
452 1 50       14 return 'EXIF segment must come first in X3F JpgFromRaw' unless $newData =~ /^\xff\xd8\xff\xe1/;
453             # write new data if anything changed, otherwise copy old image
454 1 50       4 my $outPt = $$et{CHANGED} ? \$newData : \$buff;
455 1 50       5 Write($outfile, $$outPt) or return -1;
456             # set $len to the total subsection data length
457 1         3 $len = length($$outPt);
458 1         4 $didContain = 1;
459             } else {
460 1 50       3 Write($outfile, $buff) or return -1;
461             }
462             } else {
463             # copy original image data
464 1 50       4 Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F image';
465             }
466 3         5 $len += 28; # add back header length
467             } else {
468             # copy data for this subsection
469 2 50       9 Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F directory';
470             }
471             # add directory entry and update output file position
472 5         19 $outDir .= pack('V2a4', $outPos, $len, $tag);
473 5         6 $outPos += $len;
474             # pad data to an even 4-byte boundary
475 5 100       10 if ($len & 0x03) {
476 4         6 my $pad = 4 - ($len & 0x03);
477 4 50       10 Write($outfile, "\0" x $pad) or return -1;
478 4         12 $outPos += $pad;
479             }
480             }
481             # warn if we couldn't add metadata to this image (should only be SD9 or SD10)
482 1 50       3 $didContain or $et->Warn("Can't yet write SD9 or SD10 X3F images");
483             # write out the directory and the directory pointer, and we are done
484 1 50       4 Write($outfile, $outDir, pack('V', $outPos)) or return -1;
485 1         5 return undef;
486             }
487              
488             #------------------------------------------------------------------------------
489             # Process an X3F directory
490             # Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
491             # Returns: error string or undef on success
492             sub ProcessX3FDirectory($$$)
493             {
494 2     2 0 5 my ($et, $dirInfo, $tagTablePtr) = @_;
495 2         2 my $raf = $$dirInfo{RAF};
496 2         7 my $verbose = $et->Options('Verbose');
497              
498 2 50       9 $raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start';
499              
500             # parse the X3F directory structure
501 2         5 my ($buff, $ver, $entries, $index, $dir);
502 2 50       7 $raf->Read($buff, 12) == 12 or return 'Truncated X3F image';
503 2 50       15 $buff =~ /^SECd/ or return 'Bad section header';
504 2         6 ($ver, $entries) = unpack('x4V2', $buff);
505 2 50       7 $verbose and $et->VerboseDir('X3F Subsection', $entries);
506 2 50       6 $raf->Read($dir, $entries * 12) == $entries * 12 or return 'Truncated X3F directory';
507 2         13 for ($index=0; $index<$entries; ++$index) {
508 10         17 my $pos = $index * 12;
509 10         38 my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
510 10         27 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
511 10 50       26 if ($verbose) {
512 0         0 $et->VPrint(0, "$$et{INDENT}$index) $tag Subsection ($len bytes):\n");
513 0 0       0 if ($verbose > 2) {
514 0 0       0 $raf->Seek($offset, 0) or return 'Error seeking';
515 0 0       0 $raf->Read($buff, $len) == $len or return 'Truncated image';
516 0         0 $et->VerboseDump(\$buff);
517             }
518             }
519 10 100       25 next unless $tagInfo;
520 8 50       21 $raf->Seek($offset, 0) or return "Error seeking for $$tagInfo{Name}";
521 8 100       26 if ($$tagInfo{Name} eq 'PreviewImage') {
522             # check image header to see if this is a JPEG preview image
523 6 50       18 $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
524             # ignore all image data but JPEG compressed (version 2.0, type 2, format 18)
525 6 100       23 next unless $buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/;
526 2         4 $offset += 28;
527 2         3 $len -= 28;
528 2 50       7 $raf->Read($buff, $len) == $len or return "Error reading PreviewImage data";
529             # check fore EXIF segment, and extract this image as the JpgFromRaw
530 2 100       8 if ($buff =~ /^\xff\xd8\xff\xe1/) {
531 1         3 $$et{IsJpgFromRaw} = 1;
532 1         4 $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
533 1         3 delete $$et{IsJpgFromRaw};
534             }
535             } else {
536 2 50       6 $raf->Read($buff, $len) == $len or return "Error reading $$tagInfo{Name} data";
537             }
538 4         9 my $subdir = $$tagInfo{SubDirectory};
539 4 100       9 if ($subdir) {
540 2         8 my %dirInfo = ( DataPt => \$buff );
541 2         7 my $subTable = GetTagTable($$subdir{TagTable});
542 2         8 $et->ProcessDirectory(\%dirInfo, $subTable);
543             } else {
544             # extract metadata from JpgFromRaw
545 2 100       6 if ($$tagInfo{Name} eq 'JpgFromRaw') {
546 1         5 my %dirInfo = (
547             Parent => 'X3F',
548             RAF => new File::RandomAccess(\$buff),
549             );
550 1         3 $$et{BASE} += $offset;
551 1         5 $et->ProcessJPEG(\%dirInfo);
552 1         2 $$et{BASE} -= $offset;
553 1         4 SetByteOrder('II');
554             }
555 2         15 $et->FoundTag($tagInfo, $buff);
556             }
557             }
558 2         4 return undef;
559             }
560              
561             #------------------------------------------------------------------------------
562             # Read/write information from a Sigma raw (X3F) image
563             # Inputs: 0) ExifTool ref, 1) DirInfo ref
564             # Returns: 1 on success, 0 if this wasn't a valid X3F image, or -1 on write error
565             sub ProcessX3F($$)
566             {
567 3     3 0 7 my ($et, $dirInfo) = @_;
568 3         8 my $outfile = $$dirInfo{OutFile};
569 3         7 my $raf = $$dirInfo{RAF};
570 3 100       10 my $warn = $outfile ? \&Image::ExifTool::Error : \&Image::ExifTool::Warn;
571 3         8 my ($buff, $err, $hdrLen);
572              
573 3 50       8 return 0 unless $raf->Read($buff, 40) == 40;
574 3 50       16 return 0 unless $buff =~ /^FOVb/;
575              
576 3         13 SetByteOrder('II');
577 3         15 $et->SetFileType();
578              
579             # check version number
580 3         10 my $ver = unpack('x4V',$buff);
581 3         12 $ver = ($ver >> 16) . '.' . ($ver & 0xffff);
582 3 50       14 if ($ver > 5) {
583 0         0 &$warn($et, "Untested X3F version ($ver). Please submit sample for testing", 1);
584             }
585             # read version 2.1/2.2/2.3 extended header
586 3 50       9 if ($ver > 2) {
587 3         6 my ($extra, $buf2);
588 3 50       7 if ($ver >= 4) {
589 0         0 $hdrLen = 0x300;
590 0         0 $extra = 0;
591             } else {
592 3 100       8 $hdrLen = $ver > 2.2 ? 104 : 72; # SceneCaptureType string added in 2.3
593 3         5 $extra = 160; # (extended header is 160 bytes)
594             }
595 3         6 my $more = $hdrLen - length($buff) + $extra;
596 3 50       11 unless ($raf->Read($buf2, $more) == $more) {
597 0         0 &$warn($et, 'Error reading X3F header');
598 0         0 return 1;
599             }
600 3         8 $buff .= $buf2;
601             }
602 3 50       13 my ($widPos, $hdrType) = $ver < 4 ? (28, 'Header') : (40, 'Header4');
603             # process header information
604 3         8 my $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::Main');
605 3 100       9 unless ($outfile) {
606 2         8 $et->HandleTag($tagTablePtr, $hdrType, $buff,
607             DataPt => \$buff,
608             Size => $hdrLen,
609             );
610             }
611             # read the directory pointer
612 3 50       11 $raf->Seek(-4, 2) or &$warn($et, 'Seek error'), return 1;
613 3 50       12 unless ($raf->Read($buff, 4) == 4) {
614 0         0 &$warn($et, 'Error reading X3F dir pointer');
615 0         0 return 1;
616             }
617 3         8 my $offset = unpack('V', $buff);
618 3         10 my %dirInfo = (
619             RAF => $raf,
620             DirStart => $offset,
621             );
622 3 100       14 if ($outfile) {
623 1         2 $dirInfo{OutFile} = $outfile;
624 1         5 $err = WriteX3F($et, \%dirInfo);
625 1 50 33     3 return -1 if $err and $err eq '-1';
626             } else {
627             # process the X3F subsections
628 2         11 $err = $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
629             }
630 3 50       9 $err and &$warn($et, $err);
631 3         12 return 1;
632             }
633              
634             1; # end
635              
636             __END__