summaryrefslogtreecommitdiffstats
path: root/src/imagefactory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/imagefactory.cpp')
-rw-r--r--src/imagefactory.cpp611
1 files changed, 611 insertions, 0 deletions
diff --git a/src/imagefactory.cpp b/src/imagefactory.cpp
new file mode 100644
index 0000000..00a980a
--- /dev/null
+++ b/src/imagefactory.cpp
@@ -0,0 +1,611 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "imagefactory.h"
+#include "image.h"
+#include "document.h"
+#include "filehandler.h"
+#include "tellico_utils.h"
+#include "tellico_kernel.h"
+#include "core/tellico_config.h"
+#include "tellico_debug.h"
+
+#include <ktempdir.h>
+#include <kapplication.h>
+#include <kimageeffect.h>
+
+#include <qfile.h>
+#include <qdir.h>
+
+#define RELEASE_IMAGES
+
+using Tellico::ImageFactory;
+
+bool ImageFactory::s_needInit = true;
+const Tellico::Data::Image ImageFactory::s_null;
+
+QDict<Tellico::Data::Image> ImageFactory::s_imageDict;
+// since most images get turned into pixmaps quickly, use 10 megs
+// for images and 10 megs for pixmaps
+QCache<Tellico::Data::Image> ImageFactory::s_imageCache(10 * 1024 * 1024);
+QCache<QPixmap> ImageFactory::s_pixmapCache(10 * 1024 * 1024);
+// this image info map is just for big images that don't fit
+// in the cache, so that don't have to be continually reloaded to get info
+QMap<QString, Tellico::Data::ImageInfo> ImageFactory::s_imageInfoMap;
+Tellico::StringSet ImageFactory::s_imagesInTmpDir;
+Tellico::StringSet ImageFactory::s_imagesToRelease;
+KTempDir* ImageFactory::s_tmpDir = 0;
+QString ImageFactory::s_localDir;
+
+void ImageFactory::init() {
+ if(!s_needInit) {
+ return;
+ }
+ s_imageDict.setAutoDelete(true);
+ s_imageCache.setAutoDelete(true);
+ s_imageCache.setMaxCost(Config::imageCacheSize());
+ s_pixmapCache.setAutoDelete(true);
+ s_needInit = false;
+}
+
+QString ImageFactory::tempDir() {
+ if(!s_tmpDir) {
+ s_tmpDir = new KTempDir();
+ s_tmpDir->setAutoDelete(true);
+ }
+ return s_tmpDir->name();
+}
+
+QString ImageFactory::dataDir() {
+ static const QString dataDir = Tellico::saveLocation(QString::fromLatin1("data/"));
+ return dataDir;
+}
+
+QString ImageFactory::localDir() {
+ if(s_localDir.isEmpty()) {
+ return dataDir();
+ }
+ return s_localDir;
+}
+
+QString ImageFactory::addImage(const KURL& url_, bool quiet_, const KURL& refer_, bool link_) {
+ return addImageImpl(url_, quiet_, refer_, link_).id();
+}
+
+const Tellico::Data::Image& ImageFactory::addImageImpl(const KURL& url_, bool quiet_, const KURL& refer_, bool link_) {
+ if(url_.isEmpty() || !url_.isValid()) {
+ return s_null;
+ }
+// myLog() << "ImageFactory::addImageImpl(KURL) - " << url_.prettyURL() << endl;
+ Data::Image* img = refer_.isEmpty()
+ ? FileHandler::readImageFile(url_, quiet_)
+ : FileHandler::readImageFile(url_, quiet_, refer_);
+ if(!img) {
+ myLog() << "ImageFactory::addImageImpl() - image not found: " << url_.prettyURL() << endl;
+ return s_null;
+ }
+ if(img->isNull()) {
+ delete img;
+ return s_null;
+ }
+
+ if(link_) {
+ img->setLinkOnly(true);
+ img->setID(url_.url());
+ }
+
+ if(hasImage(img->id())) {
+// myDebug() << "### ImageFactory::addImageImpl() - hasImage() is true!" << endl;
+ const Data::Image& img2 = imageById(img->id());
+ if(!img2.isNull()) {
+ delete img;
+ return img2;
+ }
+ }
+
+ if(!link_) {
+ s_imageDict.insert(img->id(), img);
+ }
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+ return *img;
+}
+
+QString ImageFactory::addImage(const QImage& image_, const QString& format_) {
+ return addImageImpl(image_, format_).id();
+}
+
+QString ImageFactory::addImage(const QPixmap& pix_, const QString& format_) {
+ return addImageImpl(pix_.convertToImage(), format_).id();
+}
+
+const Tellico::Data::Image& ImageFactory::addImageImpl(const QImage& image_, const QString& format_) {
+ Data::Image* img = new Data::Image(image_, format_);
+ if(hasImage(img->id())) {
+ const Data::Image& img2 = imageById(img->id());
+ if(!img2.isNull()) {
+ delete img;
+ return img2;
+ }
+ }
+ if(img->isNull()) {
+ delete img;
+ return s_null;
+ }
+ s_imageDict.insert(img->id(), img);
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+ return *img;
+}
+
+QString ImageFactory::addImage(const QByteArray& data_, const QString& format_, const QString& id_) {
+ return addImageImpl(data_, format_, id_).id();
+}
+
+const Tellico::Data::Image& ImageFactory::addImageImpl(const QByteArray& data_, const QString& format_,
+ const QString& id_) {
+ if(id_.isEmpty()) {
+ return s_null;
+ }
+
+ // do not call imageById(), it causes infinite looping with Document::loadImage()
+ Data::Image* img = s_imageCache.find(id_);
+ if(img) {
+ myLog() << "ImageFactory::addImageImpl(QByteArray) - already exists in cache: " << id_ << endl;
+ return *img;
+ }
+
+ img = s_imageDict.find(id_);
+ if(img) {
+ myLog() << "ImageFactory::addImageImpl(QByteArray) - already exists in dict: " << id_ << endl;
+ return *img;
+ }
+
+ img = new Data::Image(data_, format_, id_);
+ if(img->isNull()) {
+ myDebug() << "ImageFactory::addImageImpl(QByteArray) - NULL IMAGE!!!!!" << endl;
+ delete img;
+ return s_null;
+ }
+
+// myLog() << "ImageFactory::addImageImpl(QByteArray) - " << data_.size()
+// << " bytes, format = " << format_
+// << ", id = "<< img->id() << endl;
+
+ s_imageDict.insert(img->id(), img);
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+ return *img;
+}
+
+const Tellico::Data::Image& ImageFactory::addCachedImageImpl(const QString& id_, CacheDir dir_) {
+// myLog() << "ImageFactory::addCachedImageImpl() - dir = " << (dir_ == DataDir ? "DataDir" : "TmpDir" )
+// << "; id = " << id_ << endl;
+ KURL u;
+ if(dir_ == DataDir) {
+ u.setPath(dataDir() + id_);
+ } else if(dir_ == LocalDir) {
+ u.setPath(localDir() + id_);
+ } else{ // Temp
+ u.setPath(tempDir() + id_);
+ }
+
+ QString newID = addImage(u, true);
+ if(newID.isEmpty()) {
+ myLog() << "ImageFactory::addCachedImageImpl() - null image loaded" << endl;
+ return s_null;
+ }
+
+ // the id probably got changed, so reset it
+ // addImage() already inserted it in the dict
+ Data::Image* img = s_imageDict.take(newID);
+ if(!img) {
+ kdWarning() << "ImageFactory::addCachedImageImpl() - no image in dict - very bad!" << endl;
+ return s_null;
+ }
+ if(img->isNull()) {
+ kdWarning() << "ImageFactory::addCachedImageImpl() - null image in dict, should never happen!" << endl;
+ delete img;
+ return s_null;
+ }
+ img->setID(id_);
+ s_imageInfoMap.remove(newID);
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+
+ if(s_imageCache.insert(img->id(), img, img->numBytes())) {
+// myLog() << "ImageFactory::addCachedImageImpl() - removing from dict: " << img->id() << endl;
+ } else {
+ // can't hold it in the cache
+ kdWarning() << "Tellico's image cache is unable to hold the image, it might be too big!" << endl;
+ kdWarning() << "Image name is " << img->id() << endl;
+ kdWarning() << "Image size is " << img->numBytes() << endl;
+ kdWarning() << "Max cache size is " << s_imageCache.maxCost() << endl;
+
+ // add it back to the dict, but add the image to the list of
+ // images to release later. Necessary to avoid a memory leak since new Image()
+ // was called, we need to keep the pointer
+ s_imageDict.insert(img->id(), img);
+ s_imagesToRelease.add(img->id());
+ }
+ return *img;
+}
+
+bool ImageFactory::writeImage(const QString& id_, const KURL& targetDir_, bool force_) {
+// myLog() << "ImageFactory::writeImage() - target = " << targetDir_.url() << id_ << endl;
+ if(targetDir_.isEmpty()) {
+ myDebug() << "ImageFactory::writeImage() - empty target dir!" << endl;
+ return false;
+ }
+
+ const Data::Image& img = imageById(id_);
+ if(img.isNull()) {
+// myDebug() << "ImageFactory::writeImage() - null image: " << id_ << endl;
+ return false;
+ }
+
+ if(img.linkOnly()) {
+// myLog() << "ImageFactory::writeImage() - " << id_ << ": link only, not writing!" << endl;
+ return true;
+ }
+
+ KURL target = targetDir_;
+ target.addPath(id_);
+
+ return FileHandler::writeDataURL(target, img.byteArray(), force_);
+}
+
+bool ImageFactory::writeCachedImage(const QString& id_, CacheDir dir_, bool force_ /*=false*/) {
+ if(id_.isEmpty()) {
+ return false;
+ }
+// myLog() << "ImageFactory::writeCachedImage() - dir = " << (dir_ == DataDir ? "DataDir" : "TmpDir" )
+// << "; id = " << id_ << endl;
+
+ QString path = ( dir_ == DataDir ? dataDir() : dir_ == TempDir ? tempDir() : localDir() );
+
+ // images in the temp directory are erased every session, so we can track
+ // whether they've already been written with a simple string set.
+ // images in the data directory are persistent, so we have to check the
+ // actual file existence
+ bool exists = ( dir_ == TempDir ? s_imagesInTmpDir.has(id_) : QFile::exists(path + id_));
+
+ if(!force_ && exists) {
+// myDebug() << "...writeCachedImage() - exists = true: " << id_ << endl;
+ } else if(!force_ && !exists && dir_ == LocalDir) {
+ QDir dir(localDir());
+ if(!dir.exists()) {
+ myDebug() << "ImageFactory::writeCachedImage() - creating " << s_localDir << endl;
+ dir.mkdir(localDir());
+ }
+ } else {
+// myLog() << "ImageFactory::writeCachedImage() - dir = " << (dir_ == DataDir ? "DataDir" : "TmpDir" )
+// << "; id = " << id_ << endl;
+ }
+ // only write if it doesn't exist
+ bool success = (!force_ && exists) || writeImage(id_, path, true /* force */);
+
+ if(success) {
+ if(dir_ == TempDir) {
+ s_imagesInTmpDir.add(id_);
+ }
+
+ // remove from dict and add to cache
+ // it might not be in dict though
+ Data::Image* img = s_imageDict.take(id_);
+ if(img && s_imageCache.insert(img->id(), img, img->numBytes())) {
+ s_imageInfoMap.remove(id_);
+ } else if(img) {
+// myLog() << "ImageFactory::writeCachedImage() - failed writing image to cache: " << id_ << endl;
+// myLog() << "ImageFactory::writeCachedImage() - removed from dict, deleting: " << img->id() << endl;
+// myLog() << "ImageFactory::writeCachedImage() - current dict size: " << s_imageDict.count() << endl;
+ // can't insert it in the cache, so put it back in the dict
+ // No, it's written to disk now, so we're safe
+// s_imageDict.insert(img->id(), img);
+ delete img;
+ }
+ }
+ return success;
+}
+
+const Tellico::Data::Image& ImageFactory::imageById(const QString& id_) {
+ if(id_.isEmpty()) {
+ myDebug() << "ImageFactory::imageById() - empty id" << endl;
+ return s_null;
+ }
+// myLog() << "ImageFactory::imageById() - " << id_ << endl;
+
+ // can't think of a better place to regularly check for images to release
+ // but don't release image that just got asked for
+ s_imagesToRelease.remove(id_);
+ releaseImages();
+
+ // first check the cache, used for images that are in the data file, or are only temporary
+ // then the dict, used for images downloaded, but not yet saved anywhere
+ Data::Image* img = s_imageCache.find(id_);
+ if(img) {
+// myLog() << "...imageById() - found in cache" << endl;
+ return *img;
+ }
+
+ img = s_imageDict.find(id_);
+ if(img) {
+// myLog() << "...imageById() - found in dict" << endl;
+ return *img;
+ }
+
+ // if the image is link only, we need to load it
+ // but can't call imageInfo() since that might recurse into imageById()
+ // also, the image info cache might not have it so check if the
+ // id is a valid absolute url
+ // yeah, it's probably slow
+ if((s_imageInfoMap.contains(id_) && s_imageInfoMap[id_].linkOnly) || !KURL::isRelativeURL(id_)) {
+ KURL u = id_;
+ if(u.isValid()) {
+ return addImageImpl(u, false, KURL(), true);
+ }
+ }
+
+ // the document does a delayed loading of the images, sometimes
+ // so an image could be in the tmp dir and not be in the cache
+ // or it could be too big for the cache
+ if(s_imagesInTmpDir.has(id_)) {
+ const Data::Image& img2 = addCachedImageImpl(id_, TempDir);
+ if(!img2.isNull()) {
+// myLog() << "...imageById() - found in tmp dir" << endl;
+ return img2;
+ } else {
+ myLog() << "ImageFactory::imageById() - img in tmpDir list but not actually there: " << id_ << endl;
+ s_imagesInTmpDir.remove(id_);
+ }
+ }
+
+ // try to do a delayed loading of the image
+ if(Data::Document::self()->loadImage(id_)) {
+ // loadImage() could insert in either the cache or the dict!
+ img = s_imageCache.find(id_);
+ if(!img) {
+ img = s_imageDict.find(id_);
+ }
+ if(img) {
+// myLog() << "...imageById() - found in doc" << endl;
+ // go ahead and write image to disk so we don't have to keep it in memory
+ // calling pixmap() could be loading all the covers, and we don't want one
+ // to get pushed out of the cache yet
+ if(!s_imagesInTmpDir.has(id_)) {
+ writeCachedImage(id_, TempDir);
+ }
+ return *img;
+ }
+ }
+
+ // don't check Config::writeImagesInFile(), someday we might have problems
+ // and the image will exist in the data dir, but the app thinks everything should
+ // be in the zip file instead
+ bool exists = QFile::exists(dataDir() + id_);
+ if(exists) {
+ // if we're loading from the application data dir, but images are being saved in the
+ // data file instead, then consider the document to be modified since it needs
+ // the image saved
+ if(Config::imageLocation() != Config::ImagesInAppDir) {
+ Data::Document::self()->slotSetModified(true);
+ }
+ const Data::Image& img2 = addCachedImageImpl(id_, DataDir);
+ if(img2.isNull()) {
+ myDebug() << "ImageFactory::imageById() - tried to add from DataDir, but failed: " << id_ << endl;
+ } else {
+// myLog() << "...imageById() - found in data dir" << endl;
+ return img2;
+ }
+ }
+ // if localDir() == DataDir(), then there's nothing left to check
+ if(localDir() == dataDir()) {
+ return s_null;
+ }
+ exists = QFile::exists(localDir() + id_);
+ if(exists) {
+ // if we're loading from the application data dir, but images are being saved in the
+ // data file instead, then consider the document to be modified since it needs
+ // the image saved
+ if(Config::imageLocation() != Config::ImagesInLocalDir) {
+ Data::Document::self()->slotSetModified(true);
+ }
+ const Data::Image& img2 = addCachedImageImpl(id_, LocalDir);
+ if(img2.isNull()) {
+ myDebug() << "ImageFactory::imageById() - tried to add from LocalDir, but failed: " << id_ << endl;
+ } else {
+// myLog() << "...imageById() - found in data dir" << endl;
+ return img2;
+ }
+ }
+ myDebug() << "***ImageFactory::imageById() - not found: " << id_ << endl;
+ return s_null;
+}
+
+Tellico::Data::ImageInfo ImageFactory::imageInfo(const QString& id_) {
+ if(s_imageInfoMap.contains(id_)) {
+ return s_imageInfoMap[id_];
+ }
+
+ const Data::Image& img = imageById(id_);
+ if(img.isNull()) {
+ return Data::ImageInfo();
+ }
+ return Data::ImageInfo(img);
+}
+
+void ImageFactory::cacheImageInfo(const Data::ImageInfo& info) {
+ s_imageInfoMap.insert(info.id, info);
+}
+
+bool ImageFactory::validImage(const QString& id_) {
+ // don't try s_imageInfoMap[id_] cause it inserts an empty image info
+ return s_imageInfoMap.contains(id_) || hasImage(id_) || !imageById(id_).isNull();
+}
+
+QPixmap ImageFactory::pixmap(const QString& id_, int width_, int height_) {
+ if(id_.isEmpty()) {
+ return QPixmap();
+ }
+
+ const QString key = id_ + '|' + QString::number(width_) + '|' + QString::number(height_);
+ QPixmap* pix = s_pixmapCache.find(key);
+ if(pix) {
+ return *pix;
+ }
+
+ const Data::Image& img = imageById(id_);
+ if(img.isNull()) {
+ return QPixmap();
+ }
+
+ if(width_ > 0 && height_ > 0) {
+ pix = new QPixmap(img.convertToPixmap(width_, height_));
+ } else {
+ pix = new QPixmap(img.convertToPixmap());
+ }
+
+ // pixmap size is w x h x d, divided by 8 bits
+ if(!s_pixmapCache.insert(key, pix, pix->width()*pix->height()*pix->depth()/8)) {
+ kdWarning() << "ImageFactory::pixmap() - can't save in cache: " << id_ << endl;
+ kdWarning() << "### Current pixmap size is " << (pix->width()*pix->height()*pix->depth()/8) << endl;
+ kdWarning() << "### Max pixmap cache size is " << s_pixmapCache.maxCost() << endl;
+ QPixmap pix2(*pix);
+ delete pix;
+ return pix2;
+ }
+ return *pix;
+}
+
+void ImageFactory::clean(bool deleteTempDirectory_) {
+ // the dict and caches all auto-delete
+ s_imagesToRelease.clear();
+ s_imageDict.clear();
+ s_imageInfoMap.clear();
+ s_imageCache.clear();
+ s_pixmapCache.clear();
+ if(deleteTempDirectory_) {
+ s_imagesInTmpDir.clear();
+ delete s_tmpDir;
+ s_tmpDir = 0;
+ }
+}
+
+void ImageFactory::createStyleImages(const StyleOptions& opt_) {
+ const int collType = Kernel::self()->collectionType();
+
+ const QColor& baseColor = opt_.baseColor.isValid()
+ ? opt_.baseColor
+ : Config::templateBaseColor(collType);
+ const QColor& highColor = opt_.highlightedBaseColor.isValid()
+ ? opt_.highlightedBaseColor
+ : Config::templateHighlightedBaseColor(collType);
+
+ const QColor& bgc1 = Tellico::blendColors(baseColor, highColor, 30);
+ const QColor& bgc2 = Tellico::blendColors(baseColor, highColor, 50);
+
+ const QString bgname = QString::fromLatin1("gradient_bg.png");
+ QImage bgImage = KImageEffect::gradient(QSize(400, 1), bgc1, baseColor,
+ KImageEffect::PipeCrossGradient);
+ bgImage = KImageEffect::rotate(bgImage, KImageEffect::Rotate90);
+
+ const QString hdrname = QString::fromLatin1("gradient_header.png");
+ QImage hdrImage = KImageEffect::unbalancedGradient(QSize(1, 10), highColor, bgc2,
+ KImageEffect::VerticalGradient, 100, -100);
+
+ if(opt_.imgDir.isEmpty()) {
+ // write the style images both to the tmp dir and the data dir
+ // doesn't really hurt and lets the user switch back and forth
+ ImageFactory::removeImage(bgname, true /*delete */);
+ ImageFactory::addImageImpl(Data::Image::byteArray(bgImage, "PNG"), QString::fromLatin1("PNG"), bgname);
+ ImageFactory::writeCachedImage(bgname, DataDir, true /*force*/);
+ ImageFactory::writeCachedImage(bgname, TempDir, true /*force*/);
+
+ ImageFactory::removeImage(hdrname, true /*delete */);
+ ImageFactory::addImageImpl(Data::Image::byteArray(hdrImage, "PNG"), QString::fromLatin1("PNG"), hdrname);
+ ImageFactory::writeCachedImage(hdrname, DataDir, true /*force*/);
+ ImageFactory::writeCachedImage(hdrname, TempDir, true /*force*/);
+ } else {
+ bgImage.save(opt_.imgDir + bgname, "PNG");
+ hdrImage.save(opt_.imgDir + hdrname, "PNG");
+ }
+}
+
+void ImageFactory::removeImage(const QString& id_, bool deleteImage_) {
+ //be careful using this
+ s_imageDict.remove(id_);
+ s_imageCache.remove(id_);
+ s_imagesInTmpDir.remove(id_);
+
+ if(deleteImage_) {
+ // remove from both data dir and temp dir
+ QFile::remove(dataDir() + id_);
+ QFile::remove(tempDir() + id_);
+ }
+}
+
+Tellico::StringSet ImageFactory::imagesNotInCache() {
+ StringSet set;
+ for(QDictIterator<Tellico::Data::Image> it(s_imageDict); it.current(); ++it) {
+ if(s_imageCache.find(it.currentKey()) == 0) {
+ set.add(it.currentKey());
+ }
+ }
+ return set;
+}
+
+bool ImageFactory::hasImage(const QString& id_) {
+ return s_imageCache.find(id_, false) || s_imageDict.find(id_);
+}
+
+// the purpose here is to remove images from the dict if they're is on the disk somewhere,
+// either in tempDir() or in dataDir(). The use for this is for calling pixmap() on an
+// image too big to stay in the cache. Then it stays in the dict forever.
+void ImageFactory::releaseImages() {
+#ifdef RELEASE_IMAGES
+ if(s_imagesToRelease.isEmpty()) {
+ return;
+ }
+
+ const QStringList images = s_imagesToRelease.toList();
+ for(QStringList::ConstIterator it = images.begin(); it != images.end(); ++it) {
+ s_imagesToRelease.remove(*it);
+ if(!s_imageDict.find(*it)) {
+ continue;
+ }
+// myLog() << "ImageFactory::releaseImage() - id = " << *it << endl;
+ if(QFile::exists(dataDir() + *it)) {
+// myDebug() << "...exists in dataDir() - removing from dict" << endl;
+ s_imageDict.remove(*it);
+ } else if(QFile::exists(tempDir() + *it)) {
+// myDebug() << "...exists in tempDir() - removing from dict" << endl;
+ s_imageDict.remove(*it);
+ }
+ }
+#endif
+}
+
+void ImageFactory::setLocalDirectory(const KURL& url_) {
+ if(url_.isEmpty()) {
+ return;
+ }
+ if(!url_.isLocalFile()) {
+ myWarning() << "ImageFactory::setLocalDirectory() - Tellico can only save images to local disk" << endl;
+ myWarning() << "unable to save to " << url_ << endl;
+ } else {
+ s_localDir = url_.directory(false);
+ // could have already been set once
+ if(!url_.fileName().contains(QString::fromLatin1("_files"))) {
+ s_localDir += url_.fileName().section('.', 0, 0) + QString::fromLatin1("_files/");
+ }
+ myLog() << "ImageFactory::setLocalDirectory() - local dir = " << s_localDir << endl;
+ }
+}
+
+#undef RELEASE_IMAGES