diff --git a/core/dplugins/generic/tools/htmlgallery/themes/html5responsive/html5responsive.desktop b/core/dplugins/generic/tools/htmlgallery/themes/html5responsive/html5responsive.desktop
index 07d2ae5f2e..bfe13a8445 100644
--- a/core/dplugins/generic/tools/htmlgallery/themes/html5responsive/html5responsive.desktop
+++ b/core/dplugins/generic/tools/htmlgallery/themes/html5responsive/html5responsive.desktop
@@ -1,233 +1,206 @@
[Desktop Entry]
Type=Theme
Name=HTML5 Responsive
-Name[en_GB]=HTML5 Responsive
Comment=Generates a gallery using responsive HTML5 and CSS3 and UTF-8 encoding. Optionally uses PhotoSwipe by Dmitry Semenov to display the images.
-Comment[en_GB]=Generates a gallery using responsive HTML5 and CSS3 and UTF-8 encoding. Optionally uses PhotoSwipe by Dmitry Semenov to display the images.
[X-HTMLGallery Author]
Name=Bobulous
Url=https://www.bobulous.org.uk/
[X-HTMLGallery Preview]
Name=HTML5 Responsive
-Name[en_GB]=HTML5 Responsive
Url=preview.png
[X-HTMLGallery Options]
Allow-non-square-thumbnails=true
[X-HTMLGallery Parameter author]
Name=Author
+Name[ca]=Autor
+Name[ca@valencia]=Autor
+Name[cs]=Autor
+Name[de]=Autor
+Name[el]=Συγγραφέας
Name[en_GB]=Author
Name[es]=Autor
Name[eu]=Egilea
Name[fi]=Tekijä
Name[fr]=Auteur
Name[gl]=Autor
Name[is]=Höfundur
Name[it]=Autore
Name[nl]=Auteur
Name[nn]=Opphavsperson
Name[pl]=Autor
Name[pt]=Autor
Name[pt_BR]=Autor
Name[ru]=Автор
Name[sv]=Upphovsman
Name[tr]=Yazar
Name[uk]=Автор
Name[x-test]=xxAuthorxx
Name[zh_CN]=作者
Type=string
Default=
[X-HTMLGallery Parameter style]
Name=Style
Name[ca]=Estil
Name[ca@valencia]=Estil
Name[cs]=Styl
Name[de]=Stil
Name[el]=Στιλ
Name[en_GB]=Style
Name[es]=Estilo
Name[eu]=Estiloa
Name[fi]=Tyyli
Name[fr]=Style
Name[gl]=Estilo
Name[is]=Stíll
Name[it]=Stile
Name[nb]=Stil
Name[nl]=Stijl
Name[nn]=Stil
Name[pl]=Wygląd
Name[pt]=Estilo
Name[pt_BR]=Estilo
Name[ru]=Цветовая гамма
Name[se]=Stiila
Name[sv]=Stil
Name[tr]=Stil
Name[uk]=Стиль
Name[x-test]=xxStylexx
Name[zh_CN]=样式
Type=list
Default=basic.css
Value-0=basic.css
Caption-0=Basic
Value-1=lightbox.css
Caption-1=Lightbox
Value-2=feed.css
Caption-2=Feed
Value-3=brownCard.css
Caption-3=Brown Card
[X-HTMLGallery Parameter pageSize]
Name=Images per page
Name[ca]=Imatges per pàgina
Name[ca@valencia]=Imatges per pàgina
Name[cs]=Obrázků na stránku
Name[de]=Bilder pro Seite
Name[el]=Εικόνες ανά σελίδα
Name[en_GB]=Images per page
Name[es]=Imágenes por página
Name[eu]=Irudiak orriko
Name[fi]=Kuvaa/sivu
Name[fr]=Images par page
Name[gl]=Imaxes por páxina
Name[is]=Myndir á hverri síðu
Name[it]=Immagini per pagina
Name[nl]=Afbeeldingen per pagina
Name[nn]=Bilete per side
Name[pl]=Obrazów na stronę
Name[pt]=Imagens por página
Name[pt_BR]=Imagens por página
Name[ru]=Изображений на странице
Name[sv]=Bilder per sida
Name[tr]=Sayfa başına resim
Name[uk]=Кількість зображень на сторінку
Name[x-test]=xxImages per pagexx
Name[zh_CN]=每页的图像
Type=int
Default=20
[X-HTMLGallery Parameter paginationLocation]
Name=Page links location
Name[ca]=La pàgina enllaça amb la ubicació
Name[ca@valencia]=La pàgina enllaça amb la ubicació
Name[el]=Θέση δεσμών σελίδας
Name[en_GB]=Page links location
Name[es]=Ubicación de los enlaces de páginas
Name[eu]=Orriko esteken kokalekua
Name[fi]=Kuvalinkkien sijainti
Name[fr]=Position des liens de page
Name[gl]=Lugar das ligazóns das páxinas
Name[is]=Staðsetning síðutengla
Name[it]=Posizione collegamenti della pagina
Name[nl]=Locatie van paginakoppelingen
Name[nn]=Plassering av sidelenkjer
Name[pl]=Położenie odsyłaczy stron
Name[pt]=Localização das ligações à página
Name[pt_BR]=Localização dos links da página
Name[ru]=Расположение ссылок на страницы
Name[sv]=Plats för sidlänkar
Name[tr]=Sayfa bağlantıları konumu
Name[uk]=Розташування посилань на сторінці
Name[x-test]=xxPage links locationxx
Name[zh_CN]=页面链接位置
Type=list
Default=top
Value-0=bottom
Caption-0=Bottom
Value-1=top
Caption-1=Top
Value-2=both
Caption-2=Both
[X-HTMLGallery Parameter paginationModeTop]
Name=Top pagination mode
-Name[en_GB]=Top pagination mode
Type=list
Default=currentOfTotal
Value-0=currentOfTotal
Caption-0=Current
Value-1=fullList
Caption-1=List all
[X-HTMLGallery Parameter paginationModeBottom]
Name=Bottom pagination mode
-Name[en_GB]=Bottom pagination mode
Type=list
Default=fullList
Value-0=currentOfTotal
Caption-0=Current
Value-1=fullList
Caption-1=List all
[X-HTMLGallery Parameter usePhotoSwipe]
Name=Use PhotoSwipe
-Name[ca]=Usa el PhotoSwipe
-Name[ca@valencia]=Usa el PhotoSwipe
-Name[cs]=Použít PhotoSwipe
-Name[el]=Χρήση PhotoSwipe
-Name[en_GB]=Use PhotoSwipe
-Name[es]=Usar PhotoSwipe
-Name[eu]=Erabili PhotoSwipe
-Name[fi]=Käytä PhotoSwipe
-Name[fr]=Utiliser PhotoSwipe
-Name[gl]=Usar PhotoSwipe
-Name[is]=Nota PhotoSwipe
-Name[it]=Usa PhotoSwipe
-Name[nl]=PhotoSwipe gebruiken
-Name[nn]=Bruk PhotoSwipe
-Name[pl]=Użyj PhotoSwipe
-Name[pt]=Usar o PhotoSwipe
-Name[pt_BR]=Usar o PhotoSwipe
-Name[ru]=Использовать PhotoSwipe
-Name[sv]=Använd PhotoSwipe
-Name[tr]=PhotoSwipe Kullan
-Name[uk]=Використання PhotoSwipe
-Name[x-test]=xxUse PhotoSwipexx
-Name[zh_CN]=使用 PhotoSwipe
Type=list
Default=true
Value-0=true
Caption-0=Yes
Value-1=false
Caption-1=No
[X-HTMLGallery Parameter photoSwipeBackgroundOpacity]
Name=PhotoSwipe Background Opacity
-Name[en_GB]=PhotoSwipe Background Opacity
Type=int
Default=10
Min=0
Max=10
[X-HTMLGallery Parameter photoSwipeShowSharingButton]
Name=PhotoSwipe sharing button
-Name[en_GB]=PhotoSwipe sharing button
Type=list
Default=false
Value-0=true
Caption-0=Yes
Value-1=false
Caption-1=No
[X-HTMLGallery Parameter addJiggle]
Name=Add jiggle
-Name[en_GB]=Add jiggle
Type=list
Default=true
Value-0=true
Caption-0=Yes
Value-1=false
Caption-1=No
[X-HTMLGallery Parameter showGPS]
Name=Show GPS data
-Name[en_GB]=Show GPS data
Type=list
Default=false
Value-0=true
Caption-0=Yes
Value-1=false
-Caption-1=No
\ No newline at end of file
+Caption-1=No
diff --git a/core/libs/dimg/dimg_data.cpp b/core/libs/dimg/dimg_data.cpp
index 515bf08be5..b57d456d68 100644
--- a/core/libs/dimg/dimg_data.cpp
+++ b/core/libs/dimg/dimg_data.cpp
@@ -1,190 +1,189 @@
/* ============================================================
*
* This file is a part of digiKam project
* https://www.digikam.org
*
* Date : 2005-06-14
* Description : digiKam 8/16 bits image management API.
* Data management.
*
* Copyright (C) 2005-2019 by Gilles Caulier
* Copyright (C) 2006-2013 by Marcel Wiesweg
*
* 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 "dimg_p.h"
namespace Digikam
{
DImg& DImg::operator=(const DImg& image)
{
- //qCDebug(DIGIKAM_DIMG_LOG) << "Original image: " << m_priv->imageHistory.entries().count() << " | " << &m_priv;
- //qCDebug(DIGIKAM_DIMG_LOG) << "New image: " << image.m_priv->imageHistory.entries().count() << " | " << &(image.m_priv);
m_priv = image.m_priv;
- //qCDebug(DIGIKAM_DIMG_LOG) << "Original new image: " << m_priv->imageHistory.entries().count() << " | " << &m_priv;
return *this;
}
bool DImg::operator==(const DImg& image) const
{
return m_priv == image.m_priv;
}
void DImg::reset()
{
m_priv = new Private;
}
void DImg::detach()
{
// are we being shared?
if (m_priv->ref == 1)
{
return;
}
QExplicitlySharedDataPointer old = m_priv;
m_priv = new Private;
copyImageData(old.constData());
copyMetaData(old.constData());
if (old->data)
{
- size_t size = allocateData();
- memcpy(m_priv->data, old->data, size);
+ if (size_t size = allocateData())
+ {
+ memcpy(m_priv->data, old->data, size);
+ }
}
}
void DImg::putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar* const data, bool copyData)
{
// set image data, metadata is untouched
bool null = (width == 0) || (height == 0);
// allocateData, or code below will set null to false
setImageData(true, width, height, sixteenBit, alpha);
// replace data
delete [] m_priv->data;
if (null)
{
// image is null - no data
m_priv->data = nullptr;
}
else if (copyData)
{
size_t size = allocateData();
if (data)
{
memcpy(m_priv->data, data, size);
}
}
else
{
if (data)
{
m_priv->data = data;
m_priv->null = false;
}
else
{
allocateData();
}
}
}
void DImg::putImageData(uchar* const data, bool copyData)
{
if (!data)
{
delete [] m_priv->data;
m_priv->data = nullptr;
m_priv->null = true;
}
else if (copyData)
{
memcpy(m_priv->data, data, numBytes());
}
else
{
m_priv->data = data;
}
}
void DImg::resetMetaData()
{
m_priv->attributes.clear();
m_priv->embeddedText.clear();
m_priv->metaData = MetaEngineData();
}
uchar* DImg::stripImageData()
{
uchar* const data = m_priv->data;
m_priv->data = nullptr;
m_priv->null = true;
return data;
}
void DImg::copyMetaData(const Private* const src)
{
m_priv->metaData = src->metaData;
m_priv->attributes = src->attributes;
m_priv->embeddedText = src->embeddedText;
m_priv->iccProfile = src->iccProfile;
m_priv->imageHistory = src->imageHistory;
//FIXME: what about sharing and deleting lanczos_func?
}
void DImg::copyImageData(const Private* const src)
{
setImageData(src->null, src->width, src->height, src->sixteenBit, src->alpha);
}
size_t DImg::allocateData()
{
size_t size = m_priv->width * m_priv->height * (m_priv->sixteenBit ? 8 : 4);
m_priv->data = DImgLoader::new_failureTolerant(size);
if (!m_priv->data)
{
m_priv->null = true;
m_priv->width = 0;
m_priv->height = 0;
return 0;
}
m_priv->null = false;
return size;
}
void DImg::setImageDimension(uint width, uint height)
{
m_priv->width = width;
m_priv->height = height;
}
void DImg::setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha)
{
m_priv->null = null;
m_priv->width = width;
m_priv->height = height;
m_priv->alpha = alpha;
m_priv->sixteenBit = sixteenBit;
}
} // namespace Digikam
diff --git a/core/libs/threadimageio/fileio/loadingcache.cpp b/core/libs/threadimageio/fileio/loadingcache.cpp
index 55c5b15fd8..9d5bfa7dd6 100644
--- a/core/libs/threadimageio/fileio/loadingcache.cpp
+++ b/core/libs/threadimageio/fileio/loadingcache.cpp
@@ -1,527 +1,527 @@
/* ============================================================
*
* This file is a part of digiKam project
* https://www.digikam.org
*
* Date : 2006-01-11
* Description : shared image loading and caching
*
* Copyright (C) 2005-2011 by Marcel Wiesweg
*
* 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 "loadingcache.h"
// Qt includes
#include
#include
#include
#include
// Local includes
#include "digikam_debug.h"
#include "iccsettings.h"
#include "kmemoryinfo.h"
#include "dmetadata.h"
#include "thumbnailsize.h"
namespace Digikam
{
class Q_DECL_HIDDEN LoadingCache::Private
{
public:
explicit Private(LoadingCache* const q)
: q(q)
{
// Note: Don't make the mutex recursive, we need to use a wait condition on it
watch = nullptr;
}
void mapImageFilePath(const QString& filePath, const QString& cacheKey);
void mapThumbnailFilePath(const QString& filePath, const QString& cacheKey);
void cleanUpImageFilePathHash();
void cleanUpThumbnailFilePathHash();
LoadingCacheFileWatch* fileWatch() const;
public:
QCache imageCache;
QCache thumbnailImageCache;
QCache thumbnailPixmapCache;
QMultiMap imageFilePathHash;
QMultiMap thumbnailFilePathHash;
QMap loadingDict;
QMutex mutex;
QWaitCondition condVar;
LoadingCacheFileWatch* watch;
LoadingCache* q;
};
LoadingCacheFileWatch* LoadingCache::Private::fileWatch() const
{
// install default watch if no watch is set yet
if (!watch)
{
q->setFileWatch(new ClassicLoadingCacheFileWatch);
}
return watch;
}
void LoadingCache::Private::mapImageFilePath(const QString& filePath, const QString& cacheKey)
{
if (imageFilePathHash.size() > 5*imageCache.size())
{
cleanUpImageFilePathHash();
}
imageFilePathHash.insert(filePath, cacheKey);
}
void LoadingCache::Private::mapThumbnailFilePath(const QString& filePath, const QString& cacheKey)
{
if (thumbnailFilePathHash.size() > 5*(thumbnailImageCache.size() + thumbnailPixmapCache.size()))
{
cleanUpThumbnailFilePathHash();
}
thumbnailFilePathHash.insert(filePath, cacheKey);
}
void LoadingCache::Private::cleanUpImageFilePathHash()
{
// Remove all entries from hash whose value is no longer a key in the cache
QSet keys = imageCache.keys().toSet();
QMultiMap::iterator it;
for (it = imageFilePathHash.begin() ; it != imageFilePathHash.end() ; )
{
if (!keys.contains(it.value()))
{
it = imageFilePathHash.erase(it);
}
else
{
++it;
}
}
}
void LoadingCache::Private::cleanUpThumbnailFilePathHash()
{
QSet keys;
keys += thumbnailImageCache.keys().toSet();
keys += thumbnailPixmapCache.keys().toSet();
QMultiMap::iterator it;
for (it = thumbnailFilePathHash.begin() ; it != thumbnailFilePathHash.end() ; )
{
if (!keys.contains(it.value()))
{
it = thumbnailFilePathHash.erase(it);
}
else
{
++it;
}
}
}
LoadingCache* LoadingCache::m_instance = nullptr;
LoadingCache* LoadingCache::cache()
{
if (!m_instance)
{
m_instance = new LoadingCache;
}
return m_instance;
}
void LoadingCache::cleanUp()
{
delete m_instance;
}
LoadingCache::LoadingCache()
: d(new Private(this))
{
KMemoryInfo memory = KMemoryInfo::currentInfo();
setCacheSize(qBound(60, int(memory.megabytes(KMemoryInfo::TotalRam)*0.05), 200));
setThumbnailCacheSize(5, 100); // the pixmap number should not be based on system memory, it's graphics memory
// good place to call it here as LoadingCache is a singleton
qRegisterMetaType("LoadingDescription");
qRegisterMetaType("DImg");
qRegisterMetaType("DMetadata");
connect(IccSettings::instance(), SIGNAL(settingsChanged(ICCSettingsContainer,ICCSettingsContainer)),
this, SLOT(iccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)));
}
LoadingCache::~LoadingCache()
{
delete d->watch;
delete d;
m_instance = nullptr;
}
DImg* LoadingCache::retrieveImage(const QString& cacheKey) const
{
return d->imageCache[cacheKey];
}
bool LoadingCache::putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const
{
int cost = img.numBytes();
- bool successfulyInserted = d->imageCache.insert(cacheKey, new DImg(img), cost);
+ bool successfulyInserted = d->imageCache.insert(cacheKey, new DImg(img.copy()), cost);
if (successfulyInserted && !filePath.isEmpty())
{
d->mapImageFilePath(filePath, cacheKey);
d->fileWatch()->addedImage(filePath);
}
return successfulyInserted;
}
void LoadingCache::removeImage(const QString& cacheKey)
{
d->imageCache.remove(cacheKey);
}
void LoadingCache::removeImages()
{
d->imageCache.clear();
}
bool LoadingCache::isCacheable(const DImg& img) const
{
// return whether image fits in cache
return (uint)d->imageCache.maxCost() >= img.numBytes();
}
void LoadingCache::addLoadingProcess(LoadingProcess* const process)
{
d->loadingDict[process->cacheKey()] = process;
}
LoadingProcess* LoadingCache::retrieveLoadingProcess(const QString& cacheKey) const
{
return d->loadingDict.value(cacheKey);
}
void LoadingCache::removeLoadingProcess(LoadingProcess* const process)
{
d->loadingDict.remove(process->cacheKey());
}
void LoadingCache::notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description)
{
for (QMap::const_iterator it = d->loadingDict.constBegin() ;
it != d->loadingDict.constEnd() ; ++it)
{
it.value()->notifyNewLoadingProcess(process, description);
}
}
void LoadingCache::setCacheSize(int megabytes)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Allowing a cache size of" << megabytes << "MB";
d->imageCache.setMaxCost(megabytes * 1024 * 1024);
}
// --- Thumbnails ----
const QImage* LoadingCache::retrieveThumbnail(const QString& cacheKey) const
{
return d->thumbnailImageCache[cacheKey];
}
const QPixmap* LoadingCache::retrieveThumbnailPixmap(const QString& cacheKey) const
{
return d->thumbnailPixmapCache[cacheKey];
}
bool LoadingCache::hasThumbnailPixmap(const QString& cacheKey) const
{
return d->thumbnailPixmapCache.contains(cacheKey);
}
void LoadingCache::putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
int cost = thumb.sizeInBytes();
#else
int cost = thumb.byteCount();
#endif
if (d->thumbnailImageCache.insert(cacheKey, new QImage(thumb), cost))
{
d->mapThumbnailFilePath(filePath, cacheKey);
d->fileWatch()->addedThumbnail(filePath);
}
}
void LoadingCache::putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath)
{
int cost = thumb.width() * thumb.height() * thumb.depth() / 8;
if (d->thumbnailPixmapCache.insert(cacheKey, new QPixmap(thumb), cost))
{
d->mapThumbnailFilePath(filePath, cacheKey);
d->fileWatch()->addedThumbnail(filePath);
}
}
void LoadingCache::removeThumbnail(const QString& cacheKey)
{
d->thumbnailImageCache.remove(cacheKey);
d->thumbnailPixmapCache.remove(cacheKey);
}
void LoadingCache::removeThumbnails()
{
d->thumbnailImageCache.clear();
d->thumbnailPixmapCache.clear();
}
void LoadingCache::setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps)
{
d->thumbnailImageCache.setMaxCost(numberOfQImages *
ThumbnailSize::maxThumbsSize() *
ThumbnailSize::maxThumbsSize() * 4);
d->thumbnailPixmapCache.setMaxCost(numberOfQPixmaps *
ThumbnailSize::maxThumbsSize() *
ThumbnailSize::maxThumbsSize() * QPixmap::defaultDepth() / 8);
}
void LoadingCache::setFileWatch(LoadingCacheFileWatch* const watch)
{
delete d->watch;
d->watch = watch;
d->watch->m_cache = this;
}
void LoadingCache::removeFromFileWatch(const QString& filePath)
{
d->fileWatch()->removeFile(filePath);
}
QStringList LoadingCache::imageFilePathsInCache() const
{
d->cleanUpImageFilePathHash();
return d->imageFilePathHash.uniqueKeys();
}
QStringList LoadingCache::thumbnailFilePathsInCache() const
{
d->cleanUpThumbnailFilePathHash();
return d->thumbnailFilePathHash.uniqueKeys();
}
void LoadingCache::notifyFileChanged(const QString& filePath, bool notify)
{
QList keys = d->imageFilePathHash.values(filePath);
foreach (const QString& cacheKey, keys)
{
d->imageCache.remove(cacheKey);
}
keys = d->thumbnailFilePathHash.values(filePath);
foreach (const QString& cacheKey, keys)
{
d->thumbnailImageCache.remove(cacheKey);
d->thumbnailPixmapCache.remove(cacheKey);
}
if (notify)
{
emit fileChanged(filePath);
}
}
void LoadingCache::iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous)
{
if (current.enableCM != previous.enableCM ||
current.useManagedPreviews != previous.useManagedPreviews ||
current.monitorProfile != previous.monitorProfile)
{
LoadingCache::CacheLock lock(this);
removeImages();
removeThumbnails();
}
}
//---------------------------------------------------------------------------------------------------
LoadingCacheFileWatch::~LoadingCacheFileWatch()
{
if (m_cache)
{
LoadingCache::CacheLock lock(m_cache);
if (m_cache->d->watch == this)
{
m_cache->d->watch = nullptr;
}
}
}
void LoadingCacheFileWatch::notifyFileChanged(const QString& filePath)
{
if (m_cache)
{
LoadingCache::CacheLock lock(m_cache);
m_cache->notifyFileChanged(filePath);
}
}
void LoadingCacheFileWatch::removeFile(const QString&)
{
// default: do nothing
}
void LoadingCacheFileWatch::addedImage(const QString&)
{
// default: do nothing
}
void LoadingCacheFileWatch::addedThumbnail(const QString&)
{
// default: do nothing
}
//---------------------------------------------------------------------------------------------------
ClassicLoadingCacheFileWatch::ClassicLoadingCacheFileWatch()
{
if (thread() != QCoreApplication::instance()->thread())
{
moveToThread(QCoreApplication::instance()->thread());
}
m_watch = new QFileSystemWatcher;
connect(m_watch, SIGNAL(fileChanged(QString)),
this, SLOT(slotFileDirty(QString)));
// Make sure the signal gets here directly from the event loop.
// If putImage is called from the main thread, with CacheLock,
// a deadlock would result (mutex is not recursive)
connect(this, SIGNAL(signalUpdateDirWatch()),
this, SLOT(slotUpdateDirWatch()),
Qt::QueuedConnection);
}
ClassicLoadingCacheFileWatch::~ClassicLoadingCacheFileWatch()
{
delete m_watch;
}
void ClassicLoadingCacheFileWatch::removeFile(const QString& filePath)
{
m_watch->removePath(filePath);
}
void ClassicLoadingCacheFileWatch::addedImage(const QString& filePath)
{
Q_UNUSED(filePath)
// schedule update of file watch
// QFileSystemWatch can only be accessed from main thread!
emit signalUpdateDirWatch();
}
void ClassicLoadingCacheFileWatch::addedThumbnail(const QString& filePath)
{
Q_UNUSED(filePath);
// ignore, we do not watch thumbnails
}
void ClassicLoadingCacheFileWatch::slotFileDirty(const QString& path)
{
// Signal comes from main thread
qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache slotFileDirty:" << path;
// This method acquires a lock itself
notifyFileChanged(path);
// No need for locking here, we are in main thread
m_watch->removePath(path);
}
void ClassicLoadingCacheFileWatch::slotUpdateDirWatch()
{
// Event comes from main thread, we need to lock ourselves.
LoadingCache::CacheLock lock(m_cache);
// get a list of files in cache that need watch
QStringList watchedFiles = m_watch->files();
QList filePaths = m_cache->imageFilePathsInCache();
foreach (const QString& path, watchedFiles)
{
if (!path.isEmpty())
{
if (!filePaths.contains(path))
{
m_watch->removePath(path);
}
}
}
foreach (const QString& path, filePaths)
{
if (!path.isEmpty())
{
if (!watchedFiles.contains(path))
{
m_watch->addPath(path);
}
}
}
}
//---------------------------------------------------------------------------------------------------
LoadingCache::CacheLock::CacheLock(LoadingCache* const 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/core/libs/threadimageio/preview/previewtask.cpp b/core/libs/threadimageio/preview/previewtask.cpp
index d41ab72934..f2627bcf6e 100644
--- a/core/libs/threadimageio/preview/previewtask.cpp
+++ b/core/libs/threadimageio/preview/previewtask.cpp
@@ -1,587 +1,581 @@
/* ============================================================
*
* This file is a part of digiKam project
* https://www.digikam.org
*
* Date : 2006-12-26
* Description : Multithreaded loader for previews
*
* Copyright (C) 2006-2011 by Marcel Wiesweg
* Copyright (C) 2006-2019 by Gilles Caulier
*
* 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 "previewtask.h"
// Qt includes
#include
#include
#include
// Local includes
#include "dimgloader.h"
#include "drawdecoder.h"
#include "digikam_debug.h"
#include "dmetadata.h"
#include "jpegutils.h"
#include "metaenginesettings.h"
#include "previewloadthread.h"
namespace Digikam
{
void PreviewLoadingTask::execute()
{
if (m_loadingTaskStatus == LoadingTaskStatusStopping)
{
return;
}
// Check if preview is in cache first.
LoadingCache* const cache = LoadingCache::cache();
{
LoadingCache::CacheLock lock(cache);
// find possible cached images
DImg* cachedImg = nullptr;
QStringList 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.prepend(m_loadingDescription.cacheKey());
foreach (const QString& key, lookupKeys)
{
if ((cachedImg = cache->retrieveImage(key)))
{
if (m_loadingDescription.needCheckRawDecoding())
{
if (cachedImg->rawDecodingSettings() == m_loadingDescription.rawDecodingSettings)
{
break;
}
else
{
cachedImg = nullptr;
}
}
else
{
break;
}
}
}
if (cachedImg)
{
// image is found in image cache, loading is successful
m_img = *cachedImg;
}
else
{
// find possible running loading process
m_usedProcess = nullptr;
for (QStringList::const_iterator it = lookupKeys.constBegin() ; it != lookupKeys.constEnd() ; ++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_loadingTaskStatus != LoadingTaskStatusStopping &&
m_usedProcess &&
!m_usedProcess->completed())
{
lock.timedWait();
}
// remove listener from process
if (m_usedProcess)
{
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 = nullptr;
// m_img is now set to the result
}
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);
}
}
}
if (continueQuery(&m_img) && m_img.isNull())
{
// Preview is not in cache, we will load image from file.
DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath);
m_fromRawEmbeddedPreview = false;
if (format == DImg::RAW)
{
MetaEnginePreviews previews(m_loadingDescription.filePath);
// Check original image size using Exiv2.
QSize originalSize = previews.originalSize();
// If not valid, get original size from LibRaw
if (!originalSize.isValid())
{
DRawInfo container;
if (DRawDecoder::rawFileIdentify(container, m_loadingDescription.filePath))
{
originalSize = container.imageSize;
}
}
switch (m_loadingDescription.previewParameters.previewSettings.quality)
{
case PreviewSettings::FastPreview:
case PreviewSettings::FastButLargePreview:
{
// Size calculations
int sizeLimit = -1;
int bestSize = qMax(originalSize.width(), originalSize.height());
// for RAWs, the alternative is the half preview, so best size is already originalSize / 2
bestSize /= 2;
if (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastButLargePreview)
{
sizeLimit = qMin(m_loadingDescription.previewParameters.size, bestSize);
}
if (loadExiv2Preview(previews, sizeLimit))
{
break;
}
if (loadLibRawPreview(sizeLimit))
{
break;
}
loadHalfSizeRaw();
break;
}
case PreviewSettings::HighQualityPreview:
{
switch (m_loadingDescription.previewParameters.previewSettings.rawLoading)
{
case PreviewSettings::RawPreviewAutomatic:
{
// If we find a preview that is larger than half size (which is what we get from half-size original data), we take it
int acceptableSize = qMax(lround(originalSize.width() * 0.48), lround(originalSize.height() * 0.48));
if (loadExiv2Preview(previews, acceptableSize))
{
break;
}
if (loadLibRawPreview(acceptableSize))
{
break;
}
loadHalfSizeRaw();
break;
}
case PreviewSettings::RawPreviewFromEmbeddedPreview:
{
if (loadExiv2Preview(previews))
{
break;
}
if (loadLibRawPreview())
{
break;
}
loadHalfSizeRaw();
break;
}
case PreviewSettings::RawPreviewFromRawHalfSize:
{
loadHalfSizeRaw();
break;
}
}
}
}
// So far, everything loaded QImage. Convert to DImg.
convertQImageToDImg();
}
else // Non-RAW images
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Try to get preview from" << m_loadingDescription.filePath;
qCDebug(DIGIKAM_GENERAL_LOG) << "Preview quality: " << m_loadingDescription.previewParameters.previewSettings.quality;
bool isFast = (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastPreview);
switch (m_loadingDescription.previewParameters.previewSettings.quality)
{
case PreviewSettings::FastPreview:
case PreviewSettings::FastButLargePreview:
{
if (isFast && loadImagePreview(m_loadingDescription.previewParameters.size))
{
convertQImageToDImg();
break;
}
// Set a hint to try to load a JPEG or PGF with the fast scale-before-decoding method
if (isFast)
{
m_img.setAttribute(QLatin1String("scaledLoadingSize"), m_loadingDescription.previewParameters.size);
}
m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
break;
}
case PreviewSettings::HighQualityPreview:
{
m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
break;
}
}
}
if (continueQuery(&m_img))
{
if (!m_img.isNull() && MetaEngineSettings::instance()->settings().exifRotate)
{
m_img.exifRotate(m_loadingDescription.filePath);
}
{
LoadingCache::CacheLock lock(cache);
// Put valid image into cache of loaded images
if (!m_img.isNull())
{
cache->putImage(m_loadingDescription.cacheKey(), m_img,
m_loadingDescription.filePath);
}
// remove this from the list of loading processes in cache
cache->removeLoadingProcess(this);
}
{
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 (int i = 0 ; i < m_listeners.count() ; ++i)
{
- LoadingProcessListener* const l = m_listeners.at(i);
- LoadSaveNotifier* const notifier = l->loadSaveNotifier();
+ LoadingProcessListener* const l = m_listeners.at(i);
if (l->accessMode() == LoadSaveThread::AccessModeReadWrite)
{
// If a listener requested ReadWrite access, it gets a deep copy.
// DImg is explicitly shared.
l->setResult(m_loadingDescription, m_img.copy());
}
else
{
l->setResult(m_loadingDescription, m_img);
}
-
- if (notifier)
- {
- notifier->imageLoaded(m_loadingDescription, m_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 = nullptr;
}
}
}
// following the golden rule to avoid deadlocks, do this when CacheLock is not held
if (continueQuery(&m_img) && !m_img.isNull())
{
// The image from the cache may or may not be rotated and post processed.
// exifRotate() and postProcess() will detect if work is needed.
// We check before to find out if we need to provide a deep copy
const bool needExifRotate = MetaEngineSettings::instance()->settings().exifRotate &&
!m_img.wasExifRotated();
const bool needImageScale = needToScale();
const bool needPostProcess = needsPostProcessing();
const bool needConvertToEightBit = m_loadingDescription.previewParameters.previewSettings.convertToEightBit;
if (accessMode() == LoadSaveThread::AccessModeReadWrite ||
needExifRotate || needImageScale || needPostProcess ||
needConvertToEightBit)
{
m_img.detach();
}
if (needImageScale)
{
QSize scaledSize = m_img.size();
scaledSize.scale(m_loadingDescription.previewParameters.size,
m_loadingDescription.previewParameters.size,
Qt::KeepAspectRatio);
m_img = m_img.smoothScale(scaledSize.width(), scaledSize.height());
}
if (needConvertToEightBit)
{
m_img.convertToEightBit();
}
if (needExifRotate)
{
m_img.exifRotate(m_loadingDescription.filePath);
}
if (needPostProcess)
{
postProcess();
}
}
else if (continueQuery(&m_img))
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot extract preview for" << m_loadingDescription.filePath;
}
else
{
m_img = DImg();
}
if (m_thread)
{
m_thread->taskHasFinished();
m_thread->imageLoaded(m_loadingDescription, m_img);
}
}
bool PreviewLoadingTask::needToScale()
{
switch (m_loadingDescription.previewParameters.previewSettings.quality)
{
case PreviewSettings::FastPreview:
if (m_loadingDescription.previewParameters.size > 0)
{
int maxSize = qMax(m_img.width(), m_img.height());
int acceptableUpperSize = lround(1.25 * (double)m_loadingDescription.previewParameters.size);
return (maxSize >= acceptableUpperSize);
}
break;
case PreviewSettings::FastButLargePreview:
case PreviewSettings::HighQualityPreview:
break;
}
return false;
}
// -- Exif/IPTC preview extraction using Exiv2 --------------------------------------------------------
bool PreviewLoadingTask::loadExiv2Preview(MetaEnginePreviews& previews, int sizeLimit)
{
if (previews.isEmpty() || !continueQuery(&m_img))
{
return false;
}
if (sizeLimit == -1 || qMax(previews.width(), previews.height()) >= sizeLimit)
{
m_qimage = previews.image();
if (!m_qimage.isNull())
{
m_fromRawEmbeddedPreview = true;
return true;
}
}
return false;
}
bool PreviewLoadingTask::loadLibRawPreview(int sizeLimit)
{
if (!continueQuery(&m_img))
{
return false;
}
QImage rawPreview;
DRawDecoder::loadEmbeddedPreview(rawPreview, m_loadingDescription.filePath);
if (!rawPreview.isNull() &&
(sizeLimit == -1 || qMax(rawPreview.width(), rawPreview.height()) >= sizeLimit))
{
m_qimage = rawPreview;
m_fromRawEmbeddedPreview = true;
return true;
}
return false;
}
bool PreviewLoadingTask::loadHalfSizeRaw()
{
if (!continueQuery(&m_img))
{
return false;
}
DRawDecoder::loadHalfPreview(m_qimage, m_loadingDescription.filePath);
return (!m_qimage.isNull());
}
void PreviewLoadingTask::convertQImageToDImg()
{
if (!continueQuery(&m_img))
{
return;
}
// convert from QImage
m_img = DImg(m_qimage);
DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath);
m_img.setAttribute(QLatin1String("detectedFileFormat"), format);
m_img.setAttribute(QLatin1String("originalFilePath"), m_loadingDescription.filePath);
DMetadata metadata(m_loadingDescription.filePath);
QSize orgSize = metadata.getPixelSize();
if (format == DImg::RAW && LoadSaveThread::infoProvider())
{
orgSize = LoadSaveThread::infoProvider()->dimensionsHint(m_loadingDescription.filePath);
}
// Set the ratio of width and height of the
// original size to the same ratio of the loaded image.
// Because a half RAW preview was probably already rotated.
if (format == DImg::RAW && !m_fromRawEmbeddedPreview)
{
if ((m_img.width() < m_img.height() && orgSize.width() > orgSize.height()) ||
(m_img.width() > m_img.height() && orgSize.width() < orgSize.height()))
{
orgSize.transpose();
}
}
m_img.setAttribute(QLatin1String("originalSize"), orgSize);
m_img.setMetadata(metadata.data());
// mark as embedded preview (for Exif rotation)
if (m_fromRawEmbeddedPreview)
{
m_img.setAttribute(QLatin1String("fromRawEmbeddedPreview"), true);
// If we loaded the embedded preview, the Exif of the RAW indicates
// the color space of the preview (see bug 195950 for NEF files)
m_img.setIccProfile(metadata.getIccProfile());
}
// free memory
m_qimage = QImage();
}
bool PreviewLoadingTask::loadImagePreview(int sizeLimit)
{
DMetadata metadata(m_loadingDescription.filePath);
QImage previewImage;
if (metadata.getItemPreview(previewImage))
{
if (sizeLimit == -1 || qMax(previewImage.width(), previewImage.height()) > sizeLimit)
{
m_qimage = previewImage;
return true;
}
}
qDebug(DIGIKAM_GENERAL_LOG) << "Try to load DImg preview from:" << m_loadingDescription.filePath;
DImg img;
DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo |
DImgLoader::LoadMetadata |
DImgLoader::LoadICCData |
DImgLoader::LoadPreview;
if (img.load(m_loadingDescription.filePath, loadFlags, this))
{
if (sizeLimit == -1 || qMax(img.width(), img.height()) > (uint)sizeLimit)
{
m_qimage = img.copyQImage();
return true;
}
}
return false;
}
} // namespace Digikam