Warning, file /office/calligra/libs/flake/KoImageData.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2007, 2009 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
0004  * Copyright (C) 2008 C. Boemann <cbo@boemann.dk>
0005  * Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
0006  *
0007  * This library is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Library General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Library General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Library General Public License
0018  * along with this library; see the file COPYING.LIB.  If not, write to
0019  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  * Boston, MA 02110-1301, USA.
0021  */
0022 
0023 #include "KoImageData.h"
0024 #include "KoImageData_p.h"
0025 
0026 #include "KoImageCollection.h"
0027 
0028 #include <KoUnit.h>
0029 #include <KoStore.h>
0030 #include <KoStoreDevice.h>
0031 
0032 #include <FlakeDebug.h>
0033 
0034 #include <QBuffer>
0035 #include <QCryptographicHash>
0036 #include <QTemporaryFile>
0037 #include <QPainter>
0038 
0039 /// the maximum amount of bytes the image can be while we store it in memory instead of
0040 /// spooling it to disk in a temp-file.
0041 #define MAX_MEMORY_IMAGESIZE 90000
0042 
0043 KoImageData::KoImageData()
0044     : d(0)
0045 {
0046 }
0047 
0048 KoImageData::KoImageData(const KoImageData &imageData)
0049     : KoShapeUserData(),
0050     d(imageData.d)
0051 {
0052     if (d)
0053         d->refCount.ref();
0054 }
0055 
0056 KoImageData::KoImageData(KoImageDataPrivate *priv)
0057     : d(priv)
0058 {
0059     d->refCount.ref();
0060 }
0061 
0062 KoImageData::~KoImageData()
0063 {
0064     if (d && !d->refCount.deref())
0065         delete d;
0066 }
0067 
0068 QPixmap KoImageData::pixmap(const QSize &size)
0069 {
0070     if (!d) return QPixmap();
0071     QSize wantedSize = size;
0072     if (! wantedSize.isValid()) {
0073         if (d->pixmap.isNull()) // we have a problem, Houston..
0074             wantedSize = QSize(100, 100);
0075         else
0076             wantedSize = d->pixmap.size();
0077     }
0078     if (d->pixmap.isNull() || d->pixmap.size() != wantedSize) {
0079         switch (d->dataStoreState) {
0080         case KoImageDataPrivate::StateEmpty: {
0081 #if 0       // this is not possible as it gets called during the paint method
0082             // and will crash. Therefore create a tmp pixmap and return it.
0083             d->pixmap = QPixmap(1, 1);
0084             QPainter p(&d->pixmap);
0085             p.setPen(QPen(Qt::gray, 0));
0086             p.drawPoint(0, 0);
0087             p.end();
0088             break;
0089 #endif
0090             QPixmap tmp(1, 1);
0091             tmp.fill(Qt::gray);
0092             return tmp;
0093         }
0094         case KoImageDataPrivate::StateNotLoaded:
0095             image(); // forces load
0096             // fall through
0097         case KoImageDataPrivate::StateImageLoaded:
0098         case KoImageDataPrivate::StateImageOnly:
0099             if (!d->image.isNull()) {
0100                 // create pixmap from image.
0101                 // this is the highest quality and lowest memory usage way of doing the conversion.
0102                 d->pixmap = QPixmap::fromImage(d->image.scaled(wantedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
0103             }
0104         }
0105 
0106         if (d->dataStoreState == KoImageDataPrivate::StateImageLoaded) {
0107             if (d->cleanCacheTimer.isActive())
0108                 d->cleanCacheTimer.stop();
0109             // schedule an auto-unload of the big QImage in a second.
0110             d->cleanCacheTimer.start();
0111         }
0112     }
0113     return d->pixmap;
0114 }
0115 
0116 bool KoImageData::hasCachedPixmap() const
0117 {
0118     return d && !d->pixmap.isNull();
0119 }
0120 
0121 QSizeF KoImageData::imageSize()
0122 {
0123     if (!d->imageSize.isValid()) {
0124         // The imagesize have not yet been calculated
0125         if (image().isNull()) // auto loads the image
0126             return QSizeF(100, 100);
0127 
0128         if (d->image.dotsPerMeterX())
0129             d->imageSize.setWidth(DM_TO_POINT(d->image.width() / (qreal) d->image.dotsPerMeterX() * 10.0));
0130         else
0131             d->imageSize.setWidth(d->image.width() / 72.0);
0132 
0133         if (d->image.dotsPerMeterY())
0134             d->imageSize.setHeight(DM_TO_POINT(d->image.height() / (qreal) d->image.dotsPerMeterY() * 10.0));
0135         else
0136             d->imageSize.setHeight(d->image.height() / 72.0);
0137     }
0138     return d->imageSize;
0139 }
0140 
0141 QImage KoImageData::image() const
0142 {
0143     if (d->dataStoreState == KoImageDataPrivate::StateNotLoaded) {
0144         // load image
0145         if (d->temporaryFile) {
0146             bool r = d->temporaryFile->open();
0147             if (!r) {
0148                 d->errorCode = OpenFailed;
0149             }
0150             else if (d->errorCode == Success && !d->image.load(d->temporaryFile->fileName(), d->suffix.toLatin1())) {
0151                 qWarning() << "Failed to open image" << d->temporaryFile->fileName() << "with format" << d->suffix;
0152                 d->errorCode = OpenFailed;
0153             }
0154             d->temporaryFile->close();
0155         } else {
0156             if (d->errorCode == Success && !d->image.load(d->imageLocation.toLocalFile())) {
0157                 d->errorCode = OpenFailed;
0158             }
0159         }
0160         if (d->errorCode == Success) {
0161             d->dataStoreState = KoImageDataPrivate::StateImageLoaded;
0162         }
0163     }
0164     return d->image;
0165 }
0166 
0167 bool KoImageData::hasCachedImage() const
0168 {
0169     return d && !d->image.isNull();
0170 }
0171 
0172 void KoImageData::setImage(const QImage &image, KoImageCollection *collection)
0173 {
0174   qint64 oldKey = 0;
0175   if (d) {
0176     oldKey = d->key;
0177   }
0178   Q_ASSERT(!image.isNull());
0179     if (collection) {
0180         // let the collection first check if it already has one. If it doesn't it'll call this method
0181         // again and well go to the other clause
0182         KoImageData *other = collection->createImageData(image);
0183         this->operator=(*other);
0184         delete other;
0185     } else {
0186         if (d == 0) {
0187             d = new KoImageDataPrivate(this);
0188             d->refCount.ref();
0189         }
0190         delete d->temporaryFile;
0191         d->temporaryFile = 0;
0192         d->clear();
0193         d->suffix = "png"; // good default for non-lossy storage.
0194         if (image.byteCount() > MAX_MEMORY_IMAGESIZE) {
0195             // store image
0196             QBuffer buffer;
0197             buffer.open(QIODevice::WriteOnly);
0198             if (!image.save(&buffer, d->suffix.toLatin1())) {
0199                 warnFlake << "Write temporary file failed";
0200                 d->errorCode = StorageFailed;
0201                 delete d->temporaryFile;
0202                 d->temporaryFile = 0;
0203                 return;
0204             }
0205             buffer.close();
0206             buffer.open(QIODevice::ReadOnly);
0207             d->copyToTemporary(buffer);
0208         } else {
0209             d->image = image;
0210             d->dataStoreState = KoImageDataPrivate::StateImageOnly;
0211 
0212             QByteArray ba;
0213             QBuffer buffer(&ba);
0214             buffer.open(QIODevice::WriteOnly);
0215             image.save(&buffer, "PNG"); // use .png for images we get as QImage
0216             QCryptographicHash md5(QCryptographicHash::Md5);
0217             md5.addData(ba);
0218             d->key = KoImageDataPrivate::generateKey(md5.result());
0219         }
0220         if (oldKey != 0 && d->collection) {
0221             d->collection->update(oldKey, d->key);
0222         }
0223 
0224     }
0225 
0226 }
0227 
0228 void KoImageData::setImage(const QString &url, KoStore *store, KoImageCollection *collection)
0229 {
0230     if (collection) {
0231         // Let the collection first check if it already has one. If it
0232         // doesn't it'll call this method again and we'll go to the
0233         // other clause.
0234         KoImageData *other = collection->createImageData(url, store);
0235         this->operator=(*other);
0236         delete other;
0237     } else {
0238         if (d == 0) {
0239             d = new KoImageDataPrivate(this);
0240             d->refCount.ref();
0241         } else {
0242             d->clear();
0243         }
0244         d->setSuffix(url);
0245 
0246         if (store->open(url)) {
0247             struct Finalizer {
0248                 ~Finalizer() { store->close(); }
0249                 KoStore *store;
0250             };
0251             Finalizer closer;
0252             closer.store = store;
0253             KoStoreDevice device(store);
0254             const bool lossy = url.endsWith(".jpg", Qt::CaseInsensitive) || url.endsWith(".gif", Qt::CaseInsensitive);
0255             if (!lossy && device.size() < MAX_MEMORY_IMAGESIZE) {
0256                 QByteArray data = device.readAll();
0257                 if (d->image.loadFromData(data)) {
0258                     QCryptographicHash md5(QCryptographicHash::Md5);
0259                     md5.addData(data);
0260                     qint64 oldKey = d->key;
0261                     d->key = KoImageDataPrivate::generateKey(md5.result());
0262                     if (oldKey != 0 && d->collection) {
0263                         d->collection->update(oldKey, d->key);
0264                     }
0265                     d->dataStoreState = KoImageDataPrivate::StateImageOnly;
0266                     return;
0267                 }
0268             }
0269             if (!device.open(QIODevice::ReadOnly)) {
0270                 warnFlake << "open file from store " << url << "failed";
0271                 d->errorCode = OpenFailed;
0272                 return;
0273             }
0274             d->copyToTemporary(device);
0275         } else {
0276             warnFlake << "Find file in store " << url << "failed";
0277             d->errorCode = OpenFailed;
0278             return;
0279         }
0280     }
0281 }
0282 
0283 void KoImageData::setImage(const QByteArray &imageData, KoImageCollection *collection)
0284 {
0285     if (collection) {
0286         // let the collection first check if it already has one. If it doesn't it'll call this method
0287         // again and we'll go to the other clause
0288         KoImageData *other = collection->createImageData(imageData);
0289         this->operator=(*other);
0290         delete other;
0291     }
0292     else {
0293         if (d == 0) {
0294             d = new KoImageDataPrivate(this);
0295             d->refCount.ref();
0296         }
0297 
0298         d->suffix = "png"; // good default for non-lossy storage.
0299 
0300         if (imageData.size() <= MAX_MEMORY_IMAGESIZE) {
0301             QImage image;
0302             if (!image.loadFromData(imageData)) {
0303                 // mark the image as invalid, but keep the data in memory
0304                 // even if Calligra cannot handle the format, the data should
0305                 // be retained
0306                 d->errorCode = OpenFailed;
0307             }
0308             d->image = image;
0309             d->dataStoreState = KoImageDataPrivate::StateImageOnly;
0310         }
0311 
0312         if (imageData.size() > MAX_MEMORY_IMAGESIZE
0313                 || d->errorCode == OpenFailed) {
0314             d->image = QImage();
0315             // store image data
0316             QBuffer buffer;
0317             buffer.setData(imageData);
0318             buffer.open(QIODevice::ReadOnly);
0319             d->copyToTemporary(buffer);
0320             d->suffix.clear(); // let QImage find out what the data contains
0321         }
0322 
0323         QCryptographicHash md5(QCryptographicHash::Md5);
0324         md5.addData(imageData);
0325         qint64 oldKey = d->key;
0326         d->key = KoImageDataPrivate::generateKey(md5.result());
0327         if (oldKey != 0 && d->collection) {
0328             d->collection->update(oldKey, d->key);
0329         }
0330 
0331     }
0332 }
0333 
0334 bool KoImageData::isValid() const
0335 {
0336     return d && d->dataStoreState != KoImageDataPrivate::StateEmpty
0337         && d->errorCode == Success;
0338 }
0339 
0340 bool KoImageData::operator==(const KoImageData &other) const
0341 {
0342     return other.d == d;
0343 }
0344 
0345 KoImageData &KoImageData::operator=(const KoImageData &other)
0346 {
0347     if (other.d)
0348         other.d->refCount.ref();
0349     if (d && !d->refCount.deref())
0350         delete d;
0351     d = other.d;
0352     return *this;
0353 }
0354 
0355 qint64 KoImageData::key() const
0356 {
0357     return d->key;
0358 }
0359 
0360 QString KoImageData::suffix() const
0361 {
0362     return d->suffix;
0363 }
0364 
0365 KoImageData::ErrorCode KoImageData::errorCode() const
0366 {
0367     return d->errorCode;
0368 }
0369 
0370 bool KoImageData::saveData(QIODevice &device)
0371 {
0372     return d->saveData(device);
0373 }
0374 
0375 //have to include this because of Q_PRIVATE_SLOT
0376 #include "moc_KoImageData.cpp"