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