File indexing completed on 2024-05-12 04:19:49

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2012 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "thumbnailgenerator.h"
0023 
0024 // Local
0025 #include "gwenview_lib_debug.h"
0026 #include "gwenviewconfig.h"
0027 #include "jpegcontent.h"
0028 
0029 // KDCRAW
0030 #ifdef KDCRAW_FOUND
0031 #include <KDCRAW/KDcraw>
0032 #endif
0033 
0034 // KF
0035 
0036 // Qt
0037 #include <QBuffer>
0038 #include <QCoreApplication>
0039 #include <QImageReader>
0040 
0041 namespace Gwenview
0042 {
0043 #undef ENABLE_LOG
0044 #undef LOG
0045 // #define ENABLE_LOG
0046 #ifdef ENABLE_LOG
0047 #define LOG(x) // qCDebug(GWENVIEW_LIB_LOG) << x
0048 #else
0049 #define LOG(x) ;
0050 #endif
0051 
0052 //------------------------------------------------------------------------
0053 //
0054 // ThumbnailContext
0055 //
0056 //------------------------------------------------------------------------
0057 bool ThumbnailContext::load(const QString &pixPath, int pixelSize)
0058 {
0059     mImage = QImage();
0060     mNeedCaching = true;
0061     QImage originalImage;
0062     QSize originalSize;
0063 
0064     QByteArray formatHint = pixPath.section(QLatin1Char('.'), -1).toLocal8Bit().toLower();
0065     QImageReader reader(pixPath);
0066 
0067     JpegContent content;
0068     QByteArray format;
0069     QByteArray data;
0070     QBuffer buffer;
0071     int previewRatio = 1;
0072 
0073     bool useRawPlugin = QImageReader::imageFormat(pixPath) == QByteArray("raw");
0074     if (useRawPlugin) { // make preview generation faster (same as KDcrawIface::KDcraw::loadHalfPreview)
0075         reader.setQuality(1);
0076         previewRatio = 2;
0077     }
0078 
0079 #ifdef KDCRAW_FOUND
0080     // raw images deserve special treatment
0081     if (!useRawPlugin && KDcrawIface::KDcraw::rawFilesList().contains(QString::fromLatin1(formatHint))) {
0082         // use KDCraw to extract the preview
0083         bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(data, pixPath);
0084 
0085         // We need QImage. Loading JpegContent from QImage - exif lost
0086         // Loading QImage from JpegContent - unimplemented, would go with loadFromData
0087         if (!ret) {
0088             // if the embedded preview loading failed, load half preview instead. That's slower...
0089             if (!KDcrawIface::KDcraw::loadHalfPreview(data, pixPath)) {
0090                 qCWarning(GWENVIEW_LIB_LOG) << "unable to get preview for " << pixPath.toUtf8().constData();
0091                 return false;
0092             }
0093             previewRatio = 2;
0094         }
0095 
0096         // And we need JpegContent too because of EXIF (orientation!).
0097         if (!content.loadFromData(data)) {
0098             qCWarning(GWENVIEW_LIB_LOG) << "unable to load preview for " << pixPath.toUtf8().constData();
0099             return false;
0100         }
0101 
0102         buffer.setBuffer(&data);
0103         buffer.open(QIODevice::ReadOnly);
0104         reader.setDevice(&buffer);
0105         reader.setFormat(formatHint);
0106     } else {
0107 #else
0108     {
0109 #endif
0110         if (!reader.canRead()) {
0111             reader.setDecideFormatFromContent(true);
0112             // Set filename again, otherwise QImageReader won't restart from scratch
0113             reader.setFileName(pixPath);
0114         }
0115 
0116         if (reader.format() == "jpeg" && GwenviewConfig::applyExifOrientation()) {
0117             content.load(pixPath);
0118         }
0119     }
0120 
0121     // If there's jpeg content (from jpg or raw files), try to load an embedded thumbnail, if available.
0122     if (!content.rawData().isEmpty()) {
0123         QImage thumbnail = content.thumbnail();
0124 
0125         // If the user does not care about the generated thumbnails (by deleting them on exit), use ANY
0126         // embedded thumnail, even if it's too small.
0127         if (!thumbnail.isNull() && (GwenviewConfig::lowResourceUsageMode() || qMax(thumbnail.width(), thumbnail.height()) >= pixelSize)) {
0128             mImage = std::move(thumbnail);
0129             mOriginalWidth = content.size().width();
0130             mOriginalHeight = content.size().height();
0131             return true;
0132         }
0133     }
0134 
0135     // Generate thumbnail from full image
0136     originalSize = reader.size();
0137     if (originalSize.isValid() && reader.supportsOption(QImageIOHandler::ScaledSize) && qMax(originalSize.width(), originalSize.height()) >= pixelSize) {
0138         QSizeF scaledSize = originalSize;
0139         scaledSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio);
0140         if (!scaledSize.isEmpty()) {
0141             reader.setScaledSize(scaledSize.toSize());
0142         }
0143     }
0144 
0145     // Rotate if necessary
0146     if (GwenviewConfig::applyExifOrientation()) {
0147         reader.setAutoTransform(true);
0148     }
0149 
0150     // format() is empty after QImageReader::read() is called
0151     format = reader.format();
0152     if (!reader.read(&originalImage)) {
0153         return false;
0154     }
0155 
0156     if (!originalSize.isValid()) {
0157         originalSize = originalImage.size();
0158     }
0159     mOriginalWidth = originalSize.width() * previewRatio;
0160     mOriginalHeight = originalSize.height() * previewRatio;
0161 
0162     if (qMax(mOriginalWidth, mOriginalHeight) <= pixelSize) {
0163         mImage = originalImage;
0164         mNeedCaching = format != "png";
0165     } else {
0166         mImage = originalImage.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio);
0167     }
0168 
0169     if (reader.autoTransform() && (reader.transformation() & QImageIOHandler::TransformationRotate90)) {
0170         std::swap(mOriginalWidth, mOriginalHeight);
0171     }
0172 
0173     return true;
0174 }
0175 
0176 //------------------------------------------------------------------------
0177 //
0178 // ThumbnailGenerator
0179 //
0180 //------------------------------------------------------------------------
0181 ThumbnailGenerator::ThumbnailGenerator()
0182     : mCancel(false)
0183 {
0184     connect(
0185         qApp,
0186         &QCoreApplication::aboutToQuit,
0187         this,
0188         [=]() {
0189             cancel();
0190             wait();
0191         },
0192         Qt::DirectConnection);
0193     start();
0194 }
0195 
0196 void ThumbnailGenerator::load(const QString &originalUri,
0197                               time_t originalTime,
0198                               KIO::filesize_t originalFileSize,
0199                               const QString &originalMimeType,
0200                               const QString &pixPath,
0201                               const QString &thumbnailPath,
0202                               ThumbnailGroup::Enum group)
0203 {
0204     QMutexLocker lock(&mMutex);
0205     Q_ASSERT(mPixPath.isNull());
0206 
0207     mOriginalUri = originalUri;
0208     mOriginalTime = originalTime;
0209     mOriginalFileSize = originalFileSize;
0210     mOriginalMimeType = originalMimeType;
0211     mPixPath = pixPath;
0212     mThumbnailPath = thumbnailPath;
0213     mThumbnailGroup = group;
0214     mCond.wakeOne();
0215 }
0216 
0217 QString ThumbnailGenerator::originalUri() const
0218 {
0219     return mOriginalUri;
0220 }
0221 
0222 bool ThumbnailGenerator::isStopped()
0223 {
0224     QMutexLocker lock(&mMutex);
0225     return mStopped;
0226 }
0227 
0228 time_t ThumbnailGenerator::originalTime() const
0229 {
0230     return mOriginalTime;
0231 }
0232 
0233 KIO::filesize_t ThumbnailGenerator::originalFileSize() const
0234 {
0235     return mOriginalFileSize;
0236 }
0237 
0238 QString ThumbnailGenerator::originalMimeType() const
0239 {
0240     return mOriginalMimeType;
0241 }
0242 
0243 bool ThumbnailGenerator::testCancel()
0244 {
0245     QMutexLocker lock(&mMutex);
0246     return mCancel;
0247 }
0248 
0249 void ThumbnailGenerator::cancel()
0250 {
0251     QMutexLocker lock(&mMutex);
0252     mCancel = true;
0253     mCond.wakeOne();
0254 }
0255 
0256 void ThumbnailGenerator::run()
0257 {
0258     while (!testCancel()) {
0259         QString pixPath;
0260         int pixelSize;
0261         {
0262             QMutexLocker lock(&mMutex);
0263             // empty mPixPath means nothing to do
0264             if (mPixPath.isNull()) {
0265                 mCond.wait(&mMutex);
0266             }
0267         }
0268         if (testCancel()) {
0269             break;
0270         }
0271         {
0272             QMutexLocker lock(&mMutex);
0273             pixPath = mPixPath;
0274             pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup);
0275         }
0276 
0277         Q_ASSERT(!pixPath.isNull());
0278         LOG("Loading" << pixPath);
0279         ThumbnailContext context;
0280         bool ok = context.load(pixPath, pixelSize);
0281 
0282         {
0283             QMutexLocker lock(&mMutex);
0284             if (ok) {
0285                 mImage = context.mImage;
0286                 mOriginalWidth = context.mOriginalWidth;
0287                 mOriginalHeight = context.mOriginalHeight;
0288                 if (context.mNeedCaching && mThumbnailGroup <= ThumbnailGroup::XXLarge) {
0289                     cacheThumbnail();
0290                 }
0291             } else {
0292                 // avoid emitting the thumb from the previous successful run
0293                 mImage = QImage();
0294                 qCWarning(GWENVIEW_LIB_LOG) << "Could not generate thumbnail for file" << mOriginalUri;
0295             }
0296             mPixPath.clear(); // done, ready for next
0297         }
0298         if (testCancel()) {
0299             break;
0300         }
0301         {
0302             QSize size(mOriginalWidth, mOriginalHeight);
0303             LOG("emitting done signal, size=" << size);
0304             QMutexLocker lock(&mMutex);
0305             Q_EMIT done(mImage, size);
0306             LOG("Done");
0307         }
0308     }
0309 
0310     LOG("Ending thread");
0311 
0312     QMutexLocker lock(&mMutex);
0313     mStopped = true;
0314     deleteLater();
0315 }
0316 
0317 void ThumbnailGenerator::cacheThumbnail()
0318 {
0319     mImage.setText(QStringLiteral("Thumb::URI"), mOriginalUri);
0320     mImage.setText(QStringLiteral("Thumb::MTime"), QString::number(mOriginalTime));
0321     mImage.setText(QStringLiteral("Thumb::Size"), QString::number(mOriginalFileSize));
0322     mImage.setText(QStringLiteral("Thumb::Mimetype"), mOriginalMimeType);
0323     mImage.setText(QStringLiteral("Thumb::Image::Width"), QString::number(mOriginalWidth));
0324     mImage.setText(QStringLiteral("Thumb::Image::Height"), QString::number(mOriginalHeight));
0325     mImage.setText(QStringLiteral("Software"), QStringLiteral("Gwenview"));
0326 
0327     Q_EMIT thumbnailReadyToBeCached(mThumbnailPath, mImage);
0328 }
0329 
0330 } // namespace
0331 
0332 #include "moc_thumbnailgenerator.cpp"