diff --git a/lib/cms/cmsprofile.cpp b/lib/cms/cmsprofile.cpp index 188b90a4..7d2b753e 100644 --- a/lib/cms/cmsprofile.cpp +++ b/lib/cms/cmsprofile.cpp @@ -1,256 +1,259 @@ // 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 "cmsprofile.h" #include // Local #include #include #include #include extern "C" { #include } // KDE // Qt #include #include #include // lcms #include +// Exiv2 +#include + // X11 #ifdef HAVE_X11 #include #include #include #include #endif namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qDebug() << x #else #define LOG(x) ; #endif namespace Cms { //- JPEG ----------------------------------------------------------------------- static cmsHPROFILE loadFromJpegData(const QByteArray& data) { cmsHPROFILE profile = nullptr; struct jpeg_decompress_struct srcinfo; JPEGErrorManager srcErrorManager; srcinfo.err = &srcErrorManager; jpeg_create_decompress(&srcinfo); if (setjmp(srcErrorManager.jmp_buffer)) { qCritical() << "libjpeg error in src\n"; return nullptr; } QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); IODeviceJpegSourceManager::setup(&srcinfo, &buffer); setup_read_icc_profile(&srcinfo); jpeg_read_header(&srcinfo, true); jpeg_start_decompress(&srcinfo); uchar* profile_data; uint profile_len; if (read_icc_profile(&srcinfo, &profile_data, &profile_len)) { LOG("Found a profile, length:" << profile_len); profile = cmsOpenProfileFromMem(profile_data, profile_len); } jpeg_destroy_decompress(&srcinfo); return profile; } //- Profile class -------------------------------------------------------------- struct ProfilePrivate { cmsHPROFILE mProfile; void reset() { if (mProfile) { cmsCloseProfile(mProfile); } mProfile = nullptr; } QString readInfo(cmsInfoType info) { GV_RETURN_VALUE_IF_FAIL(mProfile, QString()); wchar_t buffer[1024]; int size = cmsGetProfileInfo(mProfile, info, "en", "US", buffer, 1024); return QString::fromWCharArray(buffer, size); } }; Profile::Profile() : d(new ProfilePrivate) { d->mProfile = nullptr; } Profile::Profile(cmsHPROFILE hProfile) : d(new ProfilePrivate) { d->mProfile = hProfile; } Profile::~Profile() { d->reset(); delete d; } Profile::Ptr Profile::loadFromImageData(const QByteArray& data, const QByteArray& format) { Profile::Ptr ptr; cmsHPROFILE hProfile = nullptr; if (format == "png") { hProfile = loadFromPngData(data); } else if (format == "jpeg") { hProfile = loadFromJpegData(data); } if (hProfile) { ptr = new Profile(hProfile); } return ptr; } Profile::Ptr Profile::loadFromExiv2Image(const Exiv2::Image* image) { Profile::Ptr ptr; cmsHPROFILE hProfile = nullptr; const Exiv2::ExifData& exifData = image->exifData(); Exiv2::ExifKey key("Exif.Image.InterColorProfile"); Exiv2::ExifData::const_iterator it = exifData.findKey(key); if (it == exifData.end()) { LOG("No profile found"); return ptr; } int size = it->size(); LOG("size:" << size); QByteArray data; data.resize(size); it->copy(reinterpret_cast(data.data()), Exiv2::invalidByteOrder); hProfile = cmsOpenProfileFromMem(data, size); if (hProfile) { ptr = new Profile(hProfile); } return ptr; } cmsHPROFILE Profile::handle() const { return d->mProfile; } QString Profile::copyright() const { return d->readInfo(cmsInfoCopyright); } QString Profile::description() const { return d->readInfo(cmsInfoDescription); } QString Profile::manufacturer() const { return d->readInfo(cmsInfoManufacturer); } QString Profile::model() const { return d->readInfo(cmsInfoModel); } Profile::Ptr Profile::getMonitorProfile() { cmsHPROFILE hProfile = nullptr; // Get the profile from you config file if the user has set it. // if the user allows override through the atom, do this: #ifdef HAVE_X11 if (QX11Info::isPlatformX11()) { // get the current screen... int screen = -1; Atom type; int format; unsigned long nitems; unsigned long bytes_after; quint8 *str; static Atom icc_atom = XInternAtom(QX11Info::display(), "_ICC_PROFILE", True); if (XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(screen), icc_atom, 0, INT_MAX, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (unsigned char **) &str) == Success ) { hProfile = cmsOpenProfileFromMem((void*)str, nitems); } } #endif if (!hProfile) { return getSRgbProfile(); } return Profile::Ptr(new Profile(hProfile)); } Profile::Ptr Profile::getSRgbProfile() { return Profile::Ptr(new Profile(cmsCreate_sRGBProfile())); } } // namespace Cms } // namespace Gwenview diff --git a/lib/cms/cmsprofile.h b/lib/cms/cmsprofile.h index a2a38a87..06c7672d 100644 --- a/lib/cms/cmsprofile.h +++ b/lib/cms/cmsprofile.h @@ -1,78 +1,80 @@ // 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. */ #ifndef CMSPROFILE_H #define CMSPROFILE_H #include // Local // Qt #include #include -// Exiv2 -#include - class QByteArray; class QString; +namespace Exiv2 +{ + class Image; +} + typedef void* cmsHPROFILE; namespace Gwenview { namespace Cms { struct ProfilePrivate; /** * Wrapper for lcms color profile */ class GWENVIEWLIB_EXPORT Profile : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; Profile(); ~Profile(); QString description() const; QString manufacturer() const; QString model() const; QString copyright() const; cmsHPROFILE handle() const; static Profile::Ptr loadFromImageData(const QByteArray& data, const QByteArray& format); static Profile::Ptr loadFromExiv2Image(const Exiv2::Image* image); static Profile::Ptr getMonitorProfile(); static Profile::Ptr getSRgbProfile(); private: Profile(cmsHPROFILE); ProfilePrivate* const d; }; } // namespace Cms } // namespace Gwenview #endif /* CMSPROFILE_H */ diff --git a/lib/document/abstractdocumentimpl.cpp b/lib/document/abstractdocumentimpl.cpp index d841ae5f..d3d0002c 100644 --- a/lib/document/abstractdocumentimpl.cpp +++ b/lib/document/abstractdocumentimpl.cpp @@ -1,100 +1,101 @@ // 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 "abstractdocumentimpl.h" +#include // Qt // KDE // Local #include "document.h" namespace Gwenview { struct AbstractDocumentImplPrivate { Document* mDocument; }; AbstractDocumentImpl::AbstractDocumentImpl(Document* document) : d(new AbstractDocumentImplPrivate) { d->mDocument = document; } AbstractDocumentImpl::~AbstractDocumentImpl() { delete d; } Document* AbstractDocumentImpl::document() const { return d->mDocument; } void AbstractDocumentImpl::switchToImpl(AbstractDocumentImpl* impl) { d->mDocument->switchToImpl(impl); } void AbstractDocumentImpl::setDocumentImage(const QImage& image) { d->mDocument->setImageInternal(image); } void AbstractDocumentImpl::setDocumentImageSize(const QSize& size) { d->mDocument->setSize(size); } void AbstractDocumentImpl::setDocumentFormat(const QByteArray& format) { d->mDocument->setFormat(format); } void AbstractDocumentImpl::setDocumentKind(MimeTypeUtils::Kind kind) { d->mDocument->setKind(kind); } -void AbstractDocumentImpl::setDocumentExiv2Image(Exiv2::Image::AutoPtr image) +void AbstractDocumentImpl::setDocumentExiv2Image(std::unique_ptr image) { - d->mDocument->setExiv2Image(image); + d->mDocument->setExiv2Image(std::move(image)); } void AbstractDocumentImpl::setDocumentDownSampledImage(const QImage& image, int invertedZoom) { d->mDocument->setDownSampledImage(image, invertedZoom); } void AbstractDocumentImpl::setDocumentErrorString(const QString& string) { d->mDocument->setErrorString(string); } void AbstractDocumentImpl::setDocumentCmsProfile(Cms::Profile::Ptr profile) { d->mDocument->setCmsProfile(profile); } } // namespace diff --git a/lib/document/abstractdocumentimpl.h b/lib/document/abstractdocumentimpl.h index 1f427e60..86e6bfd2 100644 --- a/lib/document/abstractdocumentimpl.h +++ b/lib/document/abstractdocumentimpl.h @@ -1,123 +1,128 @@ // 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. */ #ifndef ABSTRACTDOCUMENTIMPL_H #define ABSTRACTDOCUMENTIMPL_H // Qt #include #include // KDE // Local #include #include class QImage; class QRect; +namespace Exiv2 +{ + class Image; +} + namespace Gwenview { class Document; class DocumentJob; class AbstractDocumentEditor; struct AbstractDocumentImplPrivate; class AbstractDocumentImpl : public QObject { Q_OBJECT public: AbstractDocumentImpl(Document*); ~AbstractDocumentImpl() override; /** * This method is called by Document::switchToImpl after it has connected * signals to the object */ virtual void init() = 0; virtual Document::LoadingState loadingState() const = 0; virtual DocumentJob* save(const QUrl&, const QByteArray& /*format*/) { return nullptr; } virtual AbstractDocumentEditor* editor() { return nullptr; } virtual QByteArray rawData() const { return QByteArray(); } virtual bool isEditable() const { return false; } virtual bool isAnimated() const { return false; } virtual void startAnimation() {} virtual void stopAnimation() {} Document* document() const; virtual QSvgRenderer* svgRenderer() const { return nullptr; } Q_SIGNALS: void imageRectUpdated(const QRect&); void metaInfoLoaded(); void loaded(); void loadingFailed(); void isAnimatedUpdated(); void editorUpdated(); protected: void setDocumentImage(const QImage& image); void setDocumentImageSize(const QSize& size); void setDocumentKind(MimeTypeUtils::Kind); void setDocumentFormat(const QByteArray& format); - void setDocumentExiv2Image(Exiv2::Image::AutoPtr); + void setDocumentExiv2Image(std::unique_ptr); void setDocumentDownSampledImage(const QImage&, int invertedZoom); void setDocumentCmsProfile(Cms::Profile::Ptr profile); void setDocumentErrorString(const QString&); void switchToImpl(AbstractDocumentImpl* impl); private: AbstractDocumentImplPrivate* const d; }; } // namespace #endif /* ABSTRACTDOCUMENTIMPL_H */ diff --git a/lib/document/document.cpp b/lib/document/document.cpp index 18756700..afa9ed03 100644 --- a/lib/document/document.cpp +++ b/lib/document/document.cpp @@ -1,572 +1,575 @@ /* 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. */ #include "document.h" #include "document_p.h" // Qt #include #include #include #include #include // KDE #include #include +// Exiv2 +#include + // Local #include "documentjob.h" #include "emptydocumentimpl.h" #include "gvdebug.h" #include "imagemetainfomodel.h" #include "loadingdocumentimpl.h" #include "loadingjob.h" #include "savejob.h" namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qDebug() << x #else #define LOG(x) ; #endif #ifdef ENABLE_LOG static void logQueue(DocumentPrivate* d) { #define PREFIX " QUEUE: " if (!d->mCurrentJob) { Q_ASSERT(d->mJobQueue.isEmpty()); qDebug(PREFIX "No current job, no pending jobs"); return; } qDebug() << PREFIX "Current job:" << d->mCurrentJob.data(); if (d->mJobQueue.isEmpty()) { qDebug(PREFIX "No pending jobs"); return; } qDebug(PREFIX "%d pending job(s):", d->mJobQueue.size()); Q_FOREACH(DocumentJob* job, d->mJobQueue) { Q_ASSERT(job); qDebug() << PREFIX "-" << job; } #undef PREFIX } #define LOG_QUEUE(msg, d) \ LOG(msg); \ logQueue(d) #else #define LOG_QUEUE(msg, d) #endif //- DocumentPrivate --------------------------------------- void DocumentPrivate::scheduleImageLoading(int invertedZoom) { LoadingDocumentImpl* impl = qobject_cast(mImpl); Q_ASSERT(impl); impl->loadImage(invertedZoom); } void DocumentPrivate::scheduleImageDownSampling(int invertedZoom) { LOG("invertedZoom=" << invertedZoom); DownSamplingJob* job = qobject_cast(mCurrentJob.data()); if (job && job->mInvertedZoom == invertedZoom) { LOG("Current job is already doing it"); return; } // Remove any previously scheduled downsampling job DocumentJobQueue::Iterator it; for (it = mJobQueue.begin(); it != mJobQueue.end(); ++it) { DownSamplingJob* job = qobject_cast(*it); if (!job) { continue; } if (job->mInvertedZoom == invertedZoom) { // Already scheduled, nothing to do LOG("Already scheduled"); return; } else { LOG("Removing downsampling job"); mJobQueue.erase(it); delete job; } } q->enqueueJob(new DownSamplingJob(invertedZoom)); } void DocumentPrivate::downSampleImage(int invertedZoom) { mDownSampledImageMap[invertedZoom] = mImage.scaled(mImage.size() / invertedZoom, Qt::KeepAspectRatio, Qt::FastTransformation); if (mDownSampledImageMap[invertedZoom].size().isEmpty()) { mDownSampledImageMap[invertedZoom] = mImage; } emit q->downSampledImageReady(); } //- DownSamplingJob --------------------------------------- void DownSamplingJob::doStart() { DocumentPrivate* d = document()->d; d->downSampleImage(mInvertedZoom); setError(NoError); emitResult(); } //- Document ---------------------------------------------- qreal Document::maxDownSampledZoom() { return 0.5; } Document::Document(const QUrl &url) : QObject() , d(new DocumentPrivate) { d->q = this; d->mImpl = nullptr; d->mUrl = url; d->mKeepRawData = false; reload(); } Document::~Document() { // We do not want undo stack to emit signals, forcing us to emit signals // ourself while we are being destroyed. disconnect(&d->mUndoStack, nullptr, this, nullptr); delete d->mImpl; delete d; } void Document::reload() { d->mSize = QSize(); d->mImage = QImage(); d->mDownSampledImageMap.clear(); d->mExiv2Image.reset(); d->mKind = MimeTypeUtils::KIND_UNKNOWN; d->mFormat = QByteArray(); d->mImageMetaInfoModel.setUrl(d->mUrl); d->mUndoStack.clear(); d->mErrorString.clear(); d->mCmsProfile = nullptr; switchToImpl(new LoadingDocumentImpl(this)); } const QImage& Document::image() const { return d->mImage; } /** * invertedZoom is the biggest power of 2 for which zoom < 1/invertedZoom. * Example: * zoom = 0.4 == 1/2.5 => invertedZoom = 2 (1/2.5 < 1/2) * zoom = 0.2 == 1/5 => invertedZoom = 4 (1/5 < 1/4) */ inline int invertedZoomForZoom(qreal zoom) { int invertedZoom; for (invertedZoom = 1; zoom < 1. / (invertedZoom * 4); invertedZoom *= 2) {} return invertedZoom; } const QImage& Document::downSampledImageForZoom(qreal zoom) const { static const QImage sNullImage; int invertedZoom = invertedZoomForZoom(zoom); if (invertedZoom == 1) { return d->mImage; } if (!d->mDownSampledImageMap.contains(invertedZoom)) { if (!d->mImage.isNull()) { // Special case: if we have the full image and the down sampled // image would be too small, return the original image. const QSize downSampledSize = d->mImage.size() / invertedZoom; if (downSampledSize.isEmpty()) { return d->mImage; } } return sNullImage; } return d->mDownSampledImageMap[invertedZoom]; } Document::LoadingState Document::loadingState() const { return d->mImpl->loadingState(); } void Document::switchToImpl(AbstractDocumentImpl* impl) { Q_ASSERT(impl); LOG("old impl:" << d->mImpl << "new impl:" << impl); if (d->mImpl) { d->mImpl->deleteLater(); } d->mImpl = impl; connect(d->mImpl, SIGNAL(metaInfoLoaded()), this, SLOT(emitMetaInfoLoaded())); connect(d->mImpl, SIGNAL(loaded()), this, SLOT(emitLoaded())); connect(d->mImpl, SIGNAL(loadingFailed()), this, SLOT(emitLoadingFailed())); connect(d->mImpl, SIGNAL(imageRectUpdated(QRect)), this, SIGNAL(imageRectUpdated(QRect))); connect(d->mImpl, SIGNAL(isAnimatedUpdated()), this, SIGNAL(isAnimatedUpdated())); d->mImpl->init(); } void Document::setImageInternal(const QImage& image) { d->mImage = image; d->mDownSampledImageMap.clear(); // If we didn't get the image size before decoding the full image, set it // now setSize(d->mImage.size()); } QUrl Document::url() const { return d->mUrl; } QByteArray Document::rawData() const { return d->mImpl->rawData(); } bool Document::keepRawData() const { return d->mKeepRawData; } void Document::setKeepRawData(bool value) { d->mKeepRawData = value; } void Document::waitUntilLoaded() { startLoadingFullImage(); while (true) { LoadingState state = loadingState(); if (state == Loaded || state == LoadingFailed) { return; } qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } } DocumentJob* Document::save(const QUrl &url, const QByteArray& format) { waitUntilLoaded(); DocumentJob* job = d->mImpl->save(url, format); if (!job) { qWarning() << "Implementation does not support saving!"; setErrorString(i18nc("@info", "Gwenview cannot save this kind of documents.")); return nullptr; } job->setProperty("oldUrl", d->mUrl); job->setProperty("newUrl", url); connect(job, &DocumentJob::result, this, &Document::slotSaveResult); enqueueJob(job); return job; } void Document::slotSaveResult(KJob* job) { if (job->error()) { setErrorString(job->errorString()); } else { d->mUndoStack.setClean(); SaveJob* saveJob = static_cast(job); d->mUrl = saveJob->newUrl(); d->mImageMetaInfoModel.setUrl(d->mUrl); emit saved(saveJob->oldUrl(), d->mUrl); } } QByteArray Document::format() const { return d->mFormat; } void Document::setFormat(const QByteArray& format) { d->mFormat = format; emit metaInfoUpdated(); } MimeTypeUtils::Kind Document::kind() const { return d->mKind; } void Document::setKind(MimeTypeUtils::Kind kind) { d->mKind = kind; emit kindDetermined(d->mUrl); } QSize Document::size() const { return d->mSize; } bool Document::hasAlphaChannel() const { if (d->mImage.isNull()) { return false; } else { return d->mImage.hasAlphaChannel(); } } int Document::memoryUsage() const { // FIXME: Take undo stack into account int usage = d->mImage.byteCount(); usage += rawData().length(); return usage; } void Document::setSize(const QSize& size) { if (size == d->mSize) { return; } d->mSize = size; d->mImageMetaInfoModel.setImageSize(size); emit metaInfoUpdated(); } bool Document::isModified() const { return !d->mUndoStack.isClean(); } AbstractDocumentEditor* Document::editor() { return d->mImpl->editor(); } -void Document::setExiv2Image(Exiv2::Image::AutoPtr image) +void Document::setExiv2Image(std::unique_ptr image) { - d->mExiv2Image = image; + d->mExiv2Image = std::move(image); d->mImageMetaInfoModel.setExiv2Image(d->mExiv2Image.get()); emit metaInfoUpdated(); } void Document::setDownSampledImage(const QImage& image, int invertedZoom) { Q_ASSERT(!d->mDownSampledImageMap.contains(invertedZoom)); d->mDownSampledImageMap[invertedZoom] = image; emit downSampledImageReady(); } QString Document::errorString() const { return d->mErrorString; } void Document::setErrorString(const QString& string) { d->mErrorString = string; } ImageMetaInfoModel* Document::metaInfo() const { return &d->mImageMetaInfoModel; } void Document::startLoadingFullImage() { LoadingState state = loadingState(); if (state <= MetaInfoLoaded) { // Schedule full image loading LoadingJob* job = new LoadingJob; job->uiDelegate()->setAutoWarningHandlingEnabled(false); job->uiDelegate()->setAutoErrorHandlingEnabled(false); enqueueJob(job); d->scheduleImageLoading(1); } else if (state == Loaded) { return; } else if (state == LoadingFailed) { qWarning() << "Can't load full image: loading has already failed"; } } bool Document::prepareDownSampledImageForZoom(qreal zoom) { if (zoom >= maxDownSampledZoom()) { qWarning() << "No need to call prepareDownSampledImageForZoom if zoom >= " << maxDownSampledZoom(); return true; } int invertedZoom = invertedZoomForZoom(zoom); if (d->mDownSampledImageMap.contains(invertedZoom)) { LOG("downSampledImageForZoom=" << zoom << "invertedZoom=" << invertedZoom << "ready"); return true; } LOG("downSampledImageForZoom=" << zoom << "invertedZoom=" << invertedZoom << "not ready"); if (loadingState() == LoadingFailed) { qWarning() << "Image has failed to load, not doing anything"; return false; } else if (loadingState() == Loaded) { d->scheduleImageDownSampling(invertedZoom); return false; } // Schedule down sampled image loading d->scheduleImageLoading(invertedZoom); return false; } void Document::emitMetaInfoLoaded() { emit metaInfoLoaded(d->mUrl); } void Document::emitLoaded() { emit loaded(d->mUrl); } void Document::emitLoadingFailed() { emit loadingFailed(d->mUrl); } QUndoStack* Document::undoStack() const { return &d->mUndoStack; } void Document::imageOperationCompleted() { if (d->mUndoStack.isClean()) { // If user just undid all his changes this does not really correspond // to a save, but it's similar enough as far as Document users are // concerned emit saved(d->mUrl, d->mUrl); } else { emit modified(d->mUrl); } } bool Document::isEditable() const { return d->mImpl->isEditable(); } bool Document::isAnimated() const { return d->mImpl->isAnimated(); } void Document::startAnimation() { return d->mImpl->startAnimation(); } void Document::stopAnimation() { return d->mImpl->stopAnimation(); } void Document::enqueueJob(DocumentJob* job) { LOG("job=" << job); job->setDocument(Ptr(this)); connect(job, &LoadingJob::finished, this, &Document::slotJobFinished); if (d->mCurrentJob) { d->mJobQueue.enqueue(job); } else { d->mCurrentJob = job; LOG("Starting first job"); job->start(); emit busyChanged(d->mUrl, true); } LOG_QUEUE("Job added", d); } void Document::slotJobFinished(KJob* job) { LOG("job=" << job); GV_RETURN_IF_FAIL(job == d->mCurrentJob.data()); if (d->mJobQueue.isEmpty()) { LOG("All done"); d->mCurrentJob.clear(); emit busyChanged(d->mUrl, false); emit allTasksDone(); } else { LOG("Starting next job"); d->mCurrentJob = d->mJobQueue.dequeue(); GV_RETURN_IF_FAIL(d->mCurrentJob); d->mCurrentJob.data()->start(); } LOG_QUEUE("Removed done job", d); } bool Document::isBusy() const { return !d->mJobQueue.isEmpty(); } QSvgRenderer* Document::svgRenderer() const { return d->mImpl->svgRenderer(); } void Document::setCmsProfile(Cms::Profile::Ptr ptr) { d->mCmsProfile = ptr; } Cms::Profile::Ptr Document::cmsProfile() const { return d->mCmsProfile; } } // namespace diff --git a/lib/document/document.h b/lib/document/document.h index c0bb454b..4b40a6e1 100644 --- a/lib/document/document.h +++ b/lib/document/document.h @@ -1,250 +1,255 @@ /* 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. */ #ifndef DOCUMENT_H #define DOCUMENT_H #include -#include -#include +// STL +#include // Qt #include #include #include // Local #include #include class QImage; class QRect; class QSize; class QSvgRenderer; class QUndoStack; class KJob; class QUrl; +namespace Exiv2 +{ + class Image; +} + namespace Gwenview { class AbstractDocumentEditor; class AbstractDocumentImpl; class DocumentJob; class DocumentFactory; struct DocumentPrivate; class ImageMetaInfoModel; /** * This class represents an image. * * It handles loading and saving the image, applying operations and maintaining * the document undo stack. * * It is capable of loading down sampled versions of an image using * prepareDownSampledImageForZoom() and downSampledImageForZoom(). Down sampled * images load much faster than the full image but you need to load the full * image to manipulate it (use startLoadingFullImage() to do so). * * To get a Document instance for url, ask for one with * DocumentFactory::instance()->load(url); */ class GWENVIEWLIB_EXPORT Document : public QObject, public QSharedData { Q_OBJECT public: /** * Document won't produce down sampled images for any zoom value higher than maxDownSampledZoom(). * * Note: We can't use the enum {} trick to declare this constant, that's * why it's defined as a static method */ static qreal maxDownSampledZoom(); enum LoadingState { Loading, ///< Image is loading KindDetermined, ///< Image is still loading, but kind has been determined MetaInfoLoaded, ///< Image is still loading, but meta info has been loaded Loaded, ///< Full image has been loaded LoadingFailed ///< Image loading has failed }; typedef QExplicitlySharedDataPointer Ptr; ~Document() override; /** * Returns a message for the last error which happened */ QString errorString() const; void reload(); void startLoadingFullImage(); /** * Prepare a version of the image down sampled to be a bit bigger than * size() * @a zoom. * Do not ask for a down sampled image for @a zoom >= to MaxDownSampledZoom. * * @return true if the image is ready, false if not. In this case the * downSampledImageReady() signal will be emitted. */ bool prepareDownSampledImageForZoom(qreal zoom); LoadingState loadingState() const; MimeTypeUtils::Kind kind() const; bool isModified() const; const QImage& image() const; const QImage& downSampledImageForZoom(qreal zoom) const; /** * Returns an implementation of AbstractDocumentEditor if this document can * be edited. */ AbstractDocumentEditor* editor(); QUrl url() const; DocumentJob* save(const QUrl &url, const QByteArray& format); QByteArray format() const; void waitUntilLoaded(); QSize size() const; int width() const { return size().width(); } int height() const { return size().height(); } bool hasAlphaChannel() const; ImageMetaInfoModel* metaInfo() const; QUndoStack* undoStack() const; void setKeepRawData(bool); bool keepRawData() const; /** * Returns how much bytes the document is using */ int memoryUsage() const; /** * Returns the compressed version of the document, if it is still * available. */ QByteArray rawData() const; Cms::Profile::Ptr cmsProfile() const; /** * Returns a QSvgRenderer which can be used to render this document if it is * an SVG image. Returns a NULL pointer otherwise. */ QSvgRenderer* svgRenderer() const; /** * Returns true if the image can be edited. * You must ensure it has been fully loaded with startLoadingFullImage() first. */ bool isEditable() const; /** * Returns true if the image is animated (eg: gif or mng format) */ bool isAnimated() const; /** * Starts animation. Only sensible if isAnimated() returns true. */ void startAnimation(); /** * Stops animation. Only sensible if isAnimated() returns true. */ void stopAnimation(); void enqueueJob(DocumentJob*); void imageOperationCompleted(); /** * Returns true if there are queued tasks for this document. */ bool isBusy() const; Q_SIGNALS: void downSampledImageReady(); void imageRectUpdated(const QRect&); void kindDetermined(const QUrl&); void metaInfoLoaded(const QUrl&); void loaded(const QUrl&); void loadingFailed(const QUrl&); void saved(const QUrl &oldUrl, const QUrl& newUrl); void modified(const QUrl&); void metaInfoUpdated(); void isAnimatedUpdated(); void busyChanged(const QUrl&, bool); void allTasksDone(); private Q_SLOTS: void emitMetaInfoLoaded(); void emitLoaded(); void emitLoadingFailed(); void slotSaveResult(KJob*); void slotJobFinished(KJob*); private: friend class AbstractDocumentImpl; friend class DocumentFactory; friend struct DocumentPrivate; friend class DownSamplingJob; void setImageInternal(const QImage&); void setKind(MimeTypeUtils::Kind); void setFormat(const QByteArray&); void setSize(const QSize&); - void setExiv2Image(Exiv2::Image::AutoPtr); + void setExiv2Image(std::unique_ptr); void setDownSampledImage(const QImage&, int invertedZoom); void switchToImpl(AbstractDocumentImpl* impl); void setErrorString(const QString&); void setCmsProfile(Cms::Profile::Ptr); Document(const QUrl&); DocumentPrivate * const d; }; } // namespace #endif /* DOCUMENT_H */ diff --git a/lib/document/document_p.h b/lib/document/document_p.h index 78e0ac6e..4de25f15 100644 --- a/lib/document/document_p.h +++ b/lib/document/document_p.h @@ -1,88 +1,96 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2013 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. */ #ifndef DOCUMENT_P_H #define DOCUMENT_P_H +// STL +#include + // Local #include #include // KDE #include // Qt #include #include #include #include +namespace Exiv2 +{ + class Image; +} + namespace Gwenview { typedef QQueue DocumentJobQueue; struct DocumentPrivate { Document* q; AbstractDocumentImpl* mImpl; QUrl mUrl; bool mKeepRawData; QPointer mCurrentJob; DocumentJobQueue mJobQueue; /** * @defgroup imagedata should be reset in reload() * @{ */ QSize mSize; QImage mImage; QMap mDownSampledImageMap; - Exiv2::Image::AutoPtr mExiv2Image; + std::unique_ptr mExiv2Image; MimeTypeUtils::Kind mKind; QByteArray mFormat; ImageMetaInfoModel mImageMetaInfoModel; QUndoStack mUndoStack; QString mErrorString; Cms::Profile::Ptr mCmsProfile; /** @} */ void scheduleImageLoading(int invertedZoom); void scheduleImageDownSampling(int invertedZoom); void downSampleImage(int invertedZoom); }; class DownSamplingJob : public DocumentJob { Q_OBJECT public: DownSamplingJob(int invertedZoom) : mInvertedZoom(invertedZoom) {} void doStart() override; int mInvertedZoom; }; } // namespace #endif /* DOCUMENT_P_H */ diff --git a/lib/document/loadingdocumentimpl.cpp b/lib/document/loadingdocumentimpl.cpp index 07675783..766a1831 100644 --- a/lib/document/loadingdocumentimpl.cpp +++ b/lib/document/loadingdocumentimpl.cpp @@ -1,552 +1,555 @@ // 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 +// Exiv2 +#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 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 (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); + setDocumentExiv2Image(std::move(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/exiv2imageloader.cpp b/lib/exiv2imageloader.cpp index f2830f81..f13dff7a 100644 --- a/lib/exiv2imageloader.cpp +++ b/lib/exiv2imageloader.cpp @@ -1,91 +1,90 @@ // 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 "exiv2imageloader.h" // Qt #include #include #include // KDE // Exiv2 -#include -#include +#include // Local namespace Gwenview { struct Exiv2ImageLoaderPrivate { - Exiv2::Image::AutoPtr mImage; + std::unique_ptr mImage; QString mErrorMessage; }; Exiv2ImageLoader::Exiv2ImageLoader() : d(new Exiv2ImageLoaderPrivate) { } Exiv2ImageLoader::~Exiv2ImageLoader() { delete d; } bool Exiv2ImageLoader::load(const QString& filePath) { QByteArray filePathByteArray = QFile::encodeName(filePath); try { - d->mImage = Exiv2::ImageFactory::open(filePathByteArray.constData()); + d->mImage.reset(Exiv2::ImageFactory::open(filePathByteArray.constData()).release()); d->mImage->readMetadata(); } catch (const Exiv2::Error& error) { d->mErrorMessage = QString::fromUtf8(error.what()); return false; } return true; } bool Exiv2ImageLoader::load(const QByteArray& data) { try { - d->mImage = Exiv2::ImageFactory::open((unsigned char*)data.constData(), data.size()); + d->mImage.reset(Exiv2::ImageFactory::open((unsigned char*)data.constData(), data.size()).release()); d->mImage->readMetadata(); } catch (const Exiv2::Error& error) { d->mErrorMessage = QString::fromUtf8(error.what()); return false; } return true; } QString Exiv2ImageLoader::errorMessage() const { return d->mErrorMessage; } -Exiv2::Image::AutoPtr Exiv2ImageLoader::popImage() +std::unique_ptr Exiv2ImageLoader::popImage() { - return d->mImage; + return std::move(d->mImage); } } // namespace diff --git a/lib/exiv2imageloader.h b/lib/exiv2imageloader.h index 57ef24d2..12a45b68 100644 --- a/lib/exiv2imageloader.h +++ b/lib/exiv2imageloader.h @@ -1,65 +1,70 @@ // 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. */ #ifndef EXIV2IMAGELOADER_H #define EXIV2IMAGELOADER_H #include +// STL +#include + // Qt // KDE // Exiv2 -#include -#include +namespace Exiv2 +{ + class Image; +} // Local class QByteArray; class QString; namespace Gwenview { struct Exiv2ImageLoaderPrivate; /** * This helper class loads image using libexiv2, and takes care of exception * handling for the different versions of libexiv2. */ class GWENVIEWLIB_EXPORT Exiv2ImageLoader { public: Exiv2ImageLoader(); ~Exiv2ImageLoader(); bool load(const QString&); bool load(const QByteArray&); QString errorMessage() const; - Exiv2::Image::AutoPtr popImage(); + std::unique_ptr popImage(); private: Exiv2ImageLoaderPrivate* const d; }; } // namespace #endif /* EXIV2IMAGELOADER_H */ diff --git a/lib/jpegcontent.cpp b/lib/jpegcontent.cpp index bb810dd4..a8cf909f 100644 --- a/lib/jpegcontent.cpp +++ b/lib/jpegcontent.cpp @@ -1,644 +1,644 @@ // 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. */ #include "jpegcontent.h" // System #include #include #include #include extern "C" { #include #include "transupp.h" } // Qt #include #include #include #include #include #include // KDE #include // Exiv2 -#include -#include +#include // Local #include "jpegerrormanager.h" #include "iodevicejpegsourcemanager.h" #include "exiv2imageloader.h" #include "gwenviewconfig.h" namespace Gwenview { const int INMEM_DST_DELTA = 4096; //----------------------------------------------- // // In-memory data destination manager for libjpeg // //----------------------------------------------- struct inmem_dest_mgr : public jpeg_destination_mgr { QByteArray* mOutput; void dump() { qDebug() << "dest_mgr:\n"; qDebug() << "- next_output_byte: " << next_output_byte; qDebug() << "- free_in_buffer: " << free_in_buffer; qDebug() << "- output size: " << mOutput->size(); } }; void inmem_init_destination(j_compress_ptr cinfo) { inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); if (dest->mOutput->size() == 0) { dest->mOutput->resize(INMEM_DST_DELTA); } dest->free_in_buffer = dest->mOutput->size(); dest->next_output_byte = (JOCTET*)(dest->mOutput->data()); } boolean inmem_empty_output_buffer(j_compress_ptr cinfo) { inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA); dest->next_output_byte = (JOCTET*)(dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA); dest->free_in_buffer = INMEM_DST_DELTA; return true; } void inmem_term_destination(j_compress_ptr cinfo) { inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); int finalSize = dest->next_output_byte - (JOCTET*)(dest->mOutput->data()); Q_ASSERT(finalSize >= 0); dest->mOutput->resize(finalSize); } //--------------------- // // JpegContent::Private // //--------------------- struct JpegContent::Private { // JpegContent usually stores the image pixels as compressed JPEG data in // mRawData. However if the image is set with setImage() because the user // performed a lossy image manipulation, mRawData is cleared and the image // pixels are kept in mImage until updateRawDataFromImage() is called. QImage mImage; QByteArray mRawData; QSize mSize; QString mComment; bool mPendingTransformation; QMatrix mTransformMatrix; Exiv2::ExifData mExifData; QString mErrorString; Private() { mPendingTransformation = false; } void setupInmemDestination(j_compress_ptr cinfo, QByteArray* outputData) { Q_ASSERT(!cinfo->dest); inmem_dest_mgr* dest = (inmem_dest_mgr*) (*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(inmem_dest_mgr)); cinfo->dest = (struct jpeg_destination_mgr*)(dest); dest->init_destination = inmem_init_destination; dest->empty_output_buffer = inmem_empty_output_buffer; dest->term_destination = inmem_term_destination; dest->mOutput = outputData; } bool readSize() { struct jpeg_decompress_struct srcinfo; // Init JPEG structs JPEGErrorManager errorManager; // Initialize the JPEG decompression object srcinfo.err = &errorManager; jpeg_create_decompress(&srcinfo); if (setjmp(errorManager.jmp_buffer)) { qCritical() << "libjpeg fatal error\n"; return false; } // Specify data source for decompression QBuffer buffer(&mRawData); buffer.open(QIODevice::ReadOnly); IODeviceJpegSourceManager::setup(&srcinfo, &buffer); // Read the header jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); int result = jpeg_read_header(&srcinfo, true); if (result != JPEG_HEADER_OK) { qCritical() << "Could not read jpeg header\n"; jpeg_destroy_decompress(&srcinfo); return false; } mSize = QSize(srcinfo.image_width, srcinfo.image_height); jpeg_destroy_decompress(&srcinfo); return true; } bool updateRawDataFromImage() { QBuffer buffer; QImageWriter writer(&buffer, "jpeg"); if (!writer.write(mImage)) { mErrorString = writer.errorString(); return false; } mRawData = buffer.data(); mImage = QImage(); return true; } }; //------------ // // JpegContent // //------------ JpegContent::JpegContent() { d = new JpegContent::Private(); } JpegContent::~JpegContent() { delete d; } bool JpegContent::load(const QString& path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Could not open '" << path << "' for reading\n"; return false; } return loadFromData(file.readAll()); } bool JpegContent::loadFromData(const QByteArray& data) { - Exiv2::Image::AutoPtr image; + std::unique_ptr image; Exiv2ImageLoader loader; if (!loader.load(data)) { qCritical() << "Could not load image with Exiv2, reported error:" << loader.errorMessage(); } - image = loader.popImage(); + image.reset(loader.popImage().release()); return loadFromData(data, image.get()); } bool JpegContent::loadFromData(const QByteArray& data, Exiv2::Image* exiv2Image) { d->mPendingTransformation = false; d->mTransformMatrix.reset(); d->mRawData = data; if (d->mRawData.size() == 0) { qCritical() << "No data\n"; return false; } if (!d->readSize()) return false; d->mExifData = exiv2Image->exifData(); d->mComment = QString::fromUtf8(exiv2Image->comment().c_str()); if (!GwenviewConfig::applyExifOrientation()) { return true; } // Adjust the size according to the orientation switch (orientation()) { case TRANSPOSE: case ROT_90: case TRANSVERSE: case ROT_270: d->mSize.transpose(); break; default: break; } return true; } QByteArray JpegContent::rawData() const { return d->mRawData; } Orientation JpegContent::orientation() const { Exiv2::ExifKey key("Exif.Image.Orientation"); Exiv2::ExifData::iterator it = d->mExifData.findKey(key); // We do the same checks as in libexiv2's src/crwimage.cpp: // http://dev.exiv2.org/projects/exiv2/repository/entry/trunk/src/crwimage.cpp?rev=2681#L1336 if (it == d->mExifData.end() || it->count() == 0 || it->typeId() != Exiv2::unsignedShort) { return NOT_AVAILABLE; } return Orientation(it->toLong()); } int JpegContent::dotsPerMeterX() const { return dotsPerMeter(QStringLiteral("XResolution")); } int JpegContent::dotsPerMeterY() const { return dotsPerMeter(QStringLiteral("YResolution")); } int JpegContent::dotsPerMeter(const QString& keyName) const { Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit"); Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit); if (it == d->mExifData.end()) { return 0; } int res = it->toLong(); QString keyVal = QStringLiteral("Exif.Image.") + keyName; Exiv2::ExifKey keyResolution(keyVal.toLocal8Bit().data()); it = d->mExifData.findKey(keyResolution); if (it == d->mExifData.end()) { return 0; } // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution. // If the image resolution in unknown, 2 (inches) is designated. // Default = 2 // 2 = inches // 3 = centimeters // Other = reserved const float INCHESPERMETER = (100. / 2.54); switch (res) { case 3: // dots per cm return int(it->toLong() * 100); default: // dots per inch return int(it->toLong() * INCHESPERMETER); } return 0; } void JpegContent::resetOrientation() { Exiv2::ExifKey key("Exif.Image.Orientation"); Exiv2::ExifData::iterator it = d->mExifData.findKey(key); if (it == d->mExifData.end()) { return; } *it = uint16_t(NORMAL); } QSize JpegContent::size() const { return d->mSize; } QString JpegContent::comment() const { return d->mComment; } void JpegContent::setComment(const QString& comment) { d->mComment = comment; } static QMatrix createRotMatrix(int angle) { QMatrix matrix; matrix.rotate(angle); return matrix; } static QMatrix createScaleMatrix(int dx, int dy) { QMatrix matrix; matrix.scale(dx, dy); return matrix; } struct OrientationInfo { OrientationInfo() : orientation(NOT_AVAILABLE) , jxform(JXFORM_NONE) {} OrientationInfo(Orientation o, QMatrix m, JXFORM_CODE j) : orientation(o), matrix(m), jxform(j) {} Orientation orientation; QMatrix matrix; JXFORM_CODE jxform; }; typedef QList OrientationInfoList; static const OrientationInfoList& orientationInfoList() { static OrientationInfoList list; if (list.size() == 0) { QMatrix rot90 = createRotMatrix(90); QMatrix hflip = createScaleMatrix(-1, 1); QMatrix vflip = createScaleMatrix(1, -1); list << OrientationInfo() << OrientationInfo(NORMAL, QMatrix(), JXFORM_NONE) << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H) << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180) << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V) << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE) << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90) << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE) << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270) ; } return list; } void JpegContent::transform(Orientation orientation) { if (orientation != NOT_AVAILABLE && orientation != NORMAL) { d->mPendingTransformation = true; OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); for (; it != end; ++it) { if ((*it).orientation == orientation) { d->mTransformMatrix = (*it).matrix * d->mTransformMatrix; break; } } if (it == end) { qWarning() << "Could not find matrix for orientation\n"; } } } #if 0 static void dumpMatrix(const QMatrix& matrix) { qDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n"; qDebug() << " | " << matrix.m21() << ", " << matrix.m22() << " |\n"; qDebug() << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n"; } #endif static bool matricesAreSame(const QMatrix& m1, const QMatrix& m2, double tolerance) { return fabs(m1.m11() - m2.m11()) < tolerance && fabs(m1.m12() - m2.m12()) < tolerance && fabs(m1.m21() - m2.m21()) < tolerance && fabs(m1.m22() - m2.m22()) < tolerance && fabs(m1.dx() - m2.dx()) < tolerance && fabs(m1.dy() - m2.dy()) < tolerance; } static JXFORM_CODE findJxform(const QMatrix& matrix) { OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); for (; it != end; ++it) { if (matricesAreSame((*it).matrix, matrix, 0.001)) { return (*it).jxform; } } qWarning() << "findJxform: failed\n"; return JXFORM_NONE; } void JpegContent::applyPendingTransformation() { if (d->mRawData.size() == 0) { qCritical() << "No data loaded\n"; return; } // The following code is inspired by jpegtran.c from the libjpeg // Init JPEG structs struct jpeg_decompress_struct srcinfo; struct jpeg_compress_struct dstinfo; jvirt_barray_ptr * src_coef_arrays; jvirt_barray_ptr * dst_coef_arrays; // Initialize the JPEG decompression object JPEGErrorManager srcErrorManager; srcinfo.err = &srcErrorManager; jpeg_create_decompress(&srcinfo); if (setjmp(srcErrorManager.jmp_buffer)) { qCritical() << "libjpeg error in src\n"; return; } // Initialize the JPEG compression object JPEGErrorManager dstErrorManager; dstinfo.err = &dstErrorManager; jpeg_create_compress(&dstinfo); if (setjmp(dstErrorManager.jmp_buffer)) { qCritical() << "libjpeg error in dst\n"; return; } // Specify data source for decompression QBuffer buffer(&d->mRawData); buffer.open(QIODevice::ReadOnly); IODeviceJpegSourceManager::setup(&srcinfo, &buffer); // Enable saving of extra markers that we want to copy jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); (void) jpeg_read_header(&srcinfo, true); // Init transformation jpeg_transform_info transformoption; memset(&transformoption, 0, sizeof(jpeg_transform_info)); transformoption.transform = findJxform(d->mTransformMatrix); jtransform_request_workspace(&srcinfo, &transformoption); /* Read source file as DCT coefficients */ src_coef_arrays = jpeg_read_coefficients(&srcinfo); /* Initialize destination compression parameters from source values */ jpeg_copy_critical_parameters(&srcinfo, &dstinfo); /* Adjust destination parameters if required by transform options; * also find out which set of coefficient arrays will hold the output. */ dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, src_coef_arrays, &transformoption); /* Specify data destination for compression */ QByteArray output; output.resize(d->mRawData.size()); d->setupInmemDestination(&dstinfo, &output); /* Start compressor (note no image data is actually written here) */ jpeg_write_coefficients(&dstinfo, dst_coef_arrays); /* Copy to the output file any extra markers that we want to preserve */ jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL); /* Execute image transformation, if any */ jtransform_execute_transformation(&srcinfo, &dstinfo, src_coef_arrays, &transformoption); /* Finish compression and release memory */ jpeg_finish_compress(&dstinfo); jpeg_destroy_compress(&dstinfo); (void) jpeg_finish_decompress(&srcinfo); jpeg_destroy_decompress(&srcinfo); // Set rawData to our new JPEG d->mRawData = output; } QImage JpegContent::thumbnail() const { QImage image; if (!d->mExifData.empty()) { #if(EXIV2_TEST_VERSION(0,17,91)) Exiv2::ExifThumbC thumb(d->mExifData); Exiv2::DataBuf thumbnail = thumb.copy(); #else Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); #endif image.loadFromData(thumbnail.pData_, thumbnail.size_); } return image; } void JpegContent::setThumbnail(const QImage& thumbnail) { if (d->mExifData.empty()) { return; } QByteArray array; QBuffer buffer(&array); buffer.open(QIODevice::WriteOnly); QImageWriter writer(&buffer, "JPEG"); if (!writer.write(thumbnail)) { qCritical() << "Could not write thumbnail\n"; return; } #if (EXIV2_TEST_VERSION(0,17,91)) Exiv2::ExifThumb thumb(d->mExifData); thumb.setJpegThumbnail((unsigned char*)array.data(), array.size()); #else d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size()); #endif } bool JpegContent::save(const QString& path) { QFile file(path); if (!file.open(QIODevice::WriteOnly)) { d->mErrorString = i18nc("@info", "Could not open file for writing."); return false; } return save(&file); } bool JpegContent::save(QIODevice* device) { if (!d->mImage.isNull()) { if (!d->updateRawDataFromImage()) { return false; } } if (d->mRawData.size() == 0) { d->mErrorString = i18nc("@info", "No data to store."); return false; } if (d->mPendingTransformation) { applyPendingTransformation(); d->mPendingTransformation = false; } - Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size()); + std::unique_ptr image; + image.reset(Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size()).release()); // Store Exif info image->setExifData(d->mExifData); image->setComment(d->mComment.toUtf8().toStdString()); image->writeMetadata(); // Update mRawData Exiv2::BasicIo& io = image->io(); d->mRawData.resize(io.size()); io.read((unsigned char*)d->mRawData.data(), io.size()); QDataStream stream(device); stream.writeRawData(d->mRawData.data(), d->mRawData.size()); // Make sure we are up to date loadFromData(d->mRawData); return true; } QString JpegContent::errorString() const { return d->mErrorString; } void JpegContent::setImage(const QImage& image) { d->mRawData.clear(); d->mImage = image; d->mSize = image.size(); d->mExifData["Exif.Photo.PixelXDimension"] = image.width(); d->mExifData["Exif.Photo.PixelYDimension"] = image.height(); resetOrientation(); d->mPendingTransformation = false; d->mTransformMatrix = QMatrix(); } } // namespace diff --git a/lib/timeutils.cpp b/lib/timeutils.cpp index 9e8836a9..3c519098 100644 --- a/lib/timeutils.cpp +++ b/lib/timeutils.cpp @@ -1,150 +1,152 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 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 "timeutils.h" +// STL +#include + // Qt #include #include #include // KDE #include // Exiv2 -#include -#include +#include // Local #include #include namespace Gwenview { namespace TimeUtils { static Exiv2::ExifData::const_iterator findDateTimeKey(const Exiv2::ExifData& exifData) { // Ordered list of keys to try static QList lst = QList() << Exiv2::ExifKey("Exif.Photo.DateTimeOriginal") << Exiv2::ExifKey("Exif.Image.DateTimeOriginal") << Exiv2::ExifKey("Exif.Photo.DateTimeDigitized") << Exiv2::ExifKey("Exif.Image.DateTime"); Exiv2::ExifData::const_iterator it, end = exifData.end(); Q_FOREACH(const Exiv2::ExifKey& key, lst) { it = exifData.findKey(key); if (it != end) { return it; } } return end; } struct CacheItem { QDateTime fileMTime; QDateTime realTime; void update(const KFileItem& fileItem) { QDateTime time = fileItem.time(KFileItem::ModificationTime); if (fileMTime == time) { return; } fileMTime = time; if (!updateFromExif(fileItem.url())) { realTime = time; } } bool updateFromExif(const QUrl &url) { if (!UrlUtils::urlIsFastLocalFile(url)) { return false; } QString path = url.path(); Exiv2ImageLoader loader; if (!loader.load(path)) { return false; } - Exiv2::Image::AutoPtr img = loader.popImage(); + std::unique_ptr img(loader.popImage().release()); try { Exiv2::ExifData exifData = img->exifData(); if (exifData.empty()) { return false; } Exiv2::ExifData::const_iterator it = findDateTimeKey(exifData); if (it == exifData.end()) { qWarning() << "No date in exif header of" << path; return false; } std::ostringstream stream; stream << *it; QString value = QString::fromLocal8Bit(stream.str().c_str()); QDateTime dt = QDateTime::fromString(value, QStringLiteral("yyyy:MM:dd hh:mm:ss")); if (!dt.isValid()) { qWarning() << "Invalid date in exif header of" << path; return false; } realTime = dt; return true; } catch (const Exiv2::Error& error) { qWarning() << "Failed to read date from exif header of" << path << ". Error:" << error.what(); return false; } } }; typedef QHash Cache; QDateTime dateTimeForFileItem(const KFileItem& fileItem, CachePolicy cachePolicy) { if (cachePolicy == SkipCache) { CacheItem item; item.update(fileItem); return item.realTime; } static Cache cache; const QUrl url = fileItem.targetUrl(); Cache::iterator it = cache.find(url); if (it == cache.end()) { it = cache.insert(url, CacheItem()); } it.value().update(fileItem); return it.value().realTime; } } // namespace } // namespace diff --git a/tests/auto/cmsprofiletest.cpp b/tests/auto/cmsprofiletest.cpp index 4efc441b..f4c7f9e5 100644 --- a/tests/auto/cmsprofiletest.cpp +++ b/tests/auto/cmsprofiletest.cpp @@ -1,96 +1,96 @@ // 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 "cmsprofiletest.h" // Local #include #include #include // KDE #include // Qt QTEST_MAIN(CmsProfileTest) using namespace Gwenview; void CmsProfileTest::testLoadFromImageData() { QFETCH(QString, fileName); QFETCH(QByteArray, format); QByteArray data; { QString path = pathForTestFile(fileName); QFile file(path); QVERIFY(file.open(QIODevice::ReadOnly)); data = file.readAll(); } Cms::Profile::Ptr ptr = Cms::Profile::loadFromImageData(data, format); QVERIFY(ptr); } #define NEW_ROW(fileName, format) QTest::newRow(fileName) << fileName << QByteArray(format) void CmsProfileTest::testLoadFromImageData_data() { QTest::addColumn("fileName"); QTest::addColumn("format"); NEW_ROW("cms/colourTestFakeBRG.png", "png"); NEW_ROW("cms/colourTestsRGB.png", "png"); NEW_ROW("cms/Upper_Left.jpg", "jpeg"); NEW_ROW("cms/Upper_Right.jpg", "jpeg"); NEW_ROW("cms/Lower_Left.jpg", "jpeg"); NEW_ROW("cms/Lower_Right.jpg", "jpeg"); } #undef NEW_ROW #if 0 void CmsProfileTest::testLoadFromExiv2Image() { QFETCH(QString, fileName); - Exiv2::Image::AutoPtr image; + std::unique_ptr image; { QByteArray data; QString path = pathForTestFile(fileName); qWarning() << path; QFile file(path); QVERIFY(file.open(QIODevice::ReadOnly)); data = file.readAll(); Exiv2ImageLoader loader; QVERIFY(loader.load(data)); - image = loader.popImage(); + image.reset(loader.popImage().release()); } Cms::Profile::Ptr ptr = Cms::Profile::loadFromExiv2Image(image.get()); QVERIFY(!ptr.isNull()); } #define NEW_ROW(fileName) QTest::newRow(fileName) << fileName void CmsProfileTest::testLoadFromExiv2Image_data() { QTest::addColumn("fileName"); } #undef NEW_ROW #endif diff --git a/tests/auto/imagemetainfomodeltest.cpp b/tests/auto/imagemetainfomodeltest.cpp index e3ec8d30..5a286b00 100644 --- a/tests/auto/imagemetainfomodeltest.cpp +++ b/tests/auto/imagemetainfomodeltest.cpp @@ -1,58 +1,63 @@ /* 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, Boston, MA 02110-1301, USA. */ + +// STL +#include + // Qt // KDE #include #include // Local #include "../lib/exiv2imageloader.h" #include "../lib/imagemetainfomodel.h" #include "testutils.h" -#include +// Exiv2 +#include #include "imagemetainfomodeltest.h" QTEST_MAIN(ImageMetaInfoModelTest) using namespace Gwenview; void ImageMetaInfoModelTest::testCatchExiv2Errors() { QByteArray data; { QString path = pathForTestFile("302350_exiv_0.23_exception.jpg"); QFile file(path); QVERIFY(file.open(QIODevice::ReadOnly)); data = file.readAll(); } - Exiv2::Image::AutoPtr image; + std::unique_ptr image; { Exiv2ImageLoader loader; QVERIFY(loader.load(data)); image = loader.popImage(); } ImageMetaInfoModel model; model.setExiv2Image(image.get()); }