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"