diff options
| author | craig <craig@11d20701-8431-0410-a711-e3c959e3b870> | 2012-01-01 11:40:09 +0000 |
|---|---|---|
| committer | craig <craig@11d20701-8431-0410-a711-e3c959e3b870> | 2012-01-01 11:40:09 +0000 |
| commit | 7ed83b6c6666eb8b6b104c211ae7e52907350372 (patch) | |
| tree | 4430b556abac0ad660a0aacf1887d77f85d8be02 /scribus/exif.cpp | |
| download | scribus-7ed83b6c6666eb8b6b104c211ae7e52907350372.tar.gz scribus-7ed83b6c6666eb8b6b104c211ae7e52907350372.tar.xz scribus-7ed83b6c6666eb8b6b104c211ae7e52907350372.zip | |
Branch 1.3.5 tree to 1.4.x tree, goodbye 1.3.x
git-svn-id: svn://scribus.net/branches/Version14x/Scribus@17163 11d20701-8431-0410-a711-e3c959e3b870
Diffstat (limited to 'scribus/exif.cpp')
| -rw-r--r-- | scribus/exif.cpp | 984 |
1 files changed, 984 insertions, 0 deletions
diff --git a/scribus/exif.cpp b/scribus/exif.cpp new file mode 100644 index 0000000..a6bb2bb --- /dev/null +++ b/scribus/exif.cpp @@ -0,0 +1,984 @@ +/* +For general Scribus (>=1.3.2) copyright and licensing information please refer +to the COPYING file provided with the program. Following this notice may exist +a copyright and/or license notice that predates the release of Scribus 1.3.2 +for which a new license (GPL+exception) is in place. +*/ +//-------------------------------------------------------------------------- +// 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 <QMatrix> + +//-------------------------------------------------------------------------- +// 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 ProcessTable[] = + { + TagTable ( M_SOF0, "Baseline" ), + TagTable ( M_SOF1, "Extended sequential" ), + TagTable ( M_SOF2, "Progressive" ), + TagTable ( M_SOF3, "Lossless" ), + TagTable ( M_SOF5, "Differential sequential" ), + TagTable ( M_SOF6, "Differential progressive" ), + TagTable ( M_SOF7, "Differential lossless" ), + TagTable ( M_SOF9, "Extended sequential, arithmetic coding" ), + TagTable ( M_SOF10, "Progressive, arithmetic coding" ), + TagTable ( M_SOF11, "Lossless, arithmetic coding" ), + TagTable ( M_SOF13, "Differential sequential, arithmetic coding" ), + TagTable ( M_SOF14, "Differential progressive, arithmetic coding" ), + TagTable ( M_SOF15, "Differential lossless, arithmetic coding" ), + TagTable ( 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} +} ; +*/ + +int ExifData::getch ( QFile &infile ) +{ + QByteArray a = infile.read ( 1 ); + uint r = 0; + r = static_cast<uint> ( a[0] ); + r &= 0x000000FF; + return static_cast<int> ( r ); +} +//-------------------------------------------------------------------------- +// Parse the marker stream until SOS or EOI is seen; +//-------------------------------------------------------------------------- +int ExifData::ReadJpegSections ( QFile & infile, ReadMode_t ReadMode ) +{ + int a; + int SectionsRead; + + a = getch ( infile ); + + if ( a != 0xff || getch ( infile ) != M_SOI ) + { + SectionsRead = 0; + return false; + } + for ( SectionsRead = 0; SectionsRead < MAX_SECTIONS-1; SectionsRead++ ) + { + int marker = 0; + int got; + unsigned int ll,lh; + unsigned int itemlen; + uchar * Data; + + for ( a=0;a<7;a++ ) + { + marker = getch ( infile ); + if ( marker != 0xff ) + break; + if ( a >= 6 ) + return false; + } + if ( marker == 0xff ) + return false; + // Read the length of the section. + lh = ( uchar ) getch ( infile ); + ll = ( uchar ) getch ( infile ); + itemlen = ( lh << 8 ) | ll; + if ( itemlen < 2 ) + return false; + Data = ( uchar * ) malloc ( itemlen+1 ); // Add 1 to allow sticking a 0 at the end. + + // Store first two pre-read bytes. + Data[0] = ( uchar ) lh; + Data[1] = ( uchar ) ll; + + got = infile.read ( ( char* ) Data+2, itemlen-2 ); // Read the whole section. + if ( ( unsigned ) got != itemlen-2 ) + { + free(Data); + return false; + } + + switch ( marker ) + { + + case M_SOS: // stop before hitting compressed data + free(Data); + return true; + + case M_EOI: // in case it's a tables-only JPEG stream + free(Data); + 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. + 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. + exifDataValid = true; + } +// else +// { +// // Discard this section. +// free ( Sections[--SectionsRead].Data ); +// return false; +// } + 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; + } + free(Data); + } + 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 ) +{ + int de; + int a; + int NumDirEntries; + unsigned ThumbnailOffset = 0; + unsigned ThumbnailSize = 0; + recurseLevel++; + if (recurseLevel > 3) + return; + 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. + return; +// throw FatalError("Illegally sized directory"); + } + } + if ( DirEnd < LastExifRefd ) LastExifRefd = DirEnd; + } + + for ( de=0;de<NumDirEntries;de++ ) + { + int Tag, Format, Components; + unsigned char * ValuePtr; + int 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. + return; +// throw FatalError("Illegal format code in EXIF dir"); + } + if ((unsigned)Components > 0x10000) + 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); + + return ; +// 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 ( ( char* ) ValuePtr ); + break; + + case TAG_MODEL: + ExifData::CameraModel = QString ( ( char* ) ValuePtr ); + break; + + case TAG_ORIENTATION: + Orientation = ( int ) ConvertAnyFormat ( ValuePtr, Format ); + break; + + case TAG_DATETIME_ORIGINAL: + DateTime = QString ( ( char* ) ValuePtr ); + 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 != ' ' ) + { + //strncpy(ImageInfo.Comments, (const char*)(a+ValuePtr), 199); + UserComment.sprintf ( "%s", ( const char* ) ( a+ValuePtr ) ); + break; + } + } + } + else + { + //strncpy(ImageInfo.Comments, (const char*)ValuePtr, 199); + UserComment.sprintf ( "%s", ( const char* ) ValuePtr ); + } + 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) ) + { + return ; +// throw FatalError("Illegal subdirectory link"); + } + ProcessExifDir ( SubdirStart, OffsetBase, ExifLength ); + recurseLevel--; + 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 +// qDebug ( "Thumbnail removed with Jhead 1.3 or earlier" ); + } + else + { + return ; +// throw FatalError("Illegal subdirectory link 2"); + } + } + else + { + if ( SubdirStart <= OffsetBase+ExifLength ) + { + ProcessExifDir ( SubdirStart, OffsetBase, ExifLength ); + recurseLevel--; + } + } + } + } + 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 = QString::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 ) ) + return ; + + if ( memcmp ( CharBuf+8,"II",2 ) == 0 ) + MotorolaOrder = 0; + else + { + if ( memcmp ( CharBuf+8,"MM",2 ) == 0 ) + MotorolaOrder = 1; + else + return ; + } + + // Check the next two values for correctness. + if ( Get16u ( CharBuf+10 ) != 0x2a || Get32u ( CharBuf+12 ) != 0x08 ) + return ; + 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); + recurseLevel--; + // 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 ) + { + 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; + exifDataValid = false; + recurseLevel = 0; +} + +//-------------------------------------------------------------------------- +// process a EXIF jpeg file +//-------------------------------------------------------------------------- +bool ExifData::scan ( const QString & path ) +{ + int ret; + + QFile f ( path ); + if ( !f.open ( QIODevice::ReadOnly ) ) + return false; + ret = ReadJpegSections ( f, READ_EXIF ); + if ( ret == false ) + { + f.close(); + return false; + } + f.close(); + + //now make the strings clean, + // for exmaple my Casio is a "QV-4000 " + CameraMake = CameraMake.trimmed(); + CameraModel = CameraModel.trimmed(); + UserComment = UserComment.trimmed(); + Comment = Comment.trimmed(); + return true; +} + +//-------------------------------------------------------------------------- +// Does the embedded thumbnail match the jpeg image? +//-------------------------------------------------------------------------- +#ifndef JPEG_TOL +#define JPEG_TOL 0.02 +#endif +bool ExifData::isThumbnailSane() +{ + bool ret = true; + if ( Thumbnail.isNull() ) + ret = false; + + // check whether thumbnail dimensions match the image + // not foolproof, but catches some altered images (jpegtran -rotate) + if ( ExifImageLength != 0 && ExifImageLength != Height ) + ret = false; + if ( ExifImageWidth != 0 && ExifImageWidth != Width ) + ret = false; + if ( Thumbnail.width() == 0 || Thumbnail.height() == 0 ) + ret = false; + if ( Height == 0 || Width == 0 ) + ret = false; + double d = ( double ) Height/Width*Thumbnail.width() /Thumbnail.height(); + if (!(( 1-JPEG_TOL < d ) && ( d < 1+JPEG_TOL ))) + ret = false; + exifDataValid = ret; + return ret; +} + + +//-------------------------------------------------------------------------- +// return a thumbnail that respects the orientation flag +// only if it seems sane +//-------------------------------------------------------------------------- +QImage ExifData::getThumbnail() +{ + if ( Thumbnail.isNull() ) return QImage(); // Qt4 NULL->QImage() is it sane? + if ( !Orientation || Orientation == 1 ) return Thumbnail; + + // now fix orientation + QMatrix M; + QMatrix flip = QMatrix ( -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.transformed ( M ); +} |
