diff options
author | Michele Calgaro <michele.calgaro@yahoo.it> | 2024-11-22 18:41:30 +0900 |
---|---|---|
committer | Michele Calgaro <michele.calgaro@yahoo.it> | 2024-11-22 18:41:30 +0900 |
commit | ee0d99607c14cb63d3ebdb3a970b508949fa8219 (patch) | |
tree | 94ac1efedb94cb38bf6879ba0610fe75b554216b /src/libs/threadimageio | |
parent | 4adff739380e4ae9f30e443ee95644f184456869 (diff) | |
download | digikam-ee0d99607c14cb63d3ebdb3a970b508949fa8219.tar.gz digikam-ee0d99607c14cb63d3ebdb3a970b508949fa8219.zip |
Rename 'digikam' folder to 'src'
Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
Diffstat (limited to 'src/libs/threadimageio')
19 files changed, 3157 insertions, 0 deletions
diff --git a/src/libs/threadimageio/Makefile.am b/src/libs/threadimageio/Makefile.am new file mode 100644 index 00000000..a301012e --- /dev/null +++ b/src/libs/threadimageio/Makefile.am @@ -0,0 +1,23 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libthreadimageio.la + +libthreadimageio_la_SOURCES = loadsavethread.cpp \ + managedloadsavethread.cpp \ + sharedloadsavethread.cpp \ + previewloadthread.cpp \ + loadingdescription.cpp \ + loadsavetask.cpp \ + previewtask.cpp \ + loadingcache.cpp \ + loadingcacheinterface.cpp + +libthreadimageio_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/dimg/loaders \ + -I$(top_srcdir)/src/libs/dmetadata \ + $(LIBKEXIV2_CFLAGS) \ + -I$(top_srcdir)/src/libs/jpegutils \ + -I$(top_srcdir)/src/digikam \ + $(LIBKDCRAW_CFLAGS) $(all_includes) diff --git a/src/libs/threadimageio/loadingcache.cpp b/src/libs/threadimageio/loadingcache.cpp new file mode 100644 index 00000000..04dd3e4f --- /dev/null +++ b/src/libs/threadimageio/loadingcache.cpp @@ -0,0 +1,256 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-11 + * Description : shared image loading and caching + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include <tqapplication.h> +#include <tqvariant.h> + +// KDE includes. + +#include <kdirwatch.h> + +// Local includes. + +#include "ddebug.h" +#include "loadingcache.h" +#include "loadingcache.moc" + +namespace Digikam +{ + +class LoadingCachePriv +{ +public: + + TQCache<DImg> imageCache; + TQDict<LoadingProcess> loadingDict; + TQMutex mutex; + TQWaitCondition condVar; + KDirWatch *watch; + TQStringList watchedFiles; +}; + + +LoadingCache *LoadingCache::m_instance = 0; + +LoadingCache *LoadingCache::cache() +{ + if (!m_instance) + m_instance = new LoadingCache; + return m_instance; +} + +void LoadingCache::cleanUp() +{ + if (m_instance) + delete m_instance; +} + + +LoadingCache::LoadingCache() +{ + d = new LoadingCachePriv; + + d->imageCache.setAutoDelete(true); + // default value: 60 MB of cache + setCacheSize(60); + + d->watch = new KDirWatch; + + connect(d->watch, TQ_SIGNAL(dirty(const TQString &)), + this, TQ_SLOT(slotFileDirty(const TQString &))); +} + +LoadingCache::~LoadingCache() +{ + delete d->watch; + delete d; + m_instance = 0; +} + +DImg *LoadingCache::retrieveImage(const TQString &cacheKey) +{ + return d->imageCache.find(cacheKey); +} + +bool LoadingCache::putImage(const TQString &cacheKey, DImg *img, const TQString &filePath) +{ + bool successfulyInserted; + + // use size of image as cache cost, take care for wrapped preview TQImages + int cost = img->numBytes(); + TQVariant attribute(img->attribute("previewTQImage")); + if (attribute.isValid()) + { + cost = attribute.toImage().numBytes(); + } + + if ( d->imageCache.insert(cacheKey, img, cost) ) + { + if (!filePath.isEmpty()) + { + // store file path as attribute for our own use + img->setAttribute("loadingCacheFilePath", TQVariant(filePath)); + } + successfulyInserted = true; + } + else + { + // need to delete object if it was not successfuly inserted (too large) + delete img; + successfulyInserted = false; + } + + if (!filePath.isEmpty()) + { + // schedule update of file watch + // KDirWatch can only be accessed from main thread! + TQApplication::postEvent(this, new TQCustomEvent(TQEvent::User)); + } + return successfulyInserted; +} + +void LoadingCache::removeImage(const TQString &cacheKey) +{ + d->imageCache.remove(cacheKey); +} + +void LoadingCache::removeImages() +{ + d->imageCache.clear(); +} + +void LoadingCache::slotFileDirty(const TQString &path) +{ + // Signal comes from main thread, we need to lock ourselves. + CacheLock lock(this); + //DDebug() << "LoadingCache slotFileDirty " << path << endl; + for (TQCacheIterator<DImg> it(d->imageCache); it.current(); ++it) + { + if (it.current()->attribute("loadingCacheFilePath").toString() == path) + { + //DDebug() << " removing watch and cache entry for " << path << endl; + d->imageCache.remove(it.currentKey()); + d->watch->removeFile(path); + d->watchedFiles.remove(path); + } + } +} + +void LoadingCache::customEvent(TQCustomEvent *) +{ + // Event comes from main thread, we need to lock ourselves. + CacheLock lock(this); + + // get a list of files in cache that need watch + TQStringList toBeAdded; + TQStringList toBeRemoved = d->watchedFiles; + for (TQCacheIterator<DImg> it(d->imageCache); it.current(); ++it) + { + TQString watchPath = it.current()->attribute("loadingCacheFilePath").toString(); + if (!watchPath.isEmpty()) + { + if (!d->watchedFiles.contains(watchPath)) + toBeAdded.append(watchPath); + toBeRemoved.remove(watchPath); + } + } + + for (TQStringList::iterator it = toBeRemoved.begin(); it != toBeRemoved.end(); ++it) + { + //DDebug() << "removing watch for " << *it << endl; + d->watch->removeFile(*it); + d->watchedFiles.remove(*it); + } + + for (TQStringList::iterator it = toBeAdded.begin(); it != toBeAdded.end(); ++it) + { + //DDebug() << "adding watch for " << *it << endl; + d->watch->addFile(*it); + d->watchedFiles.append(*it); + } + +} + +bool LoadingCache::isCacheable(const DImg *img) +{ + // return whether image fits in cache + return (uint)d->imageCache.maxCost() >= img->numBytes(); +} + +void LoadingCache::addLoadingProcess(LoadingProcess *process) +{ + d->loadingDict.insert(process->cacheKey(), process); +} + +LoadingProcess *LoadingCache::retrieveLoadingProcess(const TQString &cacheKey) +{ + return d->loadingDict.find(cacheKey); +} + +void LoadingCache::removeLoadingProcess(LoadingProcess *process) +{ + d->loadingDict.remove(process->cacheKey()); +} + +void LoadingCache::notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description) +{ + for (TQDictIterator<LoadingProcess> it(d->loadingDict); it.current(); ++it) + { + it.current()->notifyNewLoadingProcess(process, description); + } +} + +void LoadingCache::setCacheSize(int megabytes) +{ + d->imageCache.setMaxCost(megabytes * 1024 * 1024); +} + +//--------------------------------------------------------------------------------------------------- + +LoadingCache::CacheLock::CacheLock(LoadingCache *cache) + : m_cache(cache) +{ + m_cache->d->mutex.lock(); +} + +LoadingCache::CacheLock::~CacheLock() +{ + m_cache->d->mutex.unlock(); +} + +void LoadingCache::CacheLock::wakeAll() +{ + // obviously the mutex is locked when this function is called + m_cache->d->condVar.wakeAll(); +} + +void LoadingCache::CacheLock::timedWait() +{ + // same as above, the mutex is certainly locked + m_cache->d->condVar.wait(&m_cache->d->mutex, 1000); +} + +} // namespace Digikam + diff --git a/src/libs/threadimageio/loadingcache.h b/src/libs/threadimageio/loadingcache.h new file mode 100644 index 00000000..d4689305 --- /dev/null +++ b/src/libs/threadimageio/loadingcache.h @@ -0,0 +1,135 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-11 + * Description : shared image loading and caching + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef LOADING_CACHE_H +#define LOADING_CACHE_H + +#include <tqptrlist.h> +#include <tqcache.h> +#include <tqdict.h> +#include <tqmutex.h> + +#include "dimg.h" +#include "loadsavethread.h" + +namespace Digikam +{ + +class LoadingProcessListener +{ +public: + + virtual bool querySendNotifyEvent() = 0; + virtual TQObject *eventReceiver() = 0; + virtual LoadSaveThread::AccessMode accessMode() = 0; + +}; + +class LoadingProcess +{ +public: + + virtual bool completed() = 0; + virtual TQString filePath() = 0; + virtual TQString cacheKey() = 0; + virtual void addListener(LoadingProcessListener *listener) = 0; + virtual void removeListener(LoadingProcessListener *listener) = 0; + virtual void notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description) = 0; + +}; + +class LoadingCachePriv; + +class LoadingCache : public TQObject +{ + + TQ_OBJECT + + +public: + + static LoadingCache *cache(); + static void cleanUp(); + ~LoadingCache(); + + // all functions shall only be called when a CacheLock is held + class CacheLock + { + public: + CacheLock(LoadingCache *cache); + ~CacheLock(); + void wakeAll(); + void timedWait(); + private: + LoadingCache *m_cache; + }; + + // Retrieves an image for the given string from the cache, + // or 0 if no image is found. + DImg *retrieveImage(const TQString &cacheKey); + // Returns whether the given DImg fits in the cache. + bool isCacheable(const DImg *img); + // Put image into for given string into the cache. + // Returns true if image has been put in the cache, false otherwise. + // Ownership of the DImg instance is passed to the cache. + // When it cannot be put in the cache it is deleted. + // The third parameter specifies a file path that will be watched. + // If this file changes, the object will be removed from the cache. + bool putImage(const TQString &cacheKey, DImg *img, const TQString &filePath); + void removeImage(const TQString &cacheKey); + void removeImages(); + + // Find the loading process for given cacheKey, or 0 if not found + LoadingProcess *retrieveLoadingProcess(const TQString &cacheKey); + // Add a loading process to the list. Only one loading process + // for the same cache key is registered at a time. + void addLoadingProcess(LoadingProcess *process); + // Remove loading process for given cache key + void removeLoadingProcess(LoadingProcess *process); + // Notify all currently registered loading processes + void notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description); + + void setCacheSize(int megabytes); + +protected: + + virtual void customEvent (TQCustomEvent *event); + +private slots: + + void slotFileDirty(const TQString &path); + +private: + + static LoadingCache *m_instance; + + LoadingCache(); + + friend class CacheLock; + LoadingCachePriv *d; + +}; + +} // namespace Digikam + +#endif diff --git a/src/libs/threadimageio/loadingcacheinterface.cpp b/src/libs/threadimageio/loadingcacheinterface.cpp new file mode 100644 index 00000000..8cfa5ab9 --- /dev/null +++ b/src/libs/threadimageio/loadingcacheinterface.cpp @@ -0,0 +1,73 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-06 + * Description : shared image loading and caching + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + + +#include "loadingcacheinterface.h" +#include "loadingcache.h" + +namespace Digikam +{ + +void LoadingCacheInterface::cleanUp() +{ + LoadingCache::cleanUp(); +} + +void LoadingCacheInterface::cleanFromCache(const TQString &filePath) +{ + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + TQStringList possibleCacheKeys = LoadingDescription::possibleCacheKeys(filePath); + for (TQStringList::iterator it = possibleCacheKeys.begin(); it != possibleCacheKeys.end(); ++it) + { + cache->removeImage(*it); + } +} + +void LoadingCacheInterface::cleanCache() +{ + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + cache->removeImages(); +} + +void LoadingCacheInterface::putImage(const TQString &filePath, const DImg &img) +{ + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + if (cache->isCacheable(&img)) + { + DImg *copy = new DImg(img); + copy->detach(); + cache->putImage(filePath, copy, filePath); + } +} + +void LoadingCacheInterface::setCacheOptions(int cacheSize) +{ + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + cache->setCacheSize(cacheSize); +} + +} // namespace Digikam diff --git a/src/libs/threadimageio/loadingcacheinterface.h b/src/libs/threadimageio/loadingcacheinterface.h new file mode 100644 index 00000000..ab3f4eca --- /dev/null +++ b/src/libs/threadimageio/loadingcacheinterface.h @@ -0,0 +1,56 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-06 + * Description : shared image loading and caching + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef LOADING_CACHE_INTERFACE_H +#define LOADING_CACHE_INTERFACE_H + +#include <tqstring.h> + +#include "digikam_export.h" +#include "dimg.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT LoadingCacheInterface +{ +public: + // clean up cache at shutdown + static void cleanUp(); + // remove an image from the cache + // (e.g. when image has changed on disk) + static void cleanFromCache(const TQString &filePath); + // remove all images from the cache + // (e.g. when loading settings changed) + static void cleanCache(); + // add a copy of the image to cache + static void putImage(const TQString &filePath, const DImg &img); + // Set cache size in Megabytes. + // Set to 0 to disable caching. + static void setCacheOptions(int cacheSize); +}; + +} // namespace Digikam + +#endif + diff --git a/src/libs/threadimageio/loadingdescription.cpp b/src/libs/threadimageio/loadingdescription.cpp new file mode 100644 index 00000000..423ee99c --- /dev/null +++ b/src/libs/threadimageio/loadingdescription.cpp @@ -0,0 +1,152 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-02-03 + * Description : Loading parameters for multithreaded loading + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Local includes. + +#include "loadingdescription.h" + +namespace Digikam +{ + +bool LoadingDescription::PreviewParameters::operator==(const PreviewParameters &other) const +{ + return isPreview == other.isPreview + && size == other.size + && exifRotate == other.exifRotate; +} + +LoadingDescription::LoadingDescription(const TQString &filePath) + : filePath(filePath) +{ + rawDecodingSettings = DRawDecoding(); +} + +LoadingDescription::LoadingDescription(const TQString &filePath, DRawDecoding settings) + : filePath(filePath), rawDecodingSettings(settings) +{ +} + +LoadingDescription::LoadingDescription(const TQString &filePath, int size, bool exifRotate) + : filePath(filePath) +{ + rawDecodingSettings = DRawDecoding(); + previewParameters.isPreview = false; + previewParameters.size = size; + previewParameters.exifRotate = exifRotate; +} + +TQString LoadingDescription::cacheKey() const +{ + // Here we have the knowledge which LoadingDescriptions / RawFileDecodingSettings + // must be cached separately. + // Current assumption: + // Eight-bit images are needed for LightTable, and if 16-bit is enabled, + // 16-bit half size images for the histogram sidebar, + // and 16-bit full size images for the image editor. + // Use previewParameters.size, not isPreview - if it is 0, full loading is used. + + TQString suffix = rawDecodingSettings.sixteenBitsImage ? "-16" : "-8"; + + if (rawDecodingSettings.halfSizeColorImage) + return filePath + suffix + "-halfSizeColorImage"; + else if (previewParameters.size) + return filePath + suffix + "-previewImage"; + else + return filePath + suffix; +} + +TQStringList LoadingDescription::lookupCacheKeys() const +{ + // Build a hierarchy which cache entries may be used for this LoadingDescription. + // Typically, the first is the best, but an actual loading operation may use a + // lower-quality loading and will effectively only add the last entry of the + // list to the cache, although it can accept the first if already available. + // Sixteen-bit images cannot be used used instead of eight-bit ones because + // color management is needed to display them. + + TQString suffix = rawDecodingSettings.sixteenBitsImage ? "-16" : "-8"; + + TQStringList keys; + keys.append(filePath + suffix); + if (rawDecodingSettings.halfSizeColorImage) + keys.append(filePath + suffix + "-halfSizeColorImage"); + if (previewParameters.size) + keys.append(filePath + suffix + "-previewImage"); + return keys; +} + +bool LoadingDescription::isReducedVersion() const +{ + // return true if this loads anything but the full version + return rawDecodingSettings.halfSizeColorImage + || previewParameters.isPreview; +} + +bool LoadingDescription::operator==(const LoadingDescription &other) const +{ + return filePath == other.filePath && + rawDecodingSettings == other.rawDecodingSettings && + previewParameters == other.previewParameters; +} + +bool LoadingDescription::equalsIgnoreReducedVersion(const LoadingDescription &other) const +{ + return filePath == other.filePath; +} + +bool LoadingDescription::equalsOrBetterThan(const LoadingDescription &other) const +{ + // This method is similar to operator==. But it returns true as well if other + // Loads a "better" version than this. + // Preview parameters must have the same size, or other has no size restriction. + // All raw decoding settings must be equal, only the half size parameter is allowed to vary. + + DRawDecoding fullSize = other.rawDecodingSettings; + fullSize.halfSizeColorImage = false; + + return filePath == other.filePath && + ( + rawDecodingSettings == other.rawDecodingSettings || + rawDecodingSettings == fullSize + ) && + ( + (previewParameters.size == other.previewParameters.size) || + other.previewParameters.size + ); +} + +TQStringList LoadingDescription::possibleCacheKeys(const TQString &filePath) +{ + TQStringList keys; + keys.append(filePath + "-16"); + keys.append(filePath + "-16-halfSizeColorImage"); + keys.append(filePath + "-16-previewImage"); + keys.append(filePath + "-8"); + keys.append(filePath + "-8-halfSizeColorImage"); + keys.append(filePath + "-8-previewImage"); + return keys; +} + + +} // namespace Digikam + diff --git a/src/libs/threadimageio/loadingdescription.h b/src/libs/threadimageio/loadingdescription.h new file mode 100644 index 00000000..848f2b17 --- /dev/null +++ b/src/libs/threadimageio/loadingdescription.h @@ -0,0 +1,135 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-16 + * Description : image file IO threaded interface. + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef LOADING_DESCRIPTION_H +#define LOADING_DESCRIPTION_H + +// Digikam includes. + +#include "dimg.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT LoadingDescription +{ +public: + + class PreviewParameters + { + public: + PreviewParameters() + { + isPreview = false; + size = 0; + exifRotate = false; + } + + bool isPreview; + int size; + bool exifRotate; + + bool operator==(const PreviewParameters &other) const; + }; + + /** + * An invalid LoadingDescription + */ + LoadingDescription() + { + } + + /** + * Use this for files that are not raw files. + * Stores only the filePath. + */ + LoadingDescription(const TQString &filePath); + + /** + * For raw files: + * Stores filePath and RawDecodingSettings + */ + LoadingDescription(const TQString &filePath, DRawDecoding settings); + + /** + * For preview jobs: + * Stores preview max size and exif rotation. + * Exif Rotation: + * The exif rotation is only a hint. + * Call LoadSaveThread::exifRotate to make sure that the image is really + * rotated. It is safe to call this method even if the image is rotated. + * Raw files: + * If size is not 0, the embedded preview will be loaded if available. + * If size is 0, DImg based loading will be used with default raw decoding settings. + */ + LoadingDescription(const TQString &filePath, int size, bool exifRotate); + + TQString filePath; + DRawDecoding rawDecodingSettings; + PreviewParameters previewParameters; + + /** + * Return the cache key this description shall be stored as + */ + TQString cacheKey() const; + /** + * Return all possible cache keys, starting with the best choice, + * for which a result may be found in the cache for this description. + * Included in the list are better quality versions, if this description is reduced. + */ + TQStringList lookupCacheKeys() const; + /** + * Returns whether this description describes a loading operation which + * loads the image in a reduced version (quality, size etc.) + */ + bool isReducedVersion() const; + + /** + * Returns whether the other loading task equals this one + */ + bool operator==(const LoadingDescription &other) const; + bool operator!=(const LoadingDescription &other) const + { return !operator==(other); } + /** + * Returns whether the other loading task equals this one + * ignoring parameters used to specify a reduced version. + */ + bool equalsIgnoreReducedVersion(const LoadingDescription &other) const; + + /** + * Returns whether this loading task equals the other one + * or is superior to it, if the other one is a reduced version + */ + bool equalsOrBetterThan(const LoadingDescription &other) const; + + /** + * Returns all possible cacheKeys for the given file path + * (all cache keys under which the given file could be stored in the cache). + */ + static TQStringList possibleCacheKeys(const TQString &filePath); +}; + +} // namespace Digikam + +#endif // LOADING_DESCRIPTION_H diff --git a/src/libs/threadimageio/loadsavetask.cpp b/src/libs/threadimageio/loadsavetask.cpp new file mode 100644 index 00000000..6879e533 --- /dev/null +++ b/src/libs/threadimageio/loadsavetask.cpp @@ -0,0 +1,424 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-12-17 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#include "loadsavetask.h" + +// TQt includes. + +#include <tqapplication.h> + +// Local includes. + +#include "ddebug.h" +#include "loadsavethread.h" +#include "managedloadsavethread.h" +#include "sharedloadsavethread.h" +#include "loadingcache.h" + +namespace Digikam +{ + +void LoadingProgressEvent::notify(LoadSaveThread *thread) +{ + thread->loadingProgress(m_loadingDescription, m_progress); +} + +void SavingProgressEvent::notify(LoadSaveThread *thread) +{ + thread->savingProgress(m_filePath, m_progress); +} + +void StartedLoadingEvent::notify(LoadSaveThread *thread) +{ + thread->imageStartedLoading(m_loadingDescription); +} + +void StartedSavingEvent::notify(LoadSaveThread *thread) +{ + thread->imageStartedSaving(m_filePath); +} + +void LoadedEvent::notify(LoadSaveThread *thread) +{ + thread->imageLoaded(m_loadingDescription, m_img); +} + +void MoreCompleteLoadingAvailableEvent::notify(LoadSaveThread *thread) +{ + thread->moreCompleteLoadingAvailable(m_oldDescription, m_newDescription); +} + +void SavedEvent::notify(LoadSaveThread *thread) +{ + thread->imageSaved(m_filePath, m_success); +} + +//--------------------------------------------------------------------------------------------------- + +void LoadingTask::execute() +{ + if (m_loadingTaskStatus == LoadingTaskStatusStopping) + return; + DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); + m_thread->taskHasFinished(); + TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img)); +} + +LoadingTask::TaskType LoadingTask::type() +{ + return TaskTypeLoading; +} + +void LoadingTask::progressInfo(const DImg *, float progress) +{ + if (m_loadingTaskStatus == LoadingTaskStatusLoading) + { + if (m_thread->querySendNotifyEvent()) + TQApplication::postEvent(m_thread, new LoadingProgressEvent(m_loadingDescription.filePath, progress)); + } +} + +bool LoadingTask::continueQuery(const DImg *) +{ + return m_loadingTaskStatus != LoadingTaskStatusStopping; +} + +void LoadingTask::setStatus(LoadingTaskStatus status) +{ + m_loadingTaskStatus = status; +} + + +// This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader) +// is waiting for the process to finish, but the main thread is waiting +// for the thread to finish and no TDEProcess events are delivered. +// Remove when porting to TQt4. +bool LoadingTask::isShuttingDown() +{ + return m_thread->isShuttingDown(); +} + +//--------------------------------------------------------------------------------------------------- + +void SharedLoadingTask::execute() +{ + if (m_loadingTaskStatus == LoadingTaskStatusStopping) + return; + // send StartedLoadingEvent from each single Task, not via LoadingProcess list + TQApplication::postEvent(m_thread, new StartedLoadingEvent(m_loadingDescription.filePath)); + + LoadingCache *cache = LoadingCache::cache(); + { + LoadingCache::CacheLock lock(cache); + + // find possible cached images + DImg *cachedImg = 0; + TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys(); + for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) { + if ( (cachedImg = cache->retrieveImage(*it)) ) + break; + } + + if (cachedImg) + { + // image is found in image cache, loading is successful + DImg img(*cachedImg); + if (accessMode() == LoadSaveThread::AccessModeReadWrite) + img = img.copy(); + TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img)); + return; + } + else + { + // find possible running loading process + m_usedProcess = 0; + for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) { + if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) ) + { + break; + } + } + + if (m_usedProcess) + { + // Other process is right now loading this image. + // Add this task to the list of listeners and + // attach this thread to the other thread, wait until loading + // has finished. + m_usedProcess->addListener(this); + // break loop when either the loading has completed, or this task is being stopped + while ( !m_usedProcess->completed() && m_loadingTaskStatus != LoadingTaskStatusStopping ) + lock.timedWait(); + // remove listener from process + m_usedProcess->removeListener(this); + // wake up the process which is waiting until all listeners have removed themselves + lock.wakeAll(); + // set to 0, as checked in setStatus + m_usedProcess = 0; + //DDebug() << "SharedLoadingTask " << this << ": waited" << endl; + return; + } + else + { + // Neither in cache, nor currently loading in different thread. + // Load it here and now, add this LoadingProcess to cache list. + cache->addLoadingProcess(this); + // Add this to the list of listeners + addListener(this); + // for use in setStatus + m_usedProcess = this; + // Notify other processes that we are now loading this image. + // They might be interested - see notifyNewLoadingProcess below + cache->notifyNewLoadingProcess(this, m_loadingDescription); + } + } + } + + // load image + DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); + + bool isCached = false; + { + LoadingCache::CacheLock lock(cache); + // put (valid) image into cache of loaded images + if (!img.isNull()) + isCached = cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath); + // remove this from the list of loading processes in cache + cache->removeLoadingProcess(this); + } + + // following the golden rule to avoid deadlocks, do this when CacheLock is not held + m_thread->taskHasFinished(); + + { + LoadingCache::CacheLock lock(cache); + //DDebug() << "SharedLoadingTask " << this << ": image loaded, " << img.isNull() << endl; + // indicate that loading has finished so that listeners can stop waiting + m_completed = true; + + // Optimize so that no unnecessary copying is done. + // If image has been put in cache, the initial copy has been consumed for this. + // If image is too large for cache, the initial copy is still available. + bool usedInitialCopy = isCached; + // dispatch image to all listeners, including this + for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) + { + // This code sends a copy only when ReadWrite access is requested. + // Otherwise, the image from the cache is sent. + // As the image in the cache will be deleted from any thread, the explicit sharing + // needs to be thread-safe to avoid the risk of memory leaks. + // This is the case only for TQt4, so uncomment this code when porting. + /* + if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) + { + // If a listener requested ReadWrite access, it gets a deep copy. + // DImg is explicitly shared. + DImg copy = img.copy(); + TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription.filePath, copy)); + } + else + TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription.filePath, img)); + */ + // TQt3: The same copy for all Read listeners (it is assumed that they will delete it only in the main thread), + // an extra copy for each ReadWrite listener + DImg readerCopy; + if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) + { + // If a listener requested ReadWrite access, it gets a deep copy. + // DImg is explicitly shared. + DImg copy; + if (usedInitialCopy) + { + copy = img.copy(); + } + else + { + copy = img; + usedInitialCopy = true; + } + TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, copy)); + } + else + { + if (readerCopy.isNull()) + { + if (usedInitialCopy) + { + readerCopy = img.copy(); + } + else + { + readerCopy = img; + usedInitialCopy = true; + } + } + TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, readerCopy)); + } + } + + // remove myself from list of listeners + removeListener(this); + // wake all listeners waiting on cache condVar, so that they remove themselves + lock.wakeAll(); + // wait until all listeners have removed themselves + while (m_listeners.count() != 0) + lock.timedWait(); + // set to 0, as checked in setStatus + m_usedProcess = 0; + } +} + +void SharedLoadingTask::progressInfo(const DImg *, float progress) +{ + if (m_loadingTaskStatus == LoadingTaskStatusLoading) + { + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + + for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) + { + if (l->querySendNotifyEvent()) + TQApplication::postEvent(l->eventReceiver(), new LoadingProgressEvent(m_loadingDescription, progress)); + } + } +} + +bool SharedLoadingTask::continueQuery(const DImg *) +{ + // If this is called, the thread is currently loading an image. + // In shared loading, we cannot stop until all listeners have been removed as well + return (m_loadingTaskStatus != LoadingTaskStatusStopping) || (m_listeners.count() != 0); +} + +void SharedLoadingTask::setStatus(LoadingTaskStatus status) +{ + m_loadingTaskStatus = status; + if (m_loadingTaskStatus == LoadingTaskStatusStopping) + { + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + + // check for m_usedProcess, to avoid race condition that it has finished before + if (m_usedProcess) + { + // remove this from list of listeners - check in continueQuery() of active thread + m_usedProcess->removeListener(this); + // wake all listeners - particularly this - from waiting on cache condvar + lock.wakeAll(); + } + } +} + +bool SharedLoadingTask::completed() +{ + return m_completed; +} + +TQString SharedLoadingTask::filePath() +{ + return m_loadingDescription.filePath; +} + +TQString SharedLoadingTask::cacheKey() +{ + return m_loadingDescription.cacheKey(); +} + +void SharedLoadingTask::addListener(LoadingProcessListener *listener) +{ + m_listeners.append(listener); +} + +void SharedLoadingTask::removeListener(LoadingProcessListener *listener) +{ + m_listeners.remove(listener); +} + +void SharedLoadingTask::notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description) +{ + // Ok, we are notified that another task has been started in another thread. + // We are of course only interested if the task loads the same file, + // and we are right now loading a reduced version, and the other task is loading the full version. + // In this case, we notify our own thread (a signal to the API user is emitted) of this. + // The fact that we are receiving the method call shows that this task is registered with the LoadingCache, + // somewhere in between the calls to addLoadingProcess(this) and removeLoadingProcess(this) above. + if (process != this && + m_loadingDescription.isReducedVersion() && + m_loadingDescription.equalsIgnoreReducedVersion(description) && + !description.isReducedVersion() + ) + { + for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) + { + TQApplication::postEvent(l->eventReceiver(), new MoreCompleteLoadingAvailableEvent(m_loadingDescription, description)); + } + } +} + +bool SharedLoadingTask::querySendNotifyEvent() +{ + return m_thread->querySendNotifyEvent(); +} + +TQObject *SharedLoadingTask::eventReceiver() +{ + return m_thread; +} + +LoadSaveThread::AccessMode SharedLoadingTask::accessMode() +{ + return m_accessMode; +} + +//--------------------------------------------------------------------------------------------------- + +void SavingTask::execute() +{ + bool success = m_img.save(m_filePath, m_format, this); + m_thread->taskHasFinished(); + TQApplication::postEvent(m_thread, new SavedEvent(m_filePath, success)); +}; + +LoadingTask::TaskType SavingTask::type() +{ + return TaskTypeSaving; +} + +void SavingTask::progressInfo(const DImg *, float progress) +{ + if (m_thread->querySendNotifyEvent()) + TQApplication::postEvent(m_thread, new SavingProgressEvent(m_filePath, progress)); +} + +bool SavingTask::continueQuery(const DImg *) +{ + return m_savingTaskStatus != SavingTaskStatusStopping; +} + +void SavingTask::setStatus(SavingTaskStatus status) +{ + m_savingTaskStatus = status; +} + +} //namespace Digikam diff --git a/src/libs/threadimageio/loadsavetask.h b/src/libs/threadimageio/loadsavetask.h new file mode 100644 index 00000000..833f86fa --- /dev/null +++ b/src/libs/threadimageio/loadsavetask.h @@ -0,0 +1,360 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-20 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + + +#ifndef LOAD_SAVE_TASK_H +#define LOAD_SAVE_TASK_H + +// TQt includes. + +#include <tqevent.h> + +// Local includes. + +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "loadingdescription.h" +#include "loadingcache.h" + +namespace Digikam +{ + +class LoadSaveThread; + +class LoadSaveTask +{ +public: + + LoadSaveTask(LoadSaveThread* thread) + : m_thread(thread) + {}; + virtual ~LoadSaveTask() {}; + + virtual void execute() = 0; + + enum TaskType + { + TaskTypeLoading, + TaskTypeSaving + }; + + virtual TaskType type() = 0; + +protected: + + LoadSaveThread *m_thread; +}; + +//--------------------------------------------------------------------------------------------------- + +class NotifyEvent : public TQCustomEvent +{ +public: + + static TQEvent::Type notifyEventId() + { return TQEvent::User; }; + + NotifyEvent() : TQCustomEvent(notifyEventId()) {}; + + virtual void notify(LoadSaveThread *thread) = 0; +}; + +//--------------------------------------------------------------------------------------------------- + +class ProgressEvent : public NotifyEvent +{ +public: + + ProgressEvent(float progress) + : m_progress(progress) + {}; + +protected: + + float m_progress; +}; + +//--------------------------------------------------------------------------------------------------- + +class LoadingProgressEvent : public ProgressEvent +{ +public: + + LoadingProgressEvent(const LoadingDescription &loadingDescription, float progress) + : ProgressEvent(progress), + m_loadingDescription(loadingDescription) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + LoadingDescription m_loadingDescription; +}; + +//--------------------------------------------------------------------------------------------------- + +class SavingProgressEvent : public ProgressEvent +{ +public: + + SavingProgressEvent(const TQString& filePath, float progress) + : ProgressEvent(progress), + m_filePath(filePath) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + TQString m_filePath; +}; + +//--------------------------------------------------------------------------------------------------- + +class StartedLoadingEvent : public NotifyEvent +{ +public: + + StartedLoadingEvent(const LoadingDescription &loadingDescription) + : m_loadingDescription(loadingDescription) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + LoadingDescription m_loadingDescription; +}; + +//--------------------------------------------------------------------------------------------------- + +class StartedSavingEvent : public NotifyEvent +{ +public: + + StartedSavingEvent(const TQString& filePath) + : m_filePath(filePath) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + TQString m_filePath; +}; + +//--------------------------------------------------------------------------------------------------- + +class LoadedEvent : public NotifyEvent +{ +public: + + LoadedEvent(const LoadingDescription &loadingDescription, DImg &img) + : m_loadingDescription(loadingDescription), m_img(img) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + LoadingDescription m_loadingDescription; + DImg m_img; +}; + +//--------------------------------------------------------------------------------------------------- + +class MoreCompleteLoadingAvailableEvent : public NotifyEvent +{ +public: + + MoreCompleteLoadingAvailableEvent(const LoadingDescription &oldLoadingDescription, + const LoadingDescription &newLoadingDescription) + : m_oldDescription(oldLoadingDescription), m_newDescription(newLoadingDescription) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + LoadingDescription m_oldDescription; + LoadingDescription m_newDescription; +}; + +//--------------------------------------------------------------------------------------------------- + +class LoadingTask : public LoadSaveTask, public DImgLoaderObserver +{ +public: + + enum LoadingTaskStatus + { + LoadingTaskStatusLoading, + LoadingTaskStatusPreloading, + LoadingTaskStatusStopping + }; + + LoadingTask(LoadSaveThread* thread, LoadingDescription description, + LoadingTaskStatus loadingTaskStatus = LoadingTaskStatusLoading) + : LoadSaveTask(thread), m_loadingDescription(description), m_loadingTaskStatus(loadingTaskStatus) + {} + + // LoadSaveTask + + virtual void execute(); + virtual TaskType type(); + + // DImgLoaderObserver + + virtual void progressInfo(const DImg *, float progress); + virtual bool continueQuery(const DImg *); + virtual bool isShuttingDown(); + + virtual void setStatus(LoadingTaskStatus status); + + LoadingTaskStatus status() const + { + return m_loadingTaskStatus; + } + + TQString filePath() const + { + return m_loadingDescription.filePath; + } + + LoadingDescription loadingDescription() const + { + return m_loadingDescription; + } + +protected: + + LoadingDescription m_loadingDescription; + LoadingTaskStatus m_loadingTaskStatus; +}; + +//--------------------------------------------------------------------------------------------------- + +class SharedLoadingTask : public LoadingTask, public LoadingProcess, public LoadingProcessListener +{ +public: + + SharedLoadingTask(LoadSaveThread* thread, LoadingDescription description, + LoadSaveThread::AccessMode mode = LoadSaveThread::AccessModeReadWrite, + LoadingTaskStatus loadingTaskStatus = LoadingTaskStatusLoading) + : LoadingTask(thread, description, loadingTaskStatus), + m_accessMode(mode), m_completed(false), m_usedProcess(0) + {} + + virtual void execute(); + virtual void progressInfo(const DImg *, float progress); + virtual bool continueQuery(const DImg *); + virtual void setStatus(LoadingTaskStatus status); + + // LoadingProcess + + virtual bool completed(); + virtual TQString filePath(); + virtual TQString cacheKey(); + virtual void addListener(LoadingProcessListener *listener); + virtual void removeListener(LoadingProcessListener *listener); + virtual void notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description); + + // LoadingProcessListener + + virtual bool querySendNotifyEvent(); + virtual TQObject *eventReceiver(); + virtual LoadSaveThread::AccessMode accessMode(); + +protected: + + LoadSaveThread::AccessMode m_accessMode; + bool m_completed; + LoadingProcess *m_usedProcess; + TQPtrList<LoadingProcessListener> m_listeners; +}; + +//--------------------------------------------------------------------------------------------------- + +class SavedEvent : public NotifyEvent +{ +public: + + SavedEvent(const TQString &filePath, bool success) + : m_filePath(filePath), m_success(success) + {}; + + virtual void notify(LoadSaveThread *thread); + +private: + + TQString m_filePath; + bool m_success; +}; + +//--------------------------------------------------------------------------------------------------- + +class SavingTask : public LoadSaveTask, public DImgLoaderObserver +{ +public: + + enum SavingTaskStatus + { + SavingTaskStatusSaving, + SavingTaskStatusStopping + }; + + SavingTask(LoadSaveThread* thread, DImg &img, const TQString &filePath, const TQString &format) + : LoadSaveTask(thread), m_img(img), m_filePath(filePath), m_format(format) + {}; + + virtual void execute(); + virtual TaskType type(); + + virtual void progressInfo(const DImg *, float progress); + virtual bool continueQuery(const DImg *); + + virtual void setStatus(SavingTaskStatus status); + + SavingTaskStatus status() const + { + return m_savingTaskStatus; + } + + TQString filePath() const + { + return m_filePath; + } + +private: + + DImg m_img; + TQString m_filePath; + TQString m_format; + SavingTaskStatus m_savingTaskStatus; +}; + +} // namespace Digikam + +#endif // LOAD_SAVE_TASK_H diff --git a/src/libs/threadimageio/loadsavethread.cpp b/src/libs/threadimageio/loadsavethread.cpp new file mode 100644 index 00000000..188592e9 --- /dev/null +++ b/src/libs/threadimageio/loadsavethread.cpp @@ -0,0 +1,331 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-12-17 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "loadsavethread.h" +#include "managedloadsavethread.h" +#include "sharedloadsavethread.h" +#include "loadsavetask.h" + +namespace Digikam +{ + +class LoadSaveThreadPriv +{ +public: + + LoadSaveThreadPriv() + { + running = true; + blockNotification = false; + lastTask = 0; + } + + bool running; + bool blockNotification; + LoadSaveTask *lastTask; + + TQTime notificationTime; +}; + +//--------------------------------------------------------------------------------------------------- + +LoadSaveThread::LoadSaveThread() +{ + d = new LoadSaveThreadPriv; + m_currentTask = 0; + m_notificationPolicy = NotificationPolicyTimeLimited; + + start(); +} + +LoadSaveThread::~LoadSaveThread() +{ + d->running = false; + { + TQMutexLocker lock(&m_mutex); + m_condVar.wakeAll(); + } + + wait(); + + if (d->lastTask) + delete d->lastTask; + delete d; +} + +void LoadSaveThread::load(LoadingDescription description) +{ + TQMutexLocker lock(&m_mutex); + m_todo.append(new LoadingTask(this, description)); + m_condVar.wakeAll(); +} + +void LoadSaveThread::save(DImg &image, const TQString& filePath, const TQString &format) +{ + TQMutexLocker lock(&m_mutex); + m_todo.append(new SavingTask(this, image, filePath, format)); + m_condVar.wakeAll(); +} + +void LoadSaveThread::run() +{ + while (d->running) + { + { + TQMutexLocker lock(&m_mutex); + if (d->lastTask) + { + delete d->lastTask; + d->lastTask = 0; + } + m_currentTask = m_todo.getFirst(); + if (m_currentTask) + { + m_todo.removeFirst(); + if (m_notificationPolicy == NotificationPolicyTimeLimited) + { + // set timing values so that first event is sent only + // after an initial time span. + d->notificationTime = TQTime::currentTime(); + d->blockNotification = true; + } + } + else + m_condVar.wait(&m_mutex, 1000); + } + if (m_currentTask) + m_currentTask->execute(); + } +} + +void LoadSaveThread::taskHasFinished() +{ + // This function is called by the tasks before they send their final message. + // This is to guarantee the user of the API that at least the final message + // is sent after load() has been called. This might not been the case + // if m_currentTask is currently loading the same image and a race condition + // between the return from execute and the next run of the loop above occurs. + TQMutexLocker lock(&m_mutex); + d->lastTask = m_currentTask; + m_currentTask = 0; +} + +void LoadSaveThread::customEvent(TQCustomEvent *event) +{ + if (event->type() == NotifyEvent::notifyEventId()) + { + switch (m_notificationPolicy) + { + case NotificationPolicyDirect: + d->blockNotification = false; + break; + case NotificationPolicyTimeLimited: + break; + } + ((NotifyEvent *)event)->notify(this); + } +} + +void LoadSaveThread::setNotificationPolicy(NotificationPolicy notificationPolicy) +{ + m_notificationPolicy = notificationPolicy; + d->blockNotification = false; +} + +bool LoadSaveThread::querySendNotifyEvent() +{ + // This function is called from the thread to ask for permission to send a notify event. + switch (m_notificationPolicy) + { + case NotificationPolicyDirect: + // Note that m_blockNotification is not protected by a mutex. However, if there is a + // race condition, the worst case is that one event is not sent, which is no problem. + if (d->blockNotification) + return false; + else + { + d->blockNotification = true; + return true; + } + break; + case NotificationPolicyTimeLimited: + // Current default time value: 100 millisecs. + if (d->blockNotification) + d->blockNotification = d->notificationTime.msecsTo(TQTime::currentTime()) < 100; + + if (d->blockNotification) + return false; + else + { + d->notificationTime = TQTime::currentTime(); + d->blockNotification = true; + return true; + } + break; + } + return false; +} + + +// This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader) +// is waiting for the process to finish, but the main thread is waiting +// for the thread to finish and no TDEProcess events are delivered. +// Remove when porting to TQt4. +bool LoadSaveThread::isShuttingDown() +{ + // the condition is met after d->running is set to false in the destructor + return running() && !d->running; +} + +bool LoadSaveThread::exifRotate(DImg &image, const TQString& filePath) +{ + TQVariant attribute(image.attribute("exifRotated")); + if (attribute.isValid() && attribute.toBool()) + return false; + + // Raw files are already rotated properlly by dcraw. Only perform auto-rotation with JPEG/PNG/TIFF file. + // We don't have a feedback from dcraw about auto-rotated RAW file during decoding. Return true anyway. + + attribute = image.attribute("fromRawEmbeddedPreview"); + if (DImg::fileFormat(filePath) == DImg::RAW && !(attribute.isValid() && attribute.toBool()) ) + { + return true; + } + + // Rotate thumbnail based on metadata orientation information + + DMetadata metadata(filePath); + DMetadata::ImageOrientation orientation = metadata.getImageOrientation(); + + bool rotatedOrFlipped = false; + + if(orientation != DMetadata::ORIENTATION_NORMAL) + { + switch (orientation) + { + case DMetadata::ORIENTATION_NORMAL: + case DMetadata::ORIENTATION_UNSPECIFIED: + break; + + case DMetadata::ORIENTATION_HFLIP: + image.flip(DImg::HORIZONTAL); + rotatedOrFlipped = true; + break; + + case DMetadata::ORIENTATION_ROT_180: + image.rotate(DImg::ROT180); + rotatedOrFlipped = true; + break; + + case DMetadata::ORIENTATION_VFLIP: + image.flip(DImg::VERTICAL); + rotatedOrFlipped = true; + break; + + case DMetadata::ORIENTATION_ROT_90_HFLIP: + image.rotate(DImg::ROT90); + image.flip(DImg::HORIZONTAL); + rotatedOrFlipped = true; + break; + + case DMetadata::ORIENTATION_ROT_90: + image.rotate(DImg::ROT90); + rotatedOrFlipped = true; + break; + + case DMetadata::ORIENTATION_ROT_90_VFLIP: + image.rotate(DImg::ROT90); + image.flip(DImg::VERTICAL); + rotatedOrFlipped = true; + break; + + case DMetadata::ORIENTATION_ROT_270: + image.rotate(DImg::ROT270); + rotatedOrFlipped = true; + break; + } + } + + image.setAttribute("exifRotated", true); + return rotatedOrFlipped; + + /* + if (orientation == DMetadata::ORIENTATION_NORMAL || + orientation == DMetadata::ORIENTATION_UNSPECIFIED) + return; + + TQWMatrix matrix; + + switch (orientation) + { + case DMetadata::ORIENTATION_NORMAL: + case DMetadata::ORIENTATION_UNSPECIFIED: + break; + + case DMetadata::ORIENTATION_HFLIP: + matrix.scale(-1, 1); + break; + + case DMetadata::ORIENTATION_ROT_180: + matrix.rotate(180); + break; + + case DMetadata::ORIENTATION_VFLIP: + matrix.scale(1, -1); + break; + + case DMetadata::ORIENTATION_ROT_90_HFLIP: + matrix.scale(-1, 1); + matrix.rotate(90); + break; + + case DMetadata::ORIENTATION_ROT_90: + matrix.rotate(90); + break; + + case DMetadata::ORIENTATION_ROT_90_VFLIP: + matrix.scale(1, -1); + matrix.rotate(90); + break; + + case DMetadata::ORIENTATION_ROT_270: + matrix.rotate(270); + break; + } + + // transform accordingly + thumb = thumb.xForm( matrix ); + */ +} + + + + +} // namespace Digikam + +#include "loadsavethread.moc" diff --git a/src/libs/threadimageio/loadsavethread.h b/src/libs/threadimageio/loadsavethread.h new file mode 100644 index 00000000..c935e47a --- /dev/null +++ b/src/libs/threadimageio/loadsavethread.h @@ -0,0 +1,184 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-12-17 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2006 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2006 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef LOAD_SAVE_THREAD_H +#define LOAD_SAVE_THREAD_H + +// TQt includes. + +#include <tqthread.h> +#include <tqobject.h> +#include <tqmutex.h> +#include <tqptrlist.h> +#include <tqdatetime.h> +#include <tqwaitcondition.h> + +// Digikam includes. + +#include "dimg.h" +#include "digikam_export.h" +#include "loadingdescription.h" + +namespace Digikam +{ + +class LoadSaveThreadPriv; +class LoadSaveTask; + +class DIGIKAM_EXPORT LoadSaveThread : public TQObject, public TQThread +{ + + TQ_OBJECT + + +public: + + enum NotificationPolicy + { + /** Always send notification, unless the last event is still in the event queue */ + NotificationPolicyDirect, + /** + * Always wait for a certain amount of time after the last event sent. + * In particular, the first event will be sent only after waiting for this time span. + * (Or no event will be sent, when the loading has finished before) + * This is the default. + */ + NotificationPolicyTimeLimited + }; + + // used by SharedLoadSaveThread only + enum AccessMode + { + // image will only be used for reading + AccessModeRead, + // image data will possibly be changed + AccessModeReadWrite + }; + + LoadSaveThread(); + /** + * Destructor: + * The thread will execute all pending tasks and wait for this upon destruction + */ + ~LoadSaveThread(); + + /** Append a task to load the given file to the task list */ + void load(LoadingDescription description); + /** Append a task to save the image to the task list */ + void save(DImg &image, const TQString& filePath, const TQString &format); + + void setNotificationPolicy(NotificationPolicy notificationPolicy); + + bool isShuttingDown(); + + /** + * Utility to make sure that an image is rotated according to exif tag. + * Detects if an image has previously already been rotated: You can + * call this method more than one time on the same image. + * Returns true if the image has actually been rotated or flipped. + * Returns false if a rotation was not needed. + */ + static bool exifRotate(DImg &image, const TQString& filePath); + +signals: + + /** This signal is emitted when the loading process begins. */ + void signalImageStartedLoading(const LoadingDescription &loadingDescription); + /** + * This signal is emitted whenever new progress info is available + * and the notification policy allows emitting the signal. + * No progress info will be sent for preloaded images (ManagedLoadSaveThread). + */ + void signalLoadingProgress(const LoadingDescription &loadingDescription, float progress); + /** + * This signal is emitted when the loading process has finished. + * If the process failed, img is null. + */ + void signalImageLoaded(const LoadingDescription &loadingDescription, const DImg& img); + /** + * This signal is emitted if + * - you are doing shared loading (SharedLoadSaveThread) + * - you started a loading operation with a LoadingDescription for + * a reduced version of the image + * - another thread started a loading operation for a more complete version + * You may want to cancel the current operation and start with the given loadingDescription + */ + void signalMoreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription, + const LoadingDescription &newLoadingDescription); + + void signalImageStartedSaving(const TQString& filePath); + void signalSavingProgress(const TQString& filePath, float progress); + void signalImageSaved(const TQString& filePath, bool success); + +public: + + void imageStartedLoading(const LoadingDescription &loadingDescription) + { emit signalImageStartedLoading(loadingDescription); }; + + void loadingProgress(const LoadingDescription &loadingDescription, float progress) + { emit signalLoadingProgress(loadingDescription, progress); }; + + void imageLoaded(const LoadingDescription &loadingDescription, const DImg& img) + { emit signalImageLoaded(loadingDescription, img); }; + + void moreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription, + const LoadingDescription &newLoadingDescription) + { emit signalMoreCompleteLoadingAvailable(oldLoadingDescription, newLoadingDescription); } + + void imageStartedSaving(const TQString& filePath) + { emit signalImageStartedSaving(filePath); }; + + void savingProgress(const TQString& filePath, float progress) + { emit signalSavingProgress(filePath, progress); }; + + void imageSaved(const TQString& filePath, bool success) + { emit signalImageSaved(filePath, success); }; + + virtual bool querySendNotifyEvent(); + virtual void taskHasFinished(); + +protected: + + virtual void run(); + virtual void customEvent(TQCustomEvent *event); + + TQMutex m_mutex; + + TQWaitCondition m_condVar; + + TQPtrList<LoadSaveTask> m_todo; + + LoadSaveTask *m_currentTask; + + NotificationPolicy m_notificationPolicy; + +private: + + LoadSaveThreadPriv* d; + +}; + +} // namespace Digikam + +#endif // LOAD_SAVE_THREAD_H diff --git a/src/libs/threadimageio/managedloadsavethread.cpp b/src/libs/threadimageio/managedloadsavethread.cpp new file mode 100644 index 00000000..51d9f1b7 --- /dev/null +++ b/src/libs/threadimageio/managedloadsavethread.cpp @@ -0,0 +1,362 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-20 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Local includes. + +#include "ddebug.h" +#include "managedloadsavethread.h" +#include "loadsavetask.h" +#include "previewtask.h" + +namespace Digikam +{ + +ManagedLoadSaveThread::ManagedLoadSaveThread() +{ + m_terminationPolicy = TerminationPolicyTerminateLoading; +} + +ManagedLoadSaveThread::~ManagedLoadSaveThread() +{ + LoadingTask *loadingTask; + switch (m_terminationPolicy) + { + case TerminationPolicyTerminateLoading: + { + TQMutexLocker lock(&m_mutex); + if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)) ) + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + removeLoadingTasks(LoadingDescription(TQString()), LoadingTaskFilterAll); + break; + } + case TerminationPolicyTerminatePreloading: + { + TQMutexLocker lock(&m_mutex); + if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)) ) + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + removeLoadingTasks(LoadingDescription(TQString()), LoadingTaskFilterPreloading); + break; + } + case TerminationPolicyWait: + break; + } +} + +LoadingTask *ManagedLoadSaveThread::checkLoadingTask(LoadSaveTask *task, LoadingTaskFilter filter) +{ + if (task && task->type() == LoadSaveTask::TaskTypeLoading) + { + LoadingTask *loadingTask = (LoadingTask *)task; + if (filter == LoadingTaskFilterAll) + return loadingTask; + else if (filter == LoadingTaskFilterPreloading) + if (loadingTask->status() == LoadingTask::LoadingTaskStatusPreloading) + return loadingTask; + } + return 0; +} + +LoadingTask *ManagedLoadSaveThread::findExistingTask(const LoadingDescription &loadingDescription) +{ + LoadingTask *loadingTask; + if (m_currentTask) + { + if (m_currentTask->type() == LoadSaveTask::TaskTypeLoading) + { + loadingTask = (LoadingTask *)m_currentTask; + LoadingDescription taskDescription = loadingTask->loadingDescription(); + if (taskDescription == loadingDescription) + return loadingTask; + } + } + for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next()) + { + if (task->type() == LoadSaveTask::TaskTypeLoading) + { + loadingTask = (LoadingTask *)task; + if (loadingTask->loadingDescription() == loadingDescription) + return loadingTask; + } + } + return 0; +} + +void ManagedLoadSaveThread::setTerminationPolicy(TerminationPolicy terminationPolicy) +{ + m_terminationPolicy = terminationPolicy; +} + +void ManagedLoadSaveThread::load(LoadingDescription description, LoadingPolicy policy) +{ + load(description, LoadingModeNormal, policy); +} + +void ManagedLoadSaveThread::load(LoadingDescription description, LoadingMode loadingMode, LoadingPolicy policy, AccessMode accessMode) +{ + TQMutexLocker lock(&m_mutex); + LoadingTask *loadingTask = 0; + LoadingTask *existingTask = findExistingTask(description); + + //DDebug() << "ManagedLoadSaveThread::load " << description.filePath << ", policy " << policy << endl; + switch(policy) + { + case LoadingPolicyFirstRemovePrevious: + // reuse task if it exists + if (existingTask) + { + existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading); + } + // stop current task + if (m_currentTask && m_currentTask != existingTask) + { + if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)) ) + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + } + //DDebug() << "LoadingPolicyFirstRemovePrevious, Existing task " << existingTask << + //", m_currentTask " << m_currentTask << ", loadingTask " << loadingTask << endl; + // remove all loading tasks + for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next()) + { + if (task != existingTask && checkLoadingTask(task, LoadingTaskFilterAll)) + { + //DDebug() << "Removing task " << task << " from list" << endl; + m_todo.remove(); + m_todo.prev(); + } + } + // append new, exclusive loading task + if (existingTask) + break; + m_todo.append(createLoadingTask(description, false, loadingMode, accessMode)); + break; + case LoadingPolicyPrepend: + if (existingTask) + { + existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading); + } + // stop and postpone current task if it is a preloading task + if (m_currentTask) + { + if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)) ) + { + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + load(loadingTask->filePath(), LoadingPolicyPreload); + } + } + //DDebug() << "LoadingPolicyPrepend, Existing task " << existingTask << ", m_currentTask " << m_currentTask << endl; + // prepend new loading task + if (existingTask) + break; + m_todo.prepend(createLoadingTask(description, false, loadingMode, accessMode)); + break; + case LoadingPolicyAppend: + if (existingTask) + { + existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading); + } + // stop and postpone current task if it is a preloading task + if (m_currentTask) + { + if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)) ) + { + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + load(loadingTask->filePath(), LoadingPolicyPreload); + } + } + if (existingTask) + break; + //DDebug() << "LoadingPolicyAppend, Existing task " << existingTask << ", m_currentTask " << m_currentTask << endl; + // append new loading task, put it in front of preloading tasks + for (uint i = 0; i<m_todo.count(); i++) + { + LoadSaveTask *task = m_todo.at(i); + if ( (loadingTask = checkLoadingTask(task, LoadingTaskFilterPreloading)) ) + { + m_todo.insert(i, createLoadingTask(description, false, loadingMode, accessMode)); + break; + } + } + break; + case LoadingPolicyPreload: + // append to the very end of the list + //DDebug() << "LoadingPolicyPreload, Existing task " << existingTask << endl; + if (existingTask) + break; + m_todo.append(createLoadingTask(description, true, loadingMode, accessMode)); + break; + } + m_condVar.wakeAll(); +} + +void ManagedLoadSaveThread::loadPreview(LoadingDescription description) +{ + // This is similar to the LoadingPolicyFirstRemovePrevious policy with normal loading tasks. + // Preview threads typically only support preview tasks, + // so no need to differentiate with normal loading tasks. + + TQMutexLocker lock(&m_mutex); + LoadingTask *loadingTask = 0; + LoadingTask *existingTask = findExistingTask(description); + + // reuse task if it exists + if (existingTask) + { + existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading); + } + // stop current task + if (m_currentTask && m_currentTask != existingTask) + { + if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)) ) + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + } + // remove all loading tasks + for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next()) + { + if (task != existingTask && checkLoadingTask(task, LoadingTaskFilterAll)) + { + m_todo.remove(); + m_todo.prev(); + } + } + //DDebug()<<"loadPreview for " << description.filePath << " existingTask " << existingTask << " currentTask " << m_currentTask << endl; + // append new loading task + if (existingTask) + return; + m_todo.append(new PreviewLoadingTask(this, description)); + m_condVar.wakeAll(); +} + + +LoadingTask *ManagedLoadSaveThread::createLoadingTask(const LoadingDescription &description, + bool preloading, LoadingMode loadingMode, AccessMode accessMode) +{ + if (loadingMode == LoadingModeShared) + { + if (preloading) + return new SharedLoadingTask(this, description, accessMode, LoadingTask::LoadingTaskStatusPreloading); + else + return new SharedLoadingTask(this, description, accessMode); + } + else + { + if (preloading) + return new LoadingTask(this, description, LoadingTask::LoadingTaskStatusPreloading); + else + return new LoadingTask(this, description); + } +} + +void ManagedLoadSaveThread::stopLoading(const TQString& filePath, LoadingTaskFilter filter) +{ + TQMutexLocker lock(&m_mutex); + removeLoadingTasks(LoadingDescription(filePath), filter); +} + +void ManagedLoadSaveThread::stopLoading(const LoadingDescription& desc, LoadingTaskFilter filter) +{ + TQMutexLocker lock(&m_mutex); + removeLoadingTasks(desc, filter); +} + +void ManagedLoadSaveThread::stopSaving(const TQString& filePath) +{ + TQMutexLocker lock(&m_mutex); + + // stop current task if it is matching the criteria + if (m_currentTask && m_currentTask->type() == LoadSaveTask::TaskTypeSaving) + { + SavingTask *savingTask = (SavingTask *)m_currentTask; + if (filePath.isNull() || savingTask->filePath() == filePath) + { + savingTask->setStatus(SavingTask::SavingTaskStatusStopping); + } + } + + // remove relevant tasks from list + for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next()) + { + if (task->type() == LoadSaveTask::TaskTypeSaving) + { + SavingTask *savingTask = (SavingTask *)m_currentTask; + if (filePath.isNull() || savingTask->filePath() == filePath) + { + m_todo.remove(); + m_todo.prev(); + } + } + } +} + + +void ManagedLoadSaveThread::removeLoadingTasks(const LoadingDescription &description, LoadingTaskFilter filter) +{ + LoadingTask *loadingTask; + + // stop current task if it is matching the criteria + if ( (loadingTask = checkLoadingTask(m_currentTask, filter)) ) + { + if (description.filePath.isNull() || loadingTask->loadingDescription() == description) + { + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + } + } + + // remove relevant tasks from list + for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next()) + { + if ( (loadingTask = checkLoadingTask(task, filter)) ) + { + if (description.filePath.isNull() || loadingTask->loadingDescription() == description) + { + m_todo.remove(); + m_todo.prev(); + } + } + } +} + +void ManagedLoadSaveThread::save(DImg &image, const TQString& filePath, const TQString &format) +{ + TQMutexLocker lock(&m_mutex); + LoadingTask *loadingTask; + + // stop and postpone current task if it is a preloading task + if (m_currentTask && (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading))) + { + loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping); + load(loadingTask->filePath(), LoadingPolicyPreload); + } + // append new loading task, put it in front of preloading tasks + uint i; + for (i = 0; i<m_todo.count(); i++) + { + LoadSaveTask *task = m_todo.at(i); + if ( (loadingTask = checkLoadingTask(task, LoadingTaskFilterPreloading)) ) + break; + } + m_todo.insert(i, new SavingTask(this, image, filePath, format)); +} + +} // namespace Digikam + diff --git a/src/libs/threadimageio/managedloadsavethread.h b/src/libs/threadimageio/managedloadsavethread.h new file mode 100644 index 00000000..ec6a563c --- /dev/null +++ b/src/libs/threadimageio/managedloadsavethread.h @@ -0,0 +1,134 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-16 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef MANAGED_LOAD_SAVE_THREAD_H +#define MANAGED_LOAD_SAVE_THREAD_H + +#include "loadsavethread.h" + +namespace Digikam +{ + +class LoadingTask; + +class DIGIKAM_EXPORT ManagedLoadSaveThread : public LoadSaveThread +{ + +public: + + // Termination is controlled by setting the TerminationPolicy + // Default is TerminationPolicyTerminateLoading + ManagedLoadSaveThread(); + ~ManagedLoadSaveThread(); + + enum LoadingPolicy + { + // Load image immediately, remove and stop all previous loading tasks. + LoadingPolicyFirstRemovePrevious, + // Prepend loading in front of all other tasks, but wait for the current task to finish. + // No other tasks will be removed, preloading tasks will be stopped and postponed. + LoadingPolicyPrepend, + // Append loading task to the end of the list, but in front of all preloading tasks. + // No other tasks will be removed, preloading tasks will be stopped and postponed. + // This is similar to the simple load() operation from LoadSaveThread, except for the + // special care taken for preloading. + LoadingPolicyAppend, + // Preload image, i.e. load it with low priority when no other tasks are scheduled. + // All other tasks will take precedence, and preloading tasks will be stopped and + // postponed when another task is added. + // No progress info will be sent for preloaded images + LoadingPolicyPreload + }; + + enum TerminationPolicy + { + // Wait for saving tasks, stop and remove loading tasks + // This is the default. + TerminationPolicyTerminateLoading, + // Wait for loading and saving tasks, stop and remove preloading tasks + TerminationPolicyTerminatePreloading, + // Wait for all pending tasks + TerminationPolicyWait + }; + + enum LoadingTaskFilter + { + // filter all loading tasks + LoadingTaskFilterAll, + // filter only tasks with preloading policy + LoadingTaskFilterPreloading + }; + + // used by SharedLoadSaveThread only + enum LoadingMode + { + // no sharing of loading process, no caching of image + LoadingModeNormal, + // loading process is shared, image is cached + LoadingModeShared + }; + + // Append a task to load the given file to the task list. + // If there is already a task for the given file, it will possibly be rescheduled, + // but no second task will be added. + // Only loading tasks will - if required by the policy - be stopped or removed, + // saving tasks will not be touched. + void load(LoadingDescription description, LoadingPolicy policy = LoadingPolicyAppend); + + // Stop and remove tasks filtered by filePath and policy. + // If filePath isNull, applies to all file paths. + void stopLoading(const TQString& filePath = TQString(), LoadingTaskFilter filter = LoadingTaskFilterAll); + + // Same than previous method, but Stop and remove tasks filtered by LoadingDescription. + void stopLoading(const LoadingDescription& desc, LoadingTaskFilter filter = LoadingTaskFilterAll); + + // Stop and remove saving tasks filtered by filePath. + // If filePath isNull, applies to all file paths. + void stopSaving(const TQString& filePath = TQString()); + + // Append a task to save the image to the task list + void save(DImg &image, const TQString& filePath, const TQString &format); + + void setTerminationPolicy(TerminationPolicy terminationPolicy); + +protected: + + void load(LoadingDescription description, LoadingMode loadingMode, + LoadingPolicy policy = LoadingPolicyAppend, AccessMode mode = AccessModeReadWrite); + void loadPreview(LoadingDescription description); + +private: + + LoadingTask *checkLoadingTask(class LoadSaveTask *task, LoadingTaskFilter filter); + LoadingTask *findExistingTask(const LoadingDescription &description); + LoadingTask *createLoadingTask(const LoadingDescription &description, bool preloading, LoadingMode loadingMode, AccessMode accessMode); + void removeLoadingTasks(const LoadingDescription &description, LoadingTaskFilter filter); + + TerminationPolicy m_terminationPolicy; +}; + +} // namespace Digikam + + +#endif // MANAGED_LOAD_SAVE_THREAD_H diff --git a/src/libs/threadimageio/previewloadthread.cpp b/src/libs/threadimageio/previewloadthread.cpp new file mode 100644 index 00000000..d243ab32 --- /dev/null +++ b/src/libs/threadimageio/previewloadthread.cpp @@ -0,0 +1,52 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-20 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Local includes. + +#include "previewtask.h" +#include "previewloadthread.h" +#include "previewloadthread.moc" + +namespace Digikam +{ + +PreviewLoadThread::PreviewLoadThread() +{ +} + +void PreviewLoadThread::load(LoadingDescription description) +{ + description.rawDecodingSettings.sixteenBitsImage = false; + ManagedLoadSaveThread::loadPreview(description); +} + +void PreviewLoadThread::loadHighQuality(LoadingDescription description) +{ + description.rawDecodingSettings.optimizeTimeLoading(); + description.rawDecodingSettings.sixteenBitsImage = false; + ManagedLoadSaveThread::load(description, LoadingModeShared, LoadingPolicyFirstRemovePrevious); +} + +} // namespace Digikam + diff --git a/src/libs/threadimageio/previewloadthread.h b/src/libs/threadimageio/previewloadthread.h new file mode 100644 index 00000000..59cc6d72 --- /dev/null +++ b/src/libs/threadimageio/previewloadthread.h @@ -0,0 +1,57 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-16 + * Description : image file IO threaded interface. + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef PREVIEW_LOAD_THREAD_H +#define PREVIEW_LOAD_THREAD_H + +#include "managedloadsavethread.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT PreviewLoadThread : public ManagedLoadSaveThread +{ + TQ_OBJECT + + +public: + + PreviewLoadThread(); + + /** + * Load a preview that is optimized for fast loading. + */ + void load(LoadingDescription description); + /** + * Load a preview with higher resolution, trading more quality + * for less speed. + * In the LoadingDescription container, provide "0" as maximum size. + */ + void loadHighQuality(LoadingDescription description); + +}; + +} // namespace Digikam + + +#endif // SHARED_LOAD_SAVE_THREAD_H diff --git a/src/libs/threadimageio/previewtask.cpp b/src/libs/threadimageio/previewtask.cpp new file mode 100644 index 00000000..5f277481 --- /dev/null +++ b/src/libs/threadimageio/previewtask.cpp @@ -0,0 +1,258 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-12-26 + * Description : Multithreaded loader for previews + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include <cmath> + +// TQt includes. + +#include <tqapplication.h> +#include <tqimage.h> +#include <tqvariant.h> +#include <tqwmatrix.h> + +// LibKDcraw includes. + +#include <libkdcraw/kdcraw.h> + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "jpegutils.h" +#include "previewloadthread.h" +#include "previewtask.h" + +namespace Digikam +{ + +void PreviewLoadingTask::execute() +{ + if (m_loadingTaskStatus == LoadingTaskStatusStopping) + return; + + LoadingCache *cache = LoadingCache::cache(); + { + LoadingCache::CacheLock lock(cache); + + // find possible cached images + DImg *cachedImg = 0; + TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys(); + // lookupCacheKeys returns "best first". Prepend the cache key to make the list "fastest first": + // Scaling a full version takes longer! + lookupKeys.push_front(m_loadingDescription.cacheKey()); + for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) { + if ( (cachedImg = cache->retrieveImage(*it)) ) + break; + } + + if (cachedImg) + { + // image is found in image cache, loading is successful + + DImg img(*cachedImg); + + // rotate if needed - images are unrotated in the cache, + // except for RAW images, which are already rotated by dcraw. + if (m_loadingDescription.previewParameters.exifRotate) + { + img = img.copy(); + LoadSaveThread::exifRotate(img, m_loadingDescription.filePath); + } + + TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img)); + return; + } + else + { + // find possible running loading process + m_usedProcess = 0; + for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) { + if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) ) + { + break; + } + } + // do not wait on other loading processes? + //m_usedProcess = cache->retrieveLoadingProcess(m_loadingDescription.cacheKey()); + + if (m_usedProcess) + { + // Other process is right now loading this image. + // Add this task to the list of listeners and + // attach this thread to the other thread, wait until loading + // has finished. + m_usedProcess->addListener(this); + // break loop when either the loading has completed, or this task is being stopped + while ( !m_usedProcess->completed() && m_loadingTaskStatus != LoadingTaskStatusStopping ) + lock.timedWait(); + // remove listener from process + m_usedProcess->removeListener(this); + // wake up the process which is waiting until all listeners have removed themselves + lock.wakeAll(); + // set to 0, as checked in setStatus + m_usedProcess = 0; + return; + } + else + { + // Neither in cache, nor currently loading in different thread. + // Load it here and now, add this LoadingProcess to cache list. + cache->addLoadingProcess(this); + // Add this to the list of listeners + addListener(this); + // for use in setStatus + m_usedProcess = this; + // Notify other processes that we are now loading this image. + // They might be interested - see notifyNewLoadingProcess below + cache->notifyNewLoadingProcess(this, m_loadingDescription); + } + } + } + + // load image + int size = m_loadingDescription.previewParameters.size; + + DImg img; + TQImage qimage; + bool fromEmbeddedPreview = false; + + // -- Get the image preview -------------------------------- + + // First the TQImage-dependent loading methods + // Trying to load with dcraw: RAW files. + if (KDcrawIface::KDcraw::loadEmbeddedPreview(qimage, m_loadingDescription.filePath)) + fromEmbeddedPreview = true; + + if (qimage.isNull()) + { + //TODO: Use DImg based loader instead? + KDcrawIface::KDcraw::loadHalfPreview(qimage, m_loadingDescription.filePath); + } + + // Try to extract Exif/Iptc preview. + if (qimage.isNull()) + { + loadImagePreview(qimage, m_loadingDescription.filePath); + } + + if (!qimage.isNull()) + { + // convert from TQImage + img = DImg(qimage); + // mark as embedded preview (for exif rotation) + if (fromEmbeddedPreview) + img.setAttribute("fromRawEmbeddedPreview", true); + // free memory + qimage = TQImage(); + } + + // DImg-dependent loading methods + if (img.isNull()) + { + // Set a hint to try to load a JPEG with the fast scale-before-decoding method + img.setAttribute("jpegScaledLoadingSize", size); + img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); + } + + if (img.isNull()) + { + DWarning() << "Cannot extract preview for " << m_loadingDescription.filePath << endl; + } + + img.convertToEightBit(); + + // Reduce size of image: + // - only scale down if size is considerably larger + // - only scale down, do not scale up + TQSize scaledSize = img.size(); + if (needToScale(scaledSize, size)) + { + scaledSize.scale(size, size, TQSize::ScaleMin); + img = img.smoothScale(scaledSize.width(), scaledSize.height()); + } + + // Scale if hinted, Store previews rotated in the cache (?) + if (m_loadingDescription.previewParameters.exifRotate) + LoadSaveThread::exifRotate(img, m_loadingDescription.filePath); + + { + LoadingCache::CacheLock lock(cache); + // put (valid) image into cache of loaded images + if (!img.isNull()) + cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath); + // remove this from the list of loading processes in cache + cache->removeLoadingProcess(this); + } + + // following the golden rule to avoid deadlocks, do this when CacheLock is not held + m_thread->taskHasFinished(); + + { + LoadingCache::CacheLock lock(cache); + // indicate that loading has finished so that listeners can stop waiting + m_completed = true; + + // dispatch image to all listeners, including this + for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) + { + TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, img)); + } + + // remove myself from list of listeners + removeListener(this); + // wake all listeners waiting on cache condVar, so that they remove themselves + lock.wakeAll(); + // wait until all listeners have removed themselves + while (m_listeners.count() != 0) + lock.timedWait(); + // set to 0, as checked in setStatus + m_usedProcess = 0; + } +} + +bool PreviewLoadingTask::needToScale(const TQSize &imageSize, int previewSize) +{ + int maxSize = imageSize.width() > imageSize.height() ? imageSize.width() : imageSize.height(); + int acceptableUpperSize = lround(1.25 * (double)previewSize); + return maxSize >= acceptableUpperSize; +} + +// -- Exif/IPTC preview extraction using Exiv2 -------------------------------------------------------- + +bool PreviewLoadingTask::loadImagePreview(TQImage& image, const TQString& path) +{ + DMetadata metadata(path); + if (metadata.getImagePreview(image)) + { + DDebug() << "Use Exif/Iptc preview extraction. Size of image: " + << image.width() << "x" << image.height() << endl; + return true; + } + + return false; +} + +} // namespace Digikam diff --git a/src/libs/threadimageio/previewtask.h b/src/libs/threadimageio/previewtask.h new file mode 100644 index 00000000..f5715499 --- /dev/null +++ b/src/libs/threadimageio/previewtask.h @@ -0,0 +1,57 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-12-26 + * Description : Multithreaded loader for previews + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef PREVIEW_TASK_H +#define PREVIEW_TASK_H + +// TQt includes. + +#include <tqimage.h> + +// Local includes. + +#include "loadsavetask.h" + +namespace Digikam +{ + +class PreviewLoadingTask : public SharedLoadingTask +{ +public: + + PreviewLoadingTask(LoadSaveThread* thread, LoadingDescription description) + : SharedLoadingTask(thread, description, LoadSaveThread::AccessModeRead, LoadingTaskStatusLoading) + {} + + virtual void execute(); + +private: + + bool needToScale(const TQSize &imageSize, int previewSize); + bool loadImagePreview(TQImage& image, const TQString& path); +}; + +} // namespace Digikam + +#endif // PREVIEW_TASK_H diff --git a/src/libs/threadimageio/sharedloadsavethread.cpp b/src/libs/threadimageio/sharedloadsavethread.cpp new file mode 100644 index 00000000..6a9cccbf --- /dev/null +++ b/src/libs/threadimageio/sharedloadsavethread.cpp @@ -0,0 +1,65 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-20 + * Description : image file IO threaded interface. + * + * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Local includes. + +#include "ddebug.h" +#include "sharedloadsavethread.h" +#include "loadingcache.h" +#include "loadsavetask.h" + +namespace Digikam +{ + +void SharedLoadSaveThread::load(LoadingDescription description, AccessMode mode, LoadingPolicy policy) +{ + ManagedLoadSaveThread::load(description, LoadingModeShared, policy, mode); +} + +DImg SharedLoadSaveThread::cacheLookup(const TQString& filePath, AccessMode /*accessMode*/) +{ + LoadingCache *cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + DImg *cachedImg = cache->retrieveImage(filePath); + // TQt4: uncomment this code. + // See comments in SharedLoadingTask::execute for explanation. + /* + if (cachedImg) + { + if (accessMode == AccessModeReadWrite) + return cachedImg->copy(); + else + return *cachedImg; + } + else + return DImg(); + */ + if (cachedImg) + return cachedImg->copy(); + else + return DImg(); +} + +} // namespace Digikam + diff --git a/src/libs/threadimageio/sharedloadsavethread.h b/src/libs/threadimageio/sharedloadsavethread.h new file mode 100644 index 00000000..aa03a708 --- /dev/null +++ b/src/libs/threadimageio/sharedloadsavethread.h @@ -0,0 +1,43 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-16 + * Description : image file IO threaded interface. + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef SHARED_LOAD_SAVE_THREAD_H +#define SHARED_LOAD_SAVE_THREAD_H + +#include "managedloadsavethread.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT SharedLoadSaveThread : public ManagedLoadSaveThread +{ +public: + + void load(LoadingDescription description, AccessMode mode, LoadingPolicy policy = LoadingPolicyAppend); + DImg cacheLookup(const TQString& filePath, AccessMode mode); +}; + +} // namespace Digikam + + +#endif // SHARED_LOAD_SAVE_THREAD_H |