diff options
Diffstat (limited to 'tdefile-plugins/jpeg/exif.cpp')
-rw-r--r-- | tdefile-plugins/jpeg/exif.cpp | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/tdefile-plugins/jpeg/exif.cpp b/tdefile-plugins/jpeg/exif.cpp new file mode 100644 index 00000000..60c689bd --- /dev/null +++ b/tdefile-plugins/jpeg/exif.cpp @@ -0,0 +1,961 @@ +//-------------------------------------------------------------------------- +// Program to pull the information out of various types of EFIF digital +// camera files and show it in a reasonably consistent way +// +// This module parses the very complicated exif structures. +// +// Matthias Wandel, Dec 1999 - August 2000 +//-------------------------------------------------------------------------- + + +#include "exif.h" +#include <tqwmatrix.h> +#include <kglobal.h> + + +static unsigned char * LastExifRefd; +static int ExifSettingsLength; +static double FocalplaneXRes; +static double FocalplaneUnits; +static int MotorolaOrder = 0; +static int SectionsRead; +//static int HaveAll; + +//-------------------------------------------------------------------------- +// Table of Jpeg encoding process names + +#define M_SOF0 0xC0 // Start Of Frame N +#define M_SOF1 0xC1 // N indicates which compression process +#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use +#define M_SOF3 0xC3 +#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_SOI 0xD8 // Start Of Image (beginning of datastream) +#define M_EOI 0xD9 // End Of Image (end of datastream) +#define M_SOS 0xDA // Start Of Scan (begins compressed data) +#define M_JFIF 0xE0 // Jfif marker +#define M_EXIF 0xE1 // Exif marker +#define M_COM 0xFE // COMment + + +TagTable_t ProcessTable[] = { + { M_SOF0, "Baseline"}, + { M_SOF1, "Extended sequential"}, + { M_SOF2, "Progressive"}, + { M_SOF3, "Lossless"}, + { M_SOF5, "Differential sequential"}, + { M_SOF6, "Differential progressive"}, + { M_SOF7, "Differential lossless"}, + { M_SOF9, "Extended sequential, arithmetic coding"}, + { M_SOF10, "Progressive, arithmetic coding"}, + { M_SOF11, "Lossless, arithmetic coding"}, + { M_SOF13, "Differential sequential, arithmetic coding"}, + { M_SOF14, "Differential progressive, arithmetic coding"}, + { M_SOF15, "Differential lossless, arithmetic coding"}, + { 0, "Unknown"} +}; + + + +//-------------------------------------------------------------------------- +// Describes format descriptor +static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; +#define NUM_FORMATS 12 + +#define FMT_BYTE 1 +#define FMT_STRING 2 +#define FMT_USHORT 3 +#define FMT_ULONG 4 +#define FMT_URATIONAL 5 +#define FMT_SBYTE 6 +#define FMT_UNDEFINED 7 +#define FMT_SSHORT 8 +#define FMT_SLONG 9 +#define FMT_SRATIONAL 10 +#define FMT_SINGLE 11 +#define FMT_DOUBLE 12 + +//-------------------------------------------------------------------------- +// Describes tag values + +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTEROP_OFFSET 0xa005 + +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_ORIENTATION 0x0112 + +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D + +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_MAXAPERTURE 0x9205 +#define TAG_FOCALLENGTH 0x920A + +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_USERCOMMENT 0x9286 + +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_FLASH 0x9209 + +#define TAG_FOCALPLANEXRES 0xa20E +#define TAG_FOCALPLANEUNITS 0xa210 +#define TAG_EXIF_IMAGEWIDTH 0xA002 +#define TAG_EXIF_IMAGELENGTH 0xA003 + +// the following is added 05-jan-2001 vcs +#define TAG_EXPOSURE_BIAS 0x9204 +#define TAG_WHITEBALANCE 0x9208 +#define TAG_METERING_MODE 0x9207 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_COMPRESSION_LEVEL 0x9102 + +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 + + +/*static TagTable_t TagTable[] = { + { 0x100, "ImageWidth"}, + { 0x101, "ImageLength"}, + { 0x102, "BitsPerSample"}, + { 0x103, "Compression"}, + { 0x106, "PhotometricInterpretation"}, + { 0x10A, "FillOrder"}, + { 0x10D, "DocumentName"}, + { 0x10E, "ImageDescription"}, + { 0x10F, "Make"}, + { 0x110, "Model"}, + { 0x111, "StripOffsets"}, + { 0x112, "Orientation"}, + { 0x115, "SamplesPerPixel"}, + { 0x116, "RowsPerStrip"}, + { 0x117, "StripByteCounts"}, + { 0x11A, "XResolution"}, + { 0x11B, "YResolution"}, + { 0x11C, "PlanarConfiguration"}, + { 0x128, "ResolutionUnit"}, + { 0x12D, "TransferFunction"}, + { 0x131, "Software"}, + { 0x132, "DateTime"}, + { 0x13B, "Artist"}, + { 0x13E, "WhitePoint"}, + { 0x13F, "PrimaryChromaticities"}, + { 0x156, "TransferRange"}, + { 0x200, "JPEGProc"}, + { 0x201, "ThumbnailOffset"}, + { 0x202, "ThumbnailLength"}, + { 0x211, "YCbCrCoefficients"}, + { 0x212, "YCbCrSubSampling"}, + { 0x213, "YCbCrPositioning"}, + { 0x214, "ReferenceBlackWhite"}, + { 0x828D, "CFARepeatPatternDim"}, + { 0x828E, "CFAPattern"}, + { 0x828F, "BatteryLevel"}, + { 0x8298, "Copyright"}, + { 0x829A, "ExposureTime"}, + { 0x829D, "FNumber"}, + { 0x83BB, "IPTC/NAA"}, + { 0x8769, "ExifOffset"}, + { 0x8773, "InterColorProfile"}, + { 0x8822, "ExposureProgram"}, + { 0x8824, "SpectralSensitivity"}, + { 0x8825, "GPSInfo"}, + { 0x8827, "ISOSpeedRatings"}, + { 0x8828, "OECF"}, + { 0x9000, "ExifVersion"}, + { 0x9003, "DateTimeOriginal"}, + { 0x9004, "DateTimeDigitized"}, + { 0x9101, "ComponentsConfiguration"}, + { 0x9102, "CompressedBitsPerPixel"}, + { 0x9201, "ShutterSpeedValue"}, + { 0x9202, "ApertureValue"}, + { 0x9203, "BrightnessValue"}, + { 0x9204, "ExposureBiasValue"}, + { 0x9205, "MaxApertureValue"}, + { 0x9206, "SubjectDistance"}, + { 0x9207, "MeteringMode"}, + { 0x9208, "LightSource"}, + { 0x9209, "Flash"}, + { 0x920A, "FocalLength"}, + { 0x927C, "MakerNote"}, + { 0x9286, "UserComment"}, + { 0x9290, "SubSecTime"}, + { 0x9291, "SubSecTimeOriginal"}, + { 0x9292, "SubSecTimeDigitized"}, + { 0xA000, "FlashPixVersion"}, + { 0xA001, "ColorSpace"}, + { 0xA002, "ExifImageWidth"}, + { 0xA003, "ExifImageLength"}, + { 0xA005, "InteroperabilityOffset"}, + { 0xA20B, "FlashEnergy"}, // 0x920B in TIFF/EP + { 0xA20C, "SpatialFrequencyResponse"}, // 0x920C - - + { 0xA20E, "FocalPlaneXResolution"}, // 0x920E - - + { 0xA20F, "FocalPlaneYResolution"}, // 0x920F - - + { 0xA210, "FocalPlaneResolutionUnit"}, // 0x9210 - - + { 0xA214, "SubjectLocation"}, // 0x9214 - - + { 0xA215, "ExposureIndex"}, // 0x9215 - - + { 0xA217, "SensingMethod"}, // 0x9217 - - + { 0xA300, "FileSource"}, + { 0xA301, "SceneType"}, + { 0, NULL} +} ; +*/ + + +//-------------------------------------------------------------------------- +// Parse the marker stream until SOS or EOI is seen; +//-------------------------------------------------------------------------- +int ExifData::ReadJpegSections (TQFile & infile, ReadMode_t ReadMode) +{ + int a; + + a = infile.getch(); + + if (a != 0xff || infile.getch() != M_SOI) { + SectionsRead = 0; + return false; + } + for(SectionsRead = 0; SectionsRead < MAX_SECTIONS-1; ){ + int marker = 0; + int got; + unsigned int ll,lh; + unsigned int itemlen; + uchar * Data; + + for (a=0;a<7;a++){ + marker = infile.getch(); + if (marker != 0xff) break; + + if (a >= 6){ + + kdDebug(7034) << "too many padding bytes\n"; + return false; + + } + } + + if (marker == 0xff){ + // 0xff is legal padding, but if we get that many, something's wrong. + throw FatalError("too many padding bytes!"); + } + + Sections[SectionsRead].Type = marker; + + // Read the length of the section. + lh = (uchar) infile.getch(); + ll = (uchar) infile.getch(); + + itemlen = (lh << 8) | ll; + + if (itemlen < 2) { + throw FatalError("invalid marker"); + } + + Sections[SectionsRead].Size = itemlen; + + Data = (uchar *)malloc(itemlen+1); // Add 1 to allow sticking a 0 at the end. + Sections[SectionsRead].Data = Data; + + // Store first two pre-read bytes. + Data[0] = (uchar)lh; + Data[1] = (uchar)ll; + + got = infile.readBlock((char*)Data+2, itemlen-2); // Read the whole section. + if (( unsigned ) got != itemlen-2){ + throw FatalError("reading from file"); + } + SectionsRead++; + + switch(marker){ + + case M_SOS: // stop before hitting compressed data + // If reading entire image is requested, read the rest of the data. + if (ReadMode & READ_IMAGE){ + unsigned long size; + + size = kMax( 0ul, (unsigned long)(infile.size()-infile.at()) ); + Data = (uchar *)malloc(size); + if (Data == NULL){ + throw FatalError("could not allocate data for entire image"); + } + + got = infile.readBlock((char*)Data, size); + if (( unsigned ) got != size){ + throw FatalError("could not read the rest of the image"); + } + + Sections[SectionsRead].Data = Data; + Sections[SectionsRead].Size = size; + Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER; + SectionsRead ++; + //HaveAll = 1; + } + return true; + + case M_EOI: // in case it's a tables-only JPEG stream + kdDebug(7034) << "No image in jpeg!\n"; + return false; + + case M_COM: // Comment section + // pieczy 2002-02-12 + // now the User comment goes to UserComment + // so we can store a Comment section also in READ_EXIF mode + process_COM(Data, itemlen); + break; + + case M_JFIF: + // Regular jpegs always have this tag, exif images have the exif + // marker instead, althogh ACDsee will write images with both markers. + // this program will re-create this marker on absence of exif marker. + // hence no need to keep the copy from the file. + free(Sections[--SectionsRead].Data); + break; + + case M_EXIF: + // Seen files from some 'U-lead' software with Vivitar scanner + // that uses marker 31 for non exif stuff. Thus make sure + // it says 'Exif' in the section before treating it as exif. + if ((ReadMode & READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){ + process_EXIF((uchar *)Data, itemlen); // FIXME: This call + // requires Data to be array of at least 8 bytes. Code + // above only checks for itemlen < 2. + }else{ + // Discard this section. + free(Sections[--SectionsRead].Data); + } + break; + + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + process_SOFn(Data, marker); //FIXME: This call requires Data to + // be array of at least 8 bytes. Code above only checks for + // itemlen < 2. + break; + default: + break; + } + } + return true; +} + + +//-------------------------------------------------------------------------- +// Discard read data. +//-------------------------------------------------------------------------- +void ExifData::DiscardData(void) +{ + for (int a=0; a < SectionsRead; a++) + free(Sections[a].Data); + SectionsRead = 0; +} + +//-------------------------------------------------------------------------- +// Convert a 16 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +int ExifData::Get16u(void * Short) +{ + if (MotorolaOrder){ + return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; + }else{ + return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0]; + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit signed value from file's native byte order +//-------------------------------------------------------------------------- +int ExifData::Get32s(void * Long) +{ + if (MotorolaOrder){ + return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) + | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 ); + }else{ + return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) + | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 ); + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +unsigned ExifData::Get32u(void * Long) +{ + return (unsigned)Get32s(Long) & 0xffffffff; +} + +//-------------------------------------------------------------------------- +// Evaluate number, be it int, rational, or float from directory. +//-------------------------------------------------------------------------- +double ExifData::ConvertAnyFormat(void * ValuePtr, int Format) +{ + double Value; + Value = 0; + + switch(Format){ + case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; + case FMT_BYTE: Value = *(uchar *)ValuePtr; break; + + case FMT_USHORT: Value = Get16u(ValuePtr); break; + + case FMT_ULONG: Value = Get32u(ValuePtr); break; + + case FMT_URATIONAL: + case FMT_SRATIONAL: + { + int 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 = (signed short)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; +} + +//-------------------------------------------------------------------------- +// 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 = TQString::fromLatin1((const char*)ValuePtr, 31); + break; + + case TAG_MODEL: + ExifData::CameraModel = TQString::fromLatin1((const char*)ValuePtr, 39); + break; + + case TAG_ORIENTATION: + Orientation = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_DATETIME_ORIGINAL: + DateTime = TQString::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 = TQString::fromLatin1((const char*)(a+ValuePtr), 199); + break; + } + } + }else{ + UserComment = TQString::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 (schoech@gmx.de) + + 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"); + } + } +} + +//-------------------------------------------------------------------------- +// Process a COM marker. We want to leave the bytes unchanged. The +// progam that displays this text may decide to remove blanks, convert +// newlines, or otherwise modify the text. In particular we want to be +// safe for passing utf-8 text. +//-------------------------------------------------------------------------- +void ExifData::process_COM (const uchar * Data, int length) +{ + Comment = TQString::fromUtf8((char *)Data+2, (length-2)); +} + + +//-------------------------------------------------------------------------- +// Process a SOFn marker. This is useful for the image dimensions +//-------------------------------------------------------------------------- +void ExifData::process_SOFn (const uchar * Data, int marker) +{ + int data_precision, num_components; + + data_precision = Data[2]; + ExifData::Height = Get16m(Data+3); + ExifData::Width = Get16m(Data+5); + num_components = Data[7]; + + if (num_components == 3){ + ExifData::IsColor = 1; + }else{ + ExifData::IsColor = 0; + } + + ExifData::Process = marker; + +} + +//-------------------------------------------------------------------------- +// Get 16 bits motorola order (always) for jpeg header stuff. +//-------------------------------------------------------------------------- +int ExifData::Get16m(const void * Short) +{ + return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; +} + + +//-------------------------------------------------------------------------- +// Process a EXIF marker +// Describes all the drivel that most digital cameras include... +//-------------------------------------------------------------------------- +void ExifData::process_EXIF(unsigned char * CharBuf, unsigned int length) +{ + ExifData::FlashUsed = 0; // If it s from a digicam, and it used flash, it says so. + + FocalplaneXRes = 0; + FocalplaneUnits = 0; + ExifImageWidth = 0; + ExifImageLength = 0; + + { // Check the EXIF header component + static const uchar ExifHeader[] = "Exif\0\0"; + if (memcmp(CharBuf+2, ExifHeader,6)){ + throw FatalError("Incorrect Exif header"); + } + } + + if (memcmp(CharBuf+8,"II",2) == 0){ + // printf("Exif section in Intel order\n"); + MotorolaOrder = 0; + }else{ + if (memcmp(CharBuf+8,"MM",2) == 0){ + // printf("Exif section in Motorola order\n"); + MotorolaOrder = 1; + }else{ + throw FatalError("Invalid Exif alignment marker."); + } + } + + // Check the next two values for correctness. + if (Get16u(CharBuf+10) != 0x2a){ + throw FatalError("Invalid Exif start (1)"); + } + + long IFDoffset = Get32u(CharBuf+12); + + LastExifRefd = CharBuf; + + // First directory starts 16 bytes in. Offsets start at 8 bytes in. + ProcessExifDir(&CharBuf[8+IFDoffset], CharBuf+8, length-6, 0); + + // This is how far the interesting (non thumbnail) part of the exif went. + ExifSettingsLength = LastExifRefd - CharBuf; + + // Compute the CCD width, in milimeters. + if (FocalplaneXRes != 0){ + kdDebug(7034) << "ExifImageWidth " << ExifImageWidth << " FocalplaneUnits " << FocalplaneUnits << " FocalplaneXRes " << FocalplaneXRes << endl; + ExifData::CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); + } +} + +//-------------------------------------------------------------------------- +// Convert exif time to Unix time structure +//-------------------------------------------------------------------------- +int ExifData::Exif2tm(struct tm * timeptr, char * ExifTime) +{ + int a; + + timeptr->tm_wday = -1; + + // Check for format: YYYY:MM:DD HH:MM:SS format. + a = sscanf(ExifTime, "%d:%d:%d %d:%d:%d", + &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday, + &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec); + + if (a == 6){ + timeptr->tm_isdst = -1; + timeptr->tm_mon -= 1; // Adjust for unix zero-based months + timeptr->tm_year -= 1900; // Adjust for year starting at 1900 + return true; // worked. + } + + return false; // Wasn't in Exif date format. +} + +//-------------------------------------------------------------------------- +// Contructor for initialising +//-------------------------------------------------------------------------- +ExifData::ExifData() +{ + ExifData::Whitebalance = -1; + ExifData::MeteringMode = -1; + ExifData::FlashUsed = 0; + Orientation = 0; + Height = 0; + Width = 0; + IsColor = 0; + Process = 0; + FocalLength = 0; + ExposureTime = 0; + ApertureFNumber = 0; + Distance = 0; + CCDWidth = 0; + ExposureBias = 0; + ExposureProgram = 0; + ISOequivalent = 0; + CompressionLevel = 0; +} + +//-------------------------------------------------------------------------- +// process a EXIF jpeg file +//-------------------------------------------------------------------------- +bool ExifData::scan(const TQString & path) +{ + int ret; + + TQFile f(path); + if ( !f.open(IO_ReadOnly) ) + return false; + + try { + // Scan the JPEG headers. + ret = ReadJpegSections(f, READ_EXIF); + } + catch (FatalError& e) { + e.debug_print(); + f.close(); + return false; + } + + if (ret == false){ + kdDebug(7034) << "Not JPEG file!\n"; + DiscardData(); + f.close(); + return false; + } + f.close(); + DiscardData(); + + //now make the strings clean, + // for exmaple my Casio is a "QV-4000 " + CameraMake = CameraMake.stripWhiteSpace(); + CameraModel = CameraModel.stripWhiteSpace(); + UserComment = UserComment.stripWhiteSpace(); + Comment = Comment.stripWhiteSpace(); + return true; +} + +//-------------------------------------------------------------------------- +// Does the embedded thumbnail match the jpeg image? +//-------------------------------------------------------------------------- +#ifndef JPEG_TOL +#define JPEG_TOL 0.02 +#endif +bool ExifData::isThumbnailSane() { + if (Thumbnail.isNull()) return false; + + // check whether thumbnail dimensions match the image + // not foolproof, but catches some altered images (jpegtran -rotate) + if (ExifImageLength != 0 && ExifImageLength != Height) return false; + if (ExifImageWidth != 0 && ExifImageWidth != Width) return false; + if (Thumbnail.width() == 0 || Thumbnail.height() == 0) return false; + if (Height == 0 || Width == 0) return false; + double d = (double)Height/Width*Thumbnail.width()/Thumbnail.height(); + return (1-JPEG_TOL < d) && (d < 1+JPEG_TOL); +} + + +//-------------------------------------------------------------------------- +// return a thumbnail that respects the orientation flag +// only if it seems sane +//-------------------------------------------------------------------------- +TQImage ExifData::getThumbnail() { + if (!isThumbnailSane()) return NULL; + if (!Orientation || Orientation == 1) return Thumbnail; + + // now fix orientation + TQWMatrix M; + TQWMatrix flip= TQWMatrix(-1,0,0,1,0,0); + switch (Orientation) { // notice intentional fallthroughs + case 2: M = flip; break; + case 4: M = flip; + case 3: M.rotate(180); break; + case 5: M = flip; + case 6: M.rotate(90); break; + case 7: M = flip; + case 8: M.rotate(270); break; + default: break; // should never happen + } + return Thumbnail.xForm(M); +} |