photo.cpp

Go to the documentation of this file.
00001 //==============================================
00002 //  copyright            : (C) 2003-2005 by Will Stokes
00003 //==============================================
00004 //  This program is free software; you can redistribute it
00005 //  and/or modify it under the terms of the GNU General
00006 //  Public License as published by the Free Software
00007 //  Foundation; either version 2 of the License, or
00008 //  (at your option) any later version.
00009 //==============================================
00010 //Systemwide includes
00011 #include <qpixmap.h>
00012 #include <qimage.h>
00013 #include <qstring.h>
00014 #include <qtextstream.h>
00015 #include <qdom.h>
00016 #include <qdir.h>
00017 #include <qfileinfo.h>
00018 #include <qregexp.h>
00019 
00020 //Projectwide includes
00021 #include "photo.h"
00022 #include "subalbum.h"
00023 #include "album.h"
00024 #include "tools/fileTools.h"
00025 #include "tools/imageTools.h"
00026 #include "tools/xmlTools.h"
00027 #include "tools/md5.h"
00028 #include "../config.h"
00029 
00030 //==============================================
00031 Photo::Photo(Subalbum* subalbum, Photo* prev, int photoNumber)
00032 {
00033   //set subalbum pointer
00034   this->subalbum = subalbum;
00035 
00036   //set prev pointer
00037   this->prev = prev;
00038   
00039   //set next pointer to NULL, new photos are always
00040   //inserted at the end of collections
00041   next = NULL;
00042   
00043   //set initial photo and subalbum numbers
00044   initialPhotoNumber = photoNumber;
00045   initialSubalbumNumber = subalbum->getSubalbumNumber();
00046 
00047   //set default to the empty string
00048   description = QString::null;
00049 
00050   //set thumbnail image
00051   thumbnailImage = NULL;
00052 
00053   //set filenames and checksums to null until the actual photo data has been set
00054   imageLocation     = QString::null;
00055   imageChecksum     = QString::null;
00056   
00057   slideshowLocation = QString::null;
00058   slideshowChecksum = QString::null;
00059   
00060   thumbnailLocation = QString::null;
00061   thumbnailChecksum = QString::null;
00062 
00063   //a default photo is not interesting. once the 
00064   //actual photo data or description files have
00065   //been reset the photo will need to be saved.
00066   needsSaving = false;
00067   
00068   //by default a photos are assumed to be saved. 
00069   //new photos are setup with a uniqueID and we'll set this bool false there
00070   everSaved = true;
00071   
00072   //photo not recently reverted
00073   recentlyReverted = false;
00074 }
00075 //==============================================
00076 Photo::~Photo()
00077 {
00078   //free the thumbnail image
00079   delete thumbnailImage;
00080 }
00081 //==============================================
00082 QImage* Photo::getThumbnailImage() { return thumbnailImage; }
00083 //==============================================
00084 bool Photo::constructSmallerImages()
00085 {
00086   //construct and save slideshow and thumbnail images
00087   QImage TslideshowImage, TthumbnailImage;
00088   constructImages( imageLocation, TslideshowImage, TthumbnailImage );  
00089   TslideshowImage.save( slideshowLocation, "JPEG", 95 );
00090   TthumbnailImage.save( thumbnailLocation, "JPEG", 95 );
00091 
00092   //load up thumbnail image
00093   delete thumbnailImage;
00094   thumbnailImage = new QImage(thumbnailLocation);
00095 
00096   //image is being stored in temp location, needs saving!
00097   needsSaving = true;
00098 
00099   //set the subalbum as being modified and return
00100   subalbum->setModified();
00101   return true;
00102 }
00103 //==============================================
00104 bool Photo::setImage(QString imageName,
00105                      QString slideshowName,
00106                      QString thumbnailName)
00107 {  
00108   //set filenames, we'll lazily compute MD5 checksums for files when saving
00109   imageLocation     = imageName;
00110   slideshowLocation = slideshowName;
00111   thumbnailLocation = thumbnailName;
00112   
00113   //load thumbnail image
00114   delete thumbnailImage;
00115   thumbnailImage = new QImage(thumbnailName);
00116   if(thumbnailImage->isNull()) return false;
00117   
00118   //image just loaded, no changes yet
00119   needsSaving = false;
00120   return true;
00121 }
00122 //==============================================
00123 bool Photo::setImage(QString imageName, int uniqueID)
00124 { 
00125   //this is a new photo, use a unique ID to construct temporary filenames
00126   setEverSaved(false);
00127   initialSubalbumNumber = 0;
00128   initialPhotoNumber = uniqueID;
00129 
00130   QString tmpDir = subalbum->getAlbum()->getTmpDir();
00131   imageLocation     = QString("%1/%2_%3.jpg")          .arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber);
00132   slideshowLocation = QString("%1/%2_%3_slideshow.jpg").arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber);
00133   thumbnailLocation = QString("%1/%2_%3_thumb.jpg")    .arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber);
00134   
00135   //if image in jpeg format simply copy file over
00136   if( isJpeg(imageName) )
00137   {
00138     copyFile( imageName, imageLocation );
00139   }
00140   //otherwise we must load it up and save it out as jpeg
00141   else
00142   {
00143     //if unable to open image at all using Qt then giveup
00144     QImage tempImage(imageName);
00145     if( tempImage.isNull() ) { return false; }
00146     
00147     //save out as jpeg
00148     tempImage.save( imageLocation, "JPEG", 95 );
00149   }  
00150   
00151   //construct smaller iamges
00152   return constructSmallerImages();
00153 }
00154 //==============================================
00155 bool Photo::setImage(QString editedImageFilename)
00156 {
00157   //if the image has been saved then simply change the image,slideshow/thubnail 
00158   //filename handles to point to the temporary directory. We don't need to worry about backing up the
00159   //image because the new version will be written to the temporary directory and the save location
00160   if( getEverSaved() )
00161   {
00162     imageLocation = subalbum->getAlbum()->getTmpDir() + 
00163                     QString("/%1_%2.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
00164     slideshowLocation = subalbum->getAlbum()->getTmpDir() + 
00165                         QString("/%1_%2_slideshow.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
00166     thumbnailLocation = subalbum->getAlbum()->getTmpDir() + 
00167                         QString("/%1_%2_thumb.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
00168   }
00169   //otherwise image has never been saved, make sure original form has been backed up!
00170   else
00171   {
00172     QString tempOrigLocation = imageLocation;
00173     tempOrigLocation.truncate( imageLocation.length() - 4 );
00174     tempOrigLocation = tempOrigLocation + "_orig.jpg";
00175        
00176     QDir tmpDir;
00177     if(!tmpDir.exists( tempOrigLocation ) )
00178     { copyFile( imageLocation, tempOrigLocation ); }    
00179   }
00180   
00181   //copy over full size image
00182   copyFile( editedImageFilename, imageLocation );
00183   
00184   //reset revert flag since image has not been modified
00185   recentlyReverted = false;
00186   
00187   //construct smaller iamges
00188   return constructSmallerImages();
00189 }
00190 //==============================================
00191 QString Photo::getImageFilename()             { return imageLocation;     }
00192 QString Photo::getSlideshowFilename()         { return slideshowLocation; }
00193 QString Photo::getThumbnailFilename()         { return thumbnailLocation; }
00194 
00195 void Photo::setImageFilename(QString val)     { imageLocation = val;     }
00196 void Photo::setSlideshowFilename(QString val) { slideshowLocation = val; }
00197 void Photo::setThumbnailFilename(QString val) { thumbnailLocation = val; }
00198 //==============================================
00199 QString Photo::getImageChecksum()             { return imageChecksum; }
00200 QString Photo::getThumbnailChecksum()         { return thumbnailChecksum; }
00201 QString Photo::getSlideshowChecksum()         { return slideshowChecksum; }
00202 
00203 void Photo::setImageChecksum(QString val)     { imageChecksum = val; }
00204 void Photo::setThumbnailChecksum(QString val) { thumbnailChecksum = val; }
00205 void Photo::setSlideshowChecksum(QString val) { slideshowChecksum = val; }
00206 //==============================================
00207 QString Photo::getDescription() { return QString(description); }
00208 //==============================================
00209 void Photo::setDescription(QString val)
00210 {
00211   //set empty strings as null, takes up less space and necessary
00212   //to check for string modification
00213   if( val.isEmpty() )
00214     val = QString::null;
00215 
00216   if(description.compare(val) != 0)
00217   {
00218     description = val;
00219     subalbum->setModified();
00220   }
00221 }
00222 //==============================================
00223 Photo* Photo::getPrev() { return prev; }
00224 Photo* Photo::getNext() { return next; }
00225 //==============================================
00226 void Photo::setPrev(Photo* val) 
00227 { 
00228   prev = val;
00229   subalbum->setModified();
00230 }
00231 //==============================================
00232 void Photo::setNext(Photo* val) 
00233 { 
00234   next = val;
00235   subalbum->setModified();
00236 }
00237 //==============================================
00238 QDateTime* Photo::importFromDisk(QDomNode* root)
00239 {
00240   //create modified date/time object for returning
00241   QDateTime* modified = new QDateTime[3];
00242 
00243   QDomNode node = root->firstChild();
00244   QDomText val;
00245   while( !node.isNull() )
00246   {
00247     //------------------------------------------------------------
00248     //photo description
00249     if( node.isElement() && node.nodeName() == "description" )
00250     {
00251       val = node.firstChild().toText();
00252       if(!val.isNull())
00253         description = val.nodeValue();
00254      description.replace("\\&quot;","\"");
00255     }
00256     //------------------------------------------------------------
00257     //image information
00258     else if( node.isElement() && node.nodeName() == "image" )
00259     {
00260       QDomNode childNode = node.firstChild();
00261       while( !childNode.isNull() )
00262       {
00263         //------------------------------------------------------------
00264         if( childNode.isElement() && childNode.nodeName() == "md5" )
00265         {
00266           val = childNode.firstChild().toText();
00267           if(!val.isNull())
00268             imageChecksum = val.nodeValue();
00269         }
00270         //------------------------------------------------------------
00271         else if( childNode.isElement() && childNode.nodeName() == "modified" )
00272         {
00273           val = childNode.firstChild().toText();
00274 
00275           //split value based on spaces, should be 7 fields
00276           QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() );
00277           int i=0;
00278           int intVals[7];
00279           QStringList::Iterator it;
00280           for ( it = vals.begin(); it != vals.end(); ++it )
00281           {
00282             //sanity check incase more fields are provided than there should be
00283             if(i >6)
00284               break;
00285 
00286             intVals[i] = QString(*it).toInt();
00287             i++;
00288           }
00289           modified[0].setDate( QDate(intVals[0], intVals[1], intVals[2]) );
00290           modified[0].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) );
00291         }
00292         //------------------------------------------------------------
00293         childNode = childNode.nextSibling();
00294       }
00295     }
00296     //------------------------------------------------------------
00297     //slideshow information
00298     else if( node.isElement() && node.nodeName() == "slideshow" )
00299     {
00300       QDomNode childNode = node.firstChild();
00301       while( !childNode.isNull() )
00302       {
00303         //------------------------------------------------------------
00304         if( childNode.isElement() && childNode.nodeName() == "md5" )
00305         {
00306           val = childNode.firstChild().toText();
00307           if(!val.isNull())
00308             slideshowChecksum = val.nodeValue();
00309         }
00310         //------------------------------------------------------------
00311         else if( childNode.isElement() && childNode.nodeName() == "modified" )
00312         {
00313           val = childNode.firstChild().toText();
00314 
00315           //split value based on spaces, should be 6 fields
00316           QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() );
00317           int i=0;
00318           int intVals[7];
00319           QStringList::Iterator it;
00320           for ( it = vals.begin(); it != vals.end(); ++it )
00321           {
00322             //sanity check incase more fields are provided than there should be
00323             if(i >6)
00324               break;
00325 
00326             intVals[i] = QString(*it).toInt();
00327             i++;
00328           }
00329           modified[1].setDate( QDate(intVals[0], intVals[1], intVals[2]) );
00330           modified[1].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) );
00331         }
00332         //------------------------------------------------------------
00333         childNode = childNode.nextSibling();
00334       }
00335     }
00336     //------------------------------------------------------------
00337     //slideshow information
00338     else if( node.isElement() && node.nodeName() == "thumb" )
00339     {
00340       QDomNode childNode = node.firstChild();
00341       while( !childNode.isNull() )
00342       {
00343         //------------------------------------------------------------
00344         if( childNode.isElement() && childNode.nodeName() == "md5" )
00345         {
00346           val = childNode.firstChild().toText();
00347           if(!val.isNull())
00348             thumbnailChecksum = val.nodeValue();
00349         }
00350         //------------------------------------------------------------
00351         else if( childNode.isElement() && childNode.nodeName() == "modified" )
00352         {
00353           val = childNode.firstChild().toText();
00354 
00355           //split value based on spaces, should be 6 fields
00356           QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() );
00357           int i=0;
00358           int intVals[7];
00359           QStringList::Iterator it;
00360           for ( it = vals.begin(); it != vals.end(); ++it )
00361           {
00362             //sanity check incase more fields are provided than there should be
00363             if(i >6)
00364               break;
00365 
00366             intVals[i] = QString(*it).toInt();
00367             i++;
00368           }
00369           modified[2].setDate( QDate(intVals[0], intVals[1], intVals[2]) );
00370           modified[2].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) );
00371         }
00372         //------------------------------------------------------------
00373         childNode = childNode.nextSibling();
00374       }
00375     }
00376     //------------------------------------------------------------
00377     //------------------------------------------------------------
00378     //Handle md5 info as specified in 1.0a and 1.0a2 xml format
00379     //image md5
00380     else if( node.isElement() && node.nodeName() == "imageMD5" )
00381     {
00382       val = node.firstChild().toText();
00383       if(!val.isNull())
00384         imageChecksum = val.nodeValue();
00385     }
00386     //------------------------------------------------------------
00387     //slideshow md5
00388     else if( node.isElement() && node.nodeName() == "slideMD5" )
00389     {
00390       val = node.firstChild().toText();
00391       if(!val.isNull())
00392         slideshowChecksum = val.nodeValue();
00393     }
00394     //------------------------------------------------------------
00395     //thumbnail md5
00396     else if( node.isElement() && node.nodeName() == "thumbMD5" )
00397     {
00398       val = node.firstChild().toText();
00399       if(!val.isNull())
00400         thumbnailChecksum = val.nodeValue();
00401     }
00402     //------------------------------------------------------------
00403     //------------------------------------------------------------
00404     //advance to next node
00405     node = node.nextSibling();
00406     //------------------------------------------------------------
00407   }
00408 
00409   //return modification dates read in
00410   return modified;
00411 }
00412 //==============================================
00413 void Photo::exportToXML(QTextStream& stream)
00414 {
00415   QFileInfo info;
00416 
00417   //write photo information
00418   stream << "    <photo>\n";
00419   //-----
00420   stream << "      <description>" << fixXMLString(description) << "</description>\n";
00421   //-----
00422   //full image
00423   info.setFile( getImageFilename() );
00424   QDateTime modified = info.lastModified();
00425   stream << "      <image>\n";
00426   stream << "        <md5>" << fixXMLString(imageChecksum) << "</md5>\n";
00427   stream << "        <modified>";
00428   stream << modified.date().year() << " ";
00429   stream << modified.date().month() << " ";
00430   stream << modified.date().day() << " ";
00431   stream << modified.time().hour() << " ";
00432   stream << modified.time().minute() << " ";
00433   stream << modified.time().second() << " ";
00434   stream << modified.time().msec() << "</modified>\n";
00435   stream << "      </image>\n";
00436   //-----
00437   //slidehow image
00438   info.setFile( getSlideshowFilename() );
00439   modified = info.lastModified();
00440   stream << "      <slideshow>\n";
00441   stream << "        <md5>" << fixXMLString(slideshowChecksum) << "</md5>\n";
00442   stream << "        <modified>";
00443   stream << modified.date().year() << " ";
00444   stream << modified.date().month() << " ";
00445   stream << modified.date().day() << " ";
00446   stream << modified.time().hour() << " ";
00447   stream << modified.time().minute() << " ";
00448   stream << modified.time().second() << " ";
00449   stream << modified.time().msec() << "</modified>\n";
00450   stream << "      </slideshow>\n";
00451   //-----
00452   //thumbnail image
00453   info.setFile( getThumbnailFilename() );
00454   modified = info.lastModified();
00455   stream << "      <thumb>\n";
00456   stream << "        <md5>" << fixXMLString(thumbnailChecksum) << "</md5>\n";
00457   stream << "        <modified>";
00458   stream << modified.date().year() << " ";
00459   stream << modified.date().month() << " ";
00460   stream << modified.date().day() << " ";
00461   stream << modified.time().hour() << " ";
00462   stream << modified.time().minute() << " ";
00463   stream << modified.time().second() << " ";
00464   stream << modified.time().msec() << "</modified>\n";
00465   stream << "      </thumb>\n";
00466   //-----
00467   stream << "    </photo>\n";
00468 }
00469 //==============================================
00470 void Photo::rotate90()         { applyTransformation( ROTATE_90 );  }
00471 void Photo::rotate270()        { applyTransformation( ROTATE_270 ); }
00472 void Photo::flipHorizontally() { applyTransformation( FLIP_H );     }
00473 void Photo::flipVertically()   { applyTransformation( FLIP_V );     }
00474 //==============================================
00475 void Photo::applyTransformation(TRANSFORM_CODE transformation)
00476 {  
00477   //backup old filename
00478   QString oldName = imageLocation;
00479    
00480   //if the image did not previously need saving, 
00481   //reset filenames to point to temporary location and
00482   //immediately perform transformation
00483   if(!needsSaving)
00484   {
00485     imageLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2.jpg")
00486                                                                 .arg(initialSubalbumNumber)
00487                                                                 .arg(initialPhotoNumber);
00488     slideshowLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_slideshow.jpg")
00489                                                                 .arg(initialSubalbumNumber)
00490                                                                 .arg(initialPhotoNumber);
00491     thumbnailLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_thumb.jpg")
00492                                                                 .arg(initialSubalbumNumber)
00493                                                                 .arg(initialPhotoNumber);
00494     transformImage( oldName, imageLocation, transformation );
00495   }
00496   else
00497   {
00498     //images that need saving already exist in the temporary directory
00499     //this poses two problems:
00500     //1.) fast jpeg transformations cannot be done in place, so we'll employ an
00501     //    intermediate image
00502     QString intermediateName = QString("%1_intermdiate.jpg").arg(oldName);
00503     transformImage( oldName, intermediateName, transformation );    
00504     
00505     //2.) If the photo has never been saved and an orig file in the temporary
00506     //    directory does not exist then the current file is the original version. we
00507     //    must make sure that this original photo is maintained using an orig file so
00508     //    in the future users can revert the photo to it's original form.
00509     QString origName = subalbum->getAlbum()->getTmpDir() + QString("/0_%1_orig.jpg")
00510       .arg(initialPhotoNumber);      
00511     QDir tmpDir;
00512     if( !getEverSaved() && !tmpDir.exists(origName) )
00513     {
00514       moveFile( oldName, origName );
00515       moveFile( intermediateName, imageLocation );
00516     }
00517     else
00518     {
00519       moveFile( intermediateName, imageLocation );
00520     }
00521   }
00522   
00523   //image now modified from original form so orig file needs to be kept
00524   recentlyReverted = false;
00525   
00526   //construct smaller iamges
00527   constructSmallerImages();
00528 }
00529 //==============================================
00530 bool Photo::getNeedsSavingVal()               { return needsSaving; }
00531 void Photo::setNeedsSavingVal(bool val)       { needsSaving = val; }
00532 //==============================================
00533 bool Photo::getEverSaved()                    { return everSaved; }
00534 void Photo::setEverSaved(bool val)            { everSaved = val; }
00535 //==============================================
00536 bool Photo::revertPossible()
00537 {
00538   //if photo not recently reverted and orig and current filenames differ
00539   QString newName = getImageFilename();
00540   QString origName = originalImageFilename();
00541   
00542   return ( !recentlyReverted &&
00543            origName.compare( newName ) !=0 );
00544 }
00545 //==============================================
00546 bool Photo::getRecentlyReverted()
00547 {
00548   return recentlyReverted;
00549 }
00550 //==============================================
00551 void Photo::setRecentlyReverted(bool val)
00552 {
00553   recentlyReverted = val;
00554 }
00555 //==============================================
00556 void Photo::revertPhoto()
00557 {
00558   //ignore if revert is not possible
00559   if(!revertPossible())
00560     return;
00561   
00562   //set image to reverted form
00563   QString origName = originalImageFilename();
00564   setImage( origName );
00565   
00566   //recently reverted, orig file should be
00567   //removed during next save since it's redundant
00568   recentlyReverted = true;
00569 }
00570 //==============================================
00571 QString Photo::originalImageFilename()
00572 {
00573   //determining the an images original filename is tricky
00574   //if the photo has never been saved check for presence of an _orig file,
00575   //otherwise use the current filename since the photo has not yet been modified
00576   if( !getEverSaved() )
00577   {
00578     QString tempOrigLocation = imageLocation;
00579     tempOrigLocation.truncate( imageLocation.length() - 4 );
00580     tempOrigLocation = tempOrigLocation + "_orig.jpg";
00581     
00582     QDir tmpDir;
00583     if(tmpDir.exists( tempOrigLocation ) )
00584       return tempOrigLocation;
00585     else
00586       return imageLocation;
00587   }
00588   //if the photo was previously saved, it's original form could either be:
00589   //1.) the permanant storage location + _orig
00590   //2.) the permanant storage location
00591   else
00592   {
00593     QString storedOrigLocation = subalbum->getAlbum()->getSaveLocation() +  
00594       QString("/img/%1/%2_orig.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
00595    
00596     QString lastSavedLocation = subalbum->getAlbum()->getSaveLocation() +  
00597       QString("/img/%1/%2.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);    
00598     
00599     QDir tmpDir;
00600     if(tmpDir.exists( storedOrigLocation ) )
00601       return storedOrigLocation;
00602     else
00603       return lastSavedLocation;
00604   }
00605 }
00606 //==============================================
00607 int Photo::getInitialPhotoNumber()            { return initialPhotoNumber; }
00608 void Photo::setInitialPhotoNumber(int val)    { initialPhotoNumber = val; }
00609 
00610 int Photo::getInitialSubalbumNumber()         { return initialSubalbumNumber; }
00611 void Photo::setInitialSubalbumNumber(int val) { initialSubalbumNumber = val; }
00612 //==============================================

Generated on Wed Nov 8 16:37:13 2006 for AlbumShaper by  doxygen 1.4.7