File indexing completed on 2024-05-12 16:34:23

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
0004  * Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
0005  * Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
0006  * Copyright (C) 2012 Inge Wallin <inge@lysator.liu.se>
0007  * Copyright (C) 2012 C.Boemann <cbo@boemann.dk>
0008  *
0009  * This library is free software; you can redistribute it and/or
0010  * modify it under the terms of the GNU Library General Public
0011  * License as published by the Free Software Foundation; either
0012  * version 2 of the License, or (at your option) any later version.
0013  *
0014  * This library is distributed in the hope that it will be useful,
0015  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017  * Library General Public License for more details.
0018  *
0019  * You should have received a copy of the GNU Library General Public License
0020  * along with this library; see the file COPYING.LIB.  If not, write to
0021  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0022  * Boston, MA 02110-1301, USA.
0023  */
0024 
0025 #include "PictureShape.h"
0026 
0027 #include "filters/GreyscaleFilterEffect.h"
0028 #include "filters/MonoFilterEffect.h"
0029 #include "filters/WatermarkFilterEffect.h"
0030 #include "filters/ColoringFilterEffect.h"
0031 #include "filters/GammaFilterEffect.h"
0032 #include "PictureDebug.h"
0033 
0034 #include <KoOdfWorkaround.h>
0035 #include <KoViewConverter.h>
0036 #include <KoImageCollection.h>
0037 #include <KoImageData.h>
0038 #include <KoShapeLoadingContext.h>
0039 #include <KoShapePaintingContext.h>
0040 #include <KoOdfLoadingContext.h>
0041 #include <KoShapeSavingContext.h>
0042 #include <KoXmlWriter.h>
0043 #include <KoXmlNS.h>
0044 #include <KoUnit.h>
0045 #include <KoGenStyle.h>
0046 #include <KoStyleStack.h>
0047 #include <KoFilterEffectStack.h>
0048 #include <KoClipPath.h>
0049 #include <SvgSavingContext.h>
0050 #include <SvgLoadingContext.h>
0051 #include <SvgUtil.h>
0052 #include <KoPathShape.h>
0053 
0054 #include <QPainter>
0055 #include <QTimer>
0056 #include <QPixmapCache>
0057 #include <QThreadPool>
0058 #include <QImage>
0059 #include <QColor>
0060 
0061 QString generate_key(qint64 key, const QSize & size)
0062 {
0063     return QString("%1-%2-%3").arg(key).arg(size.width()).arg(size.height());
0064 }
0065 
0066 // ----------------------------------------------------------------- //
0067 
0068 _Private::PixmapScaler::PixmapScaler(PictureShape *pictureShape, const QSize &pixmapSize):
0069     m_size(pixmapSize)
0070 {
0071     m_image = pictureShape->imageData()->image();
0072     m_imageKey = pictureShape->imageData()->key();
0073     connect(this, SIGNAL(finished(QString,QImage)), &pictureShape->m_proxy, SLOT(setImage(QString,QImage)));
0074 }
0075 
0076 void _Private::PixmapScaler::run()
0077 {
0078     QString key = generate_key(m_imageKey, m_size);
0079 
0080     m_image = m_image.scaled(
0081         m_size.width(),
0082         m_size.height(),
0083         Qt::IgnoreAspectRatio,
0084         Qt::SmoothTransformation
0085     );
0086 
0087     emit finished(key, m_image);
0088 }
0089 
0090 // ----------------------------------------------------------------- //
0091 
0092 void _Private::PictureShapeProxy::setImage(const QString &key, const QImage &image)
0093 {
0094     QPixmapCache::insert(key, QPixmap::fromImage(image));
0095     m_pictureShape->update();
0096 }
0097 
0098 // ----------------------------------------------------------------- //
0099 
0100 QPainterPath _Private::generateOutline(const QImage &imageIn, int threshold)
0101 {
0102     int leftArray[100];
0103     int rightArray[100];
0104 
0105     QImage image = imageIn.scaled(QSize(100, 100));
0106 
0107     QPainterPath path;
0108 
0109     for (int y = 0; y < 100; y++) {
0110         leftArray[y] = -1;
0111         for (int x = 0; x < 100; x++) {
0112             int a = qAlpha(image.pixel(x,y));
0113             if (a > threshold) {
0114                 leftArray[y] = x;
0115                 break;
0116             }
0117         }
0118     }
0119     for (int y = 0; y < 100; y++) {
0120         rightArray[y] = -1;
0121         if (leftArray[y] != -1) {
0122             for (int x = 100-1; x >= 0; x--) {
0123                 int a = qAlpha(image.pixel(x,y));
0124                 if (a > threshold) {
0125                     rightArray[y] = x;
0126                     break;
0127                 }
0128             }
0129         }
0130     }
0131 
0132     // Now we know the outline let's make a path out of it
0133     bool first = true;
0134     for (int y = 0; y < 100; y++) {
0135         if (rightArray[y] != -1) {
0136             if (first) {
0137                 path.moveTo(rightArray[y] / 99.0, y / 99.0);
0138                 first = false;
0139             } else {
0140                 path.lineTo(rightArray[y] / 99.0, y / 99.0);
0141             }
0142         }
0143     }
0144     if (first) {
0145         // Completely empty
0146         return path;
0147     }
0148 
0149     for (int y = 100-1; y >= 0; --y) {
0150         if (leftArray[y] != -1) {
0151             path.lineTo(leftArray[y] / 99.0, y / 99.0);
0152         }
0153     }
0154     return path;
0155 }
0156 
0157 // ----------------------------------------------------------------- //
0158 
0159 PictureShape::PictureShape()
0160     : KoFrameShape(KoXmlNS::draw, "image")
0161     , m_imageCollection(0)
0162     , m_mirrorMode(MirrorNone)
0163     , m_colorMode(Standard)
0164     , m_proxy(this)
0165 {
0166     setKeepAspectRatio(true);
0167     KoFilterEffectStack * effectStack = new KoFilterEffectStack();
0168     effectStack->setClipRect(QRectF(0, 0, 1, 1));
0169     setFilterEffectStack(effectStack);
0170     filterEffectStack()->insertFilterEffect(0, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); // let's just set 3
0171     filterEffectStack()->insertFilterEffect(1, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); // no-op filters
0172     filterEffectStack()->insertFilterEffect(2, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect"));
0173 }
0174 
0175 KoImageData* PictureShape::imageData() const
0176 {
0177     return qobject_cast<KoImageData*>(userData());
0178 }
0179 
0180 QRectF PictureShape::cropRect() const
0181 {
0182     return m_clippingRect.toRect();
0183 }
0184 
0185 bool PictureShape::isPictureInProportion() const
0186 {
0187     QSizeF clippingRectSize(
0188         imageData()->imageSize().width() * m_clippingRect.width(),
0189         imageData()->imageSize().height() * m_clippingRect.height()
0190     );
0191 
0192     qreal shapeAspect = size().width() / size().height();
0193     qreal rectAspect = clippingRectSize.width() / clippingRectSize.height();
0194 
0195     return qAbs(shapeAspect - rectAspect) <= 0.025;
0196 }
0197 
0198 void PictureShape::setCropRect(const QRectF& rect)
0199 {
0200     m_clippingRect.setRect(rect, true);
0201     update();
0202 }
0203 
0204 QSize PictureShape::calcOptimalPixmapSize(const QSizeF& shapeSize, const QSizeF& imageSize) const
0205 {
0206     qreal imageAspect = imageSize.width() / imageSize.height();
0207     qreal shapeAspect = shapeSize.width() / shapeSize.height();
0208     qreal scale = 1.0;
0209 
0210     if (shapeAspect > imageAspect) {
0211         scale = shapeSize.width()  / imageSize.width()  / m_clippingRect.width();
0212     }
0213     else {
0214         scale = shapeSize.height() / imageSize.height() / m_clippingRect.height();
0215     }
0216 
0217     scale = qMin<qreal>(1.0, scale); // prevent upscaling
0218     return (imageSize * scale).toSize();
0219 }
0220 
0221 ClippingRect PictureShape::parseClippingRectString(const QString &originalString) const
0222 {
0223     ClippingRect rect;
0224     QString string = originalString.trimmed();
0225 
0226     if (string.startsWith(QLatin1String("rect(")) &&
0227         string.endsWith(QLatin1Char(')'))) {
0228         // remove "rect(" & ")"
0229         string.remove(0,5).chop(1);
0230 
0231 #ifndef NWORKAROUND_ODF_BUGS
0232         KoOdfWorkaround::fixClipRectOffsetValuesString(string);
0233 #endif
0234         // split into the 4 values
0235         const QStringList valueStrings = string.split(QLatin1Char(','));
0236 
0237         if (valueStrings.count() != 4) {
0238             warnPicture << "Not exactly 4 values for attribute fo:clip=rect(...):" << originalString << ", please report.";
0239             // hard to guess which value is for which offset, so just cancel parsing and return with the default rect
0240             return rect;
0241         }
0242 
0243         // default is 0.0 for all offsets
0244         qreal values[4] = { 0, 0, 0, 0 };
0245         const QLatin1String autoValueString("auto");
0246 
0247         for (int i=0; i<4; ++i) {
0248             const QString valueString = valueStrings.at(i).trimmed();
0249             // "auto" means: keep default 0.0
0250             if (valueString != autoValueString) {
0251                 values[i] = KoUnit::parseValue(valueString, 0.0);
0252             }
0253         }
0254 
0255         rect.top = values[0];
0256         rect.right = values[1];
0257         rect.bottom = values[2];
0258         rect.left = values[3];
0259         rect.uniform = false;
0260         rect.inverted = true;
0261     }
0262 
0263     return rect;
0264 }
0265 
0266 QPainterPath PictureShape::shadowOutline() const
0267 {
0268     // Always return an outline for a shadow even if no fill is defined.
0269     return outline();
0270 }
0271 
0272 void PictureShape::paint(QPainter &painter, const KoViewConverter &converter,
0273                          KoShapePaintingContext &paintContext)
0274 {
0275     Q_UNUSED(paintContext);
0276 
0277     QRectF viewRect = converter.documentToView(QRectF(QPointF(0,0), size()));
0278     if (imageData() == 0) {
0279         painter.fillRect(viewRect, QColor(Qt::gray));
0280         return;
0281     }
0282 
0283     painter.save();
0284     applyConversion(painter, converter);
0285     paintBorder(painter, converter);
0286     painter.restore();
0287 
0288     QSize pixmapSize = calcOptimalPixmapSize(viewRect.size(), imageData()->image().size());
0289 
0290     // Normalize the clipping rect if it isn't already done.
0291     m_clippingRect.normalize(imageData()->imageSize());
0292 
0293     // Handle style:mirror, i.e. mirroring horizontally and/or vertically.
0294     // 
0295     // NOTE: At this time we don't handle HorizontalOnEven
0296     //       and HorizontalOnOdd, which have to know which
0297     //       page they are on.  In those cases we treat it as
0298     //       no horizontal mirroring at all.
0299     bool   doFlip = false;
0300     QSizeF shapeSize = size();
0301     QSizeF viewSize = converter.documentToView(shapeSize);
0302     qreal  midpointX = 0.0;
0303     qreal  midpointY = 0.0;
0304     qreal  scaleX = 1.0;
0305     qreal  scaleY = 1.0;
0306     if (m_mirrorMode & MirrorHorizontal) {
0307         midpointX = viewSize.width() / qreal(2.0);
0308         scaleX = -1.0;
0309         doFlip = true;
0310     }
0311     if (m_mirrorMode & MirrorVertical) {
0312         midpointY = viewSize.height() / qreal(2.0);
0313         scaleY = -1.0;
0314         doFlip = true;
0315     }
0316     if (doFlip) {
0317         QTransform outputTransform = painter.transform();
0318         QTransform worldTransform  = QTransform();
0319 
0320         //debugPicture << "Flipping" << midpointX << midpointY << scaleX << scaleY;
0321         worldTransform.translate(midpointX, midpointY);
0322         worldTransform.scale(scaleX, scaleY);
0323         worldTransform.translate(-midpointX, -midpointY);
0324         //debugPicture << "After flipping for window" << worldTransform;
0325 
0326         QTransform newTransform = worldTransform * outputTransform;
0327         painter.setWorldTransform(newTransform);
0328     }
0329 
0330     // Paint the image as prepared in waitUntilReady()
0331     if (!m_printQualityImage.isNull() && pixmapSize != m_printQualityRequestedSize) {
0332         QSizeF imageSize = m_printQualityImage.size();
0333         QRectF cropRect(
0334             imageSize.width()  * m_clippingRect.left,
0335             imageSize.height() * m_clippingRect.top,
0336             imageSize.width()  * m_clippingRect.width(),
0337             imageSize.height() * m_clippingRect.height()
0338         );
0339 
0340         painter.drawImage(viewRect, m_printQualityImage, cropRect);
0341         m_printQualityImage = QImage(); // free memory
0342     }
0343     else {
0344         QPixmap pixmap;
0345         QString key(generate_key(imageData()->key(), pixmapSize));
0346 
0347         // If the required pixmap is not in the cache
0348         // launch a task in a background thread that scales
0349         // the source image to the required size
0350         if (!QPixmapCache::find(key, &pixmap)) {
0351             QThreadPool::globalInstance()->start(new _Private::PixmapScaler(this, pixmapSize));
0352             painter.fillRect(viewRect, QColor(Qt::gray)); // just paint a gray rect as long as we don't have the required pixmap
0353         }
0354         else {
0355             QRectF cropRect(
0356                 pixmapSize.width()  * m_clippingRect.left,
0357                 pixmapSize.height() * m_clippingRect.top,
0358                 pixmapSize.width()  * m_clippingRect.width(),
0359                 pixmapSize.height() * m_clippingRect.height()
0360             );
0361 
0362             painter.drawPixmap(viewRect, pixmap, cropRect);
0363         }
0364     }
0365 }
0366 
0367 void PictureShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const
0368 {
0369     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
0370     if (imageData == 0) {
0371         return;
0372     }
0373 
0374     if (asynchronous) {
0375         // get pixmap and schedule it if not
0376         QSize pixels = converter.documentToView(QRectF(QPointF(0,0), size())).size().toSize();
0377         QImage image = imageData->image();
0378         if (image.isNull()) {
0379             return;
0380         }
0381         m_printQualityRequestedSize = pixels;
0382         if (image.size().width() < pixels.width()) { // don't scale up.
0383             pixels = image.size();
0384         }
0385         m_printQualityImage = image.scaled(pixels, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0386     }
0387     else {
0388         QSize pixmapSize = calcOptimalPixmapSize(converter.documentToView(QRectF(QPointF(0,0), size())).size(), imageData->image().size());
0389         QString key(generate_key(imageData->key(), pixmapSize));
0390         if (QPixmapCache::find(key) == 0) {
0391             QPixmap pixmap = imageData->pixmap(pixmapSize);
0392             QPixmapCache::insert(key, pixmap);
0393         }
0394     }
0395 }
0396 
0397 void PictureShape::saveOdf(KoShapeSavingContext &context) const
0398 {
0399     // make sure we have a valid image data pointer before saving
0400     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
0401     if (imageData == 0) {
0402         return;
0403     }
0404 
0405     KoXmlWriter &writer = context.xmlWriter();
0406 
0407     writer.startElement("draw:frame");
0408     saveOdfAttributes(context, OdfAllAttributes);
0409     writer.startElement("draw:image");
0410     // In the spec, only the xlink:href attribute is marked as mandatory, cool :)
0411     QString name = context.imageHref(imageData);
0412     writer.addAttribute("xlink:type", "simple");
0413     writer.addAttribute("xlink:show", "embed");
0414     writer.addAttribute("xlink:actuate", "onLoad");
0415     writer.addAttribute("xlink:href", name);
0416     saveText(context);
0417     writer.endElement(); // draw:image
0418     QSizeF scaleFactor(imageData->imageSize().width() / size().width(),
0419                   imageData->imageSize().height() / size().height());
0420     saveOdfClipContour(context, scaleFactor);
0421     writer.endElement(); // draw:frame
0422 
0423     context.addDataCenter(m_imageCollection);
0424 }
0425 
0426 bool PictureShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
0427 {
0428     loadOdfAttributes(element, context, OdfAllAttributes);
0429 
0430     if (loadOdfFrame(element, context)) {
0431         // load contour (clip)
0432         KoImageData *imageData = qobject_cast<KoImageData*>(userData());
0433         Q_ASSERT(imageData);
0434 
0435         QSizeF scaleFactor(size().width() / imageData->imageSize().width(),
0436                  size().height() / imageData->imageSize().height());
0437 
0438         loadOdfClipContour(element, context, scaleFactor);
0439         // this is needed so that the image is already normalized when calling waitUntilReady e.g. by cstester
0440         m_clippingRect.normalize(imageData->imageSize());
0441 
0442         return true;
0443     }
0444     return false;
0445 }
0446 
0447 static const char *s_emptyImageXpm[] = {
0448     /* <Values> */
0449     /* <width/columns> <height/rows> <colors> <chars per pixel>*/
0450     "16 16 2 1",
0451     /* <Colors> */
0452     "a c #ffffff",
0453     "b c #000000",
0454     /* <Pixels> */
0455     "bbbbbbbbbbbbbbbb",
0456     "baaaaaaaaaaaaaab",
0457     "baaaaaaaaaaaaaab",
0458     "baaaaaaaaaaaaaab",
0459     "baaaaaaaaaaaaaab",
0460     "baaaaaaaaaaaaaab",
0461     "baaaaaaaaaaaaaab",
0462     "baaaaaaaaaaaaaab",
0463     "baaaaaaaaaaaaaab",
0464     "baaaaaaaaaaaaaab",
0465     "baaaaaaaaaaaaaab",
0466     "baaaaaaaaaaaaaab",
0467     "baaaaaaaaaaaaaab",
0468     "baaaaaaaaaaaaaab",
0469     "baaaaaaaaaaaaaab",
0470     "bbbbbbbbbbbbbbbb"
0471 };
0472 
0473 // image formats (possibly) supported by Qt
0474 // According to docs, only jpg and png are checked for by default
0475 const int s_numImageFormats = 10;
0476 const char *s_imageFormat[s_numImageFormats] = { "jpg", "jpeg", "png", "bmp", "gif", "pbm", "pgm", "ppm", "xbm", "xpm" };
0477 
0478 bool PictureShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context)
0479 {
0480     if (m_imageCollection) {
0481         const QString href = element.attribute("href");
0482         // this can happen in case it is a presentation:placeholder
0483         if (!href.isEmpty()) {
0484             KoStore *store = context.odfLoadingContext().store();
0485             KoImageData *data = m_imageCollection->createImageData(href, store);
0486             setUserData(data);
0487         } else {
0488             // check if we have an office:binary data element containing the image data
0489             const KoXmlElement &binaryData(KoXml::namedItemNS(element, KoXmlNS::office, "binary-data"));
0490             if (!binaryData.isNull()) {
0491                 QImage image;
0492                 for (int i = 0; i < s_numImageFormats; ++i) {
0493                     if (image.loadFromData(QByteArray::fromBase64(binaryData.text().toLatin1()), s_imageFormat[i])) {
0494                         KoImageData *data = m_imageCollection->createImageData(image);
0495                         setUserData(data);
0496                         debugPicture<<"Found image format:"<<s_imageFormat[i];
0497                         break;
0498                     }
0499                 }
0500             } else {
0501                 debugPicture<<"No image binary data";
0502             }
0503         }
0504         if (!userData()) {
0505             // We must crate an image or else things crashes later on
0506             warnPicture<<"Could not find an image, creating an empty one";
0507             KoImageData *data = m_imageCollection->createImageData(QImage(s_emptyImageXpm));
0508             setUserData(data);
0509         }
0510     }
0511 
0512     loadText(element, context);
0513 
0514     return true;
0515 }
0516 
0517 KoImageCollection *PictureShape::imageCollection() const
0518 {
0519     return m_imageCollection;
0520 }
0521 
0522 QString PictureShape::saveStyle(KoGenStyle& style, KoShapeSavingContext& context) const
0523 {
0524     if(transparency() > 0.0) {
0525         style.addProperty("draw:image-opacity", QString("%1%").arg((1.0 - transparency()) * 100.0));
0526     }
0527 
0528     // this attribute is need to work around a bug in LO 3.4 to make it recognize us as an
0529     // image and not just any shape. But we shouldn't produce illegal odf so: only for testing!
0530     // style.addAttribute("style:parent-style-name", "dummy");
0531 
0532     // Mirroring
0533     if (m_mirrorMode != MirrorNone) {
0534         QString mode;
0535 
0536         if (m_mirrorMode & MirrorHorizontal)
0537             mode = "horizontal";
0538         else if (m_mirrorMode & MirrorHorizontalOnEven)
0539             mode = "horizontal-on-even";
0540         else if (m_mirrorMode & MirrorHorizontalOnOdd)
0541             mode = "horizontal-on-odd";
0542 
0543         if (m_mirrorMode & MirrorVertical) {
0544             if (!mode.isEmpty())
0545                 mode += ' ';
0546             mode += "vertical";
0547         }
0548 
0549         style.addProperty("style:mirror", mode);
0550     }
0551 
0552     switch(m_colorMode)
0553     {
0554     case Standard:
0555         style.addProperty("draw:color-mode", "standard");
0556         break;
0557     case Greyscale:
0558         style.addProperty("draw:color-mode", "greyscale");
0559         break;
0560     case Watermark:
0561         style.addProperty("draw:color-mode", "watermark");
0562         break;
0563     case Mono:
0564         style.addProperty("draw:color-mode", "mono");
0565         break;
0566     }
0567 
0568     if (ColoringFilterEffect *cEffect = dynamic_cast<ColoringFilterEffect *>(filterEffectStack()->filterEffects()[1])) {
0569         style.addProperty("draw:red", QString("%1%").arg(100*cEffect->red()));
0570         style.addProperty("draw:green", QString("%1%").arg(100*cEffect->green()));
0571         style.addProperty("draw:blue", QString("%1%").arg(100*cEffect->blue()));
0572         style.addProperty("draw:luminance", QString("%1%").arg(100*cEffect->luminance()));
0573         style.addProperty("draw:contrast", QString("%1%").arg(100*cEffect->contrast()));
0574     }
0575 
0576     if (GammaFilterEffect *gEffect = dynamic_cast<GammaFilterEffect *>(filterEffectStack()->filterEffects()[2])) {
0577         style.addProperty("draw:gamma", QString("%1%").arg(100*gEffect->gamma()));
0578     }
0579 
0580     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
0581 
0582     if (imageData != 0) {
0583         QSizeF imageSize = imageData->imageSize();
0584         ClippingRect rect = m_clippingRect;
0585 
0586         rect.normalize(imageSize);
0587         rect.bottom = 1.0 - rect.bottom;
0588         rect.right = 1.0 - rect.right;
0589 
0590         if (!qFuzzyCompare(rect.left + rect.right + rect.top + rect.bottom, qreal(0))) {
0591             style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)")
0592                 .arg(rect.top * imageSize.height())
0593                 .arg(rect.right * imageSize.width())
0594                 .arg(rect.bottom * imageSize.height())
0595                 .arg(rect.left * imageSize.width())
0596             );
0597         }
0598     }
0599 
0600     return KoTosContainer::saveStyle(style, context);
0601 }
0602 
0603 void PictureShape::loadStyle(const KoXmlElement& element, KoShapeLoadingContext& context)
0604 {
0605     // Load the common parts of the style.
0606     KoTosContainer::loadStyle(element, context);
0607 
0608     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0609     styleStack.setTypeProperties("graphic");
0610 
0611     // Mirroring
0612     if (styleStack.hasProperty(KoXmlNS::style, "mirror")) {
0613         QString mirrorMode = styleStack.property(KoXmlNS::style, "mirror");
0614 
0615         QFlags<PictureShape::MirrorMode>  mode = 0;
0616 
0617         // Only one of the horizontal modes
0618         if (mirrorMode.contains("horizontal-on-even")) {
0619             mode |= MirrorHorizontalOnEven;
0620         }
0621         else if (mirrorMode.contains("horizontal-on-odd")) {
0622             mode |= MirrorHorizontalOnOdd;
0623         }
0624         else if (mirrorMode.contains("horizontal")) {
0625             mode |= MirrorHorizontal;
0626         }
0627 
0628         if (mirrorMode.contains("vertical")) {
0629             mode |= MirrorVertical;
0630         }
0631 
0632         m_mirrorMode = mode;
0633     }
0634 
0635     // Color-mode (effects)
0636     if (styleStack.hasProperty(KoXmlNS::draw, "color-mode")) {
0637         QString colorMode = styleStack.property(KoXmlNS::draw, "color-mode");
0638         if (colorMode == "greyscale") {
0639             setColorMode(Greyscale);
0640         }
0641         else if (colorMode == "mono") {
0642             setColorMode(Mono);
0643         }
0644         else if (colorMode == "watermark") {
0645             setColorMode(Watermark);
0646         }
0647     }
0648 
0649     QString red = styleStack.property(KoXmlNS::draw, "red");
0650     QString green = styleStack.property(KoXmlNS::draw, "green");
0651     QString blue = styleStack.property(KoXmlNS::draw, "blue");
0652     QString luminance = styleStack.property(KoXmlNS::draw, "luminance");
0653     QString contrast = styleStack.property(KoXmlNS::draw, "contrast");
0654     setColoring(red.right(1) == "%" ? (red.left(red.length() - 1).toDouble() / 100.0) : 0.0
0655               , green.right(1) == "%" ? (green.left(green.length() - 1).toDouble() / 100.0) : 0.0
0656               , blue.right(1) == "%" ? (blue.left(blue.length() - 1).toDouble() / 100.0) : 0.0
0657               , luminance.right(1) == "%" ? (luminance.left(luminance.length() - 1).toDouble() / 100.0) : 0.0
0658               , contrast.right(1) == "%" ? (contrast.left(contrast.length() - 1).toDouble() / 100.0) : 0.0);
0659 
0660     QString gamma = styleStack.property(KoXmlNS::draw, "gamma");
0661     setGamma(gamma.right(1) == "%" ? (gamma.left(gamma.length() - 1).toDouble() / 100.0) : 0.0);
0662 
0663     // image opacity
0664     QString opacity(styleStack.property(KoXmlNS::draw, "image-opacity"));
0665     if (! opacity.isEmpty() && opacity.right(1) == "%") {
0666         setTransparency(1.0 - (opacity.left(opacity.length() - 1).toFloat() / 100.0));
0667     }
0668 
0669     // clip rect
0670     m_clippingRect = parseClippingRectString(styleStack.property(KoXmlNS::fo, "clip"));
0671 }
0672 
0673 QFlags<PictureShape::MirrorMode> PictureShape::mirrorMode() const
0674 {
0675     return m_mirrorMode;
0676 }
0677 
0678 PictureShape::ColorMode PictureShape::colorMode() const
0679 {
0680     return m_colorMode;
0681 }
0682 
0683 void PictureShape::setMirrorMode(QFlags<PictureShape::MirrorMode> mode)
0684 {
0685     // Sanity check
0686     mode &= MirrorMask;
0687 
0688     // Make sure only one bit of the horizontal modes is set.
0689     if (mode & MirrorHorizontal)
0690         mode &= ~(MirrorHorizontalOnEven | MirrorHorizontalOnOdd);
0691     else if (mode & MirrorHorizontalOnEven)
0692         mode &= ~MirrorHorizontalOnOdd;
0693 
0694     // If the mode changes, redraw the image.
0695     if (mode != m_mirrorMode) {
0696         m_mirrorMode = mode;
0697         update();
0698     }
0699 }
0700 
0701 void PictureShape::setColorMode(PictureShape::ColorMode mode)
0702 {
0703     if (mode != m_colorMode) {
0704         filterEffectStack()->removeFilterEffect(0);
0705 
0706         switch(mode)
0707         {
0708         case Greyscale:
0709             filterEffectStack()->insertFilterEffect(0, new GreyscaleFilterEffect());
0710             break;
0711         case Mono:
0712             filterEffectStack()->insertFilterEffect(0, new MonoFilterEffect());
0713             break;
0714         case Watermark:
0715             filterEffectStack()->insertFilterEffect(0, new WatermarkFilterEffect());
0716             break;
0717         case Standard:
0718         default:
0719             filterEffectStack()->insertFilterEffect(0, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect"));
0720             break;
0721         }
0722 
0723         m_colorMode = mode;
0724         update();
0725     }
0726 }
0727 
0728 void PictureShape::setColoring(qreal red, qreal green, qreal blue, qreal luminance, qreal contrast)
0729 {
0730     filterEffectStack()->removeFilterEffect(1);
0731 
0732     ColoringFilterEffect *cEffect = new ColoringFilterEffect();
0733     cEffect->setColoring(red, green, blue, luminance, contrast);
0734 
0735     filterEffectStack()->insertFilterEffect(1, cEffect);
0736 
0737     update();
0738 }
0739 
0740 void PictureShape::setGamma(qreal gamma)
0741 {
0742     filterEffectStack()->removeFilterEffect(2);
0743 
0744     GammaFilterEffect *gEffect = new GammaFilterEffect();
0745     gEffect->setGamma(gamma);
0746 
0747     filterEffectStack()->insertFilterEffect(2, gEffect);
0748 
0749     update();
0750 }
0751 
0752 KoClipPath *PictureShape::generateClipPath()
0753 {
0754     QPainterPath path = _Private::generateOutline(imageData()->image());
0755     path = path * QTransform().scale(size().width(), size().height());
0756 
0757     KoPathShape *pathShape = KoPathShape::createShapeFromPainterPath(path);
0758 
0759     //createShapeFromPainterPath converts the path topleft into a shape topleft
0760     //and the pathShape needs to be on top of us. So to preserve both we do:
0761     pathShape->setTransformation(pathShape->transformation() * transformation());
0762 
0763     return new KoClipPath(this, new KoClipData(pathShape));
0764 }
0765 
0766 bool PictureShape::saveSvg(SvgSavingContext &context)
0767 {
0768     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
0769     if (!imageData) {
0770         warnPicture << "Picture has no image data. Omitting.";
0771         return false;
0772     }
0773 
0774     context.shapeWriter().startElement("image");
0775     context.shapeWriter().addAttribute("id", context.getID(this));
0776 
0777     QTransform m = transformation();
0778     if (m.type() == QTransform::TxTranslate) {
0779         const QPointF pos = position();
0780         context.shapeWriter().addAttributePt("x", pos.x());
0781         context.shapeWriter().addAttributePt("y", pos.y());
0782     } else {
0783         context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(m));
0784     }
0785 
0786     const QSizeF s = size();
0787     context.shapeWriter().addAttributePt("width", s.width());
0788     context.shapeWriter().addAttributePt("height", s.height());
0789     context.shapeWriter().addAttribute("xlink:href", context.saveImage(imageData));
0790     context.shapeWriter().endElement();
0791 
0792     return true;
0793 }
0794 
0795 bool PictureShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context)
0796 {
0797     const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x", "0"));
0798     const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y", "0"));
0799     const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width", "0"));
0800     const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height", "0"));
0801 
0802     // zero width of height disables rendering this image (see svg spec)
0803     if (w == 0.0 || h == 0.0)
0804         return 0;
0805 
0806     const QString href = element.attribute("xlink:href");
0807 
0808     QImage image;
0809 
0810     if (href.startsWith(QLatin1String("data:"))) {
0811         int start = href.indexOf("base64,");
0812         if (start <= 0)
0813             return false;
0814         if(!image.loadFromData(QByteArray::fromBase64(href.mid(start + 7).toLatin1())))
0815             return false;
0816     } else if (!image.load(context.absoluteFilePath(href))) {
0817         return false;
0818     }
0819 
0820     KoImageCollection *imageCollection = context.imageCollection();
0821     if (!imageCollection)
0822         return false;
0823 
0824     // TODO use it already for loading
0825     KoImageData *data = imageCollection->createImageData(image);
0826 
0827     setUserData(data);
0828     setSize(QSizeF(w, h));
0829     setPosition(QPointF(x, y));
0830     return true;
0831 }