diff --git a/libs/flake/KoImageData.cpp b/libs/flake/KoImageData.cpp index 992110947e..006aa419fd 100644 --- a/libs/flake/KoImageData.cpp +++ b/libs/flake/KoImageData.cpp @@ -1,372 +1,379 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 C. Boemann * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoImageData.h" #include "KoImageData_p.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include /// the maximum amount of bytes the image can be while we store it in memory instead of /// spooling it to disk in a temp-file. #define MAX_MEMORY_IMAGESIZE 90000 KoImageData::KoImageData() + : d(0) { } KoImageData::KoImageData(const KoImageData &imageData) : KoShapeUserData(), - d(imageData.d) + d(imageData.d) { + if (d) + d->refCount.ref(); } KoImageData::KoImageData(KoImageDataPrivate *priv) : d(priv) { + d->refCount.ref(); } KoImageData::~KoImageData() { + if (d && !d->refCount.deref()) + delete d; } QPixmap KoImageData::pixmap(const QSize &size) { if (!d) return QPixmap(); QSize wantedSize = size; if (! wantedSize.isValid()) { if (d->pixmap.isNull()) // we have a problem, Houston.. wantedSize = QSize(100, 100); else wantedSize = d->pixmap.size(); } if (d->pixmap.isNull() || d->pixmap.size() != wantedSize) { switch (d->dataStoreState) { case KoImageDataPrivate::StateEmpty: { #if 0 // this is not possible as it gets called during the paint method // and will crash. Therefore create a tmp pixmap and return it. d->pixmap = QPixmap(1, 1); QPainter p(&d->pixmap); p.setPen(QPen(Qt::gray, 0)); p.drawPoint(0, 0); p.end(); break; #endif QPixmap tmp(1, 1); tmp.fill(Qt::gray); return tmp; } case KoImageDataPrivate::StateNotLoaded: image(); // forces load Q_FALLTHROUGH(); case KoImageDataPrivate::StateImageLoaded: case KoImageDataPrivate::StateImageOnly: if (!d->image.isNull()) { // create pixmap from image. // this is the highest quality and lowest memory usage way of doing the conversion. d->pixmap = QPixmap::fromImage(d->image.scaled(wantedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } if (d->dataStoreState == KoImageDataPrivate::StateImageLoaded) { - if (d->cleanCacheTimer->isActive()) - d->cleanCacheTimer->stop(); + if (d->cleanCacheTimer.isActive()) + d->cleanCacheTimer.stop(); // schedule an auto-unload of the big QImage in a second. - d->cleanCacheTimer->start(); + d->cleanCacheTimer.start(); } } return d->pixmap; } bool KoImageData::hasCachedPixmap() const { return d && !d->pixmap.isNull(); } QSizeF KoImageData::imageSize() { if (!d->imageSize.isValid()) { // The imagesize have not yet been calculated if (image().isNull()) // auto loads the image return QSizeF(100, 100); if (d->image.dotsPerMeterX()) d->imageSize.setWidth(DM_TO_POINT(d->image.width() / (qreal) d->image.dotsPerMeterX() * 10.0)); else d->imageSize.setWidth(d->image.width() / 72.0); if (d->image.dotsPerMeterY()) d->imageSize.setHeight(DM_TO_POINT(d->image.height() / (qreal) d->image.dotsPerMeterY() * 10.0)); else d->imageSize.setHeight(d->image.height() / 72.0); } return d->imageSize; } -// XXX: why const here? QImage KoImageData::image() const { if (d->dataStoreState == KoImageDataPrivate::StateNotLoaded) { // load image if (d->temporaryFile) { bool r = d->temporaryFile->open(); if (!r) { d->errorCode = OpenFailed; } else if (d->errorCode == Success && !d->image.load(d->temporaryFile->fileName(), d->suffix.toLatin1())) { d->errorCode = OpenFailed; } d->temporaryFile->close(); } else { if (d->errorCode == Success && !d->image.load(d->imageLocation.toLocalFile())) { d->errorCode = OpenFailed; } } if (d->errorCode == Success) { d->dataStoreState = KoImageDataPrivate::StateImageLoaded; } } return d->image; } bool KoImageData::hasCachedImage() const { return d && !d->image.isNull(); } void KoImageData::setImage(const QImage &image, KoImageCollection *collection) { qint64 oldKey = 0; if (d) { oldKey = d->key; } Q_ASSERT(!image.isNull()); if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and well go to the other clause KoImageData *other = collection->createImageData(image); this->operator=(*other); delete other; } else { if (d == 0) { - d = new KoImageDataPrivate(); + d = new KoImageDataPrivate(this); + d->refCount.ref(); } delete d->temporaryFile; d->temporaryFile = 0; d->clear(); d->suffix = "png"; // good default for non-lossy storage. if (image.byteCount() > MAX_MEMORY_IMAGESIZE) { // store image QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (!image.save(&buffer, d->suffix.toLatin1())) { warnFlake << "Write temporary file failed"; d->errorCode = StorageFailed; delete d->temporaryFile; d->temporaryFile = 0; return; } buffer.close(); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } else { d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // use .png for images we get as QImage QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(ba); d->key = KoImageDataPrivate::generateKey(md5.result()); } if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } void KoImageData::setImage(const QString &url, KoStore *store, KoImageCollection *collection) { if (collection) { // Let the collection first check if it already has one. If it // doesn't it'll call this method again and we'll go to the // other clause. KoImageData *other = collection->createImageData(url, store); this->operator=(*other); delete other; } else { if (d == 0) { - d = new KoImageDataPrivate(); + d = new KoImageDataPrivate(this); + d->refCount.ref(); } else { d->clear(); } d->setSuffix(url); if (store->open(url)) { struct Finalizer { ~Finalizer() { store->close(); } KoStore *store; }; Finalizer closer; closer.store = store; KoStoreDevice device(store); const bool lossy = url.endsWith(".jpg", Qt::CaseInsensitive) || url.endsWith(".gif", Qt::CaseInsensitive); if (!lossy && device.size() < MAX_MEMORY_IMAGESIZE) { QByteArray data = device.readAll(); if (d->image.loadFromData(data)) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(data); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } d->dataStoreState = KoImageDataPrivate::StateImageOnly; return; } } if (!device.open(QIODevice::ReadOnly)) { warnFlake << "open file from store " << url << "failed"; d->errorCode = OpenFailed; return; } d->copyToTemporary(device); } else { warnFlake << "Find file in store " << url << "failed"; d->errorCode = OpenFailed; return; } } } void KoImageData::setImage(const QByteArray &imageData, KoImageCollection *collection) { if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and we'll go to the other clause KoImageData *other = collection->createImageData(imageData); this->operator=(*other); delete other; } else { if (d == 0) { - d = new KoImageDataPrivate(); + d = new KoImageDataPrivate(this); + d->refCount.ref(); } d->suffix = "png"; // good default for non-lossy storage. if (imageData.size() <= MAX_MEMORY_IMAGESIZE) { QImage image; if (!image.loadFromData(imageData)) { // mark the image as invalid, but keep the data in memory // even if Calligra cannot handle the format, the data should // be retained d->errorCode = OpenFailed; } d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; } if (imageData.size() > MAX_MEMORY_IMAGESIZE || d->errorCode == OpenFailed) { d->image = QImage(); // store image data QBuffer buffer; buffer.setData(imageData); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(imageData); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } bool KoImageData::isValid() const { return d && d->dataStoreState != KoImageDataPrivate::StateEmpty && d->errorCode == Success; } -KoImageDataPrivate *KoImageData::priv() -{ - return d.data(); -} - bool KoImageData::operator==(const KoImageData &other) const { return other.d == d; } KoImageData &KoImageData::operator=(const KoImageData &other) { + if (other.d) + other.d->refCount.ref(); + if (d && !d->refCount.deref()) + delete d; d = other.d; return *this; } KoShapeUserData *KoImageData::clone() const { return new KoImageData(*this); } qint64 KoImageData::key() const { return d->key; } QString KoImageData::suffix() const { return d->suffix; } KoImageData::ErrorCode KoImageData::errorCode() const { return d->errorCode; } bool KoImageData::saveData(QIODevice &device) { return d->saveData(device); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoImageData.cpp" diff --git a/libs/flake/KoImageData.h b/libs/flake/KoImageData.h index 1648523f19..9369e7067a 100644 --- a/libs/flake/KoImageData.h +++ b/libs/flake/KoImageData.h @@ -1,141 +1,141 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOIMAGEDATA_H #define KOIMAGEDATA_H #include "kritaflake_export.h" #include #include -#include #include class QIODevice; class QPixmap; class QImage; class QSizeF; class QUrl; class KoImageCollection; class KoImageDataPrivate; class KoStore; /** * This class is meant to represent the image data so it can be shared between image shapes. * * This class inherits from KoShapeUserData which means you can set it on any KoShape using * KoShape::setUserData() and get it using KoShape::userData(). The pictureshape plugin * uses this class to show its image data. * * Plugins should not make a copy of the pixmap data, but use the pixmap() method, which * handles caching. */ class KRITAFLAKE_EXPORT KoImageData : public KoShapeUserData { Q_OBJECT public: /// Various error codes representing what has gone wrong enum ErrorCode { Success, OpenFailed, StorageFailed, ///< This is set if the image data has to be stored on disk in a temporary file, but we failed to do so LoadFailed }; /// default constructor, creates an invalid imageData object KoImageData(); ~KoImageData() override; KoImageData(const KoImageData &imageData); KoImageData &operator=(const KoImageData &other); inline bool operator!=(const KoImageData &other) const { return !operator==(other); } bool operator==(const KoImageData &other) const; KoShapeUserData* clone() const override; void setImage(const QString &location, KoStore *store, KoImageCollection *collection = 0); /** * Renders a pixmap the first time you request it is called and returns it. * @returns the cached pixmap * @see isValid(), hasCachedPixmap() */ QPixmap pixmap(const QSize &targetSize = QSize()); /** * Return the internal store of the image. * @see isValid(), hasCachedImage() */ QImage image() const; /** * The size of the image in points */ QSizeF imageSize(); /** * Save the image data to the param device. * The full file is saved. * @param device the device that is used to get the data from. * @return returns true if load was successful. */ bool saveData(QIODevice &device); /** * Get a unique key of the image data */ qint64 key() const; /// @return the original image file's extension, e.g. "png" or "gif" QString suffix() const; ErrorCode errorCode() const; /// returns if this is a valid imageData with actual image data present on it. bool isValid() const; /// \internal - KoImageDataPrivate *priv(); + KoImageDataPrivate *priv() { return d; } private: friend class KoImageCollection; friend class TestImageCollection; KoImageData(KoImageDataPrivate *priv); /// returns true only if pixmap() would return immediately with a cached pixmap bool hasCachedPixmap() const; /// returns true only if image() would return immediately with a cached image bool hasCachedImage() const; void setImage(const QImage &image, KoImageCollection *collection = 0); void setImage(const QByteArray &imageData, KoImageCollection *collection = 0); private: - QSharedDataPointer d; + KoImageDataPrivate *d; + Q_PRIVATE_SLOT(d, void cleanupImageCache()) }; Q_DECLARE_METATYPE(KoImageData*) #endif diff --git a/libs/flake/KoImageData_p.cpp b/libs/flake/KoImageData_p.cpp index a138e303ea..c01fca9fcc 100644 --- a/libs/flake/KoImageData_p.cpp +++ b/libs/flake/KoImageData_p.cpp @@ -1,163 +1,164 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 C. Boemann * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoImageData_p.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include -KoImageDataPrivate::KoImageDataPrivate() +KoImageDataPrivate::KoImageDataPrivate(KoImageData *q) : collection(0), errorCode(KoImageData::Success), key(0), + refCount(0), dataStoreState(StateEmpty), temporaryFile(0) { - cleanCacheTimer->setSingleShot(true); - cleanCacheTimer->setInterval(1000); - QObject::connect(cleanCacheTimer.data(), &QTimer::timeout, [&]() { cleanupImageCache(); }); + cleanCacheTimer.setSingleShot(true); + cleanCacheTimer.setInterval(1000); + QObject::connect(&cleanCacheTimer, SIGNAL(timeout()), q, SLOT(cleanupImageCache())); } KoImageDataPrivate::~KoImageDataPrivate() { if (collection) collection->removeOnKey(key); delete temporaryFile; } // called from the collection bool KoImageDataPrivate::saveData(QIODevice &device) { // if we have a temp file save that to the store. This is needed as to not lose data when // saving lossy formats. Also writing out gif is not supported by qt so saving temp file // also fixes the problem that gif images are empty after saving. if (temporaryFile) { if (!temporaryFile->open()) { warnFlake << "Read file from temporary store failed"; return false; } char buf[4096]; while (true) { temporaryFile->waitForReadyRead(-1); qint64 bytes = temporaryFile->read(buf, sizeof(buf)); if (bytes <= 0) break; // done! do { qint64 nWritten = device.write(buf, bytes); if (nWritten == -1) { temporaryFile->close(); return false; } bytes -= nWritten; } while (bytes > 0); } temporaryFile->close(); return true; } switch (dataStoreState) { case KoImageDataPrivate::StateEmpty: return false; case KoImageDataPrivate::StateNotLoaded: // we should not reach this state as above this will already be saved. Q_ASSERT(temporaryFile); return true; case KoImageDataPrivate::StateImageLoaded: case KoImageDataPrivate::StateImageOnly: { // save image QBuffer buffer; QImageWriter writer(&buffer, suffix.toLatin1()); bool result = writer.write(image); device.write(buffer.data(), buffer.size()); return result; } } return false; } void KoImageDataPrivate::setSuffix(const QString &name) { QFileInfo fi(name); suffix = fi.suffix(); } void KoImageDataPrivate::copyToTemporary(QIODevice &device) { delete temporaryFile; temporaryFile = new QTemporaryFile(QDir::tempPath() + "/" + qAppName() + QLatin1String("_XXXXXX")); if (!temporaryFile->open()) { warnFlake << "open temporary file for writing failed"; errorCode = KoImageData::StorageFailed; return; } QCryptographicHash md5(QCryptographicHash::Md5); char buf[8096]; while (true) { device.waitForReadyRead(-1); qint64 bytes = device.read(buf, sizeof(buf)); if (bytes <= 0) break; // done! md5.addData(buf, bytes); do { bytes -= temporaryFile->write(buf, bytes); } while (bytes > 0); } key = KoImageDataPrivate::generateKey(md5.result()); temporaryFile->close(); dataStoreState = StateNotLoaded; } void KoImageDataPrivate::cleanupImageCache() { if (dataStoreState == KoImageDataPrivate::StateImageLoaded) { image = QImage(); dataStoreState = KoImageDataPrivate::StateNotLoaded; } } void KoImageDataPrivate::clear() { errorCode = KoImageData::Success; dataStoreState = StateEmpty; imageLocation.clear(); imageSize = QSizeF(); key = 0; image = QImage(); pixmap = QPixmap(); } qint64 KoImageDataPrivate::generateKey(const QByteArray &bytes) { qint64 answer = 1; const int max = qMin(8, bytes.count()); for (int x = 0; x < max; ++x) answer += qint64(bytes[x] << (8 * x)); return answer; } diff --git a/libs/flake/KoImageData_p.h b/libs/flake/KoImageData_p.h index 53b24c3b03..38502e52cb 100644 --- a/libs/flake/KoImageData_p.h +++ b/libs/flake/KoImageData_p.h @@ -1,93 +1,91 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 C. Boemann * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOIMAGEDATA_P_H #define KOIMAGEDATA_P_H #include #include #include #include #include #include -#include - -#include #include "KoImageData.h" class KoImageCollection; class QTemporaryFile; -class KoImageDataPrivate : public QSharedData +class KoImageDataPrivate { public: - KoImageDataPrivate(); + explicit KoImageDataPrivate(KoImageData *q); virtual ~KoImageDataPrivate(); /** * Save the image data to the param device. * The full file is saved. * @param device the device that is used to get the data from. * @return returns true if load was successful. */ bool saveData(QIODevice &device); /// store the suffix based on the full filename. void setSuffix(const QString &fileName); /// take the data from \a device and store it in the temporaryFile void copyToTemporary(QIODevice &device); /// clean the image cache. void cleanupImageCache(); void clear(); static qint64 generateKey(const QByteArray &bytes); enum DataStoreState { StateEmpty, ///< No image data, either as url or as QImage StateNotLoaded, ///< Image data is set as Url StateImageLoaded,///< Image data is loaded from Url, so both are present. StateImageOnly ///< Image data is stored in a QImage. There is no external storage. }; KoImageCollection *collection; - mutable KoImageData::ErrorCode errorCode; + KoImageData::ErrorCode errorCode; QSizeF imageSize; qint64 key; QString suffix; // the suffix of the picture e.g. png TODO use a QByteArray ? - KisNewOnCopy cleanCacheTimer; + QTimer cleanCacheTimer; + + QAtomicInt refCount; // Image data store. - mutable DataStoreState dataStoreState; + DataStoreState dataStoreState; QUrl imageLocation; - // XXX this should not be needed - mutable QImage image; + QImage image; /// screen optimized cached version. QPixmap pixmap; QTemporaryFile *temporaryFile; }; #endif /* KOIMAGEDATA_P_H */