Dive pictures: save thumbnails to individual files

The old code loaded all thumbnails into memory at once. This does
not scale to logs with thousands of pictures. Therefore, save
the pictures to individual files and only load the currently
needed pictures.

Currently, this will make changing switching between dives slower,
because the thumbnails are loaded from disk. In the future, it
is planned to do this in a background thread without blocking
the user interface.

A notable difference to the old code: Thumbnails are now indexed
by the image-hash (i.e. the content of the raw image) and not
by the filename of the image. Thus, different paths to the same
image should only be saved once.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2018-04-29 23:07:05 +03:00 committed by Dirk Hohndel
parent c0bca3ad04
commit 6618c9ebfc
3 changed files with 66 additions and 9 deletions

View file

@ -1054,7 +1054,6 @@ extern "C" void reverseGeoLookup(degrees_t latitude, degrees_t longitude, uint32
QHash<QString, QByteArray> hashOf;
QMutex hashOfMutex;
QHash<QByteArray, QString> localFilenameOf;
QHash <QString, QImage > thumbnailCache;
static QByteArray getHash(const QString &filename)
{
@ -1077,6 +1076,19 @@ const QString hashfile_name()
return QString(system_default_directory()).append("/hashes");
}
static QString thumbnailDir()
{
return QString(system_default_directory()) + "/thumbnails/";
}
// Return filename of thumbnail if it is known to us.
// If this is an unknown thumbnail, return an empty string.
QString thumbnailFileName(const QString &filename)
{
QString hash = getHash(filename).toHex();
return hash.isEmpty() ? QString() : thumbnailDir() + hash;
}
extern "C" char *hashfile_name_string()
{
return copy_qstring(hashfile_name());
@ -1090,7 +1102,8 @@ void read_hashes()
QDataStream stream(&hashfile);
stream >> localFilenameOf;
stream >> hashOf;
stream >> thumbnailCache;
QHash <QString, QImage> thumbnailCache;
stream >> thumbnailCache; // For backwards compatibility
hashfile.close();
}
localFilenameOf.remove("");
@ -1100,6 +1113,9 @@ void read_hashes()
if (iter.value().isEmpty())
iter.remove();
}
// Make sure that the thumbnail directory exists
QDir().mkpath(thumbnailDir());
}
void write_hashes()
@ -1111,7 +1127,7 @@ void write_hashes()
QDataStream stream(&hashfile);
stream << localFilenameOf;
stream << hashOf;
stream << thumbnailCache;
stream << QHash<QString,QImage>(); // Empty thumbnailCache - for backwards compatibility
hashfile.commit();
} else {
qWarning() << "Cannot open hashfile for writing: " << hashfile.fileName();

View file

@ -30,6 +30,7 @@ void write_hashes();
void updateHash(struct picture *picture);
QByteArray hashFile(const QString &filename);
QString hashString(const char *filename);
QString thumbnailFileName(const QString &filename);
void learnImages(const QDir dir, int max_recursions);
void add_hash(const QString &filename, const QByteArray &hash);
void hashPicture(struct picture *picture);

View file

@ -4,17 +4,58 @@
#include "core/metrics.h"
#include "core/divelist.h"
#include "core/imagedownloader.h"
#include "core/qthelper.h"
#include "core/metadata.h"
#include <QtConcurrent>
extern QHash <QString, QImage> thumbnailCache;
static QMutex thumbnailMutex;
static const int maxZoom = 3; // Maximum zoom: thrice of standard size
static QImage getThumbnailFromCache(const PictureEntry &entry)
{
QMutexLocker l(&thumbnailMutex);
return thumbnailCache.value(entry.filename);
// 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)
@ -25,8 +66,7 @@ static void scaleImages(PictureEntry &entry, int maxSize)
if (thumbnail.isNull() || (thumbnail.size().width() < maxSize && thumbnail.size().height() < maxSize)) {
qDebug() << "No thumbnail in cache for" << entry.filename;
thumbnail = SHashedImage(entry.picture).scaled(maxSize, maxSize, Qt::KeepAspectRatio);
QMutexLocker l(&thumbnailMutex);
thumbnailCache.insert(entry.filename, thumbnail);
addThumbnailToCache(thumbnail, entry);
}
entry.image = thumbnail;