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