diff --git a/lib/document/loadingdocumentimpl.cpp b/lib/document/loadingdocumentimpl.cpp index 238699de..07675783 100644 --- a/lib/document/loadingdocumentimpl.cpp +++ b/lib/document/loadingdocumentimpl.cpp @@ -1,554 +1,552 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "loadingdocumentimpl.h" // STL #include // Qt #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #ifdef KDCRAW_FOUND #include #endif // Local #include "animateddocumentloadedimpl.h" #include "cms/cmsprofile.h" #include "document.h" #include "documentloadedimpl.h" #include "emptydocumentimpl.h" #include "exiv2imageloader.h" #include "gvdebug.h" #include "imageutils.h" #include "jpegcontent.h" #include "jpegdocumentloadedimpl.h" #include "orientation.h" #include "svgdocumentloadedimpl.h" #include "urlutils.h" #include "videodocumentloadedimpl.h" #include "gwenviewconfig.h" namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qDebug() << x #else #define LOG(x) ; #endif const int MIN_PREV_SIZE = 1000; const int HEADER_SIZE = 256; struct LoadingDocumentImplPrivate { LoadingDocumentImpl* q; QPointer mTransferJob; QFuture mMetaInfoFuture; QFutureWatcher mMetaInfoFutureWatcher; QFuture mImageDataFuture; QFutureWatcher mImageDataFutureWatcher; // If != 0, this means we need to load an image at zoom = // 1/mImageDataInvertedZoom int mImageDataInvertedZoom; bool mMetaInfoLoaded; bool mAnimated; bool mDownSampledImageLoaded; QByteArray mFormatHint; QByteArray mData; QByteArray mFormat; QSize mImageSize; Exiv2::Image::AutoPtr mExiv2Image; std::unique_ptr mJpegContent; QImage mImage; Cms::Profile::Ptr mCmsProfile; /** * Determine kind of document and switch to an implementation if it is not * necessary to download more data. * @return true if switched to another implementation. */ bool determineKind() { QString mimeType; const QUrl &url = q->document()->url(); QMimeDatabase db; if (KProtocolInfo::determineMimetypeFromExtension(url.scheme())) { mimeType = db.mimeTypeForFileNameAndData(url.fileName(), mData).name(); } else { mimeType = db.mimeTypeForData(mData).name(); } MimeTypeUtils::Kind kind = MimeTypeUtils::mimeTypeKind(mimeType); LOG("mimeType:" << mimeType); LOG("kind:" << kind); q->setDocumentKind(kind); switch (kind) { case MimeTypeUtils::KIND_RASTER_IMAGE: case MimeTypeUtils::KIND_SVG_IMAGE: return false; case MimeTypeUtils::KIND_VIDEO: q->switchToImpl(new VideoDocumentLoadedImpl(q->document())); return true; default: q->setDocumentErrorString( i18nc("@info", "Gwenview cannot display documents of type %1.", mimeType) ); emit q->loadingFailed(); q->switchToImpl(new EmptyDocumentImpl(q->document())); return true; } } void startLoading() { Q_ASSERT(!mMetaInfoLoaded); switch (q->document()->kind()) { case MimeTypeUtils::KIND_RASTER_IMAGE: // The hint is used to: // - Speed up loadMetaInfo(): QImageReader will try to decode the // image using plugins matching this format first. // - Avoid breakage: Because of a bug in Qt TGA image plugin, some // PNG were incorrectly identified as PCX! See: // https://bugs.kde.org/show_bug.cgi?id=289819 // mFormatHint = q->document()->url().fileName() .section(QLatin1Char('.'), -1).toLocal8Bit().toLower(); mMetaInfoFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadMetaInfo); mMetaInfoFutureWatcher.setFuture(mMetaInfoFuture); break; case MimeTypeUtils::KIND_SVG_IMAGE: q->switchToImpl(new SvgDocumentLoadedImpl(q->document(), mData)); break; case MimeTypeUtils::KIND_VIDEO: break; default: qWarning() << "We should not reach this point!"; break; } } void startImageDataLoading() { LOG(""); Q_ASSERT(mMetaInfoLoaded); Q_ASSERT(mImageDataInvertedZoom != 0); Q_ASSERT(!mImageDataFuture.isRunning()); mImageDataFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadImageData); mImageDataFutureWatcher.setFuture(mImageDataFuture); } bool loadMetaInfo() { LOG("mFormatHint" << mFormatHint); QBuffer buffer; buffer.setBuffer(&mData); buffer.open(QIODevice::ReadOnly); #ifdef KDCRAW_FOUND if (KDcrawIface::KDcraw::rawFilesList().contains(QString::fromLatin1(mFormatHint))) { QByteArray previewData; // if the image is in format supported by dcraw, fetch its embedded preview mJpegContent.reset(new JpegContent()); // use KDcraw for getting the embedded preview // KDcraw functionality cloned locally (temp. solution) bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(previewData, buffer); QImage originalImage; if (!ret || !originalImage.loadFromData(previewData) || qMin(originalImage.width(), originalImage.height()) < MIN_PREV_SIZE) { // if the embedded preview loading failed or gets just a small image, load // half preview instead. That's slower but it works even for images containing // small (160x120px) or none embedded preview. if (!KDcrawIface::KDcraw::loadHalfPreview(previewData, buffer)) { qWarning() << "unable to get half preview for " << q->document()->url().fileName(); return false; } } buffer.close(); // now it's safe to replace mData with the jpeg data mData = previewData; // need to fill mFormat so gwenview can tell the type when trying to save mFormat = mFormatHint; } else { #else { #endif QImageReader reader(&buffer, mFormatHint); mImageSize = reader.size(); if (!reader.canRead()) { qWarning() << "QImageReader::read() using format hint" << mFormatHint << "failed:" << reader.errorString(); if (buffer.pos() != 0) { qWarning() << "A bad Qt image decoder moved the buffer to" << buffer.pos() << "in a call to canRead()! Rewinding."; buffer.seek(0); } reader.setFormat(QByteArray()); // Set buffer again, otherwise QImageReader won't restart from scratch reader.setDevice(&buffer); if (!reader.canRead()) { qWarning() << "QImageReader::read() without format hint failed:" << reader.errorString(); return false; } qWarning() << "Image format is actually" << reader.format() << "not" << mFormatHint; } mFormat = reader.format(); if (mFormat == "jpg") { // if mFormatHint was "jpg", then mFormat is "jpg", but the rest of // Gwenview code assumes JPEG images have "jpeg" format. mFormat = "jpeg"; } } LOG("mFormat" << mFormat); GV_RETURN_VALUE_IF_FAIL(!mFormat.isEmpty(), false); Exiv2ImageLoader loader; if (loader.load(mData)) { mExiv2Image = loader.popImage(); } if (mFormat == "jpeg" && mExiv2Image.get()) { mJpegContent.reset(new JpegContent()); } if (mJpegContent.get()) { if (!mJpegContent->loadFromData(mData, mExiv2Image.get()) && !mJpegContent->loadFromData(mData)) { qWarning() << "Unable to use preview of " << q->document()->url().fileName(); return false; } // Use the size from JpegContent, as its correctly transposed if the // image has been rotated mImageSize = mJpegContent->size(); mCmsProfile = Cms::Profile::loadFromExiv2Image(mExiv2Image.get()); } LOG("mImageSize" << mImageSize); if (!mCmsProfile) { mCmsProfile = Cms::Profile::loadFromImageData(mData, mFormat); } return true; } void loadImageData() { QBuffer buffer; buffer.setBuffer(&mData); buffer.open(QIODevice::ReadOnly); QImageReader reader(&buffer, mFormat); LOG("mImageDataInvertedZoom=" << mImageDataInvertedZoom); if (mImageSize.isValid() && mImageDataInvertedZoom != 1 && reader.supportsOption(QImageIOHandler::ScaledSize) ) { // Do not use mImageSize here: QImageReader needs a non-transposed // image size QSize size = reader.size() / mImageDataInvertedZoom; if (!size.isEmpty()) { LOG("Setting scaled size to" << size); reader.setScaledSize(size); } else { LOG("Not setting scaled size as it is empty" << size); } } + if (GwenviewConfig::applyExifOrientation()) { + reader.setAutoTransform(true); + } + bool ok = reader.read(&mImage); if (!ok) { LOG("QImageReader::read() failed"); return; } - if (mJpegContent.get() && GwenviewConfig::applyExifOrientation()) { - Gwenview::Orientation orientation = mJpegContent->orientation(); - QMatrix matrix = ImageUtils::transformMatrix(orientation); - mImage = mImage.transformed(matrix); - } - if (reader.supportsAnimation() && reader.nextImageDelay() > 0 // Assume delay == 0 <=> only one frame ) { /* * QImageReader is not really helpful to detect animated gif: * - QImageReader::imageCount() returns 0 * - QImageReader::nextImageDelay() may return something > 0 if the * image consists of only one frame but includes a "Graphic * Control Extension" (usually only present if we have an * animation) (Bug #185523) * * Decoding the next frame is the only reliable way I found to * detect an animated gif */ LOG("May be an animated image. delay:" << reader.nextImageDelay()); QImage nextImage; if (reader.read(&nextImage)) { LOG("Really an animated image (more than one frame)"); mAnimated = true; } else { qWarning() << q->document()->url() << "is not really an animated image (only one frame)"; } } } }; LoadingDocumentImpl::LoadingDocumentImpl(Document* document) : AbstractDocumentImpl(document) , d(new LoadingDocumentImplPrivate) { d->q = this; d->mMetaInfoLoaded = false; d->mAnimated = false; d->mDownSampledImageLoaded = false; d->mImageDataInvertedZoom = 0; connect(&d->mMetaInfoFutureWatcher, SIGNAL(finished()), SLOT(slotMetaInfoLoaded())); connect(&d->mImageDataFutureWatcher, SIGNAL(finished()), SLOT(slotImageLoaded())); } LoadingDocumentImpl::~LoadingDocumentImpl() { LOG(""); // Disconnect watchers to make sure they do not trigger further work d->mMetaInfoFutureWatcher.disconnect(); d->mImageDataFutureWatcher.disconnect(); d->mMetaInfoFutureWatcher.waitForFinished(); d->mImageDataFutureWatcher.waitForFinished(); if (d->mTransferJob) { d->mTransferJob->kill(); } delete d; } void LoadingDocumentImpl::init() { QUrl url = document()->url(); if (UrlUtils::urlIsFastLocalFile(url)) { // Load file content directly QFile file(url.toLocalFile()); if (!file.open(QIODevice::ReadOnly)) { setDocumentErrorString(i18nc("@info", "Could not open file %1", url.toLocalFile())); emit loadingFailed(); switchToImpl(new EmptyDocumentImpl(document())); return; } d->mData = file.read(HEADER_SIZE); if (d->determineKind()) { return; } d->mData += file.readAll(); d->startLoading(); } else { // Transfer file via KIO d->mTransferJob = KIO::get(document()->url(), KIO::NoReload, KIO::HideProgressInfo); connect(d->mTransferJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotDataReceived(KIO::Job*,QByteArray))); connect(d->mTransferJob, SIGNAL(result(KJob*)), SLOT(slotTransferFinished(KJob*))); d->mTransferJob->start(); } } void LoadingDocumentImpl::loadImage(int invertedZoom) { if (d->mImageDataInvertedZoom == invertedZoom) { LOG("Already loading an image at invertedZoom=" << invertedZoom); return; } if (d->mImageDataInvertedZoom == 1) { LOG("Ignoring request: we are loading a full image"); return; } d->mImageDataFutureWatcher.waitForFinished(); d->mImageDataInvertedZoom = invertedZoom; if (d->mMetaInfoLoaded) { // Do not test on mMetaInfoFuture.isRunning() here: it might not have // started if we are downloading the image from a remote url d->startImageDataLoading(); } } void LoadingDocumentImpl::slotDataReceived(KIO::Job* job, const QByteArray& chunk) { d->mData.append(chunk); if (document()->kind() == MimeTypeUtils::KIND_UNKNOWN && d->mData.length() >= HEADER_SIZE) { if (d->determineKind()) { job->kill(); return; } } } void LoadingDocumentImpl::slotTransferFinished(KJob* job) { if (job->error()) { setDocumentErrorString(job->errorString()); emit loadingFailed(); switchToImpl(new EmptyDocumentImpl(document())); return; } d->startLoading(); } bool LoadingDocumentImpl::isEditable() const { return d->mDownSampledImageLoaded; } Document::LoadingState LoadingDocumentImpl::loadingState() const { if (!document()->image().isNull()) { return Document::Loaded; } else if (d->mMetaInfoLoaded) { return Document::MetaInfoLoaded; } else if (document()->kind() != MimeTypeUtils::KIND_UNKNOWN) { return Document::KindDetermined; } else { return Document::Loading; } } void LoadingDocumentImpl::slotMetaInfoLoaded() { LOG(""); Q_ASSERT(!d->mMetaInfoFuture.isRunning()); if (!d->mMetaInfoFuture.result()) { setDocumentErrorString( i18nc("@info", "Loading meta information failed.") ); emit loadingFailed(); switchToImpl(new EmptyDocumentImpl(document())); return; } setDocumentFormat(d->mFormat); setDocumentImageSize(d->mImageSize); setDocumentExiv2Image(d->mExiv2Image); setDocumentCmsProfile(d->mCmsProfile); d->mMetaInfoLoaded = true; emit metaInfoLoaded(); // Start image loading if necessary // We test if mImageDataFuture is not already running because code connected to // metaInfoLoaded() signal could have called loadImage() if (!d->mImageDataFuture.isRunning() && d->mImageDataInvertedZoom != 0) { d->startImageDataLoading(); } } void LoadingDocumentImpl::slotImageLoaded() { LOG(""); if (d->mImage.isNull()) { setDocumentErrorString( i18nc("@info", "Loading image failed.") ); emit loadingFailed(); switchToImpl(new EmptyDocumentImpl(document())); return; } if (d->mAnimated) { if (d->mImage.size() == d->mImageSize) { // We already decoded the first frame at the right size, let's show // it setDocumentImage(d->mImage); } switchToImpl(new AnimatedDocumentLoadedImpl( document(), d->mData)); return; } if (d->mImageDataInvertedZoom != 1 && d->mImage.size() != d->mImageSize) { LOG("Loaded a down sampled image"); d->mDownSampledImageLoaded = true; // We loaded a down sampled image setDocumentDownSampledImage(d->mImage, d->mImageDataInvertedZoom); return; } LOG("Loaded a full image"); setDocumentImage(d->mImage); DocumentLoadedImpl* impl; if (d->mJpegContent.get()) { impl = new JpegDocumentLoadedImpl( document(), d->mJpegContent.release()); } else { impl = new DocumentLoadedImpl( document(), d->mData); } switchToImpl(impl); } } // namespace diff --git a/lib/thumbnailprovider/thumbnailgenerator.cpp b/lib/thumbnailprovider/thumbnailgenerator.cpp index 6337de11..2090a0b0 100644 --- a/lib/thumbnailprovider/thumbnailgenerator.cpp +++ b/lib/thumbnailprovider/thumbnailgenerator.cpp @@ -1,322 +1,315 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2012 Aurélien Gâteau 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "thumbnailgenerator.h" // Local #include "imageutils.h" #include "jpegcontent.h" #include "gwenviewconfig.h" #include "exiv2imageloader.h" // KDE #include #ifdef KDCRAW_FOUND #include #endif // Qt #include #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qDebug() << x #else #define LOG(x) ; #endif const int MIN_PREV_SIZE = 1000; //------------------------------------------------------------------------ // // ThumbnailContext // //------------------------------------------------------------------------ bool ThumbnailContext::load(const QString &pixPath, int pixelSize) { mImage = QImage(); mNeedCaching = true; Orientation orientation = NORMAL; QImage originalImage; QSize originalSize; QByteArray formatHint = pixPath.section(QLatin1Char('.'), -1).toLocal8Bit().toLower(); QImageReader reader(pixPath); JpegContent content; QByteArray format; QByteArray data; QBuffer buffer; int previewRatio = 1; #ifdef KDCRAW_FOUND // raw images deserve special treatment if (KDcrawIface::KDcraw::rawFilesList().contains(QString::fromLatin1(formatHint))) { // use KDCraw to extract the preview bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(data, pixPath); // We need QImage. Loading JpegContent from QImage - exif lost // Loading QImage from JpegContent - unimplemented, would go with loadFromData if (!ret || !originalImage.loadFromData(data) || qMin(originalImage.width(), originalImage.height()) < MIN_PREV_SIZE) { // if the emebedded preview loading failed or gets just a small image, load // half preview instead. That's slower... if (!KDcrawIface::KDcraw::loadHalfPreview(data, pixPath)) { qWarning() << "unable to get preview for " << pixPath.toUtf8().constData(); return false; } previewRatio = 2; } // And we need JpegContent too because of EXIF (orientation!). if (!content.loadFromData(data)) { qWarning() << "unable to load preview for " << pixPath.toUtf8().constData(); return false; } buffer.setBuffer(&data); buffer.open(QIODevice::ReadOnly); reader.setDevice(&buffer); reader.setFormat(formatHint); } else { #else { #endif if (!reader.canRead()) { reader.setDecideFormatFromContent(true); // Set filename again, otherwise QImageReader won't restart from scratch reader.setFileName(pixPath); } if (reader.format() == "jpeg" && GwenviewConfig::applyExifOrientation()) { content.load(pixPath); } } // If there's jpeg content (from jpg or raw files), try to load an embedded thumbnail, if available. // If applyExifOrientation is not set, don't use the // embedded thumbnail since it might be rotated differently // than the actual image if (!content.rawData().isEmpty() && GwenviewConfig::applyExifOrientation()) { QImage thumbnail = content.thumbnail(); orientation = content.orientation(); if (qMax(thumbnail.width(), thumbnail.height()) >= pixelSize) { mImage = thumbnail; if (orientation != NORMAL && orientation != NOT_AVAILABLE) { QMatrix matrix = ImageUtils::transformMatrix(orientation); mImage = mImage.transformed(matrix); } mOriginalWidth = content.size().width(); mOriginalHeight = content.size().height(); return true; } } // Generate thumbnail from full image originalSize = reader.size(); if (originalSize.isValid() && reader.supportsOption(QImageIOHandler::ScaledSize) && qMax(originalSize.width(), originalSize.height()) >= pixelSize) { QSizeF scaledSize = originalSize; scaledSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio); if (!scaledSize.isEmpty()) { reader.setScaledSize(scaledSize.toSize()); } } + // Rotate if necessary + if (GwenviewConfig::applyExifOrientation()) { + reader.setAutoTransform(true); + } + // format() is empty after QImageReader::read() is called format = reader.format(); if (!reader.read(&originalImage)) { return false; } if (!originalSize.isValid()) { originalSize = originalImage.size(); } mOriginalWidth = originalSize.width() * previewRatio; mOriginalHeight = originalSize.height() * previewRatio; if (qMax(mOriginalWidth, mOriginalHeight) <= pixelSize) { mImage = originalImage; mNeedCaching = format != "png"; } else { mImage = originalImage.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio); } - // Rotate if necessary - if (orientation != NORMAL && orientation != NOT_AVAILABLE && GwenviewConfig::applyExifOrientation()) { - QMatrix matrix = ImageUtils::transformMatrix(orientation); - mImage = mImage.transformed(matrix); - - switch (orientation) { - case TRANSPOSE: - case ROT_90: - case TRANSVERSE: - case ROT_270: - qSwap(mOriginalWidth, mOriginalHeight); - break; - default: - break; - } + if (reader.autoTransform() && (reader.transformation() & QImageIOHandler::TransformationRotate90)) { + qSwap(mOriginalWidth, mOriginalHeight); } + return true; } //------------------------------------------------------------------------ // // ThumbnailGenerator // //------------------------------------------------------------------------ ThumbnailGenerator::ThumbnailGenerator() : mCancel(false) {} void ThumbnailGenerator::load( const QString& originalUri, time_t originalTime, KIO::filesize_t originalFileSize, const QString& originalMimeType, const QString& pixPath, const QString& thumbnailPath, ThumbnailGroup::Enum group) { QMutexLocker lock(&mMutex); Q_ASSERT(mPixPath.isNull()); mOriginalUri = originalUri; mOriginalTime = originalTime; mOriginalFileSize = originalFileSize; mOriginalMimeType = originalMimeType; mPixPath = pixPath; mThumbnailPath = thumbnailPath; mThumbnailGroup = group; if (!isRunning()) start(); mCond.wakeOne(); } QString ThumbnailGenerator::originalUri() const { return mOriginalUri; } time_t ThumbnailGenerator::originalTime() const { return mOriginalTime; } KIO::filesize_t ThumbnailGenerator::originalFileSize() const { return mOriginalFileSize; } QString ThumbnailGenerator::originalMimeType() const { return mOriginalMimeType; } bool ThumbnailGenerator::testCancel() { QMutexLocker lock(&mMutex); return mCancel; } void ThumbnailGenerator::cancel() { QMutexLocker lock(&mMutex); mCancel = true; mCond.wakeOne(); } void ThumbnailGenerator::run() { LOG(""); while (!testCancel()) { QString pixPath; int pixelSize; { QMutexLocker lock(&mMutex); // empty mPixPath means nothing to do LOG("Waiting for mPixPath"); if (mPixPath.isNull()) { LOG("mPixPath.isNull"); mCond.wait(&mMutex); } } if (testCancel()) { return; } { QMutexLocker lock(&mMutex); pixPath = mPixPath; pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup); } Q_ASSERT(!pixPath.isNull()); LOG("Loading" << pixPath); ThumbnailContext context; bool ok = context.load(pixPath, pixelSize); { QMutexLocker lock(&mMutex); if (ok) { mImage = context.mImage; mOriginalWidth = context.mOriginalWidth; mOriginalHeight = context.mOriginalHeight; if (context.mNeedCaching) { cacheThumbnail(); } } else { qWarning() << "Could not generate thumbnail for file" << mOriginalUri; } mPixPath.clear(); // done, ready for next } if (testCancel()) { return; } { QSize size(mOriginalWidth, mOriginalHeight); LOG("emitting done signal, size=" << size); QMutexLocker lock(&mMutex); done(mImage, size); LOG("Done"); } } LOG("Ending thread"); } void ThumbnailGenerator::cacheThumbnail() { mImage.setText(QStringLiteral("Thumb::URI") , mOriginalUri); mImage.setText(QStringLiteral("Thumb::MTime") , QString::number(mOriginalTime)); mImage.setText(QStringLiteral("Thumb::Size") , QString::number(mOriginalFileSize)); mImage.setText(QStringLiteral("Thumb::Mimetype") , mOriginalMimeType); mImage.setText(QStringLiteral("Thumb::Image::Width") , QString::number(mOriginalWidth)); mImage.setText(QStringLiteral("Thumb::Image::Height"), QString::number(mOriginalHeight)); mImage.setText(QStringLiteral("Software") , QStringLiteral("Gwenview")); emit thumbnailReadyToBeCached(mThumbnailPath, mImage); } } // namespace