AlbumShaper
1.0a3
|
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("\\"","\""); 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 //==============================================