diff options
-rw-r--r-- | core/imagedownloader.cpp | 112 | ||||
-rw-r--r-- | core/imagedownloader.h | 26 | ||||
-rw-r--r-- | core/qthelper.cpp | 3 | ||||
-rw-r--r-- | qt-models/divepicturemodel.cpp | 68 |
4 files changed, 141 insertions, 68 deletions
diff --git a/core/imagedownloader.cpp b/core/imagedownloader.cpp index a1f98fc9c..2949d83d9 100644 --- a/core/imagedownloader.cpp +++ b/core/imagedownloader.cpp @@ -4,9 +4,12 @@ #include "divelist.h" #include "qthelper.h" #include "imagedownloader.h" +#include "qt-models/divepicturemodel.h" +#include "metadata.h" #include <unistd.h> #include <QString> #include <QImageReader> +#include <QDataStream> #include <QtConcurrent> @@ -118,21 +121,122 @@ QImage getHashedImage(const QString &file) // That didn't produce a local filename. // Try the cloud server // TODO: This is dead code at the moment. - QtConcurrent::run(loadPicture, file, true); + loadPicture(file, true); } else { // Load locally from translated file name res = loadImage(filenameLocal); if (!res.isNull()) { // Make sure the hash still matches the image file - QtConcurrent::run(hashPicture, filenameLocal); + hashPicture(filenameLocal); } else { // Interpret filename as URL - QtConcurrent::run(loadPicture, filenameLocal, false); + loadPicture(filenameLocal, false); } } } else { // We loaded successfully. Now, make sure hash is up to date. - QtConcurrent::run(hashPicture, file); + hashPicture(file); } return res; } + +Thumbnailer::Thumbnailer() +{ + // Currently, we only process one image at a time. Stefan Fuchs reported problems when + // calculating multiple thumbnails at once and this hopefully helps. + pool.setMaxThreadCount(1); +} + +Thumbnailer *Thumbnailer::instance() +{ + static Thumbnailer self; + return &self; +} + +static QImage getThumbnailFromCache(const QString &picture_filename) +{ + // First, check if we know a hash for this filename + QString filename = thumbnailFileName(picture_filename); + if (filename.isEmpty()) + return QImage(); + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) + return QImage(); + QDataStream stream(&file); + + // Each thumbnail file is composed of a media-type and an image file. + // Currently, the type is ignored. This will be used to mark videos. + quint32 type; + QImage res; + stream >> type; + stream >> res; + return res; +} + +static void addThumbnailToCache(const QImage &thumbnail, const QString &picture_filename) +{ + if (thumbnail.isNull()) + return; + + QString filename = thumbnailFileName(picture_filename); + + // If we got a thumbnail, we are guaranteed to have its hash and therefore + // thumbnailFileName() should return a filename. + if (filename.isEmpty()) { + qWarning() << "Internal error: can't get filename of recently created thumbnail"; + return; + } + + QSaveFile file(filename); + if (!file.open(QIODevice::WriteOnly)) + return; + QDataStream stream(&file); + + // For format of the file, see comments in getThumnailForCache + quint32 type = MEDIATYPE_PICTURE; + stream << type; + stream << thumbnail; + file.commit(); +} + +void Thumbnailer::processItem(QString filename, int size) +{ + QImage thumbnail = getThumbnailFromCache(filename); + + if (thumbnail.isNull()) { + thumbnail = getHashedImage(filename); + if (thumbnail.isNull()) { + // TODO: Don't misuse filter close icon + thumbnail = QImage(":filter-close").scaled(size, size, Qt::KeepAspectRatio); + } else { + thumbnail = thumbnail.scaled(size, size, Qt::KeepAspectRatio); + addThumbnailToCache(thumbnail, filename); + } + } + + QMutexLocker l(&lock); + emit thumbnailChanged(filename, thumbnail); + workingOn.remove(filename); +} + +QImage Thumbnailer::fetchThumbnail(PictureEntry &entry, int size) +{ + QMutexLocker l(&lock); + + // We are not currently fetching this thumbnail - add it to the list. + const QString &filename = entry.filename; + if (!workingOn.contains(filename)) { + workingOn.insert(filename, + QtConcurrent::run(&pool, [this, filename, size]() { processItem(filename, size); })); + } + return QImage(":photo-icon").scaled(size, size, Qt::KeepAspectRatio); +} + +void Thumbnailer::clearWorkQueue() +{ + QMutexLocker l(&lock); + for (auto it = workingOn.begin(); it != workingOn.end(); ++it) + it->cancel(); + workingOn.clear(); +} diff --git a/core/imagedownloader.h b/core/imagedownloader.h index 337185869..d8e9d2d7d 100644 --- a/core/imagedownloader.h +++ b/core/imagedownloader.h @@ -5,6 +5,7 @@ #include <QImage> #include <QFuture> #include <QNetworkReply> +#include <QThreadPool> class ImageDownloader : public QObject { Q_OBJECT @@ -18,6 +19,31 @@ private: QString filename; }; +class PictureEntry; +class Thumbnailer : public QObject { + Q_OBJECT +public: + static Thumbnailer *instance(); + + // Schedule a thumbnail for fetching or calculation. + // Returns a placehlder thumbnail. The actual thumbnail will be sent + // via a signal later. + QImage fetchThumbnail(PictureEntry &entry, int size); + + // If we change dive, clear all unfinished thumbnail creations + void clearWorkQueue(); +signals: + void thumbnailChanged(QString filename, QImage thumbnail); +private: + Thumbnailer(); + void processItem(QString filename, int size); + + mutable QMutex lock; + QThreadPool pool; + + QMap<QString,QFuture<void>> workingOn; +}; + QImage getHashedImage(const QString &filename); #endif // IMAGEDOWNLOADER_H diff --git a/core/qthelper.cpp b/core/qthelper.cpp index 797dbf932..737e424ff 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -11,6 +11,9 @@ #include "gettextfromc.h" #include "metadata.h" #include <sys/time.h> +#include "exif.h" +#include "file.h" +#include "imagedownloader.h" #include "prefs-macros.h" #include <QFile> #include <QRegExp> diff --git a/qt-models/divepicturemodel.cpp b/qt-models/divepicturemodel.cpp index 33ac7e886..53e631e17 100644 --- a/qt-models/divepicturemodel.cpp +++ b/qt-models/divepicturemodel.cpp @@ -5,73 +5,11 @@ #include "core/divelist.h" #include "core/imagedownloader.h" #include "core/qthelper.h" -#include "core/metadata.h" -#include <QtConcurrent> +#include <QFileInfo> static const int maxZoom = 3; // Maximum zoom: thrice of standard size -static QImage getThumbnailFromCache(const PictureEntry &entry) -{ - // First, check if we know a hash for this filename - QString filename = thumbnailFileName(entry.filename); - if (filename.isEmpty()) - return QImage(); - - QFile file(filename); - if (!file.open(QIODevice::ReadOnly)) - return QImage(); - QDataStream stream(&file); - - // Each thumbnail file is composed of a media-type and an image file. - // Currently, the type is ignored. This will be used to mark videos. - quint32 type; - QImage res; - stream >> type; - stream >> res; - return res; -} - -static void addThumbnailToCache(const QImage &thumbnail, const PictureEntry &entry) -{ - if (thumbnail.isNull()) - return; - - QString filename = thumbnailFileName(entry.filename); - - // If we got a thumbnail, we are guaranteed to have its hash and therefore - // thumbnailFileName() should return a filename. - if (filename.isEmpty()) { - qWarning() << "Internal error: can't get filename of recently created thumbnail"; - return; - } - - QSaveFile file(filename); - if (!file.open(QIODevice::WriteOnly)) - return; - QDataStream stream(&file); - - // For format of the file, see comments in getThumnailForCache - quint32 type = MEDIATYPE_PICTURE; - stream << type; - stream << thumbnail; - file.commit(); -} - -static void scaleImages(PictureEntry &entry, int maxSize) -{ - QImage thumbnail = getThumbnailFromCache(entry); - // If thumbnails were written by an earlier version, they might be smaller than needed. - // Rescale in such a case to avoid resizing artifacts. - if (thumbnail.isNull() || (thumbnail.size().width() < maxSize && thumbnail.size().height() < maxSize)) { - qDebug() << "No thumbnail in cache for" << entry.filename; - thumbnail = getHashedImage(QString(entry.picture->filename)); - addThumbnailToCache(thumbnail, entry); - } - - entry.image = thumbnail; -} - DivePictureModel *DivePictureModel::instance() { static DivePictureModel *self = new DivePictureModel(); @@ -121,7 +59,8 @@ void DivePictureModel::updateThumbnails() { int maxSize = defaultSize * maxZoom; updateZoom(); - QtConcurrent::blockingMap(pictures, [maxSize](PictureEntry &entry){scaleImages(entry, maxSize);}); + for (PictureEntry &entry: pictures) + entry.image = Thumbnailer::instance()->fetchThumbnail(entry, maxSize); } void DivePictureModel::updateDivePictures() @@ -131,6 +70,7 @@ void DivePictureModel::updateDivePictures() pictures.clear(); endRemoveRows(); rowDDStart = rowDDEnd = 0; + Thumbnailer::instance()->clearWorkQueue(); } // if the dive_table is empty, quit |