/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ //-------------------------------------------------------------------------- // Program to pull the EXIF information out of various types of digital // images and present it in a reasonably consistent way // // Original code pulled from 'jhead' by Matthias Wandel (http://www.sentex.net/~mwandel/) - jhead // Adapted for XBMC by DD. //-------------------------------------------------------------------------- // Note: Jhead supports TAG_MAKER_NOTE exif field, // but that is omitted for now - to make porting easier and addition smaller #include "ExifParse.h" #ifdef TARGET_WINDOWS #include #else #include #include #endif #include #include #include #ifndef min #define min(a,b) (a)>(b)?(b):(a) #endif #ifndef max #define max(a,b) (a)<(b)?(b):(a) #endif // Prototypes for exif utility functions. static void ErrNonfatal(const char* const msg, int a1, int a2); #define DIR_ENTRY_ADDR(Start, Entry) ((Start)+2+12*(Entry)) //-------------------------------------------------------------------------- // Describes tag values #define TAG_DESCRIPTION 0x010E #define TAG_MAKE 0x010F #define TAG_MODEL 0x0110 #define TAG_ORIENTATION 0x0112 #define TAG_X_RESOLUTION 0x011A // Not processed. Format rational64u (see http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.76/html/TagNames/EXIF.html) #define TAG_Y_RESOLUTION 0x011B // Not processed. Format rational64u #define TAG_RESOLUTION_UNIT 0x0128 // Not processed. Format int16u. Values: 1-none; 2-inches; 3-cm #define TAG_SOFTWARE 0x0131 #define TAG_DATETIME 0x0132 #define TAG_THUMBNAIL_OFFSET 0x0201 #define TAG_THUMBNAIL_LENGTH 0x0202 #define TAG_Y_CB_CR_POS 0x0213 // Not processed. Format int16u. Values: 1-Centered; 2-Co-sited #define TAG_EXPOSURETIME 0x829A #define TAG_FNUMBER 0x829D #define TAG_EXIF_OFFSET 0x8769 #define TAG_EXPOSURE_PROGRAM 0x8822 #define TAG_GPSINFO 0x8825 #define TAG_ISO_EQUIVALENT 0x8827 #define TAG_EXIF_VERSION 0x9000 // Not processed. #define TAG_COMPONENT_CFG 0x9101 // Not processed. #define TAG_DATETIME_ORIGINAL 0x9003 #define TAG_DATETIME_DIGITIZED 0x9004 #define TAG_SHUTTERSPEED 0x9201 #define TAG_APERTURE 0x9202 #define TAG_EXPOSURE_BIAS 0x9204 #define TAG_MAXAPERTURE 0x9205 #define TAG_SUBJECT_DISTANCE 0x9206 #define TAG_METERING_MODE 0x9207 #define TAG_LIGHT_SOURCE 0x9208 #define TAG_FLASH 0x9209 #define TAG_FOCALLENGTH 0x920A #define TAG_MAKER_NOTE 0x927C // Not processed yet. Maybe in the future. #define TAG_USERCOMMENT 0x9286 #define TAG_XP_COMMENT 0x9c9c #define TAG_FLASHPIX_VERSION 0xA000 // Not processed. #define TAG_COLOUR_SPACE 0xA001 // Not processed. Format int16u. Values: 1-RGB; 2-Adobe RGB 65535-Uncalibrated #define TAG_EXIF_IMAGEWIDTH 0xa002 #define TAG_EXIF_IMAGELENGTH 0xa003 #define TAG_INTEROP_OFFSET 0xa005 #define TAG_FOCALPLANEXRES 0xa20E #define TAG_FOCALPLANEUNITS 0xa210 #define TAG_EXPOSURE_INDEX 0xa215 #define TAG_EXPOSURE_MODE 0xa402 #define TAG_WHITEBALANCE 0xa403 #define TAG_DIGITALZOOMRATIO 0xA404 #define TAG_FOCALLENGTH_35MM 0xa405 #define TAG_GPS_LAT_REF 1 #define TAG_GPS_LAT 2 #define TAG_GPS_LONG_REF 3 #define TAG_GPS_LONG 4 #define TAG_GPS_ALT_REF 5 #define TAG_GPS_ALT 6 //-------------------------------------------------------------------------- // Exif format descriptor stuff namespace { constexpr auto FMT_BYTE = 1; [[maybe_unused]] constexpr auto FMT_STRING = 2; constexpr auto FMT_USHORT = 3; constexpr auto FMT_ULONG = 4; constexpr auto FMT_URATIONAL = 5; constexpr auto FMT_SBYTE = 6; [[maybe_unused]] constexpr auto FMT_UNDEFINED = 7; constexpr auto FMT_SSHORT = 8; constexpr auto FMT_SLONG = 9; constexpr auto FMT_SRATIONAL = 10; constexpr auto FMT_SINGLE = 11; constexpr auto FMT_DOUBLE = 12; // NOTE: Remember to change NUM_FORMATS if you define a new format constexpr auto NUM_FORMATS = 12; const unsigned int BytesPerFormat[NUM_FORMATS] = {1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8}; } // namespace //-------------------------------------------------------------------------- // Internationalisation string IDs. The enum order must match that in the // language file (e.g. 'language/resource.language.en_gb/strings.po', and EXIF_PARSE_STRING_ID_BASE // must match the ID of the first Exif string in that file. #define EXIF_PARSE_STRING_ID_BASE 21800 enum { // Distance ExifStrDistanceInfinite = EXIF_PARSE_STRING_ID_BASE, // Whitebalance et.al. ExifStrManual, ExifStrAuto, // Flash modes ExifStrYes, ExifStrNo, ExifStrFlashNoStrobe, ExifStrFlashStrobe, ExifStrFlashManual, ExifStrFlashManualNoReturn, ExifStrFlashManualReturn, ExifStrFlashAuto, ExifStrFlashAutoNoReturn, ExifStrFlashAutoReturn, ExifStrFlashRedEye, ExifStrFlashRedEyeNoReturn, ExifStrFlashRedEyeReturn, ExifStrFlashManualRedEye, ExifStrFlashManualRedEyeNoReturn, ExifStrFlashManualRedEyeReturn, ExifStrFlashAutoRedEye, ExifStrFlashAutoRedEyeNoReturn, ExifStrFlashAutoRedEyeReturn, // Light sources ExifStrDaylight, ExifStrFluorescent, ExifStrIncandescent, ExifStrFlash, ExifStrFineWeather, ExifStrShade, // Metering Mode ExifStrMeteringCenter, ExifStrMeteringSpot, ExifStrMeteringMatrix, // Exposure Program ExifStrExposureProgram, ExifStrExposureAperture, ExifStrExposureShutter, ExifStrExposureCreative, ExifStrExposureAction, ExifStrExposurePortrait, ExifStrExposureLandscape, // Exposure mode ExifStrExposureModeAuto, // ISO equivalent ExifStrIsoEquivalent, // GPS latitude, longitude, altitude ExifStrGpsLatitude, ExifStrGpsLongitude, ExifStrGpsAltitude, }; //-------------------------------------------------------------------------- // Report non fatal errors. Now that microsoft.net modifies exif headers, // there's corrupted ones, and there could be more in the future. //-------------------------------------------------------------------------- static void ErrNonfatal(const char* const msg, int a1, int a2) { printf("ExifParse - Nonfatal Error : %s %d %d", msg, a1, a2); } //-------------------------------------------------------------------------- // Convert a 16 bit unsigned value from file's native byte order //-------------------------------------------------------------------------- int CExifParse::Get16(const void* const Short, const bool motorolaOrder) { if (motorolaOrder) { return (((const unsigned char *)Short)[0] << 8) | ((const unsigned char *)Short)[1]; } else { return (((const unsigned char *)Short)[1] << 8) | ((const unsigned char *)Short)[0]; } } //-------------------------------------------------------------------------- // Convert a 32 bit signed value from file's native byte order //-------------------------------------------------------------------------- int CExifParse::Get32(const void* const Long, const bool motorolaOrder) { if (motorolaOrder) { return (((const char *)Long)[0] << 24) | (((const unsigned char *)Long)[1] << 16) | (((const unsigned char *)Long)[2] << 8 ) | (((const unsigned char *)Long)[3] << 0 ); } else { return (((const char *)Long)[3] << 24) | (((const unsigned char *)Long)[2] << 16) | (((const unsigned char *)Long)[1] << 8 ) | (((const unsigned char *)Long)[0] << 0 ); } } //-------------------------------------------------------------------------- // It appears that CStdString constructor replaces "\n" with "\r\n" which results // in "\r\r\n" if there already is "\r\n", which in turn results in corrupted // display. So this is an attempt to undo effects of a smart constructor. Also, // replaces all nonprintable characters with "." //-------------------------------------------------------------------------- /*void CExifParse::FixComment(CStdString& comment) { comment.Replace("\r\r\n", "\r\n"); for (unsigned int i=0; i 4) { ErrNonfatal("Maximum directory nesting exceeded (corrupt exif header)", 0,0); return; } char IndentString[25]; memset(IndentString, ' ', 25); IndentString[NestingLevel * 4] = '\0'; int NumDirEntries = Get16((const void*)DirStart, m_MotorolaOrder); const unsigned char* const DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); if (DirEnd+4 > (OffsetBase+ExifLength)) { if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength) { // Version 1.3 of jhead would truncate a bit too much. // This also caught later on as well. } else { ErrNonfatal("Illegally sized directory", 0,0); return; } } for (int de=0;de NUM_FORMATS) { ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag); continue; } if ((unsigned)Components > 0x10000) { ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag); continue; } ByteCount = Components * BytesPerFormat[Format - 1]; if (ByteCount > 4) { unsigned OffsetVal; OffsetVal = (unsigned)Get32(DirEntry+8, m_MotorolaOrder); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal > UINT32_MAX - ByteCount || OffsetVal + ByteCount > ExifLength) { // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for tag %04x", Tag,0); continue; } ValuePtr = (unsigned char*)(const_cast(OffsetBase)+OffsetVal); if (OffsetVal > m_LargestExifOffset) { m_LargestExifOffset = OffsetVal; } } else { // 4 bytes or less and value is in the dir entry itself ValuePtr = (unsigned char*)(const_cast(DirEntry)+8); } // Extract useful components of tag switch(Tag) { case TAG_DESCRIPTION: { int length = max(ByteCount, 0); length = min(length, MAX_COMMENT); strncpy(m_ExifInfo->Description, (char *)ValuePtr, length); m_ExifInfo->Description[length] = '\0'; break; } case TAG_MAKE: { int space = sizeof(m_ExifInfo->CameraMake); if (space > 0) { strncpy(m_ExifInfo->CameraMake, (char *)ValuePtr, space - 1); m_ExifInfo->CameraMake[space - 1] = '\0'; } break; } case TAG_MODEL: { int space = sizeof(m_ExifInfo->CameraModel); if (space > 0) { strncpy(m_ExifInfo->CameraModel, (char *)ValuePtr, space - 1); m_ExifInfo->CameraModel[space - 1] = '\0'; } break; } // case TAG_SOFTWARE: strncpy(m_ExifInfo->Software, ValuePtr, 5); break; case TAG_FOCALPLANEXRES: m_FocalPlaneXRes = ConvertAnyFormat(ValuePtr, Format); break; case TAG_THUMBNAIL_OFFSET: m_ExifInfo->ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; case TAG_THUMBNAIL_LENGTH: m_ExifInfo->ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; case TAG_MAKER_NOTE: continue; break; case TAG_DATETIME_ORIGINAL: { int space = sizeof(m_ExifInfo->DateTime); if (space > 0) { strncpy(m_ExifInfo->DateTime, (char *)ValuePtr, space - 1); m_ExifInfo->DateTime[space - 1] = '\0'; // If we get a DATETIME_ORIGINAL, we use that one. m_DateFound = true; } break; } case TAG_DATETIME_DIGITIZED: case TAG_DATETIME: { if (!m_DateFound) { // If we don't already have a DATETIME_ORIGINAL, use whatever // time fields we may have. int space = sizeof(m_ExifInfo->DateTime); if (space > 0) { strncpy(m_ExifInfo->DateTime, (char *)ValuePtr, space - 1); m_ExifInfo->DateTime[space - 1] = '\0'; } } break; } case TAG_USERCOMMENT: { // The UserComment allows comments without the charset limitations of ImageDescription. // Therefore the UserComment field is prefixed by a CharacterCode field (8 Byte): // - ASCII: 'ASCII\0\0\0' // - Unicode: 'UNICODE\0' // - JIS X208-1990: 'JIS\0\0\0\0\0' // - Unknown: '\0\0\0\0\0\0\0\0' (application specific) m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_UNKNOWN; const int EXIF_COMMENT_CHARSET_LENGTH = 8; if (ByteCount >= EXIF_COMMENT_CHARSET_LENGTH) { // As some implementations use spaces instead of \0 for the padding, // we're not so strict and check only the prefix. if (memcmp(ValuePtr, "ASCII", 5) == 0) m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_ASCII; else if (memcmp(ValuePtr, "UNICODE", 7) == 0) m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_UNICODE; else if (memcmp(ValuePtr, "JIS", 3) == 0) m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_JIS; int length = ByteCount - EXIF_COMMENT_CHARSET_LENGTH; length = min(length, MAX_COMMENT); memcpy(m_ExifInfo->Comments, ValuePtr + EXIF_COMMENT_CHARSET_LENGTH, length); m_ExifInfo->Comments[length] = '\0'; // FixComment(comment); // Ensure comment is printable } } break; case TAG_XP_COMMENT: { // The XP user comment field is always unicode (UCS-2) encoded m_ExifInfo->XPCommentsCharset = EXIF_COMMENT_CHARSET_UNICODE; size_t length = min(ByteCount, MAX_COMMENT); memcpy(m_ExifInfo->XPComment, ValuePtr, length); m_ExifInfo->XPComment[length] = '\0'; } break; case TAG_FNUMBER: // Simplest way of expressing aperture, so I trust it the most. // (overwrite previously computd value if there is one) m_ExifInfo->ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_APERTURE: case TAG_MAXAPERTURE: // More relevant info always comes earlier, so only use this field if we don't // have appropriate aperture information yet. if (m_ExifInfo->ApertureFNumber == 0) { m_ExifInfo->ApertureFNumber = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*0.5); } break; case TAG_FOCALLENGTH: // Nice digital cameras actually save the focal length as a function // of how far they are zoomed in. m_ExifInfo->FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_SUBJECT_DISTANCE: // Inidcates the distacne the autofocus camera is focused to. // Tends to be less accurate as distance increases. { float distance = (float)ConvertAnyFormat(ValuePtr, Format); m_ExifInfo->Distance = distance; } break; case TAG_EXPOSURETIME: { // Simplest way of expressing exposure time, so I trust it most. // (overwrite previously computd value if there is one) float expTime = (float)ConvertAnyFormat(ValuePtr, Format); if (expTime) m_ExifInfo->ExposureTime = expTime; } break; case TAG_SHUTTERSPEED: // More complicated way of expressing exposure time, so only use // this value if we don't already have it from somewhere else. if (m_ExifInfo->ExposureTime == 0) { m_ExifInfo->ExposureTime = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0))); } break; case TAG_FLASH: m_ExifInfo->FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_ORIENTATION: m_ExifInfo->Orientation = (int)ConvertAnyFormat(ValuePtr, Format); if (m_ExifInfo->Orientation < 0 || m_ExifInfo->Orientation > 8) { ErrNonfatal("Undefined rotation value %d", m_ExifInfo->Orientation, 0); m_ExifInfo->Orientation = 0; } break; case TAG_EXIF_IMAGELENGTH: case TAG_EXIF_IMAGEWIDTH: // Use largest of height and width to deal with images that have been // rotated to portrait format. { int a = (int)ConvertAnyFormat(ValuePtr, Format); if (m_ExifImageWidth < a) m_ExifImageWidth = a; } break; case TAG_FOCALPLANEUNITS: switch((int)ConvertAnyFormat(ValuePtr, Format)) { // According to the information I was using, 2 means meters. // But looking at the Cannon powershot's files, inches is the only // sensible value. case 1: m_FocalPlaneUnits = 25.4; break; // inch case 2: m_FocalPlaneUnits = 25.4; break; case 3: m_FocalPlaneUnits = 10; break; // centimeter case 4: m_FocalPlaneUnits = 1; break; // millimeter case 5: m_FocalPlaneUnits = .001; break; // micrometer } break; case TAG_EXPOSURE_BIAS: m_ExifInfo->ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_WHITEBALANCE: m_ExifInfo->Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_LIGHT_SOURCE: //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both m_ExifInfo->LightSource = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_METERING_MODE: m_ExifInfo->MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURE_PROGRAM: m_ExifInfo->ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURE_INDEX: if (m_ExifInfo->ISOequivalent == 0) { // Exposure index and ISO equivalent are often used interchangeably, // so we will do the same. // http://photography.about.com/library/glossary/bldef_ei.htm m_ExifInfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); } break; case TAG_ISO_EQUIVALENT: m_ExifInfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); if (m_ExifInfo->ISOequivalent < 50) m_ExifInfo->ISOequivalent *= 200; // Fixes strange encoding on some older digicams. break; case TAG_EXPOSURE_MODE: m_ExifInfo->ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_DIGITALZOOMRATIO: m_ExifInfo->DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXIF_OFFSET: case TAG_INTEROP_OFFSET: { const unsigned char* const SubdirStart = OffsetBase + (unsigned)Get32(ValuePtr, m_MotorolaOrder); if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength) { ErrNonfatal("Illegal exif or interop ofset directory link",0,0); } else { ProcessDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); } continue; } break; case TAG_GPSINFO: { const unsigned char* const SubdirStart = OffsetBase + (unsigned)Get32(ValuePtr, m_MotorolaOrder); if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength) { ErrNonfatal("Illegal GPS directory link",0,0); } else { ProcessGpsInfo(SubdirStart, ByteCount, OffsetBase, ExifLength); } continue; } break; case TAG_FOCALLENGTH_35MM: // The focal length equivalent 35 mm is a 2.2 tag (defined as of April 2002) // if its present, use it to compute equivalent focal length instead of // computing it from sensor geometry and actual focal length. m_ExifInfo->FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; } } // In addition to linking to subdirectories via exif tags, // there's also a potential link to another directory at the end of each // directory. this has got to be the result of a committee! unsigned Offset; if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength) { Offset = (unsigned)Get32(DirStart+2+12*NumDirEntries, m_MotorolaOrder); if (Offset) { const unsigned char* const SubdirStart = OffsetBase + Offset; if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase) { if (SubdirStart > OffsetBase && SubdirStart < OffsetBase+ExifLength+20) { // Jhead 1.3 or earlier would crop the whole directory! // As Jhead produces this form of format incorrectness, // I'll just let it pass silently } else { ErrNonfatal("Illegal subdirectory link",0,0); } } else { if (SubdirStart <= OffsetBase+ExifLength) { ProcessDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); } } if (Offset > m_LargestExifOffset) { m_LargestExifOffset = Offset; } } } else { // The exif header ends before the last next directory pointer. } if (m_ExifInfo->ThumbnailOffset) { m_ExifInfo->ThumbnailAtEnd = false; if (m_ExifInfo->ThumbnailOffset <= ExifLength) { if (m_ExifInfo->ThumbnailSize > ExifLength - m_ExifInfo->ThumbnailOffset) { // If thumbnail extends past exif header, only save the part that // actually exists. Canon's EOS viewer utility will do this - the // thumbnail extracts ok with this hack. m_ExifInfo->ThumbnailSize = ExifLength - m_ExifInfo->ThumbnailOffset; } } } } //-------------------------------------------------------------------------- // Process a EXIF marker // Describes all the drivel that most digital cameras include... //-------------------------------------------------------------------------- bool CExifParse::Process (const unsigned char* const ExifSection, const unsigned short length, ExifInfo_t *info) { m_ExifInfo = info; // EXIF signature: "Exif\0\0" // Check EXIF signatures const char ExifHeader[] = "Exif\0\0"; const char ExifAlignment0[] = "II"; const char ExifAlignment1[] = "MM"; const char ExifExtra = 0x2a; const char* pos = (const char*)(ExifSection + sizeof(short)); // position data pointer after length field if (memcmp(pos, ExifHeader,6)) { printf("ExifParse: incorrect Exif header"); return false; } pos += 6; if (memcmp(pos, ExifAlignment0, strlen(ExifAlignment0)) == 0) { m_MotorolaOrder = false; } else if (memcmp(pos, ExifAlignment1, strlen(ExifAlignment1)) == 0) { m_MotorolaOrder = true; } else { printf("ExifParse: invalid Exif alignment marker"); return false; } pos += strlen(ExifAlignment0); // Check the next value for correctness. if (Get16((const void*)(pos), m_MotorolaOrder) != ExifExtra) { printf("ExifParse: invalid Exif start (1)"); return false; } pos += sizeof(short); unsigned long FirstOffset = (unsigned)Get32((const void*)pos, m_MotorolaOrder); if (FirstOffset < 8 || FirstOffset + 8 >= length) { ErrNonfatal("Invalid offset of first IFD value: %u", FirstOffset, 0); return false; } // First directory starts 16 bytes in. All offset are relative to 8 bytes in. ProcessDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0); m_ExifInfo->ThumbnailAtEnd = m_ExifInfo->ThumbnailOffset >= m_LargestExifOffset; // Compute the CCD width, in millimeters. if (m_FocalPlaneXRes != 0) { // Note: With some cameras, its not possible to compute this correctly because // they don't adjust the indicated focal plane resolution units when using less // than maximum resolution, so the CCDWidth value comes out too small. Nothing // that Jhead can do about it - its a camera problem. m_ExifInfo->CCDWidth = (float)(m_ExifImageWidth * m_FocalPlaneUnits / m_FocalPlaneXRes); } if (m_ExifInfo->FocalLength) { if (m_ExifInfo->FocalLength35mmEquiv == 0) { // Compute 35 mm equivalent focal length based on sensor geometry if we haven't // already got it explicitly from a tag. if (m_ExifInfo->CCDWidth != 0.0f) { m_ExifInfo->FocalLength35mmEquiv = (int)(m_ExifInfo->FocalLength / m_ExifInfo->CCDWidth * 36 + 0.5f); } } } return true; } //-------------------------------------------------------------------------- // GPS Lat/Long extraction helper function //-------------------------------------------------------------------------- void CExifParse::GetLatLong( const unsigned int Format, const unsigned char* ValuePtr, const int ComponentSize, char *latLongString) { if (Format != FMT_URATIONAL) { ErrNonfatal("Illegal number format %d for GPS Lat/Long", Format, 0); } else { double Values[3]; for (unsigned a=0; a<3 ;a++) { Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format); } if (Values[0] < 0 || Values[0] > 180 || Values[1] < 0 || Values[1] >= 60 || Values[2] < 0 || Values[2] >= 60) { // Ignore invalid values (DMS format expected) ErrNonfatal("Invalid Lat/Long value", 0, 0); latLongString[0] = 0; } else { char latLong[30]; sprintf(latLong, "%3.0fd %2.0f' %5.2f\"", Values[0], Values[1], Values[2]); strcat(latLongString, latLong); } } } //-------------------------------------------------------------------------- // Process GPS info directory //-------------------------------------------------------------------------- void CExifParse::ProcessGpsInfo( const unsigned char* const DirStart, int ByteCountUnused, const unsigned char* const OffsetBase, unsigned ExifLength) { int NumDirEntries = Get16(DirStart, m_MotorolaOrder); for (int de=0;de OffsetBase + ExifLength) { ErrNonfatal("GPS info directory goes past end of exif", 0, 0); return; } unsigned Tag = Get16(DirEntry, m_MotorolaOrder); unsigned Format = Get16(DirEntry+2, m_MotorolaOrder); unsigned Components = (unsigned)Get32(DirEntry+4, m_MotorolaOrder); if (Format == 0 || Format > NUM_FORMATS) { ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag); continue; } unsigned ComponentSize = BytesPerFormat[Format - 1]; unsigned ByteCount = Components * ComponentSize; const unsigned char* ValuePtr; if (ByteCount > 4) { unsigned OffsetVal = (unsigned)Get32(DirEntry+8, m_MotorolaOrder); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal > UINT32_MAX - ByteCount || OffsetVal + ByteCount > ExifLength) { // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for tag %04x", Tag,0); continue; } ValuePtr = OffsetBase+OffsetVal; } else { // 4 bytes or less and value is in the dir entry itself ValuePtr = DirEntry+8; } switch(Tag) { case TAG_GPS_LAT_REF: m_ExifInfo->GpsLat[0] = ValuePtr[0]; m_ExifInfo->GpsLat[1] = 0; break; case TAG_GPS_LONG_REF: m_ExifInfo->GpsLong[0] = ValuePtr[0]; m_ExifInfo->GpsLong[1] = 0; break; case TAG_GPS_LAT: GetLatLong(Format, ValuePtr, ComponentSize, m_ExifInfo->GpsLat); break; case TAG_GPS_LONG: GetLatLong(Format, ValuePtr, ComponentSize, m_ExifInfo->GpsLong); break; case TAG_GPS_ALT_REF: if (ValuePtr[0] != 0) m_ExifInfo->GpsAlt[0] = '-'; m_ExifInfo->GpsAlt[1] = 0; break; case TAG_GPS_ALT: { char temp[18]; sprintf(temp, "%.2fm", static_cast(ConvertAnyFormat(ValuePtr, Format))); strcat(m_ExifInfo->GpsAlt, temp); } break; } } }