//-------------------------------------------------------------------------- // 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<NumDirEntries;de++) { const unsigned char* DirEntry = DIR_ENTRY_ADDR(DirStart, de); 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+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,"%dm", Get32(ValuePtr, m_MotorolaOrder)); strcat(m_ExifInfo->GpsAlt, temp); } break; } } }
/** Process Exif directory @param dib Input FIBITMAP @param tiffp Pointer to the TIFF header @param offset 0th IFD offset @param length Length of the datafile @param msb_order Endianess order of the datafile @return */ static BOOL jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsigned int length, BOOL msb_order) { WORD de, nde; std::stack<WORD> destack; // directory entries stack std::stack<BYTE*> ifdstack; // IFD stack std::stack<TagLib::MDMODEL> modelstack; // metadata model stack // Keep a list of already visited IFD to avoid stack overflows // when recursive/cyclic directory structures exist. // This kind of recursive Exif file was encountered with Kodak images coming from // KODAK PROFESSIONAL DCS Photo Desk JPEG Export v3.2 W std::map<DWORD, int> visitedIFD; #define DIR_ENTRY_ADDR(_start, _entry) (_start + 2 + (12 * _entry)) // set the metadata model to Exif TagLib::MDMODEL md_model = TagLib::EXIF_MAIN; // set the pointer to the first IFD and follow it were it leads. BYTE *ifdp = (BYTE*)tiffp + offset; de = 0; do { // if there is anything on the stack then pop it off if(!destack.empty()) { ifdp = ifdstack.top(); ifdstack.pop(); de = destack.top(); destack.pop(); md_model = modelstack.top(); modelstack.pop(); } // remember that we've visited this directory so that we don't visit it again later DWORD visited = (DWORD)( (((size_t)ifdp & 0xFFFF) << 16) | (size_t)de ); if(visitedIFD.find(visited) != visitedIFD.end()) { continue; } else { visitedIFD[visited] = 1; // processed } // determine how many entries there are in the current IFD nde = ReadUint16(msb_order, ifdp); for(; de < nde; de++) { char *pde = NULL; // pointer to the directory entry char *pval = NULL; // pointer to the tag value // create a tag FITAG *tag = FreeImage_CreateTag(); if(!tag) return FALSE; // point to the directory entry pde = (char*) DIR_ENTRY_ADDR(ifdp, de); // get the tag ID FreeImage_SetTagID(tag, ReadUint16(msb_order, pde)); // get the tag format WORD tag_type = (WORD)ReadUint16(msb_order, pde + 2); if((tag_type - 1) >= EXIF_NUM_FORMATS) { // a problem occured : delete the tag (not free'd after) FreeImage_DeleteTag(tag); // break out of the for loop break; } FreeImage_SetTagType(tag, (FREE_IMAGE_MDTYPE)tag_type); // get number of components FreeImage_SetTagCount(tag, ReadUint32(msb_order, pde + 4)); // get the size of the tag value in bytes FreeImage_SetTagLength(tag, FreeImage_GetTagCount(tag) * FreeImage_TagDataWidth((WORD)FreeImage_GetTagType(tag))); if(FreeImage_GetTagLength(tag) <= 4) { // 4 bytes or less and value is in the dir entry itself pval = pde + 8; } else { // if its bigger than 4 bytes, the directory entry contains an offset // first check if offset exceeds buffer, at this stage FreeImage_GetTagLength may return invalid data DWORD offset_value = ReadUint32(msb_order, pde + 8); if(offset_value > length) { // a problem occured : delete the tag (not free'd after) FreeImage_DeleteTag(tag); // jump to next entry continue; } // now check if offset + tag length exceeds buffer if(offset_value > length - FreeImage_GetTagLength(tag)) { // a problem occured : delete the tag (not free'd after) FreeImage_DeleteTag(tag); // jump to next entry continue; } pval = (char*)(tiffp + offset_value); } // check for a IFD offset BOOL isIFDOffset = FALSE; switch(FreeImage_GetTagID(tag)) { case TAG_EXIF_OFFSET: case TAG_GPS_OFFSET: case TAG_INTEROP_OFFSET: case TAG_MAKER_NOTE: isIFDOffset = TRUE; break; } if(isIFDOffset) { DWORD sub_offset = 0; TagLib::MDMODEL next_mdmodel = md_model; BYTE *next_ifd = ifdp; // get offset and metadata model if (FreeImage_GetTagID(tag) == TAG_MAKER_NOTE) { processMakerNote(dib, pval, msb_order, &sub_offset, &next_mdmodel); next_ifd = (BYTE*)pval + sub_offset; } else { processIFDOffset(tag, pval, msb_order, &sub_offset, &next_mdmodel); next_ifd = (BYTE*)tiffp + sub_offset; } if((sub_offset < (DWORD) length) && (next_mdmodel != TagLib::UNKNOWN)) { // push our current directory state onto the stack ifdstack.push(ifdp); // bump to the next entry de++; destack.push(de); // push our current metadata model modelstack.push(md_model); // push new state onto of stack to cause a jump ifdstack.push(next_ifd); destack.push(0); // select a new metadata model modelstack.push(next_mdmodel); // delete the tag as it won't be stored nor deleted in the for() loop FreeImage_DeleteTag(tag); break; // break out of the for loop } else { // unsupported camera model, canon maker tag or or something unknown // process as a standard tag processExifTag(dib, tag, pval, msb_order, md_model); } } else { // process as a standard tag processExifTag(dib, tag, pval, msb_order, md_model); } // delete the tag FreeImage_DeleteTag(tag); } // for(nde) // additional thumbnail data is skipped } while (!destack.empty()); return TRUE; }
//-------------------------------------------------------------------------- // Process one of the nested EXIF directories. //-------------------------------------------------------------------------- void CExifParse::ProcessDir(const unsigned char* const DirStart, const unsigned char* const OffsetBase, const unsigned ExifLength, int NestingLevel) { if (NestingLevel > 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<NumDirEntries;de++) { int Tag, Format, Components; unsigned char* ValuePtr; int ByteCount; const unsigned char* const DirEntry = DIR_ENTRY_ADDR(DirStart, de); Tag = Get16(DirEntry, m_MotorolaOrder); Format = Get16(DirEntry+2, m_MotorolaOrder); Components = Get32(DirEntry+4, m_MotorolaOrder); if (Format <= 0 || Format > 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+ByteCount > ExifLength) { // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for tag %04x", Tag,0); continue; } ValuePtr = (unsigned char*)(const_cast<unsigned char*>(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<unsigned char*>(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 GPS info directory //-------------------------------------------------------------------------- void ProcessGpsInfo(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength) { int de; unsigned a; int NumDirEntries; NumDirEntries = Get16u(DirStart); #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) if (ShowTags){ xprintf("(dir has %d entries)\n",NumDirEntries); } ImageInfo.GpsInfoPresent = TRUE; strcpy(ImageInfo.GpsLat, "? ?"); strcpy(ImageInfo.GpsLong, "? ?"); ImageInfo.GpsAlt[0] = 0; for (de=0;de<NumDirEntries;de++){ unsigned Tag, Format, Components; unsigned char * ValuePtr; int ComponentSize; unsigned ByteCount; unsigned char * DirEntry; DirEntry = DIR_ENTRY_ADDR(DirStart, de); if (DirEntry+12 > OffsetBase+ExifLength){ ErrNonfatal("GPS info directory goes past end of exif",0,0); return; } Tag = Get16u(DirEntry); Format = Get16u(DirEntry+2); Components = Get32u(DirEntry+4); if ((Format-1) >= NUM_FORMATS) { // (-1) catches illegal zero case as unsigned underflows to positive large. ErrNonfatal("Illegal number format %d for Exif gps tag %04x", Format, Tag); continue; } ComponentSize = BytesPerFormat[Format]; ByteCount = Components * ComponentSize; if (ByteCount > 4){ unsigned OffsetVal; OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal+ByteCount > ExifLength){ // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for Exif gps 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){ char FmtString[21]; char TempString[50]; double Values[3]; case TAG_GPS_LAT_REF: ImageInfo.GpsLat[0] = ValuePtr[0]; break; case TAG_GPS_LONG_REF: ImageInfo.GpsLong[0] = ValuePtr[0]; break; case TAG_GPS_LAT: case TAG_GPS_LONG: if (Format != FMT_URATIONAL){ ErrNonfatal("Inappropriate format (%d) for Exif GPS coordinates!", Format, 0); } strcpy(FmtString, "%0.0fd %0.0fm %0.0fs"); for (a=0;a<3;a++){ int den, digits; den = Get32s(ValuePtr+4+a*ComponentSize); digits = 0; while (den > 1 && digits <= 6){ den = den / 10; digits += 1; } if (digits > 6) digits = 6; FmtString[1+a*7] = (char)('2'+digits+(digits ? 1 : 0)); FmtString[3+a*7] = (char)('0'+digits); Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format); } sprintf(TempString, FmtString, Values[0], Values[1], Values[2]); if (Tag == TAG_GPS_LAT){ strncpy(ImageInfo.GpsLat+2, TempString, 29); }else{ strncpy(ImageInfo.GpsLong+2, TempString, 29); } break; case TAG_GPS_ALT_REF: ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' '); break; case TAG_GPS_ALT: sprintf(ImageInfo.GpsAlt + 1, "%.2fm", ConvertAnyFormat(ValuePtr, Format)); break; } if (ShowTags){ // Show tag value. if (Tag < MAX_GPS_TAG){ xprintf(" GPS%s =", GpsTags[Tag]); }else{ // Show unknown tag xprintf(" Illegal GPS tag %04x=", Tag); } switch(Format){ case FMT_UNDEFINED: // Undefined is typically an ascii string. case FMT_STRING: // String arrays printed without function call (different from int arrays) { xprintf("\""); for (a=0;a<ByteCount;a++){ int ZeroSkipped = 0; if (ValuePtr[a] >= 32){ if (ZeroSkipped){ xprintf("?"); ZeroSkipped = 0; } putchar(ValuePtr[a]); }else{ if (ValuePtr[a] == 0){ ZeroSkipped = 1; } } } xprintf("\"\n"); } break; default: // Handle arrays of numbers later (will there ever be?) for (a=0;;){ PrintFormatNumber(ValuePtr+a*ComponentSize, Format, ByteCount); if (++a >= Components) break; xprintf(", "); } xprintf("\n"); } } } }
//-------------------------------------------------------------------------- // Process exif format directory, as used by Cannon maker note //-------------------------------------------------------------------------- void ProcessCanonMakerNoteDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength) { int de; int a; int NumDirEntries; NumDirEntries = Get16u(DirStart); #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) { unsigned char * DirEnd; DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); if (DirEnd > (OffsetBase+ExifLength)){ ErrNonfatal("Illegally sized directory",0,0); return; } if (DumpExifMap){ printf("Map: %05td-%05td: Directory (makernote)\n",DirStart-OffsetBase, DirEnd-OffsetBase); } } if (ShowTags){ printf("(dir has %d entries)\n",NumDirEntries); } for (de=0;de<NumDirEntries;de++){ int Tag, Format, Components; unsigned char * ValuePtr; int ByteCount; unsigned char * DirEntry; DirEntry = DIR_ENTRY_ADDR(DirStart, de); Tag = Get16u(DirEntry); Format = Get16u(DirEntry+2); Components = Get32u(DirEntry+4); if ((Format-1) >= NUM_FORMATS) { // (-1) catches illegal zero case as unsigned underflows to positive large. 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]; if (ByteCount > 4){ unsigned OffsetVal; OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal+ByteCount > ExifLength){ // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for tag %04x", Tag,0); continue; } ValuePtr = OffsetBase+OffsetVal; if (DumpExifMap){ printf("Map: %05d-%05d: Data for makernote tag %04x\n",OffsetVal, OffsetVal+ByteCount, Tag); } }else{ // 4 bytes or less and value is in the dir entry itself ValuePtr = DirEntry+8; } if (ShowTags){ // Show tag name printf(" Canon maker tag %04x Value = ", Tag); } // Show tag value. switch(Format){ case FMT_UNDEFINED: // Undefined is typically an ascii string. case FMT_STRING: // String arrays printed without function call (different from int arrays) if (ShowTags){ printf("\""); for (a=0;a<ByteCount;a++){ int ZeroSkipped = 0; if (ValuePtr[a] >= 32){ if (ZeroSkipped){ printf("?"); ZeroSkipped = 0; } putchar(ValuePtr[a]); }else{ if (ValuePtr[a] == 0){ ZeroSkipped = 1; } } } printf("\"\n"); } break; default: if (ShowTags){ PrintFormatNumber(ValuePtr, Format, ByteCount); printf("\n"); } } if (Tag == 1 && Components > 16){ int IsoCode = Get16u(ValuePtr + 16*sizeof(unsigned short)); if (IsoCode >= 16 && IsoCode <= 24){ ImageInfo.ISOequivalent = 50 << (IsoCode-16); } } if (Tag == 4 && Format == FMT_USHORT){ if (Components > 7){ int WhiteBalance = Get16u(ValuePtr + 7*sizeof(unsigned short)); switch(WhiteBalance){ // 0=Auto, 6=Custom case 1: ImageInfo.LightSource = 1; break; // Sunny case 2: ImageInfo.LightSource = 1; break; // Cloudy case 3: ImageInfo.LightSource = 3; break; // Thungsten case 4: ImageInfo.LightSource = 2; break; // Fourescent case 5: ImageInfo.LightSource = 4; break; // Flash } } if (Components > 19 && ImageInfo.Distance <= 0) { // Inidcates the distance the autofocus camera is focused to. // Tends to be less accurate as distance increases. int temp_dist = Get16u(ValuePtr + 19*sizeof(unsigned short)); printf("temp dist=%d\n",temp_dist); if (temp_dist != 65535){ ImageInfo.Distance = (float)temp_dist/100; }else{ ImageInfo.Distance = -1 /* infinity */; } } } } }
/** Process Exif directory @param dib Input FIBITMAP @param tiffp Pointer to the TIFF header @param offset 0th IFD offset @param length Length of the datafile @param msb_order Endianess order of the datafile @return */ static BOOL jpeg_read_exif_dir(FIBITMAP *dib, const BYTE *tiffp, unsigned long offset, unsigned int length, BOOL msb_order) { WORD de, nde; std::stack<WORD> destack; // directory entries stack std::stack<const BYTE*> ifdstack; // IFD stack std::stack<TagLib::MDMODEL> modelstack; // metadata model stack // Keep a list of already visited IFD to avoid stack overflows // when recursive/cyclic directory structures exist. // This kind of recursive Exif file was encountered with Kodak images coming from // KODAK PROFESSIONAL DCS Photo Desk JPEG Export v3.2 W std::map<DWORD, int> visitedIFD; /* "An Image File Directory (IFD) consists of a 2-byte count of the number of directory entries (i.e. the number of fields), followed by a sequence of 12-byte field entries, followed by a 4-byte offset of the next IFD (or 0 if none)." The "next IFD" (1st IFD) is the thumbnail. */ #define DIR_ENTRY_ADDR(_start, _entry) (_start + 2 + (12 * _entry)) // set the metadata model to Exif TagLib::MDMODEL md_model = TagLib::EXIF_MAIN; // set the pointer to the first IFD (0th IFD) and follow it were it leads. const BYTE *ifd0th = (BYTE*)tiffp + offset; const BYTE *ifdp = ifd0th; de = 0; do { // if there is anything on the stack then pop it off if(!destack.empty()) { ifdp = ifdstack.top(); ifdstack.pop(); de = destack.top(); destack.pop(); md_model = modelstack.top(); modelstack.pop(); } // remember that we've visited this directory and entry so that we don't visit it again later DWORD visited = (DWORD)( (((size_t)ifdp & 0xFFFF) << 16) | (size_t)de ); if(visitedIFD.find(visited) != visitedIFD.end()) { continue; } else { visitedIFD[visited] = 1; // processed } // determine how many entries there are in the current IFD nde = ReadUint16(msb_order, ifdp); for(; de < nde; de++) { char *pde = NULL; // pointer to the directory entry char *pval = NULL; // pointer to the tag value // create a tag FITAG *tag = FreeImage_CreateTag(); if(!tag) return FALSE; // point to the directory entry pde = (char*) DIR_ENTRY_ADDR(ifdp, de); // get the tag ID FreeImage_SetTagID(tag, ReadUint16(msb_order, pde)); // get the tag type WORD tag_type = (WORD)ReadUint16(msb_order, pde + 2); if((tag_type - 1) >= EXIF_NUM_FORMATS) { // a problem occured : delete the tag (not free'd after) FreeImage_DeleteTag(tag); // break out of the for loop break; } FreeImage_SetTagType(tag, (FREE_IMAGE_MDTYPE)tag_type); // get number of components FreeImage_SetTagCount(tag, ReadUint32(msb_order, pde + 4)); // check that tag length (size of the tag value in bytes) will fit in a DWORD unsigned tag_data_width = FreeImage_TagDataWidth(FreeImage_GetTagType(tag)); if (tag_data_width != 0 && FreeImage_GetTagCount(tag) > ~(DWORD)0 / tag_data_width) { FreeImage_DeleteTag(tag); // jump to next entry continue; } FreeImage_SetTagLength(tag, FreeImage_GetTagCount(tag) * tag_data_width); if(FreeImage_GetTagLength(tag) <= 4) { // 4 bytes or less and value is in the dir entry itself pval = pde + 8; } else { // if its bigger than 4 bytes, the directory entry contains an offset // first check if offset exceeds buffer, at this stage FreeImage_GetTagLength may return invalid data DWORD offset_value = ReadUint32(msb_order, pde + 8); if(offset_value > length) { // a problem occured : delete the tag (not free'd after) FreeImage_DeleteTag(tag); // jump to next entry continue; } // now check that length does not exceed the buffer size if(FreeImage_GetTagLength(tag) > length - offset_value) { // a problem occured : delete the tag (not free'd after) FreeImage_DeleteTag(tag); // jump to next entry continue; } pval = (char*)(tiffp + offset_value); } // check for a IFD offset BOOL isIFDOffset = FALSE; switch(FreeImage_GetTagID(tag)) { case TAG_EXIF_OFFSET: case TAG_GPS_OFFSET: case TAG_INTEROP_OFFSET: case TAG_MAKER_NOTE: isIFDOffset = TRUE; break; } if(isIFDOffset) { DWORD sub_offset = 0; TagLib::MDMODEL next_mdmodel = md_model; const BYTE *next_ifd = ifdp; // get offset and metadata model if (FreeImage_GetTagID(tag) == TAG_MAKER_NOTE) { processMakerNote(dib, pval, msb_order, &sub_offset, &next_mdmodel); next_ifd = (BYTE*)pval + sub_offset; } else { processIFDOffset(tag, pval, msb_order, &sub_offset, &next_mdmodel); next_ifd = (BYTE*)tiffp + sub_offset; } if((sub_offset < (DWORD) length) && (next_mdmodel != TagLib::UNKNOWN)) { // push our current directory state onto the stack ifdstack.push(ifdp); // bump to the next entry de++; destack.push(de); // push our current metadata model modelstack.push(md_model); // push new state onto of stack to cause a jump ifdstack.push(next_ifd); destack.push(0); // select a new metadata model modelstack.push(next_mdmodel); // delete the tag as it won't be stored nor deleted in the for() loop FreeImage_DeleteTag(tag); break; // break out of the for loop } else { // unsupported camera model, canon maker tag or something unknown // process as a standard tag processExifTag(dib, tag, pval, msb_order, md_model); } } else { // process as a standard tag processExifTag(dib, tag, pval, msb_order, md_model); } // delete the tag FreeImage_DeleteTag(tag); } // for(nde) // additional thumbnail data is skipped } while (!destack.empty()); // // --- handle thumbnail data --- // const WORD entriesCount0th = ReadUint16(msb_order, ifd0th); DWORD next_offset = ReadUint32(msb_order, DIR_ENTRY_ADDR(ifd0th, entriesCount0th)); if((next_offset == 0) || (next_offset >= length)) { return TRUE; //< no thumbnail } const BYTE* const ifd1st = (BYTE*)tiffp + next_offset; const WORD entriesCount1st = ReadUint16(msb_order, ifd1st); unsigned thCompression = 0; unsigned thOffset = 0; unsigned thSize = 0; for(int e = 0; e < entriesCount1st; e++) { // point to the directory entry const BYTE* base = DIR_ENTRY_ADDR(ifd1st, e); // check for buffer overflow const size_t remaining = (size_t)base + 12 - (size_t)tiffp; if(remaining >= length) { // bad IFD1 directory, ignore it return FALSE; } // get the tag ID WORD tag = ReadUint16(msb_order, base); // get the tag type WORD type = ReadUint16(msb_order, base + sizeof(WORD)); // get number of components DWORD count = ReadUint32(msb_order, base + sizeof(WORD) + sizeof(WORD)); // get the tag value DWORD offset = ReadUint32(msb_order, base + sizeof(WORD) + sizeof(WORD) + sizeof(DWORD)); switch(tag) { case TAG_COMPRESSION: // Tiff Compression Tag (should be COMPRESSION_OJPEG (6), but is not always respected) thCompression = offset; break; case TAG_JPEG_INTERCHANGE_FORMAT: // Tiff JPEGInterchangeFormat Tag thOffset = offset; break; case TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: // Tiff JPEGInterchangeFormatLength Tag thSize = offset; break; // ### X and Y Resolution ignored, orientation ignored case TAG_X_RESOLUTION: // XResolution case TAG_Y_RESOLUTION: // YResolution case TAG_RESOLUTION_UNIT: // ResolutionUnit case TAG_ORIENTATION: // Orientation break; default: break; } } if(/*thCompression != 6 ||*/ thOffset == 0 || thSize == 0) { return TRUE; } if(thOffset + thSize > length) { return TRUE; } // load the thumbnail const BYTE *thLocation = tiffp + thOffset; FIMEMORY* hmem = FreeImage_OpenMemory(const_cast<BYTE*>(thLocation), thSize); FIBITMAP* thumbnail = FreeImage_LoadFromMemory(FIF_JPEG, hmem); FreeImage_CloseMemory(hmem); // store the thumbnail FreeImage_SetThumbnail(dib, thumbnail); // then delete it FreeImage_Unload(thumbnail); return TRUE; }
//-------------------------------------------------------------------------- // Process GPS info directory //-------------------------------------------------------------------------- void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char * OffsetBase, unsigned ExifLength) { int de; unsigned a; int NumDirEntries; NumDirEntries = Get16u(DirStart); #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) if (ShowTags){ printf("(dir has %d entries)\n",NumDirEntries); } ImageInfo.GpsInfoPresent = TRUE; strcpy(ImageInfo.GpsLat, "? ?"); strcpy(ImageInfo.GpsLong, "? ?"); ImageInfo.GpsAlt[0] = 0; for (de=0;de<NumDirEntries;de++){ unsigned Tag, Format, Components; unsigned char * ValuePtr; int ComponentSize; unsigned ByteCount; unsigned char * DirEntry; DirEntry = DIR_ENTRY_ADDR(DirStart, de); if (DirEntry+12 > OffsetBase+ExifLength){ ErrNonfatal("GPS info directory goes past end of exif",0,0); return; } Tag = Get16u(DirEntry); Format = Get16u(DirEntry+2); Components = Get32u(DirEntry+4); if ((Format-1) >= NUM_FORMATS) { // (-1) catches illegal zero case as unsigned underflows to positive large. ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag); continue; } ComponentSize = BytesPerFormat[Format]; ByteCount = Components * ComponentSize; #ifdef SUPERDEBUG printf("GPS tag %x format %s #components %d componentsize %d bytecount %d", Tag, formatStr(Format), Components, ComponentSize, ByteCount); #endif if (ByteCount > 4){ unsigned OffsetVal; OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (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){ char FmtString[21]; char TempString[56]; double Values[3]; case TAG_GPS_LAT_REF: ImageInfo.GpsLat[0] = ValuePtr[0]; ImageInfo.GpsLatRef[0] = ValuePtr[0]; ImageInfo.GpsLatRef[1] = '\0'; break; case TAG_GPS_LONG_REF: ImageInfo.GpsLong[0] = ValuePtr[0]; ImageInfo.GpsLongRef[0] = ValuePtr[0]; ImageInfo.GpsLongRef[1] = '\0'; break; case TAG_GPS_LAT: case TAG_GPS_LONG: if (Format != FMT_URATIONAL){ ErrNonfatal("Inappropriate format (%d) for GPS coordinates!", Format, 0); } strcpy(FmtString, "%0.0fd %0.0fm %0.0fs"); for (a=0;a<3;a++){ int den, digits; den = Get32s(ValuePtr+4+a*ComponentSize); digits = 0; while (den > 1 && digits <= 6){ den = den / 10; digits += 1; } if (digits > 6) digits = 6; FmtString[1+a*7] = (char)('2'+digits+(digits ? 1 : 0)); FmtString[3+a*7] = (char)('0'+digits); Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format); } sprintf(TempString, FmtString, Values[0], Values[1], Values[2]); if (Tag == TAG_GPS_LAT){ strncpy(ImageInfo.GpsLat+2, TempString, 29); }else{ strncpy(ImageInfo.GpsLong+2, TempString, 29); } sprintf(TempString, "%d/%d,%d/%d,%d/%d", Get32s(ValuePtr), Get32s(4+(char*)ValuePtr), Get32s(8+(char*)ValuePtr), Get32s(12+(char*)ValuePtr), Get32s(16+(char*)ValuePtr), Get32s(20+(char*)ValuePtr)); if (Tag == TAG_GPS_LAT){ strncpy(ImageInfo.GpsLatRaw, TempString, 31); }else{ strncpy(ImageInfo.GpsLongRaw, TempString, 31); } break; case TAG_GPS_ALT_REF: ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' '); ImageInfo.GpsAltRef = (char)ValuePtr[0]; break; case TAG_GPS_ALT: sprintf(ImageInfo.GpsAlt + 1, "%.2fm", ConvertAnyFormat(ValuePtr, Format)); ImageInfo.GpsAltRaw.num = Get32u(ValuePtr); ImageInfo.GpsAltRaw.denom = Get32u(4+(char *)ValuePtr); break; case TAG_GPS_TIMESTAMP: snprintf(ImageInfo.GpsTimeStamp, sizeof(ImageInfo.GpsTimeStamp), "%d:%d:%d", (int) ConvertAnyFormat(ValuePtr, Format), (int) ConvertAnyFormat(ValuePtr + 8, Format), (int) ConvertAnyFormat(ValuePtr + 16, Format) ); break; case TAG_GPS_DATESTAMP: strncpy(ImageInfo.GpsDateStamp, (char*)ValuePtr, sizeof(ImageInfo.GpsDateStamp)); break; case TAG_GPS_PROCESSING_METHOD: if (ByteCount > EXIF_ASCII_PREFIX_LEN && memcmp(ValuePtr, ExifAsciiPrefix, EXIF_ASCII_PREFIX_LEN) == 0) { int length = ByteCount < GPS_PROCESSING_METHOD_LEN + EXIF_ASCII_PREFIX_LEN ? ByteCount - EXIF_ASCII_PREFIX_LEN : GPS_PROCESSING_METHOD_LEN; memcpy(ImageInfo.GpsProcessingMethod, (char*)(ValuePtr + EXIF_ASCII_PREFIX_LEN), length); ImageInfo.GpsProcessingMethod[length] = 0; } else { ALOGW("Unsupported encoding for GPSProcessingMethod"); } break; } if (ShowTags){ // Show tag value. if (Tag < MAX_GPS_TAG){ printf(" %s =", GpsTags[Tag].Desc); }else{ // Show unknown tag printf(" Illegal GPS tag %04x=", Tag); } switch(Format){ case FMT_UNDEFINED: // Undefined is typically an ascii string. case FMT_STRING: // String arrays printed without function call (different from int arrays) { printf("\""); for (a=0;a<ByteCount;a++){ int ZeroSkipped = 0; if (ValuePtr[a] >= 32){ if (ZeroSkipped){ printf("?"); ZeroSkipped = 0; } putchar(ValuePtr[a]); }else{ if (ValuePtr[a] == 0){ ZeroSkipped = 1; } } } printf("\"\n"); } break; default: // Handle arrays of numbers later (will there ever be?) for (a=0;;){ PrintFormatNumber(ValuePtr+a*ComponentSize, Format, ByteCount); if (++a >= Components) break; printf(", "); } printf("\n"); } } } }
//-------------------------------------------------------------------------- // Process one of the nested EXIF directories. //-------------------------------------------------------------------------- void ExifData::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength, unsigned NestingLevel) { int de; int a; int NumDirEntries; unsigned ThumbnailOffset = 0; unsigned ThumbnailSize = 0; if ( NestingLevel > 4) throw FatalError("Maximum directory nesting exceeded (corrupt exif header)"); NumDirEntries = Get16u(DirStart); #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) { unsigned char * DirEnd; 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 { // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier // might trigger this. throw FatalError("Illegally sized directory"); } } if (DirEnd < LastExifRefd) LastExifRefd = DirEnd; } for (de=0; de<NumDirEntries; de++) { int Tag, Format, Components; unsigned char * ValuePtr; unsigned ByteCount; char * DirEntry; DirEntry = (char *)DIR_ENTRY_ADDR(DirStart, de); Tag = Get16u(DirEntry); Format = Get16u(DirEntry+2); Components = Get32u(DirEntry+4); if ((Format-1) >= NUM_FORMATS) { // (-1) catches illegal zero case as unsigned underflows to positive large. throw FatalError("Illegal format code in EXIF dir"); } if ((unsigned)Components > 0x10000) { throw FatalError("Illegal number of components for tag"); continue; } ByteCount = Components * BytesPerFormat[Format]; if (ByteCount > 4) { unsigned OffsetVal; OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal+ByteCount > ExifLength) { // Bogus pointer offset and / or bytecount value //printf("Offset %d bytes %d ExifLen %d\n",OffsetVal, ByteCount, ExifLength); throw FatalError("Illegal pointer offset value in EXIF"); } ValuePtr = OffsetBase+OffsetVal; } else { // 4 bytes or less and value is in the dir entry itself ValuePtr = (unsigned char *)DirEntry+8; } if (LastExifRefd < ValuePtr+ByteCount) { // Keep track of last byte in the exif header that was actually referenced. // That way, we know where the discardable thumbnail data begins. LastExifRefd = ValuePtr+ByteCount; } // Extract useful components of tag switch(Tag) { case TAG_MAKE: ExifData::CameraMake = QString::fromLatin1((const char*)ValuePtr, 31); break; case TAG_MODEL: ExifData::CameraModel = QString::fromLatin1((const char*)ValuePtr, 39); break; case TAG_ORIENTATION: Orientation = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_DATETIME_ORIGINAL: DateTime = QString::fromLatin1((const char*)ValuePtr, 19); break; case TAG_USERCOMMENT: // Olympus has this padded with trailing spaces. Remove these first. for (a=ByteCount;;) { a--; if ((ValuePtr)[a] == ' ') { (ValuePtr)[a] = '\0'; } else { break; } if (a == 0) break; } // Copy the comment if (memcmp(ValuePtr, "ASCII",5) == 0) { for (a=5; a<10; a++) { int c; c = (ValuePtr)[a]; if (c != '\0' && c != ' ') { UserComment = QString::fromLatin1((const char*)(a+ValuePtr), 199); break; } } } else { UserComment = QString::fromLatin1((const char*)ValuePtr, 199); } break; case TAG_FNUMBER: // Simplest way of expressing aperture, so I trust it the most. // (overwrite previously computd value if there is one) ExifData::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 (ExifData::ApertureFNumber == 0) { ExifData::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. ExifData::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. ExifData::Distance = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURETIME: // Simplest way of expressing exposure time, so I trust it most. // (overwrite previously computd value if there is one) ExifData::ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format); 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 (ExifData::ExposureTime == 0) { ExifData::ExposureTime = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0))); } break; case TAG_FLASH: ExifData::FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXIF_IMAGELENGTH: ExifImageLength = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXIF_IMAGEWIDTH: ExifImageWidth = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_FOCALPLANEXRES: FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); break; case TAG_FOCALPLANEUNITS: switch((int)ConvertAnyFormat(ValuePtr, Format)) { case 1: FocalplaneUnits = 25.4; break; // inch case 2: // According to the information I was using, 2 means meters. // But looking at the Cannon powershot's files, inches is the only // sensible value. FocalplaneUnits = 25.4; break; case 3: FocalplaneUnits = 10; break; // centimeter case 4: FocalplaneUnits = 1; break; // milimeter case 5: FocalplaneUnits = .001; break; // micrometer } break; // Remaining cases contributed by: Volker C. Schoech ([email protected]) case TAG_EXPOSURE_BIAS: ExifData::ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_WHITEBALANCE: ExifData::Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_METERING_MODE: ExifData::MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURE_PROGRAM: ExifData::ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_ISO_EQUIVALENT: ExifData::ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); if ( ExifData::ISOequivalent < 50 ) ExifData::ISOequivalent *= 200; break; case TAG_COMPRESSION_LEVEL: ExifData::CompressionLevel = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_THUMBNAIL_OFFSET: ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; case TAG_THUMBNAIL_LENGTH: ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; } if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET) { unsigned char * SubdirStart; SubdirStart = OffsetBase + Get32u(ValuePtr); if (SubdirStart <= OffsetBase || SubdirStart >= OffsetBase+ExifLength) { throw FatalError("Illegal subdirectory link"); } ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); continue; } } { // 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 comitee! unsigned char * SubdirStart; unsigned Offset; if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength) { Offset = Get32u(DIR_ENTRY_ADDR(DirStart, NumDirEntries)); // There is at least one jpeg from an HP camera having an Offset of almost MAXUINT. // Adding OffsetBase to it produces an overflow, so compare with ExifLength here. // See http://bugs.kde.org/show_bug.cgi?id=54542 if (Offset && Offset < ExifLength) { SubdirStart = OffsetBase + Offset; if (SubdirStart > OffsetBase+ExifLength) { if (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 kdDebug(7034) << "Thumbnail removed with Jhead 1.3 or earlier\n"; } else { throw FatalError("Illegal subdirectory link 2"); } } else { if (SubdirStart <= OffsetBase+ExifLength) { ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); } } } } else { // The exif header ends before the last next directory pointer. } } if (ThumbnailSize && ThumbnailOffset) { if (ThumbnailSize + ThumbnailOffset < ExifLength) { // The thumbnail pointer appears to be valid. Store it. Thumbnail.loadFromData(OffsetBase + ThumbnailOffset, ThumbnailSize, "JPEG"); } } }