666 lines
23 KiB
Perl
666 lines
23 KiB
Perl
|
#------------------------------------------------------------------------------
|
||
|
# File: SigmaRaw.pm
|
||
|
#
|
||
|
# Description: Read Sigma/Foveon RAW (X3F) meta information
|
||
|
#
|
||
|
# Revisions: 2005/10/16 - P. Harvey Created
|
||
|
# 2009/11/30 - P. Harvey Support X3F v2.3 written by Sigma DP2
|
||
|
#
|
||
|
# References: 1) http://www.x3f.info/technotes/FileDocs/X3F_Format.pdf
|
||
|
#------------------------------------------------------------------------------
|
||
|
|
||
|
package Image::ExifTool::SigmaRaw;
|
||
|
|
||
|
use strict;
|
||
|
use vars qw($VERSION);
|
||
|
use Image::ExifTool qw(:DataAccess :Utils);
|
||
|
use Image::ExifTool::Sigma;
|
||
|
|
||
|
$VERSION = '1.26';
|
||
|
|
||
|
sub ProcessX3FHeader($$$);
|
||
|
sub ProcessX3FDirectory($$$);
|
||
|
sub ProcessX3FProperties($$$);
|
||
|
|
||
|
# main X3F sections (plus header stuff)
|
||
|
%Image::ExifTool::SigmaRaw::Main = (
|
||
|
PROCESS_PROC => \&ProcessX3FDirectory,
|
||
|
NOTES => q{
|
||
|
These tags are used in Sigma and Foveon RAW (.X3F) images. Metadata is also
|
||
|
extracted from the JpgFromRaw image if it exists (all models but the SD9 and
|
||
|
SD10). Currently, metadata may only be written to the embedded JpgFromRaw.
|
||
|
},
|
||
|
Header => {
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Header' },
|
||
|
},
|
||
|
Header4 => {
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Header4' },
|
||
|
},
|
||
|
HeaderExt => {
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::HeaderExt' },
|
||
|
},
|
||
|
PROP => {
|
||
|
Name => 'Properties',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Properties' },
|
||
|
},
|
||
|
IMAG => {
|
||
|
Name => 'PreviewImage',
|
||
|
Groups => { 2 => 'Preview' },
|
||
|
Binary => 1,
|
||
|
},
|
||
|
IMA2 => [
|
||
|
{
|
||
|
Name => 'PreviewImage',
|
||
|
Condition => 'not $$self{IsJpgFromRaw}',
|
||
|
Groups => { 2 => 'Preview' },
|
||
|
Binary => 1,
|
||
|
},
|
||
|
{
|
||
|
Name => 'JpgFromRaw',
|
||
|
Groups => { 2 => 'Preview' },
|
||
|
Binary => 1,
|
||
|
},
|
||
|
]
|
||
|
);
|
||
|
|
||
|
# common X3F header structure
|
||
|
%Image::ExifTool::SigmaRaw::Header = (
|
||
|
PROCESS_PROC => \&ProcessX3FHeader,
|
||
|
FORMAT => 'int32u',
|
||
|
NOTES => 'Information extracted from the header of an X3F file.',
|
||
|
1 => {
|
||
|
Name => 'FileVersion',
|
||
|
ValueConv => '($val >> 16) . "." . ($val & 0xffff)',
|
||
|
},
|
||
|
2 => {
|
||
|
Name => 'ImageUniqueID',
|
||
|
# the serial number (with an extra leading "0") makes up
|
||
|
# the first 8 digits of this UID,
|
||
|
Format => 'undef[16]',
|
||
|
ValueConv => 'unpack("H*", $val)',
|
||
|
},
|
||
|
6 => {
|
||
|
Name => 'MarkBits',
|
||
|
PrintConv => { BITMASK => { } },
|
||
|
},
|
||
|
7 => 'ImageWidth',
|
||
|
8 => 'ImageHeight',
|
||
|
9 => 'Rotation',
|
||
|
10 => {
|
||
|
Name => 'WhiteBalance',
|
||
|
Format => 'string[32]',
|
||
|
},
|
||
|
18 => { #PH (DP2, FileVersion 2.3)
|
||
|
Name => 'SceneCaptureType',
|
||
|
Format => 'string[32]',
|
||
|
},
|
||
|
);
|
||
|
|
||
|
# X3F version 4 header structure (ref PH)
|
||
|
%Image::ExifTool::SigmaRaw::Header4 = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
FORMAT => 'int32u',
|
||
|
NOTES => 'Header information for version 4.0 or greater X3F.',
|
||
|
1 => {
|
||
|
Name => 'FileVersion',
|
||
|
ValueConv => '($val >> 16) . "." . ($val & 0xffff)',
|
||
|
},
|
||
|
# 8 - undef[4]: 4 random ASCII characters
|
||
|
10 => 'ImageWidth',
|
||
|
11 => 'ImageHeight',
|
||
|
12 => 'Rotation',
|
||
|
# don't know what the rest of the header contains, but none of
|
||
|
# these values change in any of my samples...
|
||
|
);
|
||
|
|
||
|
# extended header tags
|
||
|
%Image::ExifTool::SigmaRaw::HeaderExt = (
|
||
|
GROUPS => { 2 => 'Camera' },
|
||
|
FORMAT => 'float',
|
||
|
NOTES => 'Extended header data found in version 2.1 and 2.2 files',
|
||
|
0 => 'Unused',
|
||
|
1 => { Name => 'ExposureAdjust',PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
2 => { Name => 'Contrast', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
3 => { Name => 'Shadow', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
4 => { Name => 'Highlight', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
5 => { Name => 'Saturation', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
6 => { Name => 'Sharpness', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
7 => { Name => 'RedAdjust', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
8 => { Name => 'GreenAdjust', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
9 => { Name => 'BlueAdjust', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
10 => { Name => 'X3FillLight', PrintConv => 'sprintf("%.1f",$val)' },
|
||
|
);
|
||
|
|
||
|
# PROP tags
|
||
|
%Image::ExifTool::SigmaRaw::Properties = (
|
||
|
PROCESS_PROC => \&ProcessX3FProperties,
|
||
|
GROUPS => { 2 => 'Camera' },
|
||
|
PRIORITY => 0, # (because these aren't writable like the EXIF ones)
|
||
|
AEMODE => {
|
||
|
Name => 'MeteringMode',
|
||
|
PrintConv => {
|
||
|
8 => '8-segment',
|
||
|
C => 'Center-weighted average',
|
||
|
A => 'Average',
|
||
|
},
|
||
|
},
|
||
|
AFAREA => 'AFArea', # observed: CENTER_V
|
||
|
AFINFOCUS => 'AFInFocus', # observed: H
|
||
|
AFMODE => 'FocusMode',
|
||
|
AP_DESC => 'ApertureDisplayed',
|
||
|
APERTURE => {
|
||
|
Name => 'FNumber',
|
||
|
Groups => { 2 => 'Image' },
|
||
|
PrintConv => 'sprintf("%.1f",$val)',
|
||
|
},
|
||
|
BRACKET => 'BracketShot',
|
||
|
BURST => 'BurstShot',
|
||
|
CAMMANUF => 'Make',
|
||
|
CAMMODEL => 'Model',
|
||
|
CAMNAME => 'CameraName',
|
||
|
CAMSERIAL => 'SerialNumber',
|
||
|
CM_DESC => 'SceneCaptureType', #PH (DP2)
|
||
|
COLORSPACE => 'ColorSpace', # observed: sRGB
|
||
|
DRIVE => {
|
||
|
Name => 'DriveMode',
|
||
|
PrintConv => {
|
||
|
SINGLE => 'Single Shot',
|
||
|
MULTI => 'Multi Shot',
|
||
|
'2S' => '2 s Timer',
|
||
|
'10S' => '10 s Timer',
|
||
|
UP => 'Mirror Up',
|
||
|
AB => 'Auto Bracket',
|
||
|
OFF => 'Off',
|
||
|
},
|
||
|
},
|
||
|
EVAL_STATE => 'EvalState', # observed: POST-EXPOSURE
|
||
|
EXPCOMP => {
|
||
|
Name => 'ExposureCompensation',
|
||
|
Groups => { 2 => 'Image' },
|
||
|
PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
|
||
|
},
|
||
|
EXPNET => {
|
||
|
Name => 'NetExposureCompensation',
|
||
|
Groups => { 2 => 'Image' },
|
||
|
PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
|
||
|
},
|
||
|
EXPTIME => {
|
||
|
Name => 'IntegrationTime',
|
||
|
Groups => { 2 => 'Image' },
|
||
|
ValueConv => '$val * 1e-6', # convert from usec
|
||
|
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||
|
},
|
||
|
FIRMVERS => 'FirmwareVersion',
|
||
|
FLASH => {
|
||
|
Name => 'FlashMode',
|
||
|
PrintConv => 'ucfirst(lc($val))',
|
||
|
},
|
||
|
FLASHEXPCOMP=> 'FlashExpComp',
|
||
|
FLASHPOWER => 'FlashPower',
|
||
|
FLASHTTLMODE=> 'FlashTTLMode', # observed: ON
|
||
|
FLASHTYPE => 'FlashType', # observed: NONE
|
||
|
FLENGTH => {
|
||
|
Name => 'FocalLength',
|
||
|
PrintConv => 'sprintf("%.1f mm",$val)',
|
||
|
},
|
||
|
FLEQ35MM => {
|
||
|
Name => 'FocalLengthIn35mmFormat',
|
||
|
PrintConv => 'sprintf("%.1f mm",$val)',
|
||
|
},
|
||
|
FOCUS => {
|
||
|
Name => 'Focus',
|
||
|
PrintConv => {
|
||
|
AF => 'Auto-focus Locked',
|
||
|
'NO LOCK' => "Auto-focus Didn't Lock",
|
||
|
M => 'Manual',
|
||
|
},
|
||
|
},
|
||
|
IMAGERBOARDID => 'ImagerBoardID',
|
||
|
IMAGERTEMP => {
|
||
|
Name => 'SensorTemperature',
|
||
|
PrintConv => '"$val C"',
|
||
|
},
|
||
|
IMAGEBOARDID=> 'ImageBoardID', #PH (DP2)
|
||
|
ISO => 'ISO',
|
||
|
LENSARANGE => 'LensApertureRange',
|
||
|
LENSFRANGE => 'LensFocalRange',
|
||
|
LENSMODEL => {
|
||
|
Name => 'LensType',
|
||
|
ValueConv => '$val =~ /^[0-9a-f]+$/i ? hex($val) : $val',
|
||
|
ValueConvInv => '$val=~s/\.\d+$//; IsInt($val) ? sprintf("%x",$val) : $val', # (truncate decimal part)
|
||
|
SeparateTable => 'Sigma LensType',
|
||
|
PrintHex => 1,
|
||
|
PrintConv => \%Image::ExifTool::Sigma::sigmaLensTypes,
|
||
|
},
|
||
|
PMODE => {
|
||
|
Name => 'ExposureProgram',
|
||
|
PrintConv => {
|
||
|
P => 'Program',
|
||
|
A => 'Aperture Priority',
|
||
|
S => 'Shutter Priority',
|
||
|
M => 'Manual',
|
||
|
},
|
||
|
},
|
||
|
RESOLUTION => {
|
||
|
Name => 'Quality',
|
||
|
PrintConv => {
|
||
|
LOW => 'Low',
|
||
|
MED => 'Medium',
|
||
|
HI => 'High',
|
||
|
},
|
||
|
},
|
||
|
SENSORID => 'SensorID',
|
||
|
SH_DESC => 'ShutterSpeedDisplayed',
|
||
|
SHUTTER => {
|
||
|
Name => 'ExposureTime',
|
||
|
Groups => { 2 => 'Image' },
|
||
|
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||
|
},
|
||
|
TIME => {
|
||
|
Name => 'DateTimeOriginal',
|
||
|
Groups => { 2 => 'Time' },
|
||
|
Description => 'Date/Time Original',
|
||
|
ValueConv => 'ConvertUnixTime($val)',
|
||
|
PrintConv => '$self->ConvertDateTime($val)',
|
||
|
},
|
||
|
WB_DESC => 'WhiteBalance',
|
||
|
VERSION_BF => 'VersionBF',
|
||
|
);
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Extract null-terminated unicode string from list of characters
|
||
|
# Inputs: 0) ExifTool ref, 1) list ref, 2) position in list
|
||
|
# Returns: Converted string
|
||
|
sub ExtractUnicodeString($$$)
|
||
|
{
|
||
|
my ($et, $chars, $pos) = @_;
|
||
|
my $i;
|
||
|
for ($i=$pos; $i<@$chars; ++$i) {
|
||
|
last unless $$chars[$i];
|
||
|
}
|
||
|
my $buff = pack('v*', @$chars[$pos..$i-1]);
|
||
|
return $et->Decode($buff, 'UCS2', 'II');
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process an X3F header
|
||
|
# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessX3FHeader($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $hdrLen = $$dirInfo{DirLen};
|
||
|
|
||
|
# process the static header structure first
|
||
|
$et->ProcessBinaryData($dirInfo, $tagTablePtr);
|
||
|
|
||
|
# process extended data if available
|
||
|
if (length($$dataPt) - $hdrLen >= 160) {
|
||
|
my $verbose = $et->Options('Verbose');
|
||
|
if ($verbose) {
|
||
|
$et->VerboseDir('X3F HeaderExt', 32);
|
||
|
$et->VerboseDump($dataPt, Start => $hdrLen);
|
||
|
}
|
||
|
$tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::HeaderExt');
|
||
|
my @tags = unpack("x${hdrLen}C32", $$dataPt);
|
||
|
my $i;
|
||
|
my $unused = 0;
|
||
|
for ($i=0; $i<32; ++$i) {
|
||
|
$tags[$i] or ++$unused, next;
|
||
|
$et->HandleTag($tagTablePtr, $tags[$i], undef,
|
||
|
Index => $i,
|
||
|
DataPt => $dataPt,
|
||
|
Start => $hdrLen + 32 + $i * 4,
|
||
|
Size => 4,
|
||
|
);
|
||
|
}
|
||
|
$et->VPrint(0, "$$et{INDENT}($unused entries unused)\n");
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process an X3F properties
|
||
|
# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessX3FProperties($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $size = length($$dataPt);
|
||
|
my $verbose = $et->Options('Verbose');
|
||
|
my $unknown = $et->Options('Unknown');
|
||
|
|
||
|
unless ($size >= 24 and $$dataPt =~ /^SECp/) {
|
||
|
$et->Warn('Bad properties header');
|
||
|
return 0;
|
||
|
}
|
||
|
my ($entries, $fmt, $len) = unpack('x8V2x4V', $$dataPt);
|
||
|
unless ($size >= 24 + 8 * $entries + $len) {
|
||
|
$et->Warn('Truncated Property directory');
|
||
|
return 0;
|
||
|
}
|
||
|
$verbose and $et->VerboseDir('Properties', $entries);
|
||
|
$fmt == 0 or $et->Warn("Unsupported character format $fmt"), return 0;
|
||
|
my $charPos = 24 + 8 * $entries;
|
||
|
my @chars = unpack('v*',substr($$dataPt, $charPos, $len * 2));
|
||
|
my $index;
|
||
|
for ($index=0; $index<$entries; ++$index) {
|
||
|
my ($namePos, $valPos) = unpack('V2',substr($$dataPt, $index*8 + 24, 8));
|
||
|
if ($namePos >= @chars or $valPos >= @chars) {
|
||
|
$et->Warn('Bad Property pointer');
|
||
|
return 0;
|
||
|
}
|
||
|
my $tag = ExtractUnicodeString($et, \@chars, $namePos);
|
||
|
my $val = ExtractUnicodeString($et, \@chars, $valPos);
|
||
|
if (not $$tagTablePtr{$tag} and $unknown and $tag =~ /^\w+$/) {
|
||
|
my $tagInfo = {
|
||
|
Name => "SigmaRaw_$tag",
|
||
|
Description => Image::ExifTool::MakeDescription('SigmaRaw', $tag),
|
||
|
Unknown => 1,
|
||
|
Writable => 0, # can't write unknown tags
|
||
|
};
|
||
|
# add tag information to table
|
||
|
AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
||
|
}
|
||
|
|
||
|
$et->HandleTag($tagTablePtr, $tag, $val,
|
||
|
Index => $index,
|
||
|
DataPt => $dataPt,
|
||
|
Start => $charPos + 2 * $valPos,
|
||
|
Size => 2 * (length($val) + 1),
|
||
|
);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Write an X3F file
|
||
|
# Inputs: 0) ExifTool ref, 1) DirInfo ref (DirStart = directory offset)
|
||
|
# Returns: error string, undef on success, or -1 on write error
|
||
|
# Notes: Writes metadata to embedded JpgFromRaw image
|
||
|
sub WriteX3F($$)
|
||
|
{
|
||
|
my ($et, $dirInfo) = @_;
|
||
|
my $raf = $$dirInfo{RAF};
|
||
|
my $outfile = $$dirInfo{OutFile};
|
||
|
my ($outDir, $buff, $ver, $entries, $dir, $outPos, $index, $didContain);
|
||
|
|
||
|
$raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start';
|
||
|
|
||
|
# read the X3F directory header (will be copied directly to output)
|
||
|
$raf->Read($outDir, 12) == 12 or return 'Truncated X3F image';
|
||
|
$outDir =~ /^SECd/ or return 'Bad section header';
|
||
|
($ver, $entries) = unpack('x4V2', $outDir);
|
||
|
|
||
|
# do sanity check on number of entries in directory
|
||
|
return 'Invalid X3F directory count' unless $entries > 2 and $entries < 20;
|
||
|
# read the directory entries
|
||
|
unless ($raf->Read($dir, $entries * 12) == $entries * 12) {
|
||
|
return 'Truncated X3F directory';
|
||
|
}
|
||
|
# do a quick scan to determine the offset of the first data subsection
|
||
|
for ($index=0; $index<$entries; ++$index) {
|
||
|
my $pos = $index * 12;
|
||
|
my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
|
||
|
# remember position of first data subsection
|
||
|
$outPos = $offset if not defined $outPos or $outPos > $offset;
|
||
|
}
|
||
|
# copy the file header up to the start of the first data subsection
|
||
|
unless ($raf->Seek(0,0) and $raf->Read($buff, $outPos) == $outPos) {
|
||
|
return 'Error reading X3F header';
|
||
|
}
|
||
|
Write($outfile, $buff) or return -1;
|
||
|
|
||
|
# loop through directory, rewriting each section
|
||
|
for ($index=0; $index<$entries; ++$index) {
|
||
|
|
||
|
my $pos = $index * 12;
|
||
|
my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
|
||
|
$raf->Seek($offset, 0) or return 'Bad data offset';
|
||
|
|
||
|
if ($tag eq 'IMA2' and $len > 28) {
|
||
|
# check subsection header (28 bytes) to see if this is a JPEG preview image
|
||
|
$raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
|
||
|
Write($outfile, $buff) or return -1;
|
||
|
$len -= 28;
|
||
|
|
||
|
# only rewrite full-sized JpgFromRaw (version 2.0, type 2, format 18)
|
||
|
if ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/ and
|
||
|
$$et{ImageWidth} == unpack('x16V', $buff))
|
||
|
{
|
||
|
$raf->Read($buff, $len) == $len or return 'Error reading JpgFromRaw';
|
||
|
# use same write directories as JPEG
|
||
|
$et->InitWriteDirs('JPEG');
|
||
|
# rewrite the embedded JPEG in memory
|
||
|
my $newData;
|
||
|
my %jpegInfo = (
|
||
|
Parent => 'X3F',
|
||
|
RAF => new File::RandomAccess(\$buff),
|
||
|
OutFile => \$newData,
|
||
|
);
|
||
|
$$et{FILE_TYPE} = 'JPEG';
|
||
|
my $success = $et->WriteJPEG(\%jpegInfo);
|
||
|
$$et{FILE_TYPE} = 'X3F';
|
||
|
SetByteOrder('II');
|
||
|
return 'Error writing X3F JpgFromRaw' unless $success and $newData;
|
||
|
return -1 if $success < 0;
|
||
|
# write new data if anything changed, otherwise copy old image
|
||
|
my $outPt = $$et{CHANGED} ? \$newData : \$buff;
|
||
|
Write($outfile, $$outPt) or return -1;
|
||
|
# set $len to the total subsection data length
|
||
|
$len = length($$outPt) + 28;
|
||
|
$didContain = 1;
|
||
|
} else {
|
||
|
# copy original image data
|
||
|
Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F image';
|
||
|
$len += 28;
|
||
|
}
|
||
|
} else {
|
||
|
# copy data for this subsection
|
||
|
Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F directory';
|
||
|
}
|
||
|
# add directory entry and update output file position
|
||
|
$outDir .= pack('V2a4', $outPos, $len, $tag);
|
||
|
$outPos += $len;
|
||
|
# pad data to an even 4-byte boundary
|
||
|
if ($len & 0x03) {
|
||
|
my $pad = 4 - ($len & 0x03);
|
||
|
Write($outfile, "\0" x $pad) or return -1;
|
||
|
$outPos += $pad;
|
||
|
}
|
||
|
}
|
||
|
# warn if we couldn't add metadata to this image (should only be SD9 or SD10)
|
||
|
$didContain or $et->Warn("Can't yet write SD9 or SD10 X3F images");
|
||
|
# write out the directory and the directory pointer, and we are done
|
||
|
Write($outfile, $outDir, pack('V', $outPos)) or return -1;
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process an X3F directory
|
||
|
# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
|
||
|
# Returns: error string or undef on success
|
||
|
sub ProcessX3FDirectory($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $raf = $$dirInfo{RAF};
|
||
|
my $verbose = $et->Options('Verbose');
|
||
|
|
||
|
$raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start';
|
||
|
|
||
|
# parse the X3F directory structure
|
||
|
my ($buff, $ver, $entries, $index, $dir);
|
||
|
$raf->Read($buff, 12) == 12 or return 'Truncated X3F image';
|
||
|
$buff =~ /^SECd/ or return 'Bad section header';
|
||
|
($ver, $entries) = unpack('x4V2', $buff);
|
||
|
$verbose and $et->VerboseDir('X3F Subsection', $entries);
|
||
|
$raf->Read($dir, $entries * 12) == $entries * 12 or return 'Truncated X3F directory';
|
||
|
for ($index=0; $index<$entries; ++$index) {
|
||
|
my $pos = $index * 12;
|
||
|
my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir);
|
||
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||
|
if ($verbose) {
|
||
|
$et->VPrint(0, "$$et{INDENT}$index) $tag Subsection ($len bytes):\n");
|
||
|
if ($verbose > 2) {
|
||
|
$raf->Seek($offset, 0) or return 'Error seeking';
|
||
|
$raf->Read($buff, $len) == $len or return 'Truncated image';
|
||
|
$et->VerboseDump(\$buff);
|
||
|
}
|
||
|
}
|
||
|
next unless $tagInfo;
|
||
|
$raf->Seek($offset, 0) or return "Error seeking for $$tagInfo{Name}";
|
||
|
if ($$tagInfo{Name} eq 'PreviewImage') {
|
||
|
# check image header to see if this is a JPEG preview image
|
||
|
$raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header';
|
||
|
# ignore all image data but JPEG compressed (version 2.0, type 2, format 18)
|
||
|
next unless $buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/;
|
||
|
# check preview image size and extract full-sized preview as JpgFromRaw
|
||
|
if ($$et{ImageWidth} == unpack('x16V', $buff)) {
|
||
|
$$et{IsJpgFromRaw} = 1;
|
||
|
$tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||
|
delete $$et{IsJpgFromRaw};
|
||
|
}
|
||
|
$offset += 28;
|
||
|
$len -= 28;
|
||
|
}
|
||
|
$raf->Read($buff, $len) == $len or return "Error reading $$tagInfo{Name} data";
|
||
|
my $subdir = $$tagInfo{SubDirectory};
|
||
|
if ($subdir) {
|
||
|
my %dirInfo = ( DataPt => \$buff );
|
||
|
my $subTable = GetTagTable($$subdir{TagTable});
|
||
|
$et->ProcessDirectory(\%dirInfo, $subTable);
|
||
|
} else {
|
||
|
# extract metadata from JpgFromRaw
|
||
|
if ($$tagInfo{Name} eq 'JpgFromRaw') {
|
||
|
my %dirInfo = (
|
||
|
Parent => 'X3F',
|
||
|
RAF => new File::RandomAccess(\$buff),
|
||
|
);
|
||
|
$$et{BASE} += $offset;
|
||
|
$et->ProcessJPEG(\%dirInfo);
|
||
|
$$et{BASE} -= $offset;
|
||
|
SetByteOrder('II');
|
||
|
}
|
||
|
$et->FoundTag($tagInfo, $buff);
|
||
|
}
|
||
|
}
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Read/write information from a Sigma raw (X3F) image
|
||
|
# Inputs: 0) ExifTool ref, 1) DirInfo ref
|
||
|
# Returns: 1 on success, 0 if this wasn't a valid X3F image, or -1 on write error
|
||
|
sub ProcessX3F($$)
|
||
|
{
|
||
|
my ($et, $dirInfo) = @_;
|
||
|
my $outfile = $$dirInfo{OutFile};
|
||
|
my $raf = $$dirInfo{RAF};
|
||
|
my $warn = $outfile ? \&Image::ExifTool::Error : \&Image::ExifTool::Warn;
|
||
|
my ($buff, $err, $hdrLen);
|
||
|
|
||
|
return 0 unless $raf->Read($buff, 40) == 40;
|
||
|
return 0 unless $buff =~ /^FOVb/;
|
||
|
|
||
|
SetByteOrder('II');
|
||
|
$et->SetFileType();
|
||
|
|
||
|
# check version number
|
||
|
my $ver = unpack('x4V',$buff);
|
||
|
$ver = ($ver >> 16) . '.' . ($ver & 0xffff);
|
||
|
if ($ver > 5) {
|
||
|
&$warn($et, "Untested X3F version ($ver). Please submit sample for testing", 1);
|
||
|
}
|
||
|
# read version 2.1/2.2/2.3 extended header
|
||
|
if ($ver > 2) {
|
||
|
my ($extra, $buf2);
|
||
|
if ($ver >= 4) {
|
||
|
$hdrLen = 0x300;
|
||
|
$extra = 0;
|
||
|
} else {
|
||
|
$hdrLen = $ver > 2.2 ? 104 : 72; # SceneCaptureType string added in 2.3
|
||
|
$extra = 160; # (extended header is 160 bytes)
|
||
|
}
|
||
|
my $more = $hdrLen - length($buff) + $extra;
|
||
|
unless ($raf->Read($buf2, $more) == $more) {
|
||
|
&$warn($et, 'Error reading X3F header');
|
||
|
return 1;
|
||
|
}
|
||
|
$buff .= $buf2;
|
||
|
}
|
||
|
my ($widPos, $hdrType) = $ver < 4 ? (28, 'Header') : (40, 'Header4');
|
||
|
# extract ImageWidth for later
|
||
|
$$et{ImageWidth} = Get32u(\$buff, $widPos);
|
||
|
# process header information
|
||
|
my $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::Main');
|
||
|
unless ($outfile) {
|
||
|
$et->HandleTag($tagTablePtr, $hdrType, $buff,
|
||
|
DataPt => \$buff,
|
||
|
Size => $hdrLen,
|
||
|
);
|
||
|
}
|
||
|
# read the directory pointer
|
||
|
$raf->Seek(-4, 2) or &$warn($et, 'Seek error'), return 1;
|
||
|
unless ($raf->Read($buff, 4) == 4) {
|
||
|
&$warn($et, 'Error reading X3F dir pointer');
|
||
|
return 1;
|
||
|
}
|
||
|
my $offset = unpack('V', $buff);
|
||
|
my %dirInfo = (
|
||
|
RAF => $raf,
|
||
|
DirStart => $offset,
|
||
|
);
|
||
|
if ($outfile) {
|
||
|
$dirInfo{OutFile} = $outfile;
|
||
|
$err = WriteX3F($et, \%dirInfo);
|
||
|
return -1 if $err and $err eq '-1';
|
||
|
} else {
|
||
|
# process the X3F subsections
|
||
|
$err = $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
||
|
}
|
||
|
$err and &$warn($et, $err);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
1; # end
|
||
|
|
||
|
__END__
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
Image::ExifTool::SigmaRaw - Read Sigma/Foveon RAW (X3F) meta information
|
||
|
|
||
|
=head1 SYNOPSIS
|
||
|
|
||
|
This module is loaded automatically by Image::ExifTool when required.
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
This module contains definitions required by Image::ExifTool to read
|
||
|
Sigma and Foveon X3F images.
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Copyright 2003-2018, Phil Harvey (phil at owl.phy.queensu.ca)
|
||
|
|
||
|
This library is free software; you can redistribute it and/or modify it
|
||
|
under the same terms as Perl itself.
|
||
|
|
||
|
=head1 REFERENCES
|
||
|
|
||
|
=over 4
|
||
|
|
||
|
=item L<http://www.x3f.info/technotes/FileDocs/X3F_Format.pdf>
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 SEE ALSO
|
||
|
|
||
|
L<Image::ExifTool::TagNames/SigmaRaw Tags>,
|
||
|
L<Image::ExifTool::Sigma(3pm)|Image::ExifTool::Sigma>,
|
||
|
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||
|
|
||
|
=cut
|