###########################################################
# A Perl package for showing/modifying JPEG (meta)data. #
# Copyright (C) 2004,2005,2006 Stefano Bettelli #
# See the COPYING and LICENSE files for license terms. #
###########################################################
package Image::MetaData::JPEG::data::Tables;
use Exporter 'import';
use strict;
use warnings;
no integer;
#============================================================================#
#============================================================================#
#============================================================================#
# This section defines the export policy of this module; no variable or #
# method is exported by default. Everything is exportable via %EXPORT_TAGS. #
#----------------------------------------------------------------------------#
our @ISA = qw(Exporter); #
our @EXPORT = qw(); #
our @EXPORT_OK = qw(); #
our %EXPORT_TAGS = #
(RecordTypes => [qw($NIBBLES $BYTE $ASCII $SHORT $LONG $RATIONAL), #
qw($SBYTE $UNDEF $SSHORT $SLONG $SRATIONAL $FLOAT), #
qw($DOUBLE $REFERENCE)], #
RecordProps => [qw(@JPEG_RECORD_TYPE_NAME @JPEG_RECORD_TYPE_LENGTH), #
qw(@JPEG_RECORD_TYPE_CATEGORY @JPEG_RECORD_TYPE_SIGN)], #
Endianness => [qw($NATIVE_ENDIANNESS $BIG_ENDIAN $LITTLE_ENDIAN)], #
JPEGgrammar => [qw($JPEG_PUNCTUATION %JPEG_MARKER $JPEG_SEG_MAX_LEN)], #
TagsAPP0 => [qw($APP0_JFIF_TAG $APP0_JFXX_TAG $APP0_JFXX_JPG), #
qw($APP0_JFXX_1B $APP0_JFXX_3B $APP0_JFXX_PAL)], #
TagsAPP1_Exif=>[qw($APP1_TH_JPEG $APP1_TH_TIFF $APP1_TH_TYPE), #
qw($APP1_EXIF_TAG $THJPEG_OFFSET $THJPEG_LENGTH), #
qw($APP1_TIFF_SIG $THTIFF_OFFSET $THTIFF_LENGTH), #
qw(%IFD_SUBDIRS $HASH_MAKERNOTES $MAKERNOTE_TAG)], #
TagsAPP1_XMP=> [qw($APP1_XMP_TAG $APP1_XMP_XPACKET_BEGIN), #
qw($APP1_XMP_XPACKET_ID $APP1_XMP_META_NS), #
qw($APP1_XMP_OUTER_RDF_NS)], #
TagsAPP2 => [qw($APP2_FPXR_TAG $APP2_ICC_TAG)], #
TagsAPP3 => [qw($APP3_EXIF_TAG %IFD_SUBDIRS)], #
TagsAPP13 => [qw($APP13_PHOTOSHOP_IPTC $APP13_PHOTOSHOP_IDS), #
qw($APP13_PHOTOSHOP_TYPE $APP13_IPTC_TAGMARKER), #
qw($APP13_PHOTOSHOP_DIRNAME $APP13_IPTC_DIRNAME)], #
TagsAPP14 => [qw($APP14_PHOTOSHOP_IDENTIFIER)], #
Lookups => [qw(&JPEG_lookup)], ); #
#----------------------------------------------------------------------------#
Exporter::export_ok_tags( #
qw(RecordTypes RecordProps Endianness JPEGgrammar), #
qw(TagsAPP0 TagsAPP1_Exif TagsAPP1_XMP), #
qw(TagsAPP2 TagsAPP3 TagsAPP13 TagsAPP14 Lookups)); #
#============================================================================#
#============================================================================#
#============================================================================#
# Constants for the grammar of a JPEG files. You can find here everything #
# about segment markers as well as the JPEG puncutation mark. The maximum #
# length of the data area of a standard JPEG segment is determined by the #
# fact that the segment lenght must be written to a two bytes field (inclu- #
# ding the two bytes themselves (so, it is 2^16 - 3). #
#----------------------------------------------------------------------------#
our $JPEG_SEG_MAX_LEN = 2**16 - 3; # data area max length for a std segment #
our $JPEG_PUNCTUATION = 0xff; # constant prefixed to every JPEG marker #
our %JPEG_MARKER = # non-repetitive JPEG markers #
(TEM => 0x01, # for TEMporary private use in arithmetic coding #
DHT => 0xc4, # Define Huffman Table(s) #
JPG => 0xc8, # reserved for JPEG extensions #
DAC => 0xcc, # Define Arithmetic Coding Conditioning(s) #
SOI => 0xd8, # Start Of Image #
EOI => 0xd9, # End Of Image #
SOS => 0xda, # Start Of Scan #
DQT => 0xdb, # Define Quantization Table(s) #
DNL => 0xdc, # Define Number of Lines #
DRI => 0xdd, # Define Restart Interval #
DHP => 0xde, # Define Hierarchical Progression #
EXP => 0xdf, # EXPand reference component(s) #
COM => 0xfe); # COMment block #
#----------------------------------------------------------------------------#
# markers 0x02 --> 0xbf are REServed for future uses #
for (0x02..0xbf) { $JPEG_MARKER{sprintf "res%02x", $_} = $_; } #
# some markers in 0xc0 --> 0xcf correspond to Start-Of-Frame typologies #
for (0xc0..0xc3, 0xc5..0xc7, 0xc9..0xcb, #
0xcd..0xcf) { $JPEG_MARKER{sprintf "SOF_%d", $_ - 0xc0} = $_; } #
# markers 0xd0 --> 0xd7 correspond to ReSTart with module 8 count #
for (0xd0..0xd7) { $JPEG_MARKER{sprintf "RST%d", $_ - 0xd0} = $_; } #
# markers 0xe0 --> 0xef are the APPlication markers #
for (0xe0..0xef) { $JPEG_MARKER{sprintf "APP%d", $_ - 0xe0} = $_; } #
# markers 0xf0 --> 0xfd are reserved for JPEG extensions #
for (0xf0..0xfd) { $JPEG_MARKER{sprintf "JPG%d", $_ - 0xf0} = $_; } #
#============================================================================#
#============================================================================#
#============================================================================#
# Functions for generating arrays (arg0=hashref, arg1=index) or references #
# to lookup tables [hashes] (arg0=hashref,arg1=index) from hashes; it is #
# assumed that the general hash they work on has array references as values. #
#----------------------------------------------------------------------------#
sub generate_lookup { my %a=map { $_ => $_[0]{$_}[$_[1]] } keys %{$_[0]}; \%a};
sub generate_array { map { $_[0]{$_}[$_[1]] } (0..(-1+scalar keys %{$_[0]}))};
#============================================================================#
#============================================================================#
#============================================================================#
# Various lists for JPEG record names, lengths, categories and signs; see #
# Image::MetaData::JPEG::Record class for further details. The general hash #
# is private to this file, the other arrays are exported if so requested. #
#----------------------------------------------------------------------------#
# I gave up trying to calculate the length of a reference. This is probably #
# allocation dependent ... I use 0 here, meaning the length is variable. #
#----------------------------------------------------------------------------#
my $RECORD_TYPE_GENERAL = #
{(our $NIBBLES = 0) => [ 'NIBBLES' , 1, 'I', 'N' ], #
(our $BYTE = 1) => [ 'BYTE' , 1, 'I', 'N' ], #
(our $ASCII = 2) => [ 'ASCII' , 0, 'S', 'N' ], #
(our $SHORT = 3) => [ 'SHORT' , 2, 'I', 'N' ], #
(our $LONG = 4) => [ 'LONG' , 4, 'I', 'N' ], #
(our $RATIONAL = 5) => [ 'RATIONAL' , 8, 'R', 'N' ], #
(our $SBYTE = 6) => [ 'SBYTE' , 1, 'I', 'Y' ], #
(our $UNDEF = 7) => [ 'UNDEF' , 0, 'S', 'N' ], #
(our $SSHORT = 8) => [ 'SSHORT' , 2, 'I', 'Y' ], #
(our $SLONG = 9) => [ 'SLONG' , 4, 'I', 'Y' ], #
(our $SRATIONAL = 10) => [ 'SRATIONAL' , 8, 'R', 'Y' ], #
(our $FLOAT = 11) => [ 'FLOAT' , 4, 'F', 'N' ], #
(our $DOUBLE = 12) => [ 'DOUBLE' , 8, 'F', 'N' ], #
(our $REFERENCE = 13) => [ 'REFERENCE' , 0, 'p', 'N' ], }; #
#----------------------------------------------------------------------------#
our @JPEG_RECORD_TYPE_NAME = generate_array($RECORD_TYPE_GENERAL, 0); #
our @JPEG_RECORD_TYPE_LENGTH = generate_array($RECORD_TYPE_GENERAL, 1); #
our @JPEG_RECORD_TYPE_CATEGORY = generate_array($RECORD_TYPE_GENERAL, 2); #
our @JPEG_RECORD_TYPE_SIGN = generate_array($RECORD_TYPE_GENERAL, 3); #
#============================================================================#
#============================================================================#
#============================================================================#
# These tags are related to endianness. The endianness of the current #
# machine is detected every time with a simple procedure. #
#----------------------------------------------------------------------------#
my ($__short, $__byte1, $__byte2) = unpack "SCC", "\111\333" x 2; #
our $BIG_ENDIAN = 'MM'; #
our $LITTLE_ENDIAN = 'II'; #
our $NATIVE_ENDIANNESS = $__byte2 + ($__byte1<<8) == $__short ? $BIG_ENDIAN #
: $__byte1 + ($__byte2<<8) == $__short ? $LITTLE_ENDIAN : undef; #
#----------------------------------------------------------------------------#
# various interesting constants which are not tags (mostly record values); #
#----------------------------------------------------------------------------#
our $APP0_JFIF_TAG = "JFIF\000"; #
our $APP0_JFXX_TAG = "JFXX\000"; #
our $APP0_JFXX_JPG = 0x10; #
our $APP0_JFXX_1B = 0x11; #
our $APP0_JFXX_3B = 0x13; #
our $APP0_JFXX_PAL = 768; #
our $APP1_EXIF_TAG = "Exif\000\000"; #
our $APP1_XMP_TAG = "http://ns.adobe.com/xap/1.0/\000"; #
our $APP1_XMP_XPACKET_ID = 'W5M0MpCehiHzreSzNTczkc9d'; #
our $APP1_XMP_XPACKET_BEGIN = "\x{FEFF}"; #
our $APP1_XMP_META_NS = 'adobe:ns:meta/'; #
our $APP1_XMP_OUTER_RDF_NS ='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
our $APP1_TIFF_SIG = 42; #
our $APP1_TH_TIFF = 1; #
our $APP1_TH_JPEG = 6; #
our $APP2_FPXR_TAG = "FPXR\000"; #
our $APP2_ICC_TAG = "ICC_PROFILE\000"; #
our $APP3_EXIF_TAG = "Meta\000\000"; #
our $APP13_PHOTOSHOP_IDS = ["Photoshop 3.0\000",'Adobe_Photoshop2.5:'];
our $APP13_PHOTOSHOP_TYPE = ['8BIM', '8BPS', 'PHUT']; #
our $APP13_PHOTOSHOP_IPTC = 0x0404; #
our $APP13_PHOTOSHOP_DIRNAME = 'Photoshop_RECORDS'; #
our $APP13_IPTC_TAGMARKER = 0x1c; #
our $APP13_IPTC_DIRNAME = 'IPTC_RECORD'; #
our $APP14_PHOTOSHOP_IDENTIFIER = 'Adobe'; #
#============================================================================#
#============================================================================#
#============================================================================#
# The following lines contain a list of general-purpose regular expressions, #
# which are used by the IFD, GPS ... and other sections. The only reason for #
# them being here is to avoid to do errors more than once ... #
#----------------------------------------------------------------------------#
my $re_integer = '\d+'; # a generic integer number #
my $re_signed = join('', '-?', $re_integer); # a generic signed integer num #
my $re_float = '[+-]?\d+(|.\d+)'; # a generic floating point #
my $re_Cstring = '.*\000'; # a null-terminated string #
my $re_yr18 = '(18|19|20)\d\d'; # YYYY (from 1800AD only ...) #
my $re_year = '\d{4}'; # YYYY (from 0AD on) #
my $re_month = '(0[1-9]|1[0-2])'; # MM (month in 1-12) #
my $re_day = '(0[1-9]|[12]\d|3[01])'; # DD (day in 1-31) #
my $re_hour = '([01]\d|2[0-3])'; # HH (hour in 0-23) #
my $re_minute = '[0-5]\d'; # MM (minute in 0-59) #
my $re_second = $re_minute; # SS (seconds like minutes) #
my $re_zone = join('', $re_hour, $re_minute); # HHMM #
my $re_dt18 = join('', $re_yr18, $re_month, $re_day); # YYYYMMDD #
my $re_date = join('', $re_year, $re_month, $re_day); # YYYYMMDD #
my $re_time = join('', $re_hour, $re_minute, $re_second); # HHMMSS #
my $re_dt18_cl = join(':', $re_yr18, $re_month, $re_day); # YYYY:MM:DD #
my $re_date_cl = join(':', $re_year, $re_month, $re_day); # YYYY:MM:DD #
my $re_time_cl = join(':', $re_hour, $re_minute, $re_second); # HH:MM:SS #
#============================================================================#
#============================================================================#
#============================================================================#
# Root level records for an Exif APP1 segment; we could avoid writing them #
# down here, but this makes syntax checks easier. Also, mandatory tags are #
# here just for reference, since I think they are already present, hence #
# never used. See the tables for IFD0 and IFD1 for further details. #
#--- Mandatory records for IFD0 and IFD1 (not calculated) -------------------#
my $HASH_APP1_ROOT_MANDATORY = {'Identifier' => $APP1_EXIF_TAG, #
'Endianness' => $BIG_ENDIAN, #
'Signature' => $APP1_TIFF_SIG, }; #
#--- Legal records' list ----------------------------------------------------#
my $HASH_APP1_ROOT_GENERAL = #
{'Identifier' => ['Idx-1', $ASCII, 6, $APP1_EXIF_TAG, 'B' ], #
'Endianness' => ['Idx-2', $UNDEF, 2, "($BIG_ENDIAN|$LITTLE_ENDIAN)" ], #
'Signature' => ['Idx-3', $SHORT, 1, $APP1_TIFF_SIG, 'B' ], #
'ThumbnailData' => ['Idx-4', $UNDEF, undef, '.*', 'T' ], }; #
#============================================================================#
#============================================================================#
#============================================================================#
# Most tags in the following three lists are the same for IFD0 and IFD1, #
# only the support level changes (some of them, indeed, must be present in #
# both directories). See the relevant sections in the Image::MetaData::JPEG #
# module perldoc page for further details on the %$HASH_APP1_IFD01_* hashes: #
# MAIN --> "Canonical Exif 2.2 and TIFF 6.0 tags for IFD0 and IFD1"; #
# ADDITIONAL --> "Additional TIFF 6.0 tags not in Exif 2.2 for IFD0"; #
# COMPANIES --> "Exif tags assigned to companies for IFD0 and IFD1". #
#----------------------------------------------------------------------------#
# The meaning of pseudo-regular-expressions is the following: #
# - 'calculated': these tags must not be set by the final user (they are #
# created, if necessary, by the module itself [this is more reliable]). #
# - 'obsoleted': this means that the corresponding tag is no more allowed. #
# Some tags do not have a fixed type (for instance, they can be $SHORT or #
# $LONG): in these cases, the most general type was chosen. Remember that #
# some tags in the main hash table are mandatory. #
#----------------------------------------------------------------------------#
# Hash keys are numeric tags, here written in hexadecimal base. #
# Fields: 0 -> name, 1 -> type, 2 -> count, 3 -> matching regular expression #
# 4 -> (optional) this tag can be set only together with the thumbnail #
#----------------------------------------------------------------------------#
my $IFD_integer = $re_integer; # a generic integer number #
my $IFD_signed = $re_signed; # a generic signed integer num #
my $IFD_float = $re_float; # a generic floating point #
my $IFD_Cstring = $re_Cstring; # a null-terminated string #
my $IFD_dt_full = $re_dt18_cl.' '.$re_time_cl; # YYYY:MM:DD HH:MM:SS #
my $IFD_datetime = '('.$IFD_dt_full.'| : : : : |\s{19})\000'; #
#--- Special screen rules for IFD0 and IFD1 ---------------------------------#
# a YCbCrSubSampling tag indicates the ratio of chrominance components. Its #
# value can be only [2,1] (for YCbCr 4:2:2) or [2,2] (for YCbCr 4:2:0). #
my $SSR_YCCsampl = sub { die unless $_[0] == 2 && $_[1] =~ /1|2/; }; #
#--- Mandatory records for IFD0 and IFD1 (not calculated) -------------------#
my $HASH_APP1_IFD01_MANDATORY = {'XResolution' => [72, 1], #
'YResolution' => [72, 1], #
'ResolutionUnit' => 2, }; #
my $HASH_APP1_IFD0_MANDATORY = {%$HASH_APP1_IFD01_MANDATORY, #
'YCbCrPositioning' => 1, }; #
my $HASH_APP1_IFD1_MANDATORY = {%$HASH_APP1_IFD01_MANDATORY, #
'YCbCrSubSampling' => [2, 1], #
'PhotometricInterpretation' => 2, #
'PlanarConfiguration' => 1, }; #
#--- Legal records' list ----------------------------------------------------#
my $HASH_APP1_IFD01_MAIN = #
{0x0100 => ['ImageWidth', $LONG, 1, $IFD_integer, 'T'], #
0x0101 => ['ImageLength', $LONG, 1, $IFD_integer, 'T'], #
0x0102 => ['BitsPerSample', $SHORT, 3, '8', 'T'], #
0x0103 => ['Compression', $SHORT, 1, '[16]', 'T'], #
0x0106 => ['PhotometricInterpretation', $SHORT, 1, '[26]', ], #
0x010e => ['ImageDescription', $ASCII, undef, $IFD_Cstring ], #
0x010f => ['Make', $ASCII, undef, $IFD_Cstring ], #
0x0110 => ['Model', $ASCII, undef, $IFD_Cstring ], #
0x0111 => ['StripOffsets', $LONG, undef, 'calculated' ], #
0x0112 => ['Orientation', $SHORT, 1, '[1-8]' ], #
0x0115 => ['SamplesPerPixel', $SHORT, 1, '3', 'T'], #
0x0116 => ['RowsPerStrip', $LONG, 1, $IFD_integer, 'T'], #
0x0117 => ['StripByteCounts', $LONG, undef, $IFD_integer, 'T'], #
0x011a => ['XResolution', $RATIONAL, 1, $IFD_integer ], #
0x011b => ['YResolution', $RATIONAL, 1, $IFD_integer ], #
0x011c => ['PlanarConfiguration', $SHORT, 1, '[12]' ], #
0x0128 => ['ResolutionUnit', $SHORT, 1, '[23]' ], #
0x012d => ['TransferFunction', $SHORT, 768, $IFD_integer ], #
0x0131 => ['Software', $ASCII, undef, $IFD_Cstring ], #
0x0132 => ['DateTime', $ASCII, 20, $IFD_datetime ], #
0x013b => ['Artist', $ASCII, undef, $IFD_Cstring ], #
0x013e => ['WhitePoint', $RATIONAL, 2, $IFD_integer ], #
0x013f => ['PrimaryChromaticities', $RATIONAL, 6, $IFD_integer ], #
0x0201 => ['JPEGInterchangeFormat', $LONG, 1, 'calculated' ], #
0x0202 => ['JPEGInterchangeFormatLength',$LONG, 1, $IFD_integer, 'T'], #
0x0211 => ['YCbCrCoefficients', $RATIONAL, 3, $IFD_integer ], #
0x0212 => ['YCbCrSubSampling', $SHORT, 2, $SSR_YCCsampl ], #
0x0213 => ['YCbCrPositioning', $SHORT, 1, '[12]' ], #
0x0214 => ['ReferenceBlackWhite', $RATIONAL, 6, $IFD_integer ], #
0x8298 => ['Copyright', $ASCII, undef, $IFD_Cstring ], #
0x8769 => ['ExifOffset', $LONG, 1, 'calculated' ], #
0x8825 => ['GPSInfo', $LONG, 1, 'calculated' ], }; #
#----------------------------------------------------------------------------#
my $HASH_APP1_IFD01_ADDITIONAL = #
{0x00fe => ['NewSubfileType', $LONG, 1, $IFD_integer ], #
0x00ff => ['SubFileType', $SHORT, 1, $IFD_integer ], #
0x0107 => ['Thresholding', $SHORT, 1, $IFD_integer ], #
0x0108 => ['CellWidth', $SHORT, 1, $IFD_integer ], #
0x0109 => ['CellLength', $SHORT, 1, $IFD_integer ], #
0x010a => ['FillOrder', $SHORT, 1, $IFD_integer ], #
0x010d => ['DocumentName', $ASCII, undef, $IFD_Cstring ], #
0x0118 => ['MinSampleValue', $SHORT, undef, $IFD_integer ], #
0x0119 => ['MaxSampleValue', $SHORT, undef, $IFD_integer ], #
0x011d => ['PageName', $ASCII, undef, $IFD_Cstring ], #
0x011e => ['XPosition', $RATIONAL, 1, $IFD_integer ], #
0x011f => ['YPosition', $RATIONAL, 1, $IFD_integer ], #
0x0120 => ['FreeOffsets', $LONG, undef, $IFD_integer ], #
0x0121 => ['FreeByteCounts', $LONG, undef, $IFD_integer ], #
0x0122 => ['GrayResponseUnit', $SHORT, 1, $IFD_integer ], #
0x0123 => ['GrayResponseCurve', $SHORT, undef, $IFD_integer ], #
0x0124 => ['T4Options', $LONG, 1, $IFD_integer ], #
0x0125 => ['T6Options', $LONG, 1, $IFD_integer ], #
0x0129 => ['PageNumber', $SHORT, 2, $IFD_integer ], #
0x012c => ['ColorResponseUnit', $SHORT, 1, 'invalid' ], #
0x013c => ['HostComputer', $ASCII, undef, $IFD_Cstring ], #
0x013d => ['Predictor', $SHORT, 1, $IFD_integer ], #
0x0140 => ['Colormap', $SHORT, undef, $IFD_integer ], #
0x0141 => ['HalftoneHints', $SHORT, 2, $IFD_integer ], #
0x0142 => ['TileWidth', $LONG, 1, $IFD_integer ], #
0x0143 => ['TileLength', $LONG, 1, $IFD_integer ], #
0x0144 => ['TileOffsets', $LONG, undef, $IFD_integer ], #
0x0145 => ['TileByteCounts', $LONG, undef, $IFD_integer ], #
0x0146 => ['BadFaxLines', $LONG, 1, $IFD_integer ], #
0x0147 => ['CleanFaxData', $SHORT, 1, $IFD_integer ], #
0x0148 => ['ConsecutiveBadFaxLines', $LONG, 1, $IFD_integer ], #
0x014a => ['SubIFD', $LONG, undef, $IFD_integer ], #
0x014c => ['InkSet', $SHORT, 1, $IFD_integer ], #
0x014d => ['InkNames', $ASCII, undef, $IFD_Cstring ], #
0x014e => ['NumberOfInks', $SHORT, 1, $IFD_integer ], #
0x0150 => ['DotRange', $SHORT, undef, $IFD_integer ], #
0x0151 => ['TargetPrinter', $ASCII, undef, $IFD_Cstring ], #
0x0152 => ['ExtraSamples', $SHORT, undef, $IFD_integer ], #
0x0153 => ['SampleFormats', $SHORT, undef, $IFD_integer ], #
0x0154 => ['SMinSampleValue', $UNDEF, undef, '.*' ], #
0x0155 => ['SMaxSampleValue', $UNDEF, undef, '.*' ], #
0x0156 => ['TransferRange', $SHORT, 6, $IFD_integer ], #
0x0157 => ['ClipPath', $BYTE, undef, $IFD_integer ], #
0x0158 => ['XClipPathUnits', $DOUBLE, 1, $IFD_float ], #
0x0159 => ['YClipPathUnits', $DOUBLE, 1, $IFD_float ], #
0x015a => ['Indexed', $SHORT, 1, $IFD_integer ], #
0x015b => ['JPEGTables', undef, undef, 'invalid' ], #
0x015f => ['OPIProxy', $SHORT, 1, $IFD_integer ], #
0x0200 => ['JPEGProc', $SHORT, 1, 'invalid' ], #
0x0203 => ['JPEGRestartInterval', $SHORT, 1, 'invalid' ], #
0x0205 => ['JPEGLosslessPredictors', $SHORT, undef, 'invalid' ], #
0x0206 => ['JPEGPointTransforms', $SHORT, undef, 'invalid' ], #
0x0207 => ['JPEGQTables', $LONG, undef, 'invalid' ], #
0x0208 => ['JPEGDCTables', $LONG, undef, 'invalid' ], #
0x0209 => ['JPEGACTables', $LONG, undef, 'invalid' ], #
0x02bc => ['XML_Packet', $BYTE, undef, $IFD_integer ], }; #
#----------------------------------------------------------------------------#
# The following company-related fields are marked as invalid because they #
# are present also in the SubIFD section (with different numerical values) #
# and I don't want the two entries to collide when setting IMAGE_DATA: #
# 'FlashEnergy', 'SpatialFrequencyResponse', FocalPlane[XY]Resolution', #
# 'FocalPlaneResolutionUnit', 'ExposureIndex', 'SensingMethod', 'CFAPattern' #
#----------------------------------------------------------------------------#
my $HASH_APP1_IFD01_COMPANIES = #
{0x800d => ['ImageID', $ASCII, undef, $IFD_Cstring ], #
0x80b9 => ['RefPts', undef, undef, 'invalid' ], #
0x80ba => ['RegionTackPoint', undef, undef, 'invalid' ], #
0x80bb => ['RegionWarpCorners', undef, undef, 'invalid' ], #
0x80bc => ['RegionAffine', undef, undef, 'invalid' ], #
0x80e3 => ['Matteing', $SHORT, 1, 'obsoleted' ], #
0x80e4 => ['DataType', $SHORT, 1, 'obsoleted' ], #
0x80e5 => ['ImageDepth', $LONG, 1, $IFD_integer ], #
0x80e6 => ['TileDepth', $LONG, 1, $IFD_integer ], #
0x8214 => ['ImageFullWidth', $LONG, 1, $IFD_integer ], #
0x8215 => ['ImageFullLength', $LONG, 1, $IFD_integer ], #
0x8216 => ['TextureFormat', $ASCII, undef, $IFD_Cstring ], #
0x8217 => ['WrapModes', $ASCII, undef, $IFD_Cstring ], #
0x8218 => ['FovCot', $FLOAT, 1, $IFD_float ], #
0x8219 => ['MatrixWorldToScreen', $FLOAT, 16, $IFD_float ], #
0x821a => ['MatrixWorldToCamera', $FLOAT, 16, $IFD_float ], #
0x827d => ['WriterSerialNumber', undef, undef, 'invalid' ], #
0x828d => ['CFARepeatPatternDim', $SHORT, 2, $IFD_integer ], #
0x828e => ['CFAPattern', $BYTE, undef, 'invalid' ], #
0x828f => ['BatteryLevel', $ASCII, undef, $IFD_Cstring ], #
0x830e => ['ModelPixelScaleTag', $DOUBLE, 3, $IFD_float ], #
0x83bb => ['IPTC/NAA', $ASCII, undef, $IFD_Cstring ], #
0x8480 => ['IntergraphMatrixTag', $DOUBLE, 16, 'obsoleted' ], #
0x8482 => ['ModelTiepointTag', $DOUBLE,undef, $IFD_float ], #
0x84e0 => ['Site', $ASCII, undef, $IFD_Cstring ], #
0x84e1 => ['ColorSequence', $ASCII, undef, $IFD_Cstring ], #
0x84e2 => ['IT8Header', $ASCII, undef, $IFD_Cstring ], #
0x84e3 => ['RasterPadding', $SHORT, 1, $IFD_integer ], #
0x84e4 => ['BitsPerRunLength', $SHORT, 1, $IFD_integer ], #
0x84e5 => ['BitsPerExtendedRunLength', $SHORT, 1, $IFD_integer ], #
0x84e6 => ['ColorTable', $BYTE, undef, $IFD_integer ], #
0x84e7 => ['ImageColorIndicator', $BYTE, 1, $IFD_integer ], #
0x84e8 => ['BackgroundColorIndicator', $BYTE, 1, $IFD_integer ], #
0x84e9 => ['ImageColorValue', $BYTE, 1, $IFD_integer ], #
0x84ea => ['BackgroundColorValue', $BYTE, 1, $IFD_integer ], #
0x84eb => ['PixelIntensityRange', $BYTE, 2, $IFD_integer ], #
0x84ec => ['TransparencyIndicator', $BYTE, 1, $IFD_integer ], #
0x84ed => ['ColorCharacterization', $ASCII, undef, $IFD_Cstring ], #
0x84ee => ['HCUsage', $LONG, 1, $IFD_integer ], #
0x84ef => ['TrapIndicator', $BYTE, 1, $IFD_integer ], #
0x84f0 => ['CMYKEquivalent', $SHORT, undef, $IFD_integer ], #
0x84f1 => ['Reserved_TIFF_IT_1', undef, undef, 'invalid' ], #
0x84f2 => ['Reserved_TIFF_IT_2', undef, undef, 'invalid' ], #
0x84f3 => ['Reserved_TIFF_IT_3', undef, undef, 'invalid' ], #
0x85b8 => ['FrameCount', $LONG, 1, $IFD_integer ], #
0x85d8 => ['ModelTransformationTag', $DOUBLE, 16, $IFD_float ], #
0x8649 => ['PhotoshopImageResources', $BYTE, undef, $IFD_integer ], #
0x8773 => ['ICCProfile', undef, undef, 'invalid' ], #
0x87af => ['GeoKeyDirectoryTag', $SHORT, undef, $IFD_integer ], #
0x87b0 => ['GeoDoubleParamsTag', $DOUBLE,undef, $IFD_float ], #
0x87b1 => ['GeoAsciiParamsTag', $ASCII, undef, $IFD_Cstring ], #
0x87be => ['JBIG_Options', undef, undef, 'invalid' ], #
0x8829 => ['Interlace', $SHORT, 1, $IFD_integer ], #
0x882a => ['TimeZoneOffset', $SSHORT,undef, $IFD_signed ], #
0x882b => ['SelfTimerMode', $SHORT, 1, $IFD_integer ], #
0x885c => ['FaxRecvParams', $LONG, 1, $IFD_integer ], #
0x885d => ['FaxSubAddress', $ASCII, undef, $IFD_Cstring ], #
0x885e => ['FaxRecvTime', $LONG, 1, $IFD_integer ], #
0x8871 => ['FedExEDR', undef, undef, 'invalid' ], #
0x920b => ['FlashEnergy', $RATIONAL,undef,'invalid' ], #
0x920c => ['SpatialFrequencyResponse', undef, undef, 'invalid' ], #
0x920d => ['Noise', undef, undef, 'invalid' ], #
0x920e => ['FocalPlaneXResolution', $RATIONAL, 1, 'invalid' ], #
0x920f => ['FocalPlaneYResolution', $RATIONAL, 1, 'invalid' ], #
0x9210 => ['FocalPlaneResolutionUnit', $SHORT, 1, 'invalid' ], #
0x9211 => ['ImageNumber', $LONG, 1, $IFD_integer ], #
0x9212 => ['SecurityClassification', $ASCII, undef, $IFD_Cstring ], #
0x9213 => ['ImageHistory', $ASCII, undef, $IFD_Cstring ], #
0x9215 => ['ExposureIndex', $RATIONAL,undef,'invalid' ], #
0x9216 => ['TIFF/EPStandardID', $BYTE, 4, $IFD_integer ], #
0x9217 => ['SensingMethod', $SHORT, 1, 'invalid' ], #
0x923f => ['StoNits', $DOUBLE, 1, $IFD_float ], #
0x935c => ['ImageSourceData', undef, undef, 'invalid' ], #
0xc4a5 => ['PrintIM_Data', undef, undef, 'invalid' ], #
0xc44f => ['PhotoshopAnnotations', undef, undef, 'invalid' ], #
0xffff => ['DCSHueShiftValues', undef, undef, 'invalid' ], }; #
#----------------------------------------------------------------------------#
my $HASH_APP1_IFD01_GENERAL = {}; #
@$HASH_APP1_IFD01_GENERAL{keys %$_} = #
values %$_ for ($HASH_APP1_IFD01_MAIN, #
$HASH_APP1_IFD01_ADDITIONAL, #
$HASH_APP1_IFD01_COMPANIES); #
#============================================================================#
#============================================================================#
#============================================================================#
# See the "Exif tags for the 0th IFD Exif private subdirectory" section in #
# the Image::MetaData::JPEG module perldoc page for further details (private #
# EXIF region in IFD0, also known as SubIFD). #
#----------------------------------------------------------------------------#
# Hash keys are numeric tags, here written in hexadecimal base. #
# Fields: 0 -> name, 1 -> type, 2 -> count, 3 -> matching regular #
# Mandatory records: ExifVersion, ComponentsConfiguration, FlashpixVersion, #
# ColorSpace, PixelXDimension and PixelYDimension. #
#----------------------------------------------------------------------------#
my $IFD_subsecs = '\d*\s*\000'; # a fraction of a second #
my $IFD_Ustring = '(ASCII\000{3}|JIS\000{5}|Unicode\000|\000{8}).*'; #
my $IFD_DOSfile = '\w{8}\.\w{3}\000'; # a DOS filename (8+3) #
my $IFD_lightsrc = '([0-49]|1[0-57-9]|2[0-4]|255)'; # possible light sources #
my $IFD_flash = '([01579]|1[356]|2[459]|3[12]|6[59]|7[1379]|89|9[35])'; #
my $IFD_hexstr = '[0-9a-fA-F]+\000+'; # hexadecimal ASCII str #
my $IFD_Exifver = '0(100|110|200|210|220|221)'; # known Exif versions #
my $IFD_setdesc = '.{4}(\376\377(.{2})*\000\000)*'; # for DeviceSettingDesc.#
my $IFD_compconf = '(\004\005\006|\001\002\003)\000';# for ComponentsConfig. #
#--- Special screen rules ---------------------------------------------------#
# a SubjectArea tag indicates the location and area of the main subject. The #
# tag can contain 2, 3 or 4 integer numbers (see Exif 2.2 for their meaning) #
my $SSR_subjectarea = sub { die if scalar @_ < 2 || scalar @_ > 4; #
die if grep { ! /^\d+$/ } @_; }; #
# a CFAPattern tag indicates a color filter array. The first four bytes are #
# two shorts giving the horizontal (m) and vertical (n) repeat pixel units. #
# Then, m x n bytes follow, with the actual filter values (in the range 0-6).#
my $SSR_cfapattern = sub { my ($x, $y) = unpack 'nn', $_[0]; #
die if length $_[0] != 4+$x*$y; #
die if $_[0] !~ /^.{4}[0-6]*$/; }; #
#--- Mandatory records ------------------------------------------------------#
my $HASH_APP1_SUBIFD_MANDATORY = {'ExifVersion' => '0220', #
'ComponentsConfiguration' => "\001\002\003\000", #
'FlashpixVersion' => '0100', #
'ColorSpace' => 1, #
'PixelXDimension' => 0, # global info #
'PixelYDimension' => 0 }; # needed here! #
#--- Legal records' list ----------------------------------------------------#
my $HASH_APP1_SUBIFD_GENERAL = #
{0x829a => ['ExposureTime', $RATIONAL, 1, $IFD_integer ], #
0x829d => ['FNumber', $RATIONAL, 1, $IFD_integer ], #
0x8822 => ['ExposureProgram', $SHORT, 1, '[0-8]' ], #
0x8824 => ['SpectralSensitivity', $ASCII, undef, $IFD_Cstring ], #
0x8827 => ['ISOSpeedRatings', $SHORT, undef, $IFD_integer ], #
0x8828 => ['OECF', $UNDEF, undef, '.*' ], #
0x9000 => ['ExifVersion', $UNDEF, 4, $IFD_Exifver ], #
0x9003 => ['DateTimeOriginal', $ASCII, 20, $IFD_datetime ], #
0x9004 => ['DateTimeDigitized', $ASCII, 20, $IFD_datetime ], #
0x9101 => ['ComponentsConfiguration', $UNDEF, 4, $IFD_compconf ], #
0x9102 => ['CompressedBitsPerPixel', $RATIONAL, 1, $IFD_integer ], #
0x9201 => ['ShutterSpeedValue', $SRATIONAL, 1, $IFD_signed ], #
0x9202 => ['ApertureValue', $RATIONAL, 1, $IFD_integer ], #
0x9203 => ['BrightnessValue', $SRATIONAL, 1, $IFD_signed ], #
0x9204 => ['ExposureBiasValue', $SRATIONAL, 1, $IFD_signed ], #
0x9205 => ['MaxApertureValue', $RATIONAL, 1, $IFD_integer ], #
0x9206 => ['SubjectDistance', $RATIONAL, 1, $IFD_integer ], #
0x9207 => ['MeteringMode', $SHORT, 1, '([0-6]|255)' ], #
0x9208 => ['LightSource', $SHORT, 1, $IFD_lightsrc ], #
0x9209 => ['Flash', $SHORT, 1, $IFD_flash ], #
0x920a => ['FocalLength', $RATIONAL, 1, $IFD_integer ], #
0x9214 => ['SubjectArea', $SHORT, undef, $SSR_subjectarea], #
0x927c => ['MakerNote', $UNDEF, undef, 'invalid' ], #
0x9286 => ['UserComment', $UNDEF, undef, $IFD_Ustring ], #
0x9290 => ['SubSecTime', $ASCII, undef, $IFD_subsecs ], #
0x9291 => ['SubSecTimeOriginal', $ASCII, undef, $IFD_subsecs ], #
0x9292 => ['SubSecTimeDigitized', $ASCII, undef, $IFD_subsecs ], #
0xa000 => ['FlashpixVersion', $UNDEF, 4, '0100' ], #
0xa001 => ['ColorSpace', $SHORT, 1, '(1|65535)' ], #
0xa002 => ['PixelXDimension', $LONG, 1, $IFD_integer ], #
0xa003 => ['PixelYDimension', $LONG, 1, $IFD_integer ], #
0xa004 => ['RelatedSoundFile', $ASCII, 13, $IFD_DOSfile ], #
0xa005 => ['InteroperabilityOffset', $LONG, 1, 'calculated' ], #
0xa20b => ['FlashEnergy', $RATIONAL, 1, $IFD_integer ], #
0xa20c => ['SpatialFrequencyResponse', $UNDEF, undef, '.*' ], #
0xa20e => ['FocalPlaneXResolution', $RATIONAL, 1, $IFD_integer ], #
0xa20f => ['FocalPlaneYResolution', $RATIONAL, 1, $IFD_integer ], #
0xa210 => ['FocalPlaneResolutionUnit', $SHORT, 1, '[23]' ], #
0xa214 => ['SubjectLocation', $SHORT, 2, $IFD_integer ], #
0xa215 => ['ExposureIndex', $RATIONAL, 1, $IFD_integer ], #
0xa217 => ['SensingMethod', $SHORT, 1, '[1-578]' ], #
0xa300 => ['FileSource', $UNDEF, 1, '\003' ], #
0xa301 => ['SceneType', $UNDEF, 1, '\001' ], #
0xa302 => ['CFAPattern', $UNDEF, undef, $SSR_cfapattern ], #
0xa401 => ['CustomRendered', $SHORT, 1, '[01]' ], #
0xa402 => ['ExposureMode', $SHORT, 1, '[012]' ], #
0xa403 => ['WhiteBalance', $SHORT, 1, '[01]' ], #
0xa404 => ['DigitalZoomRatio', $RATIONAL, 1, $IFD_integer ], #
0xa405 => ['FocalLengthIn35mmFilm', $SHORT, 1, $IFD_integer ], #
0xa406 => ['SceneCaptureType', $SHORT, 1, '[0-3]' ], #
0xa407 => ['GainControl', $SHORT, 1, '[0-4]' ], #
0xa408 => ['Contrast', $SHORT, 1, '[0-2]' ], #
0xa409 => ['Saturation', $SHORT, 1, '[0-2]' ], #
0xa40a => ['Sharpness', $SHORT, 1, '[0-2]' ], #
0xa40b => ['DeviceSettingDescription', $UNDEF, undef, $IFD_setdesc ], #
0xa40c => ['SubjectDistanceRange', $SHORT, 1, '[0-3]' ], #
0xa420 => ['ImageUniqueID', $ASCII, 33, $IFD_hexstr ], #
# --- From Photoshop >= 7.0 treatment of raw camera files (undocumented) --- #
0xfde8 => ['_OwnerName', $ASCII, undef, "Owner'".'s Name: .*\000' ], #
0xfde9 => ['_SerialNumber', $ASCII, undef, 'Serial Number: .*\000' ], #
0xfdea => ['_Lens', $ASCII, undef, 'Lens: .*\000' ], #
0xfe4c => ['_RawFile', $ASCII, undef, 'Raw File: .*\000' ], #
0xfe4d => ['_Converter', $ASCII, undef, 'Converter: .*\000' ], #
0xfe4e => ['_WhiteBalance', $ASCII, undef, 'White Balance: .*\000' ], #
0xfe51 => ['_Exposure', $ASCII, undef, 'Exposure: .*\000' ], #
0xfe52 => ['_Shadows', $ASCII, undef, 'Shadows: .*\000' ], #
0xfe53 => ['_Brightness', $ASCII, undef, 'Brightness: .*\000' ], #
0xfe54 => ['_Contrast', $ASCII, undef, 'Contrast: .*\000' ], #
0xfe55 => ['_Saturation', $ASCII, undef, 'Saturation: .*\000' ], #
0xfe56 => ['_Sharpness', $ASCII, undef, 'Sharpness: .*\000' ], #
0xfe57 => ['_Smoothness', $ASCII, undef, 'Smoothness: .*\000' ], #
0xfe58 => ['_MoireFilter', $ASCII, undef, 'Moire Filter: .*\000' ], }; #
#============================================================================#
#============================================================================#
#============================================================================#
# See the "EXIF tags for the 0th IFD Interoperability subdirectory" section #
# in the Image::MetaData::JPEG module perldoc page for further details. #
# Mandatory records: InteroperabilityIndex and InteroperabilityVersion #
#----------------------------------------------------------------------------#
# Hash keys are numeric tags, here written in hexadecimal base. #
# Fields: 0 -> name, 1 -> type, 2 -> count, 3 -> matching regular #
#--- Mandatory records ------------------------------------------------------#
my $HASH_INTEROP_MANDATORY = {'InteroperabilityVersion' => '0100', #
'InteroperabilityIndex' => 'R98' }; #
#--- Legal records' list ----------------------------------------------------#
my $HASH_INTEROP_GENERAL = #
{0x0001 => ['InteroperabilityIndex', $ASCII, 4, 'R98\000' ], #
0x0002 => ['InteroperabilityVersion', $UNDEF, 4, '[0-9]{4}' ], #
0x1000 => ['RelatedImageFileFormat', $ASCII, undef, $IFD_Cstring ], #
0x1001 => ['RelatedImageWidth', $LONG, 1, '[0-9]*' ], #
0x1002 => ['RelatedImageLength', $LONG, 1, '[0-9]*' ], }; #
#============================================================================#
#============================================================================#
#============================================================================#
# See the "EXIF tags for the 0th IFD GPS subdirectory" section in the #
# Image::MetaData::JPEG module perldoc page for further details on GPS data. #
# Mandatory records: only GPSVersionID #
#----------------------------------------------------------------------------#
# Hash keys are numeric tags, here written in hexadecimal base. #
# Fields: 0 -> name, 1 -> type, 2 -> count, 3 -> matching regular #
#----------------------------------------------------------------------------#
my $GPS_re_Cstring = $re_Cstring; # a null terminated string #
my $GPS_re_date = $re_dt18_cl . '\000'; # YYYY:MM:DD null terminated #
my $GPS_re_number = $re_integer; # a generic integer number #
my $GPS_re_NS = '[NS]\000'; # latitude reference #
my $GPS_re_EW = '[EW]\000'; # longitude reference #
my $GPS_re_spdsref = '[KMN]\000'; # speed or distance reference #
my $GPS_re_direref = '[TM]\000'; # directin reference #
my $GPS_re_string = '[AJU\000].*'; # GPS "undefined" strings #
#--- Special screen rules ---------------------------------------------------#
# a direction is a rational number in [0.00, 359.99] (we should also test #
# explicitely that the numerator and the denominator are not negative). #
my $SSR_direction = sub { die if grep { $_ < 0 } @_; #
my $dire = $_[0]/$_[1]; die if $dire >= 360; #
die unless $dire =~ /^\d+(\.\d{1,2})?$/; }; #
# a "triplet" corresponds to three rationals for units, minutes (< 60) and #
# seconds (< 60). The 1st argument must be a limit on units (helper rule). #
my $SSR_triplet = sub { my $limit = shift; die if grep { $_ < 0 } @_; #
my ($dd,$mm,$ss) = map {$_[$_]/$_[1+$_]} (0,2,4); #
die unless $mm < 60 && $ss < 60 && $dd <= $limit; #
die unless ($dd + $mm /60 + $ss/360) <= $limit;}; #
# a latitude or a longitude is stored as a sequence of three rationals nums #
# (degrees, minutes and seconds) with degrees<=90 or 180 (see $SSR_triplet). #
my $SSR_latitude = sub { &$SSR_triplet( 90, @_); }; #
my $SSR_longitude = sub { &$SSR_triplet(180, @_); }; #
# a time stamp is stored as three rationals (hours, minutes and seconds); in #
# this case hours must be <= 24 (see $SSR_triplet for further details). #
my $SSR_stupidtime = sub { &$SSR_triplet(24, @_); }; #
#--- Mandatory records ------------------------------------------------------#
my $HASH_GPS_MANDATORY = {'GPSVersionID' => [2,2,0,0]}; #
#--- Legal records' list ----------------------------------------------------#
my $HASH_GPS_GENERAL = #
{0x00 => ['GPSVersionID', $BYTE, 4, '.' ], #
0x01 => ['GPSLatitudeRef', $ASCII, 2, $GPS_re_NS ], #
0x02 => ['GPSLatitude', $RATIONAL, 3, $SSR_latitude ], #
0x03 => ['GPSLongitudeRef', $ASCII, 2, $GPS_re_EW ], #
0x04 => ['GPSLongitude', $RATIONAL, 3, $SSR_longitude ], #
0x05 => ['GPSAltitudeRef', $BYTE, 1, '[01]' ], #
0x06 => ['GPSAltitude', $RATIONAL, 1, $GPS_re_number ], #
0x07 => ['GPSTimeStamp', $RATIONAL, 3, $SSR_stupidtime ], #
0x08 => ['GPSSatellites', $ASCII, undef, $GPS_re_Cstring ], #
0x09 => ['GPSStatus', $ASCII, 2, '[AV]\000' ], #
0x0a => ['GPSMeasureMode', $ASCII, 2, '[23]\000' ], #
0x0b => ['GPSDOP', $RATIONAL, 1, $GPS_re_number ], #
0x0c => ['GPSSpeedRef', $ASCII, 2, $GPS_re_spdsref ], #
0x0d => ['GPSSpeed', $RATIONAL, 1, $GPS_re_number ], #
0x0e => ['GPSTrackRef', $ASCII, 2, $GPS_re_direref ], #
0x0f => ['GPSTrack', $RATIONAL, 1, $SSR_direction ], #
0x10 => ['GPSImgDirectionRef', $ASCII, 2, $GPS_re_direref ], #
0x11 => ['GPSImgDirection', $RATIONAL, 1, $SSR_direction ], #
0x12 => ['GPSMapDatum', $ASCII, undef, $GPS_re_Cstring ], #
0x13 => ['GPSDestLatitudeRef', $ASCII, 2, $GPS_re_NS ], #
0x14 => ['GPSDestLatitude', $RATIONAL, 3, $SSR_latitude ], #
0x15 => ['GPSDestLongitudeRef', $ASCII, 2, $GPS_re_EW ], #
0x16 => ['GPSDestLongitude', $RATIONAL, 3, $SSR_longitude ], #
0x17 => ['GPSDestBearingRef', $ASCII, 2, $GPS_re_direref ], #
0x18 => ['GPSDestBearing', $RATIONAL, 1, $SSR_direction ], #
0x19 => ['GPSDestDistanceRef', $ASCII, 2, $GPS_re_spdsref ], #
0x1a => ['GPSDestDistance', $RATIONAL, 1, $GPS_re_number ], #
0x1b => ['GPSProcessingMethod', $UNDEF, undef, $GPS_re_string ], #
0x1c => ['GPSAreaInformation', $UNDEF, undef, $GPS_re_string ], #
0x1d => ['GPSDateStamp', $ASCII, 11, $GPS_re_date ], #
0x1e => ['GPSDifferential', $SHORT, 1, '[01]' ],}; #
#============================================================================#
# Tags used for ICC data in APP2 (they are 4 bytes strings, so
# I prefer to write the string and then convert it).
sub str2hex { my $z = 0; ($z *= 256) += $_ for unpack "CCCC", $_[0]; $z; }
my $HASH_APP2_ICC =
{str2hex('A2B0') => 'AT0B0Tag',
str2hex('A2B1') => 'AToB1Tag',
str2hex('A2B2') => 'AToB2Tag',
str2hex('bXYZ') => 'BlueMatrixColumn',
str2hex('bTRC') => 'BlueTRC',
str2hex('B2A0') => 'BToA0Tag',
str2hex('B2A1') => 'BToA1Tag',
str2hex('B2A2') => 'BToA2Tag',
str2hex('calt') => 'CalibrationDateTime',
str2hex('targ') => 'CharTarget',
str2hex('chad') => 'ChromaticAdaptation',
str2hex('chrm') => 'Chromaticity',
str2hex('clro') => 'ColorantOrder',
str2hex('clrt') => 'ColorantTable',
str2hex('cprt') => 'Copyright',
str2hex('dmnd') => 'DeviceMfgDesc',
str2hex('dmdd') => 'DeviceModelDesc',
str2hex('gamt') => 'Gamut',
str2hex('kTRC') => 'GrayTRC',
str2hex('gXYZ') => 'GreenMatrixColumn',
str2hex('gTRC') => 'GreenTRC',
str2hex('lumi') => 'Luminance',
str2hex('meas') => 'Measurement',
str2hex('bkpt') => 'MediaBlackPoint',
str2hex('wtpt') => 'MediaWhitePoint',
str2hex('ncl2') => 'NamedColor2',
str2hex('resp') => 'OutputResponse',
str2hex('pre0') => 'Preview0',
str2hex('pre1') => 'Preview1',
str2hex('pre2') => 'Preview2',
str2hex('desc') => 'ProfileDescription',
str2hex('pseq') => 'ProfileSequenceDesc',
str2hex('rXYZ') => 'RedMatrixColumn',
str2hex('rTRC') => 'RedTRC',
str2hex('tech') => 'Technology',
str2hex('vued') => 'ViewingCondDesc',
str2hex('view') => 'ViewingConditions', };
# Tags used by the 0-th IFD of an APP3 segment (reference ... ?)
my $HASH_APP3_IFD =
{0xc350 => 'FilmProductCode',
0xc351 => 'ImageSource',
0xc352 => 'PrintArea',
0xc353 => 'CameraOwner',
0xc354 => 'CameraSerialNumber',
0xc355 => 'GroupCaption',
0xc356 => 'DealerID',
0xc357 => 'OrderID',
0xc358 => 'BagNumber',
0xc359 => 'ScanFrameSeqNumber',
0xc35a => 'FilmCategory',
0xc35b => 'FilmGenCode',
0xc35c => 'ScanSoftware',
0xc35d => 'FilmSize',
0xc35e => 'SBARGBShifts',
0xc35f => 'SBAInputColor',
0xc360 => 'SBAInputBitDepth',
0xc361 => 'SBAExposureRec',
0xc362 => 'UserSBARGBShifts',
0xc363 => 'ImageRotationStatus',
0xc364 => 'RollGUID',
0xc365 => 'APP3Version',
0xc36e => 'SpecialEffectsIFD', # pointer to an IFD
0xc36f => 'BordersIFD', }; # pointer to an IFD
my $HASH_APP3_SPECIAL =
{0x0000 => 'APP3_SpecialIFD_tag_0',
0x0001 => 'APP3_SpecialIFD_tag_1',
0x0002 => 'APP3_SpecialIFD_tag_2', };
my $HASH_APP3_BORDERS =
{0x0000 => 'APP3_BordersIFD_tag_0',
0x0001 => 'APP3_BordersIFD_tag_1',
0x0002 => 'APP3_BordersIFD_tag_2',
0x0003 => 'APP3_BordersIFD_tag_3',
0x0004 => 'APP3_BordersIFD_tag_4',
0x0008 => 'APP3_BordersIFD_tag_8', };
#============================================================================#
#============================================================================#
#============================================================================#
# See the "VALID TAGS FOR IPTC DATA" section in the Image::MetaData::JPEG #
# module perldoc page for further details on IPTC data. Also 1:xx datasets #
# are documented here, although only 2:xx datasets are likely to be found. #
# Note: I don't know why the standard says 4 for 'RecordVersion'; it turns #
# out that you always find 2 in JPEG files. #
#----------------------------------------------------------------------------#
# Hash keys are numeric tags in decimal (the IPTC standard uses base 10...). #
# Fields: 0 -> Tag name, 1 -> repeatability ('N' means non-repeatable), #
# 2,3 -> min and max length, 4 -> regular expression to match. #
# The regular expression for "words" is what they call graphic characters. #
#----------------------------------------------------------------------------#
my $IPTC_re_word = '^[^\000-\040\177]*$'; # words #
my $IPTC_re_line = '^[^\000-\037\177]*$'; # words + spaces #
my $IPTC_re_para = '^[^\000-\011\013\014\016-\037\177]*$'; # line + CR + LF #
my $IPTC_re_dt18 = $re_dt18; # CCYYMMDD #
my $IPTC_re_date = $re_date; # CCYYMMDD full #
my $IPTC_re_dura = $re_time; # HHMMSS #
my $IPTC_re_time = $IPTC_re_dura . '[\+-]' . $re_zone; # HHMMSS+/-HHMM #
my $vchr = '\040-\051\053-\071\073-\076\100-\176'; # (SubjectRef.) #
my $IPTC_re_sure='['.$vchr.']{1,32}?:[01]\d{7}(:['.$vchr.'\s]{0,64}?){3}'; #
#--- Mandatory records ------------------------------------------------------#
my $HASH_IPTC_MANDATORY_1 = {'ModelVersion' => "\000\004" }; #
my $HASH_IPTC_MANDATORY_2 = {'RecordVersion' => "\000\002" }; #
#--- Legal records' list ( datasets 1:xx ) ----------------------------------#
my $HASH_IPTC_GENERAL_1 = #
{0 => ['ModelVersion', 'N', 2, 2, 'binary' ], #
5 => ['Destination', ' ',1,1024, $IPTC_re_word ], #
20 => ['FileFormat', 'N', 2, 2, 'invalid,binary' ], #
22 => ['FileFormatVersion', 'N', 2, 2, 'invalid,binary' ], #
30 => ['ServiceIdentifier', 'N', 0, 10, $IPTC_re_word ], #
40 => ['EnvelopeNumber', 'N', 8, 8, 'invalid,\d{8}' ], #
50 => ['ProductID', ' ', 0, 32, $IPTC_re_word ], #
60 => ['EnvelopePriority', 'N', 1, 1, 'invalid,[1-9]' ], #
70 => ['DataSent', 'N', 8, 8, 'invalid,date' ], #
80 => ['TimeSent', 'N',11, 11, 'invalid,time' ], #
90 => ['CodedCharacterSet', 'N', 0, 32, '\033.{1,3}' ], #
100 => ['UNO', 'N',14, 80, 'invalid' ], #
120 => ['ARMIdentifier', 'N', 2, 2, 'invalid,binary' ], #
122 => ['ARMVersion', 'N', 2, 2, 'invalid,binary' ],}; #
#--- Legal records' list ( datasets 2:xx ) ----------------------------------#
my $HASH_IPTC_GENERAL_2 = #
{0 => ['RecordVersion', 'N', 2, 2, 'binary' ], #
3 => ['ObjectTypeReference', 'N', 3, 67, '\d{2}:[\w\s]{0,64}?' ], #
4 => ['ObjectAttributeReference', ' ', 4, 68, '\d{3}:[\w\s]{0,64}?' ], #
5 => ['ObjectName', 'N', 1, 64, $IPTC_re_line ], #
7 => ['EditStatus', 'N', 1, 64, $IPTC_re_line ], #
8 => ['EditorialUpdate', 'N', 2, 2, '01' ], #
10 => ['Urgency', 'N', 1, 1, '[1-8]' ], #
12 => ['SubjectReference', ' ',13,236, $IPTC_re_sure ], #
15 => ['Category', 'N', 1, 3, '[a-zA-Z]{1,3}?' ], #
20 => ['SupplementalCategory', ' ', 1, 32, $IPTC_re_line ], #
22 => ['FixtureIdentifier', 'N', 1, 32, $IPTC_re_word ], #
25 => ['Keywords', ' ', 1, 64, $IPTC_re_line ], #
26 => ['ContentLocationCode', ' ', 3, 3, '[A-Z]{3}' ], #
27 => ['ContentLocationName', ' ', 1, 64, $IPTC_re_line ], #
30 => ['ReleaseDate', 'N', 8, 8, $IPTC_re_dt18 ], #
35 => ['ReleaseTime', 'N',11, 11, $IPTC_re_time ], #
37 => ['ExpirationDate', 'N', 8, 8, $IPTC_re_dt18 ], #
38 => ['ExpirationTime', 'N',11, 11, $IPTC_re_time ], #
40 => ['SpecialInstructions', 'N', 1,256, $IPTC_re_line ], #
42 => ['ActionAdvised', 'N', 2, 2, '0[1-4]' ], #
45 => ['ReferenceService', ' ',10, 10, 'invalid' ], #
47 => ['ReferenceDate', ' ', 8, 8, 'invalid' ], #
50 => ['ReferenceNumber', ' ', 8, 8, 'invalid' ], #
55 => ['DateCreated', 'N', 8, 8, $IPTC_re_date ], #
60 => ['TimeCreated', 'N',11, 11, $IPTC_re_time ], #
62 => ['DigitalCreationDate', 'N', 8, 8, $IPTC_re_dt18 ], #
63 => ['DigitalCreationTime', 'N',11, 11, $IPTC_re_time ], #
65 => ['OriginatingProgram', 'N', 1, 32, $IPTC_re_line ], #
70 => ['ProgramVersion', 'N', 1, 10, $IPTC_re_line ], #
75 => ['ObjectCycle', 'N', 1, 1, '[apb]' ], #
80 => ['ByLine', ' ', 1, 32, $IPTC_re_line ], #
85 => ['ByLineTitle', ' ', 1, 32, $IPTC_re_line ], #
90 => ['City', 'N', 1, 32, $IPTC_re_line ], #
92 => ['SubLocation', 'N', 1, 32, $IPTC_re_line ], #
95 => ['Province/State', 'N', 1, 32, $IPTC_re_line ], #
100 => ['Country/PrimaryLocationCode', 'N', 3, 3, '[A-Z]{3}' ], #
101 => ['Country/PrimaryLocationName', 'N', 1, 64, $IPTC_re_line ], #
103 => ['OriginalTransmissionReference','N',1, 32, $IPTC_re_line ], #
105 => ['Headline', 'N', 1,256, $IPTC_re_line ], #
110 => ['Credit', 'N', 1, 32, $IPTC_re_line ], #
115 => ['Source', 'N', 1, 32, $IPTC_re_line ], #
116 => ['CopyrightNotice', 'N', 1,128, $IPTC_re_line ], #
118 => ['Contact', ' ', 1,128, $IPTC_re_line ], #
120 => ['Caption/Abstract', 'N', 1,2000,$IPTC_re_para ], #
122 => ['Writer/Editor', ' ', 1, 32, $IPTC_re_line ], #
125 => ['RasterizedCaption', 'N',7360,7360,'binary' ], #
130 => ['ImageType', 'N', 2, 2,'[0-49][WYMCKRGBTFLPS]'], #
131 => ['ImageOrientation', 'N', 1, 1, '[PLS]' ], #
135 => ['LanguageIdentifier', 'N', 2, 3, '[a-zA-Z]{2,3}?' ], #
150 => ['AudioType', 'N', 2, 2, '[012][ACMQRSTVW]' ], #
151 => ['AudioSamplingRate', 'N', 6, 6, '\d{6}' ], #
152 => ['AudioSamplingResolution', 'N', 2, 2, '\d{2}' ], #
153 => ['AudioDuration', 'N', 6, 6, $IPTC_re_dura ], #
154 => ['AudioOutcue', 'N', 1, 64, $IPTC_re_line ], #
200 => ['ObjDataPreviewFileFormat', 'N', 2, 2, 'invalid,binary' ], #
201 => ['ObjDataPreviewFileFormatVer', 'N', 2, 2, 'invalid,binary' ], #
202 => ['ObjDataPreviewData', 'N', 1,256000,'invalid,binary' ],}; #
#============================================================================#
#============================================================================#
#============================================================================#
# Esoteric tags for a Photoshop APP13 segment (not IPTC data); #
# see the "VALID TAGS FOR PHOTOSHOP-STYLE APP13 DATA" section in the #
# Image::MetaData::JPEG module perldoc page for further details. #
# [tags 0x07d0 --> 0x0bb6 are reserved for path information] #
#----------------------------------------------------------------------------#
# Hash keys are numeric tags, here written in hexadecimal base. #
# Fields: 0 -> Tag name, 1 -> repeatability ('N' means non-repeatable), #
# 2,3 -> min and max length, 4 -> regular expression to match. #
# The syntax specifications are currently just placeholder, but this could #
# change in future. The only effect is to inhibit a direct assignement of #
# the 'IPTC/NAA' dataset, which must be modified with specialised routines. #
#----------------------------------------------------------------------------#
my $HASH_PHOTOSHOP_PATHINFO = #
{( map { $_ => [ sprintf("PathInfo_%3x",$_), #
' ', 0, 65535, 'binary'] } (0x07d0..0x0bb6) ), #
0x0bb7 => ['ClippingPathName', ' ', 0, 65535, 'binary' ], }; #
#----------------------------------------------------------------------------#
my $HASH_PHOTOSHOP_GENERAL = #
{0x03e8 => ['Photoshop2Info', ' ', 0, 65535, 'binary' ], #
0x03e9 => ['MacintoshPrintInfo', ' ', 0, 65535, 'binary' ], #
0x03eb => ['Photoshop2ColorTable', ' ', 0, 65535, 'binary' ], #
0x03ed => ['ResolutionInfo', ' ', 0, 65535, 'binary' ], #
0x03ee => ['AlphaChannelsNames', ' ', 0, 65535, 'binary' ], #
0x03ef => ['DisplayInfo', ' ', 0, 65535, 'binary' ], #
0x03f0 => ['PStringCaption', ' ', 0, 65535, 'binary' ], #
0x03f1 => ['BorderInformation', ' ', 0, 65535, 'binary' ], #
0x03f2 => ['BackgroundColor', ' ', 0, 65535, 'binary' ], #
0x03f3 => ['PrintFlags', ' ', 0, 65535, 'binary' ], #
0x03f4 => ['BWHalftoningInfo', ' ', 0, 65535, 'binary' ], #
0x03f5 => ['ColorHalftoningInfo', ' ', 0, 65535, 'binary' ], #
0x03f6 => ['DuotoneHalftoningInfo', ' ', 0, 65535, 'binary' ], #
0x03f7 => ['BWTransferFunc', ' ', 0, 65535, 'binary' ], #
0x03f8 => ['ColorTransferFuncs', ' ', 0, 65535, 'binary' ], #
0x03f9 => ['DuotoneTransferFuncs', ' ', 0, 65535, 'binary' ], #
0x03fa => ['DuotoneImageInfo', ' ', 0, 65535, 'binary' ], #
0x03fb => ['EffectiveBW', ' ', 0, 65535, 'binary' ], #
0x03fc => ['ObsoletePhotoshopTag1', ' ', 0, 65535, 'binary' ], #
0x03fd => ['EPSOptions', ' ', 0, 65535, 'binary' ], #
0x03fe => ['QuickMaskInfo', ' ', 0, 65535, 'binary' ], #
0x03ff => ['ObsoletePhotoshopTag2', ' ', 0, 65535, 'binary' ], #
0x0400 => ['LayerStateInfo', ' ', 0, 65535, 'binary' ], #
0x0401 => ['WorkingPathInfo', ' ', 0, 65535, 'binary' ], #
0x0402 => ['LayersGroupInfo', ' ', 0, 65535, 'binary' ], #
0x0403 => ['ObsoletePhotoshopTag3', ' ', 0, 65535, 'binary' ], #
0x0404 => ['IPTC/NAA', ' ', 0, 65535, 'invalid' ], #
0x0405 => ['RawImageMode', ' ', 0, 65535, 'binary' ], #
0x0406 => ['JPEGQuality', ' ', 0, 65535, 'binary' ], #
0x0408 => ['GridGuidesInfo', ' ', 0, 65535, 'binary' ], #
0x0409 => ['ThumbnailResource', ' ', 0, 65535, 'binary' ], #
0x040a => ['CopyrightFlag', ' ', 0, 65535, 'binary' ], #
0x040b => ['URL', ' ', 0, 65535, 'binary' ], #
0x040c => ['ThumbnailResource2', ' ', 0, 65535, 'binary' ], #
0x040d => ['GlobalAngle', ' ', 0, 65535, 'binary' ], #
0x040e => ['ColorSamplersResource', ' ', 0, 65535, 'binary' ], #
0x040f => ['ICCProfile', ' ', 0, 65535, 'binary' ], #
0x0410 => ['Watermark', ' ', 0, 65535, 'binary' ], #
0x0411 => ['ICCUntagged', ' ', 0, 65535, 'binary' ], #
0x0412 => ['EffectsVisible', ' ', 0, 65535, 'binary' ], #
0x0413 => ['SpotHalftone', ' ', 0, 65535, 'binary' ], #
0x0414 => ['IDsBaseValue', ' ', 0, 65535, 'binary' ], #
0x0415 => ['UnicodeAlphaNames', ' ', 0, 65535, 'binary' ], #
0x0416 => ['IndexedColourTableCount', ' ', 0, 65535, 'binary' ], #
0x0417 => ['TransparentIndex', ' ', 0, 65535, 'binary' ], #
0x0419 => ['GlobalAltitude', ' ', 0, 65535, 'binary' ], #
0x041a => ['Slices', ' ', 0, 65535, 'binary' ], #
0x041b => ['WorkflowURL', ' ', 0, 65535, 'binary' ], #
0x041c => ['JumpToXPEP', ' ', 0, 65535, 'binary' ], #
0x041d => ['AlphaIdentifiers', ' ', 0, 65535, 'binary' ], #
0x041e => ['URLList', ' ', 0, 65535, 'binary' ], #
0x0421 => ['VersionInfo', ' ', 0, 65535, 'binary' ], #
0x2710 => ['PrintFlagsInfo', ' ', 0, 65535, 'binary' ], }; #
#----------------------------------------------------------------------------#
@$HASH_PHOTOSHOP_GENERAL{keys %$HASH_PHOTOSHOP_PATHINFO} = #
values %$HASH_PHOTOSHOP_PATHINFO; #
#============================================================================#
#============================================================================#
#============================================================================#
# Some scalar-valued hashes, which were once original databases, are now #
# generated with "generate_lookup" from more general array-valued hashes #
# (in practice, a single column is singled out from a multi-column table). #
# %$HASH_APP1_IFD is built by merging the first column of 3 different hashes.#
#----------------------------------------------------------------------------#
my $HASH_PHOTOSHOP_TAGS = generate_lookup($HASH_PHOTOSHOP_GENERAL ,0); #
my $HASH_PHOTOSHOP_PHUT = generate_lookup($HASH_PHOTOSHOP_PATHINFO ,0); #
my $HASH_IPTC_TAGS_1 = generate_lookup($HASH_IPTC_GENERAL_1 ,0); #
my $HASH_IPTC_TAGS_2 = generate_lookup($HASH_IPTC_GENERAL_2 ,0); #
my $HASH_APP1_ROOT = generate_lookup($HASH_APP1_ROOT_GENERAL ,0); #
my $HASH_APP1_GPS = generate_lookup($HASH_GPS_GENERAL ,0); #
my $HASH_APP1_INTEROP = generate_lookup($HASH_INTEROP_GENERAL ,0); #
my $HASH_APP1_IFD = generate_lookup($HASH_APP1_IFD01_GENERAL ,0); #
my $HASH_APP1_SUBIFD = generate_lookup($HASH_APP1_SUBIFD_GENERAL ,0); #
#============================================================================#
#============================================================================#
#============================================================================#
# Some segments (APP1 and APP3 currently) have an IFD-like structure, i.e. #
# they can have "subdirectories" pointed to by offset tags. These subdirs #
# are bifurcation points for the lookup process, and are represented by #
# hash references instead of plain strings (scalars). #
#----------------------------------------------------------------------------#
$$HASH_APP1_IFD{SubIFD} = $HASH_APP1_SUBIFD; # Exif private tags #
$$HASH_APP1_IFD{GPS} = $HASH_APP1_GPS; # GPS tags #
$$HASH_APP3_IFD{Special} = $HASH_APP3_SPECIAL; # Special effect tags #
$$HASH_APP3_IFD{Borders} = $HASH_APP3_BORDERS; # Border tags #
$$HASH_APP1_SUBIFD{Interop} = $HASH_APP1_INTEROP; # Interoperability tags #
#============================================================================#
#============================================================================#
#============================================================================#
# MakerNote stuff is stored in a separated file; the return value of this #
# inclusion is the $HASH_MAKERNOTES hash reference, containing all relevant #
# parameters. We only have to link this new table into $HASH_APP1_SUBIFD. #
#----------------------------------------------------------------------------#
our $HASH_MAKERNOTES = require 'Image/MetaData/JPEG/data/Makernotes.pl'; #
$$HASH_APP1_SUBIFD{'MakerNoteData_' . $_} = #
generate_lookup($$HASH_MAKERNOTES{$_}{tags} ,0) #
for keys %$HASH_MAKERNOTES; #
#============================================================================#
#============================================================================#
#============================================================================#
# Syntax tables and mandatory records tables for IPTC data are hidden in the #
# corresponding tag hashes. Another %IFD_SUBDIRS is overkill here. #
#----------------------------------------------------------------------------#
$$HASH_IPTC_TAGS_1{__syntax} = $HASH_IPTC_GENERAL_1; #
$$HASH_IPTC_TAGS_1{__mandatory} = $HASH_IPTC_MANDATORY_1; #
$$HASH_IPTC_TAGS_2{__syntax} = $HASH_IPTC_GENERAL_2; #
$$HASH_IPTC_TAGS_2{__mandatory} = $HASH_IPTC_MANDATORY_2; #
$$HASH_PHOTOSHOP_TAGS{__syntax} = $HASH_PHOTOSHOP_GENERAL; #
#============================================================================#
#============================================================================#
#============================================================================#
# The following hash is the database for the tag-to-tagname translation; of #
# course, records with a textual tag are not listed here. The navigation #
# through this structure is best done with the help of the JPEG_lookup #
# function, so this hash is not exported (as it was some time ago). #
#----------------------------------------------------------------------------#
my $psdirname = sub { $APP13_PHOTOSHOP_DIRNAME . '_' . $_[0] }; #
#----------------------------------------------------------------------------#
my $JPEG_RECORD_NAME = #
{APP1 => {%$HASH_APP1_ROOT, # APP1 root #
IFD0 => $HASH_APP1_IFD, # main image #
IFD1 => $HASH_APP1_IFD, }, # thumbnail #
APP2 => {TagTable => $HASH_APP2_ICC, }, # ICC data #
APP3 => {IFD0 => $HASH_APP3_IFD, }, # main image #
APP13 => {&$psdirname('8BIM') => $HASH_PHOTOSHOP_TAGS, # PS:8BIM #
&$psdirname('8BPS') => $HASH_PHOTOSHOP_TAGS, # PS: < ver 4 #
&$psdirname('PHUT') => $HASH_PHOTOSHOP_PHUT, # PS:PHUT #
$APP13_IPTC_DIRNAME.'_1' => $HASH_IPTC_TAGS_1, # PS:IPTC R:1 #
$APP13_IPTC_DIRNAME.'_2' => $HASH_IPTC_TAGS_2, }, };# PS:IPTC R:2 #
#----------------------------------------------------------------------------#
###########################################################
# This helper function returns record data from the #
# %$JPEG_RECORD_NAME hash. The arguments are first joined #
# with the '@' character, and then splitted on the same #
# character to give a list of '@'-free strings (this al- #
# lows for greater flexibility at call time); this list #
# contains keys for exploring the %$JPEG_RECORD_NAME hash;#
# e.g., the arguments ('APP1', 'IFD0@GPS', 0x1e) select #
# $JPEG_RECORD_NAME{APP1}{IFD0}{GPS}{0x1e}, i.e. the #
# textual name of the GPS record with key = 0x1e in the #
# IFD0 in the APP1 segment. If, at some point during the #
# search, an argument fails (it is not a valid key) or it #
# is not defined, the search is interrupted, and undef is #
# returned. Note also that the return value could be a #
# string as well as a hash reference, depending on the #
# search depth. If the key lookup for the last argument #
# fails, a reverse lookup is run (i.e., the key corres- #
# ponding to the value equal to the last user argument is #
# searched). If even this lookup fails, undef is returned.#
###########################################################
sub JPEG_lookup {
# all searches start from here
my $lookup = $JPEG_RECORD_NAME;
# print a debugging message and return immediately unless
# all arguments are scalars (i.e., references are not allowed)
for (@_) { print "wrong argument(s) in JPEG_lookup call", return if ref; }
# delete all undefined or "false" arguments
@_ = grep { defined $_ } @_;
# join all remaining arguments
my $keystring = join('@', @_);
# split the resulting string on '@'
my @keylist = split('@', $keystring);
# extract and save the last argument for special treatment
my $last = pop @keylist;
# delete all false arguments
@keylist = grep { $_ } @keylist;
# refuse to work with $last undefined
return unless defined $last;
# consume the list of "normal" arguments: they must be successive
# keys for navigation in a multi-level hash. Interrupt the search
# as soon as an argument is undefined or $lookup is not a hash ref
for (@keylist) {
# return undef as soon as an argument is undefined
return undef unless $_;
# go one level deeper in the hash exploration
$lookup = $$lookup{$_};
# return undef if $lookup is no more a hash reference
return undef unless ref $lookup eq 'HASH'; }
# $lookup is a hash reference now. Return the value
# corresponding to $last (used as a key) if it exists.
return $$lookup{$last} if exists $$lookup{$last};
# if we are still here, scan the hash looking for a value equal to
# $last, and return its key. Avoid each %$lookup, since we could
# exit the loop before the end and I don't want to reset the
# iterator in that stupid manner.
for (keys %$lookup) { return $_ if $$lookup{$_} eq $last; }
# if we are still here, we have lost
return undef;
};
#============================================================================#
#============================================================================#
#============================================================================#
# This hash is needed to overcome some complications due to the APP1/APP3 #
# structure: some IFDs or sub-IFDs can contain offset tags (tags whose value #
# is an offset in the JPEG file), linking to nested structures, which are #
# represented internally as sub-lists pointed to by $REFERENCE records; the #
# sub-lists deserve in general a more significant name than the offset tag #
# name. Each key in the following hash is a path to an IFD or one of its #
# subdirectories; the corresponding value is a hash reference, with the #
# pointed hash mapping offset tag numerical values to subdirectory names. #
# (the [tag names] -> [tag numerical values] translation is done afterwards) #
#----------------------------------------------------------------------------#
# A sub hash must also own the '__syntax' and '__mandatory' keys, returning #
# a reference to a hash of syntactical properties to be respected by data in #
# the corresponding IFD and a reference to a hash of mandatory records. #
# These special entries are of course treated differently from the others ...#
#----------------------------------------------------------------------------#
# When the JPEG file is read, offset tag records are not stored; insted, we #
# store a $REFERENCE record with the mapped name (and the name of the origi- #
# nating offset tag saved in the "extra" field). The following hash can then #
# be used in both directions to do data parsing/dumping. #
#----------------------------------------------------------------------------#
our %IFD_SUBDIRS = #
('APP1' => {'__syntax' => $HASH_APP1_ROOT_GENERAL, #
'__mandatory' => $HASH_APP1_ROOT_MANDATORY }, #
'APP1@IFD0' => {'__syntax' => $HASH_APP1_IFD01_GENERAL, #
'__mandatory' => $HASH_APP1_IFD0_MANDATORY, #
'GPSInfo' => 'GPS', #
'ExifOffset' => 'SubIFD'}, #
'APP1@IFD0@GPS' => {'__syntax' => $HASH_GPS_GENERAL, #
'__mandatory' => $HASH_GPS_MANDATORY }, #
'APP1@IFD0@SubIFD' => {'__syntax' => $HASH_APP1_SUBIFD_GENERAL, #
'__mandatory' => $HASH_APP1_SUBIFD_MANDATORY, #
'MakerNote' => 'MakerNoteData', #
'InteroperabilityOffset' => 'Interop'}, #
'APP1@IFD0@SubIFD@Interop' => {'__syntax' => $HASH_INTEROP_GENERAL, #
'__mandatory'=> $HASH_INTEROP_MANDATORY }, #
'APP1@IFD1' => {'__syntax' => $HASH_APP1_IFD01_GENERAL, #
'__mandatory' => $HASH_APP1_IFD1_MANDATORY }, #
'APP3@IFD0' => {'BordersIFD' => 'Borders', #
'SpecialEffectsIFD' => 'Special'}, ); #
#----------------------------------------------------------------------------#
while (my ($ifd_path, $ifd_hash) = each %IFD_SUBDIRS) { #
my %h = map { $_ =~ /__syntax|__mandatory/ ? ($_ => $$ifd_hash{$_}) : #
(JPEG_lookup($ifd_path, $_) => $$ifd_hash{$_}) #
} keys %$ifd_hash; #
$IFD_SUBDIRS{$ifd_path} = \ %h; } #
#============================================================================#
#============================================================================#
#============================================================================#
# These parameters must be initialised with JPEG_lookup, because I don't #
# want to have them written explicitely in more than one place. #
#----------------------------------------------------------------------------#
our $APP1_TH_TYPE = JPEG_lookup('APP1@IFD1@Compression'); #
our $THJPEG_OFFSET = JPEG_lookup('APP1@IFD1@JPEGInterchangeFormat'); #
our $THJPEG_LENGTH = JPEG_lookup('APP1@IFD1@JPEGInterchangeFormatLength'); #
our $THTIFF_OFFSET = JPEG_lookup('APP1@IFD1@StripOffsets'); #
our $THTIFF_LENGTH = JPEG_lookup('APP1@IFD1@StripByteCounts'); #
our $MAKERNOTE_TAG = JPEG_lookup('APP1@IFD0@SubIFD@MakerNote'); #
#----------------------------------------------------------------------------#
# successful package load
1;