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 | use strict;
|
---|
15 | use vars qw($VERSION);
|
---|
16 | use Image::ExifTool qw(:DataAccess :Utils);
|
---|
17 | use Image::ExifTool::Sigma;
|
---|
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 | my ($et, $chars, $pos) = @_;
|
---|
277 | my $i;
|
---|
278 | for ($i=$pos; $i<@$chars; ++$i) {
|
---|
279 | last unless $$chars[$i];
|
---|
280 | }
|
---|
281 | my $buff = pack('v*', @$chars[$pos..$i-1]);
|
---|
282 | 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 | my ($et, $dirInfo, $tagTablePtr) = @_;
|
---|
292 | my $dataPt = $$dirInfo{DataPt};
|
---|
293 | my $hdrLen = $$dirInfo{DirLen};
|
---|
294 |
|
---|
295 | # process the static header structure first
|
---|
296 | $et->ProcessBinaryData($dirInfo, $tagTablePtr);
|
---|
297 |
|
---|
298 | # process extended data if available
|
---|
299 | if (length($$dataPt) - $hdrLen >= 160) {
|
---|
300 | my $verbose = $et->Options('Verbose');
|
---|
301 | if ($verbose) {
|
---|
302 | $et->VerboseDir('X3F HeaderExt', 32);
|
---|
303 | $et->VerboseDump($dataPt, Start => $hdrLen);
|
---|
304 | }
|
---|
305 | $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::HeaderExt');
|
---|
306 | my @tags = unpack("x${hdrLen}C32", $$dataPt);
|
---|
307 | my $i;
|
---|
308 | my $unused = 0;
|
---|
309 | for ($i=0; $i<32; ++$i) {
|
---|
310 | $tags[$i] or ++$unused, next;
|
---|
311 | $et->HandleTag($tagTablePtr, $tags[$i], undef,
|
---|
312 | Index => $i,
|
---|
313 | DataPt => $dataPt,
|
---|
314 | Start => $hdrLen + 32 + $i * 4,
|
---|
315 | Size => 4,
|
---|
316 | );
|
---|
317 | }
|
---|
318 | $et->VPrint(0, "$$et{INDENT}($unused entries unused)\n");
|
---|
319 | }
|
---|
320 | 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 | my ($et, $dirInfo, $tagTablePtr) = @_;
|
---|
330 | my $dataPt = $$dirInfo{DataPt};
|
---|
331 | my $size = length($$dataPt);
|
---|
332 | my $verbose = $et->Options('Verbose');
|
---|
333 | my $unknown = $et->Options('Unknown');
|
---|
334 |
|
---|
335 | unless ($size >= 24 and $$dataPt =~ /^SECp/) {
|
---|
336 | $et->Warn('Bad properties header');
|
---|
337 | return 0;
|
---|
338 | }
|
---|
339 | my ($entries, $fmt, $len) = unpack('x8V2x4V', $$dataPt);
|
---|
340 | unless ($size >= 24 + 8 * $entries + $len) {
|
---|
341 | $et->Warn('Truncated Property directory');
|
---|
342 | return 0;
|
---|
343 | }
|
---|
344 | $verbose and $et->VerboseDir('Properties', $entries);
|
---|
345 | $fmt == 0 or $et->Warn("Unsupported character format $fmt"), return 0;
|
---|
346 | my $charPos = 24 + 8 * $entries;
|
---|
347 | my @chars = unpack('v*',substr($$dataPt, $charPos, $len * 2));
|
---|
348 | my $index;
|
---|
349 | for ($index=0; $index<$entries; ++$index) {
|
---|
350 | my ($namePos, $valPos) = unpack('V2',substr($$dataPt, $index*8 + 24, 8));
|
---|
351 | if ($namePos >= @chars or $valPos >= @chars) {
|
---|
352 | $et->Warn('Bad Property pointer');
|
---|
353 | return 0;
|
---|
354 | }
|
---|
355 | my $tag = ExtractUnicodeString($et, \@chars, $namePos);
|
---|
356 | my $val = ExtractUnicodeString($et, \@chars, $valPos);
|
---|
357 | if (not $$tagTablePtr{$tag} and $unknown and $tag =~ /^\w+$/) {
|
---|
358 | 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 | AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
---|
366 | }
|
---|
367 |
|
---|
368 | $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 | 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 | my ($et, $dirInfo) = @_;
|
---|
386 | my $raf = $$dirInfo{RAF};
|
---|
387 | my $outfile = $$dirInfo{OutFile};
|
---|
388 | my ($outDir, $buff, $ver, $entries, $dir, $outPos, $index, $didContain);
|
---|
389 |
|
---|
390 | $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 | $raf->Read($outDir, 12) == 12 or return 'Truncated X3F image';
|
---|
394 | $outDir =~ /^SECd/ or return 'Bad section header';
|
---|
395 | ($ver, $entries) = unpack('x4V2', $outDir);
|
---|
396 |
|
---|
397 | # do sanity check on number of entries in directory
|
---|
398 | return 'Invalid X3F directory count' unless $entries > 2 and $entries < 20;
|
---|
399 | # read the directory entries
|
---|
400 | unless ($raf->Read($dir, $entries * 12) == $entries * 12) {
|
---|
401 | return 'Truncated X3F directory';
|
---|
402 | }
|
---|
403 | # do a quick scan to determine the offset of the first data subsection
|
---|
404 | for ($index=0; $index<$entries; ++$index) {
|
---|
405 | my $pos = $index * 12;
|
---|
406 | my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
|
---|
407 | # remember position of first data subsection
|
---|
408 | $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 | unless ($raf->Seek(0,0) and $raf->Read($buff, $outPos) == $outPos) {
|
---|
412 | return 'Error reading X3F header';
|
---|
413 | }
|
---|
414 | Write($outfile, $buff) or return -1;
|
---|
415 |
|
---|
416 | # loop through directory, rewriting each section
|
---|
417 | for ($index=0; $index<$entries; ++$index) {
|
---|
418 |
|
---|
419 | my $pos = $index * 12;
|
---|
420 | my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
|
---|
421 | $raf->Seek($offset, 0) or return 'Bad data offset';
|
---|
422 |
|
---|
423 | if ($tag eq 'IMA2' and $len > 28) {
|
---|
424 | # check subsection header (28 bytes) to see if this is a JPEG preview image
|
---|
425 | $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
|
---|
426 | Write($outfile, $buff) or return -1;
|
---|
427 | $len -= 28;
|
---|
428 |
|
---|
429 | # only rewrite full-sized JpgFromRaw (version 2.0, type 2, format 18)
|
---|
430 | if ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/) {
|
---|
431 | $raf->Read($buff, $len) == $len or return 'Error reading JpgFromRaw';
|
---|
432 | if ($buff =~ /^\xff\xd8\xff\xe1/) { # does this preview contain EXIF?
|
---|
433 | # use same write directories as JPEG
|
---|
434 | $et->InitWriteDirs('JPEG');
|
---|
435 | # make sure we don't add APP0 JFIF because it would mess up our preview identification
|
---|
436 | delete $$et{ADD_DIRS}{APP0};
|
---|
437 | delete $$et{ADD_DIRS}{JFIF};
|
---|
438 | # rewrite the embedded JPEG in memory
|
---|
439 | my $newData;
|
---|
440 | my %jpegInfo = (
|
---|
441 | Parent => 'X3F',
|
---|
442 | RAF => new File::RandomAccess(\$buff),
|
---|
443 | OutFile => \$newData,
|
---|
444 | );
|
---|
445 | $$et{FILE_TYPE} = 'JPEG';
|
---|
446 | my $success = $et->WriteJPEG(\%jpegInfo);
|
---|
447 | $$et{FILE_TYPE} = 'X3F';
|
---|
448 | SetByteOrder('II');
|
---|
449 | return 'Error writing X3F JpgFromRaw' unless $success and $newData;
|
---|
450 | return -1 if $success < 0;
|
---|
451 | # (this shouldn't happen unless someone tries to delete the EXIF...)
|
---|
452 | 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 | my $outPt = $$et{CHANGED} ? \$newData : \$buff;
|
---|
455 | Write($outfile, $$outPt) or return -1;
|
---|
456 | # set $len to the total subsection data length
|
---|
457 | $len = length($$outPt);
|
---|
458 | $didContain = 1;
|
---|
459 | } else {
|
---|
460 | Write($outfile, $buff) or return -1;
|
---|
461 | }
|
---|
462 | } else {
|
---|
463 | # copy original image data
|
---|
464 | Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F image';
|
---|
465 | }
|
---|
466 | $len += 28; # add back header length
|
---|
467 | } else {
|
---|
468 | # copy data for this subsection
|
---|
469 | Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F directory';
|
---|
470 | }
|
---|
471 | # add directory entry and update output file position
|
---|
472 | $outDir .= pack('V2a4', $outPos, $len, $tag);
|
---|
473 | $outPos += $len;
|
---|
474 | # pad data to an even 4-byte boundary
|
---|
475 | if ($len & 0x03) {
|
---|
476 | my $pad = 4 - ($len & 0x03);
|
---|
477 | Write($outfile, "\0" x $pad) or return -1;
|
---|
478 | $outPos += $pad;
|
---|
479 | }
|
---|
480 | }
|
---|
481 | # warn if we couldn't add metadata to this image (should only be SD9 or SD10)
|
---|
482 | $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 | Write($outfile, $outDir, pack('V', $outPos)) or return -1;
|
---|
485 | 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 | my ($et, $dirInfo, $tagTablePtr) = @_;
|
---|
495 | my $raf = $$dirInfo{RAF};
|
---|
496 | my $verbose = $et->Options('Verbose');
|
---|
497 |
|
---|
498 | $raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start';
|
---|
499 |
|
---|
500 | # parse the X3F directory structure
|
---|
501 | my ($buff, $ver, $entries, $index, $dir);
|
---|
502 | $raf->Read($buff, 12) == 12 or return 'Truncated X3F image';
|
---|
503 | $buff =~ /^SECd/ or return 'Bad section header';
|
---|
504 | ($ver, $entries) = unpack('x4V2', $buff);
|
---|
505 | $verbose and $et->VerboseDir('X3F Subsection', $entries);
|
---|
506 | $raf->Read($dir, $entries * 12) == $entries * 12 or return 'Truncated X3F directory';
|
---|
507 | for ($index=0; $index<$entries; ++$index) {
|
---|
508 | my $pos = $index * 12;
|
---|
509 | my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
|
---|
510 | my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
---|
511 | if ($verbose) {
|
---|
512 | $et->VPrint(0, "$$et{INDENT}$index) $tag Subsection ($len bytes):\n");
|
---|
513 | if ($verbose > 2) {
|
---|
514 | $raf->Seek($offset, 0) or return 'Error seeking';
|
---|
515 | $raf->Read($buff, $len) == $len or return 'Truncated image';
|
---|
516 | $et->VerboseDump(\$buff);
|
---|
517 | }
|
---|
518 | }
|
---|
519 | next unless $tagInfo;
|
---|
520 | $raf->Seek($offset, 0) or return "Error seeking for $$tagInfo{Name}";
|
---|
521 | if ($$tagInfo{Name} eq 'PreviewImage') {
|
---|
522 | # check image header to see if this is a JPEG preview image
|
---|
523 | $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 | next unless $buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/;
|
---|
526 | $offset += 28;
|
---|
527 | $len -= 28;
|
---|
528 | $raf->Read($buff, $len) == $len or return "Error reading PreviewImage data";
|
---|
529 | # check fore EXIF segment, and extract this image as the JpgFromRaw
|
---|
530 | if ($buff =~ /^\xff\xd8\xff\xe1/) {
|
---|
531 | $$et{IsJpgFromRaw} = 1;
|
---|
532 | $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
---|
533 | delete $$et{IsJpgFromRaw};
|
---|
534 | }
|
---|
535 | } else {
|
---|
536 | $raf->Read($buff, $len) == $len or return "Error reading $$tagInfo{Name} data";
|
---|
537 | }
|
---|
538 | my $subdir = $$tagInfo{SubDirectory};
|
---|
539 | if ($subdir) {
|
---|
540 | my %dirInfo = ( DataPt => \$buff );
|
---|
541 | my $subTable = GetTagTable($$subdir{TagTable});
|
---|
542 | $et->ProcessDirectory(\%dirInfo, $subTable);
|
---|
543 | } else {
|
---|
544 | # extract metadata from JpgFromRaw
|
---|
545 | if ($$tagInfo{Name} eq 'JpgFromRaw') {
|
---|
546 | my %dirInfo = (
|
---|
547 | Parent => 'X3F',
|
---|
548 | RAF => new File::RandomAccess(\$buff),
|
---|
549 | );
|
---|
550 | $$et{BASE} += $offset;
|
---|
551 | $et->ProcessJPEG(\%dirInfo);
|
---|
552 | $$et{BASE} -= $offset;
|
---|
553 | SetByteOrder('II');
|
---|
554 | }
|
---|
555 | $et->FoundTag($tagInfo, $buff);
|
---|
556 | }
|
---|
557 | }
|
---|
558 | 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 | my ($et, $dirInfo) = @_;
|
---|
568 | my $outfile = $$dirInfo{OutFile};
|
---|
569 | my $raf = $$dirInfo{RAF};
|
---|
570 | my $warn = $outfile ? \&Image::ExifTool::Error : \&Image::ExifTool::Warn;
|
---|
571 | my ($buff, $err, $hdrLen);
|
---|
572 |
|
---|
573 | return 0 unless $raf->Read($buff, 40) == 40;
|
---|
574 | return 0 unless $buff =~ /^FOVb/;
|
---|
575 |
|
---|
576 | SetByteOrder('II');
|
---|
577 | $et->SetFileType();
|
---|
578 |
|
---|
579 | # check version number
|
---|
580 | my $ver = unpack('x4V',$buff);
|
---|
581 | $ver = ($ver >> 16) . '.' . ($ver & 0xffff);
|
---|
582 | if ($ver > 5) {
|
---|
583 | &$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 | if ($ver > 2) {
|
---|
587 | my ($extra, $buf2);
|
---|
588 | if ($ver >= 4) {
|
---|
589 | $hdrLen = 0x300;
|
---|
590 | $extra = 0;
|
---|
591 | } else {
|
---|
592 | $hdrLen = $ver > 2.2 ? 104 : 72; # SceneCaptureType string added in 2.3
|
---|
593 | $extra = 160; # (extended header is 160 bytes)
|
---|
594 | }
|
---|
595 | my $more = $hdrLen - length($buff) + $extra;
|
---|
596 | unless ($raf->Read($buf2, $more) == $more) {
|
---|
597 | &$warn($et, 'Error reading X3F header');
|
---|
598 | return 1;
|
---|
599 | }
|
---|
600 | $buff .= $buf2;
|
---|
601 | }
|
---|
602 | my ($widPos, $hdrType) = $ver < 4 ? (28, 'Header') : (40, 'Header4');
|
---|
603 | # process header information
|
---|
604 | my $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::Main');
|
---|
605 | unless ($outfile) {
|
---|
606 | $et->HandleTag($tagTablePtr, $hdrType, $buff,
|
---|
607 | DataPt => \$buff,
|
---|
608 | Size => $hdrLen,
|
---|
609 | );
|
---|
610 | }
|
---|
611 | # read the directory pointer
|
---|
612 | $raf->Seek(-4, 2) or &$warn($et, 'Seek error'), return 1;
|
---|
613 | unless ($raf->Read($buff, 4) == 4) {
|
---|
614 | &$warn($et, 'Error reading X3F dir pointer');
|
---|
615 | return 1;
|
---|
616 | }
|
---|
617 | my $offset = unpack('V', $buff);
|
---|
618 | my %dirInfo = (
|
---|
619 | RAF => $raf,
|
---|
620 | DirStart => $offset,
|
---|
621 | );
|
---|
622 | if ($outfile) {
|
---|
623 | $dirInfo{OutFile} = $outfile;
|
---|
624 | $err = WriteX3F($et, \%dirInfo);
|
---|
625 | return -1 if $err and $err eq '-1';
|
---|
626 | } else {
|
---|
627 | # process the X3F subsections
|
---|
628 | $err = $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
---|
629 | }
|
---|
630 | $err and &$warn($et, $err);
|
---|
631 | return 1;
|
---|
632 | }
|
---|
633 |
|
---|
634 | 1; # end
|
---|
635 |
|
---|
636 | __END__
|
---|
637 |
|
---|
638 | =head1 NAME
|
---|
639 |
|
---|
640 | Image::ExifTool::SigmaRaw - Read Sigma/Foveon RAW (X3F) meta information
|
---|
641 |
|
---|
642 | =head1 SYNOPSIS
|
---|
643 |
|
---|
644 | This module is loaded automatically by Image::ExifTool when required.
|
---|
645 |
|
---|
646 | =head1 DESCRIPTION
|
---|
647 |
|
---|
648 | This module contains definitions required by Image::ExifTool to read
|
---|
649 | Sigma and Foveon X3F images.
|
---|
650 |
|
---|
651 | =head1 AUTHOR
|
---|
652 |
|
---|
653 | Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
|
---|
654 |
|
---|
655 | This library is free software; you can redistribute it and/or modify it
|
---|
656 | under the same terms as Perl itself.
|
---|
657 |
|
---|
658 | =head1 REFERENCES
|
---|
659 |
|
---|
660 | =over 4
|
---|
661 |
|
---|
662 | =item L<http://www.x3f.info/technotes/FileDocs/X3F_Format.pdf>
|
---|
663 |
|
---|
664 | =back
|
---|
665 |
|
---|
666 | =head1 SEE ALSO
|
---|
667 |
|
---|
668 | L<Image::ExifTool::TagNames/SigmaRaw Tags>,
|
---|
669 | L<Image::ExifTool::Sigma(3pm)|Image::ExifTool::Sigma>,
|
---|
670 | L<Image::ExifTool(3pm)|Image::ExifTool>
|
---|
671 |
|
---|
672 | =cut
|
---|