diff --git a/DesktopEditor/cximage/CxImage/ximaexif.cpp b/DesktopEditor/cximage/CxImage/ximaexif.cpp index 6fda0f8b27..7976a04225 100644 --- a/DesktopEditor/cximage/CxImage/ximaexif.cpp +++ b/DesktopEditor/cximage/CxImage/ximaexif.cpp @@ -257,6 +257,7 @@ bool CxImageJPG::CxExifInfo::process_EXIF(uint8_t * CharBuf, uint32_t length) uint8_t * LastExifRefd = CharBuf; +#if 0 /* First directory starts 16 bytes in. Offsets start at 8 bytes in. */ if (!ProcessExifDir(CharBuf+14, CharBuf+6, length-6, m_exifinfo, &LastExifRefd)) return false; @@ -266,6 +267,18 @@ bool CxImageJPG::CxExifInfo::process_EXIF(uint8_t * CharBuf, uint32_t length) if (!ProcessExifDir(CharBuf+14+FirstOffset-8, CharBuf+6, length-6, m_exifinfo, &LastExifRefd)) return false; } +#else + CSafeReader reader(CharBuf, length); + /* First directory starts 16 bytes in. Offsets start at 8 bytes in. */ + if (!ProcessExifDir2(reader.Offset(14), reader.Offset(6), length-6, m_exifinfo, &LastExifRefd)) + return false; + + /* give a chance for a second directory */ + if (FirstOffset > 8) { + if (!ProcessExifDir2(reader.Offset(14+FirstOffset-8), reader.Offset(6), length-6, m_exifinfo, &LastExifRefd)) + return false; + } +#endif /* This is how far the interesting (non thumbnail) part of the exif went. */ // int32_t ExifSettingsLength = LastExifRefd - CharBuf; @@ -703,6 +716,322 @@ bool CxImageJPG::CxExifInfo::ProcessExifDir(uint8_t * DirStart, uint8_t * Offset return true; } +bool CxImageJPG::CxExifInfo::ProcessExifDir2(CSafeReader& DirStart, CSafeReader& OffsetBase, unsigned ExifLength, + EXIFINFO * const m_exifinfo, uint8_t ** const LastExifRefdP, int32_t NestingLevel) +{ + int32_t de; + int32_t a; + int32_t NumDirEntries; + unsigned ThumbnailOffset = 0; + unsigned ThumbnailSize = 0; + + if (NestingLevel > 4){ + strcpy(m_szLastError,"Maximum directory nesting exceeded (corrupt exif header)"); + return false; + } + + NumDirEntries = DirStart.Check(2) ? Get16u(DirStart.GetData(0)) : 0; + + if ((DirStart.GetData(2+NumDirEntries*12+2)) > (OffsetBase.GetData(ExifLength))){ + strcpy(m_szLastError,"Illegally sized directory"); + return false; + } + + for (de=0;de= NUM_FORMATS) { + /* (-1) catches illegal zero case as unsigned underflows to positive large */ + strcpy(m_szLastError,"Illegal format code in EXIF dir"); + return false; + } + + ByteCount = Components * BytesPerFormat[Format]; + + if (ByteCount > 4){ + unsigned OffsetVal; + OffsetVal = DirEntry.Check(8, 4) ? Get32u(DirEntry.GetData(8)) : 0; + /* If its bigger than 4 bytes, the dir entry contains an offset.*/ + if (OffsetVal+ByteCount > ExifLength){ + /* Bogus pointer offset and / or bytecount value */ + strcpy(m_szLastError,"Illegal pointer offset value in EXIF."); + return false; + } + ValuePtr = OffsetBase.Offset(OffsetVal); + }else{ + /* 4 bytes or less and value is in the dir entry itself */ + ValuePtr = DirEntry.Offset(8); + } + + if (*LastExifRefdP < ValuePtr.GetData(ByteCount)){ + /* Keep track of last byte in the exif header that was + actually referenced. That way, we know where the + discardable thumbnail data begins. + */ + *LastExifRefdP = ValuePtr.GetData(ByteCount); + } + + /* Extract useful components of tag */ + switch(Tag){ + + case TAG_MAKE: + if (ValuePtr.Check(31)) strncpy(m_exifinfo->CameraMake, (char*)ValuePtr.GetData(0), 31); + break; + + case TAG_MODEL: + if (ValuePtr.Check(39)) strncpy(m_exifinfo->CameraModel, (char*)ValuePtr.GetData(0), 39); + break; + + case TAG_EXIF_VERSION: + if (ValuePtr.Check(4)) strncpy(m_exifinfo->Version,(char*)ValuePtr.GetData(0), 4); + break; + + case TAG_DATETIME_ORIGINAL: + if (ValuePtr.Check(19)) strncpy(m_exifinfo->DateTime, (char*)ValuePtr.GetData(0), 19); + break; + + case TAG_USERCOMMENT: + // Olympus has this padded with trailing spaces. Remove these first. + if (ValuePtr.Check(ByteCount)) + { + for (a=ByteCount;;){ + a--; + if (*((char*)ValuePtr.GetData(a)) == ' '){ + *((char*)ValuePtr.GetData(a)) = '\0'; + }else{ + break; + } + if (a == 0) break; + } + } + + /* Copy the comment */ + if (ValuePtr.Check(5) && memcmp(ValuePtr.GetData(0), "ASCII",5) == 0){ + for (a=5;a<10;a++){ + char c; + c = *((char*)ValuePtr.GetData(a)); + if (c != '\0' && c != ' '){ + if (ValuePtr.Check(a, 199)) + strncpy(m_exifinfo->Comments, (char*)ValuePtr.GetData(a), 199); + break; + } + } + + }else{ + if (ValuePtr.Check(199)) + strncpy(m_exifinfo->Comments, (char*)ValuePtr.GetData(0), 199); + } + 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)ConvertAnyFormat2(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(ConvertAnyFormat2(ValuePtr, Format)*log(2.0f)*0.5); + } + break; + + case TAG_BRIGHTNESS: + m_exifinfo->Brightness = (float)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_FOCALLENGTH: + /* Nice digital cameras actually save the focal length + as a function of how farthey are zoomed in. + */ + + m_exifinfo->FocalLength = (float)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_SUBJECT_DISTANCE: + /* Inidcates the distacne the autofocus camera is focused to. + Tends to be less accurate as distance increases. + */ + m_exifinfo->Distance = (float)ConvertAnyFormat2(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) + */ + m_exifinfo->ExposureTime = + (float)ConvertAnyFormat2(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 (m_exifinfo->ExposureTime == 0){ + m_exifinfo->ExposureTime = (float) + (1/exp(ConvertAnyFormat2(ValuePtr, Format)*log(2.0f))); + } + break; + + case TAG_FLASH: + if ((int32_t)ConvertAnyFormat2(ValuePtr, Format) & 7){ + m_exifinfo->FlashUsed = 1; + }else{ + m_exifinfo->FlashUsed = 0; + } + break; + + case TAG_ORIENTATION: + m_exifinfo->Orientation = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + if (m_exifinfo->Orientation < 1 || m_exifinfo->Orientation > 8){ + strcpy(m_szLastError,"Undefined rotation value"); + 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. + */ + a = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + if (ExifImageWidth < a) ExifImageWidth = a; + break; + + case TAG_FOCALPLANEXRES: + m_exifinfo->FocalplaneXRes = (float)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_FOCALPLANEYRES: + m_exifinfo->FocalplaneYRes = (float)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_RESOLUTIONUNIT: + switch((int32_t)ConvertAnyFormat2(ValuePtr, Format)){ + case 1: m_exifinfo->ResolutionUnit = 1.0f; break; /* 1 inch */ + case 2: m_exifinfo->ResolutionUnit = 1.0f; break; + case 3: m_exifinfo->ResolutionUnit = 0.3937007874f; break; /* 1 centimeter*/ + case 4: m_exifinfo->ResolutionUnit = 0.03937007874f; break; /* 1 millimeter*/ + case 5: m_exifinfo->ResolutionUnit = 0.00003937007874f; /* 1 micrometer*/ + } + break; + + case TAG_FOCALPLANEUNITS: + switch((int32_t)ConvertAnyFormat2(ValuePtr, Format)){ + case 1: m_exifinfo->FocalplaneUnits = 1.0f; break; /* 1 inch */ + case 2: m_exifinfo->FocalplaneUnits = 1.0f; break; + case 3: m_exifinfo->FocalplaneUnits = 0.3937007874f; break; /* 1 centimeter*/ + case 4: m_exifinfo->FocalplaneUnits = 0.03937007874f; break; /* 1 millimeter*/ + case 5: m_exifinfo->FocalplaneUnits = 0.00003937007874f; /* 1 micrometer*/ + } + break; + + // Remaining cases contributed by: Volker C. Schoech + + case TAG_EXPOSURE_BIAS: + m_exifinfo->ExposureBias = (float) ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_WHITEBALANCE: + m_exifinfo->Whitebalance = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_METERING_MODE: + m_exifinfo->MeteringMode = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_EXPOSURE_PROGRAM: + m_exifinfo->ExposureProgram = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_ISO_EQUIVALENT: + m_exifinfo->ISOequivalent = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + if ( m_exifinfo->ISOequivalent < 50 ) m_exifinfo->ISOequivalent *= 200; + break; + + case TAG_COMPRESSION_LEVEL: + m_exifinfo->CompressionLevel = (int32_t)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_XRESOLUTION: + m_exifinfo->Xresolution = (float)ConvertAnyFormat2(ValuePtr, Format); + break; + case TAG_YRESOLUTION: + m_exifinfo->Yresolution = (float)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_OFFSET: + ThumbnailOffset = (unsigned)ConvertAnyFormat2(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_LENGTH: + ThumbnailSize = (unsigned)ConvertAnyFormat2(ValuePtr, Format); + break; + + } + + if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){ + unsigned Offset = ValuePtr.Check(4) ? Get32u(ValuePtr.GetData(0)) : 0; + if (Offset>8){ + if (!OffsetBase.Check(Offset)) + { + strcpy(m_szLastError,"Illegal subdirectory link"); + return false; + } + ProcessExifDir2(OffsetBase.Offset(Offset), OffsetBase, ExifLength, m_exifinfo, LastExifRefdP, 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 + committee! + */ + unsigned Offset; + Offset = DirStart.Check(2+12*NumDirEntries, 2) ? Get16u(DirStart.GetData(2+12*NumDirEntries)) : 0; + if (Offset){ + if (!OffsetBase.Check(Offset)) + { + strcpy(m_szLastError,"Illegal subdirectory link"); + return false; + } + ProcessExifDir2(OffsetBase.Offset(Offset), OffsetBase, ExifLength, m_exifinfo, LastExifRefdP, NestingLevel+1); + } + } + + + if (ThumbnailSize && ThumbnailOffset){ + if (ThumbnailSize + ThumbnailOffset <= ExifLength){ + /* The thumbnail pointer appears to be valid. Store it. */ + m_exifinfo->ThumbnailPointer = OffsetBase.GetData(ThumbnailOffset); + m_exifinfo->ThumbnailSize = ThumbnailSize; + } + } + + return true; +} //////////////////////////////////////////////////////////////////////////////// /*-------------------------------------------------------------------------- Evaluate number, be it int32_t, rational, or float from directory. @@ -743,6 +1072,58 @@ double CxImageJPG::CxExifInfo::ConvertAnyFormat(void * ValuePtr, int32_t Format) } return Value; } +double CxImageJPG::CxExifInfo::ConvertAnyFormat2(CSafeReader& reader, int32_t Format) +{ + double Value; + Value = 0; + + switch(Format){ + case FMT_SBYTE: + case FMT_BYTE: if (!reader.Check(1)) {return Value;} break; + case FMT_USHORT: + case FMT_SSHORT: if (!reader.Check(2)) {return Value;} break; + case FMT_ULONG: + case FMT_SLONG: if (!reader.Check(4)) {return Value;} break; + case FMT_URATIONAL: + case FMT_SRATIONAL: if (!reader.Check(8)) {return Value;} break; + case FMT_SINGLE: if (!reader.Check(sizeof(float))) {return Value;} break; + case FMT_DOUBLE: if (!reader.Check(sizeof(double))) {return Value;} break; + default: + break; + } + + void * ValuePtr = (void*)reader.GetData(0); + switch(Format){ + case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; + case FMT_BYTE: Value = *(uint8_t *)ValuePtr; break; + + case FMT_USHORT: Value = Get16u(ValuePtr); break; + case FMT_ULONG: Value = Get32u(ValuePtr); break; + + case FMT_URATIONAL: + case FMT_SRATIONAL: + { + int32_t Num,Den; + Num = Get32s(ValuePtr); + Den = Get32s(4+(char *)ValuePtr); + if (Den == 0){ + Value = 0; + }else{ + Value = (double)Num/Den; + } + break; + } + + case FMT_SSHORT: Value = (int16_t)Get16u(ValuePtr); break; + case FMT_SLONG: Value = Get32s(ValuePtr); break; + + /* Not sure if this is correct (never seen float used in Exif format) + */ + case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; + case FMT_DOUBLE: Value = *(double *)ValuePtr; break; + } + return Value; +} //////////////////////////////////////////////////////////////////////////////// void CxImageJPG::CxExifInfo::process_COM (const uint8_t * Data, int32_t length) { diff --git a/DesktopEditor/cximage/CxImage/ximajpg.h b/DesktopEditor/cximage/CxImage/ximajpg.h index 4cb2484a8b..912fbd4c61 100644 --- a/DesktopEditor/cximage/CxImage/ximajpg.h +++ b/DesktopEditor/cximage/CxImage/ximajpg.h @@ -101,6 +101,55 @@ typedef struct tag_Section_t{ unsigned Size; } Section_t; +public: + class CSafeReader + { + private: + uint8_t* data; + unsigned int len; + + public: + CSafeReader(uint8_t* _data = NULL, unsigned int _len = 0) + { + data = _data; + len = _len; + } + CSafeReader(const CSafeReader& src) + { + data = src.data; + len = src.len; + } + CSafeReader& operator=(const CSafeReader& src) + { + data = src.data; + len = src.len; + return *this; + } + CSafeReader Offset(unsigned int offset) + { + if (offset > len) + offset = len; + CSafeReader reader(data, len); + reader.data += offset; + reader.len -= offset; + return reader; + } + bool Check(unsigned int size) + { + if (len >= size) + return true; + return false; + } + bool Check(unsigned int offset, unsigned int size) + { + return Check(offset + size); + } + uint8_t* GetData(unsigned int offset) + { + return data + offset; + } + }; + public: EXIFINFO* m_exifinfo; char m_szLastError[256]; @@ -118,9 +167,12 @@ protected: int32_t Get32s(void * Long); uint32_t Get32u(void * Long); double ConvertAnyFormat(void * ValuePtr, int32_t Format); + double ConvertAnyFormat2(CSafeReader& reader, int32_t Format); void* FindSection(int32_t SectionType); bool ProcessExifDir(uint8_t * DirStart, uint8_t * OffsetBase, unsigned ExifLength, EXIFINFO * const pInfo, uint8_t ** const LastExifRefdP, int32_t NestingLevel=0); + bool ProcessExifDir2(CSafeReader& DirStart, CSafeReader& OffsetBase, unsigned ExifLength, + EXIFINFO * const pInfo, uint8_t ** const LastExifRefdP, int32_t NestingLevel=0); int32_t ExifImageWidth; int32_t MotorolaOrder; Section_t Sections[MAX_SECTIONS];