Diffstat (limited to 'libopie2/opiemm/opieexif.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r-- | libopie2/opiemm/opieexif.cpp | 8 |
1 files changed, 6 insertions, 2 deletions
diff --git a/libopie2/opiemm/opieexif.cpp b/libopie2/opiemm/opieexif.cpp index de49937..653216c 100644 --- a/libopie2/opiemm/opieexif.cpp +++ b/libopie2/opiemm/opieexif.cpp @@ -16,385 +16,385 @@ #include <math.h> #include <time.h> #include <qstring.h> #include <qfile.h> #include <qimage.h> //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 //-------------------------------------------------------------------------- // 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 namespace Opie { namespace MM { class FatalError { const char* ex; public: FatalError(const char* s) { ex = s; } void debug_print() const { owarn << "exception: " << ex << "" << oendl; } }; ExifData::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"} }; //-------------------------------------------------------------------------- // Parse the marker stream until SOS or EOI is seen; //-------------------------------------------------------------------------- int ExifData::ReadJpegSections (QFile & 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){ owarn << "too many padding bytes" << oendl; return false; } } if (marker == 0xff){ // 0xff is legal padding, but if we get that many, something's wrong. return false; } 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) { return false;; } 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){ return false; } 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 = QMAX( 0ul, infile.size()-infile.at() ); + size = infile.size()-infile.at(); Data = (uchar *)malloc(size); if (Data == NULL){ return false; } got = infile.readBlock((char*)Data, size); if (( unsigned ) got != size){ return false; } 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 owarn << "No image in jpeg!" << oendl; 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); }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); default: break; 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) { int de; int a; int NumDirEntries; unsigned ThumbnailOffset = 0; unsigned ThumbnailSize = 0; 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; } } 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); @@ -620,385 +620,389 @@ void ExifData::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBa 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 owarn << "Thumbnail removed with Jhead 1.3 or earlier" << oendl; }else{ return; } }else{ if (SubdirStart <= OffsetBase+ExifLength){ ProcessExifDir(SubdirStart, OffsetBase, ExifLength); } } } }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) { QChar ch; int a; for (a=2;a<length;a++){ ch = Data[a]; if (ch == '\000') continue; // Remove nulls Comment.append(ch); } } //-------------------------------------------------------------------------- // 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){ // 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{ return; } } // Check the next two values for correctness. if (Get16u(CharBuf+10) != 0x2a || Get32u(CharBuf+12) != 0x08){ return; } LastExifRefd = CharBuf; // First directory starts 16 bytes in. Offsets start at 8 bytes in. ProcessExifDir(CharBuf+16, CharBuf+8, length-6); // 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 = -1; 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; MotorolaOrder = 0; } ExifData::~ExifData() { } //-------------------------------------------------------------------------- // process a EXIF jpeg file //-------------------------------------------------------------------------- bool ExifData::scan(const QString & path) { int ret; QFile f(path); - f.open(IO_ReadOnly); + if ( !f.open(IO_ReadOnly) ) { + owarn << "Unable to open file " << f.name() << " readonly" << oendl; + DiscardData(); + return false; + } // Scan the JPEG headers. ret = ReadJpegSections(f, READ_EXIF); if (ret == false){ owarn << "Not JPEG file!" << oendl; 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); } static QImage flip_image( const QImage& img ); static QImage rotate_90( const QImage& img ); static QImage rotate_180( const QImage& ); static QImage rotate_270( const QImage& ); //-------------------------------------------------------------------------- // return a thumbnail that respects the orientation flag // only if it seems sane //-------------------------------------------------------------------------- QImage ExifData::getThumbnail() { if (!isThumbnailSane()) return NULL; if (!Orientation || Orientation == 1) return Thumbnail; // now fix orientation QImage dest = Thumbnail; switch (Orientation) { // notice intentional fallthroughs case 2: dest = flip_image( dest ); break; case 4: dest = flip_image( dest ); case 3: dest =rotate_180( dest ); break; case 5: dest = flip_image( dest ); case 6: dest = rotate_90( dest ); break; case 7: dest = flip_image( dest ); case 8: dest = rotate_270( dest ); break; default: break; // should never happen } return dest; } /* * */ static QImage flip_image( const QImage& img ) { return img.mirror( TRUE, FALSE ); } static QImage dest; static int x, y; static unsigned int *srcData, *destData; // we're not threaded anyway static unsigned char *srcData8, *destData8; // 8 bit is char static unsigned int *srcTable, *destTable; // destination table static QImage rotate_90_8( const QImage &img ) { dest.create(img.height(), img.width(), img.depth()); dest.setNumColors(img.numColors()); srcTable = (unsigned int *)img.colorTable(); destTable = (unsigned int *)dest.colorTable(); for ( x=0; x < img.numColors(); ++x ) destTable[x] = srcTable[x]; for ( y=0; y < img.height(); ++y ){ srcData8 = (unsigned char *)img.scanLine(y); for ( x=0; x < img.width(); ++x ){ destData8 = (unsigned char *)dest.scanLine(x); destData8[img.height()-y-1] = srcData8[x]; } } return dest; } static QImage rotate_90_all( const QImage& img ) { dest.create(img.height(), img.width(), img.depth()); for ( y=0; y < img.height(); ++y ) { srcData = (unsigned int *)img.scanLine(y); for ( x=0; x < img.width(); ++x ) { destData = (unsigned int *)dest.scanLine(x); destData[img.height()-y-1] = srcData[x]; } } return dest; } static QImage rotate_90( const QImage & img ) { if ( img.depth() > 8) return rotate_90_all( img ); else return rotate_90_8( img ); } static QImage rotate_180_all( const QImage& img ) { dest.create(img.width(), img.height(), img.depth()); for ( y=0; y < img.height(); ++y ){ srcData = (unsigned int *)img.scanLine(y); destData = (unsigned int *)dest.scanLine(img.height()-y-1); for ( x=0; x < img.width(); ++x ) destData[img.width()-x-1] = srcData[x]; } return dest; } static QImage rotate_180_8( const QImage& img ) { dest.create(img.width(), img.height(), img.depth()); dest.setNumColors(img.numColors()); srcTable = (unsigned int *)img.colorTable(); destTable = (unsigned int *)dest.colorTable(); for ( x=0; x < img.numColors(); ++x ) destTable[x] = srcTable[x]; for ( y=0; y < img.height(); ++y ){ srcData8 = (unsigned char *)img.scanLine(y); destData8 = (unsigned char *)dest.scanLine(img.height()-y-1); for ( x=0; x < img.width(); ++x ) destData8[img.width()-x-1] = srcData8[x]; } return dest; } static QImage rotate_180( const QImage& img ) { if ( img.depth() > 8 ) return rotate_180_all( img ); else return rotate_180_8( img ); } static QImage rotate_270_8( const QImage& img ) { dest.create(img.height(), img.width(), img.depth()); dest.setNumColors(img.numColors()); srcTable = (unsigned int *)img.colorTable(); destTable = (unsigned int *)dest.colorTable(); for ( x=0; x < img.numColors(); ++x ) destTable[x] = srcTable[x]; for ( y=0; y < img.height(); ++y ){ srcData8 = (unsigned char *)img.scanLine(y); for ( x=0; x < img.width(); ++x ){ destData8 = (unsigned char *)dest.scanLine(img.width()-x-1); destData8[y] = srcData8[x]; } } return dest; } static QImage rotate_270_all( const QImage& img ) { dest.create(img.height(), img.width(), img.depth()); for ( y=0; y < img.height(); ++y ){ srcData = (unsigned int *)img.scanLine(y); for ( x=0; x < img.width(); ++x ){ destData = (unsigned int *)dest.scanLine(img.width()-x-1); destData[y] = srcData[x]; } } return dest; } static QImage rotate_270( const QImage& img ) { |