1 /* This file is part of the KDE project
2  * Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
3  * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
4  * Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
5  * Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
6  * Copyright (C) 2012 Inge Wallin <inge@lysator.liu.se>
7  * Copyright (C) 2012 C.Boemann <cbo@boemann.dk>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 #include "PictureShape.h"
26 
27 #include "filters/GreyscaleFilterEffect.h"
28 #include "filters/MonoFilterEffect.h"
29 #include "filters/WatermarkFilterEffect.h"
30 #include "filters/ColoringFilterEffect.h"
31 #include "filters/GammaFilterEffect.h"
32 #include "PictureDebug.h"
33 
34 #include <KoOdfWorkaround.h>
35 #include <KoViewConverter.h>
36 #include <KoImageCollection.h>
37 #include <KoImageData.h>
38 #include <KoShapeLoadingContext.h>
39 #include <KoShapePaintingContext.h>
40 #include <KoOdfLoadingContext.h>
41 #include <KoShapeSavingContext.h>
42 #include <KoXmlWriter.h>
43 #include <KoXmlNS.h>
44 #include <KoUnit.h>
45 #include <KoGenStyle.h>
46 #include <KoStyleStack.h>
47 #include <KoFilterEffectStack.h>
48 #include <KoClipPath.h>
49 #include <SvgSavingContext.h>
50 #include <SvgLoadingContext.h>
51 #include <SvgUtil.h>
52 #include <KoPathShape.h>
53 
54 #include <QPainter>
55 #include <QTimer>
56 #include <QPixmapCache>
57 #include <QThreadPool>
58 #include <QImage>
59 #include <QColor>
60 
generate_key(qint64 key,const QSize & size)61 QString generate_key(qint64 key, const QSize & size)
62 {
63     return QString("%1-%2-%3").arg(key).arg(size.width()).arg(size.height());
64 }
65 
66 // ----------------------------------------------------------------- //
67 
PixmapScaler(PictureShape * pictureShape,const QSize & pixmapSize)68 _Private::PixmapScaler::PixmapScaler(PictureShape *pictureShape, const QSize &pixmapSize):
69     m_size(pixmapSize)
70 {
71     m_image = pictureShape->imageData()->image();
72     m_imageKey = pictureShape->imageData()->key();
73     connect(this, SIGNAL(finished(QString,QImage)), &pictureShape->m_proxy, SLOT(setImage(QString,QImage)));
74 }
75 
run()76 void _Private::PixmapScaler::run()
77 {
78     QString key = generate_key(m_imageKey, m_size);
79 
80     m_image = m_image.scaled(
81         m_size.width(),
82         m_size.height(),
83         Qt::IgnoreAspectRatio,
84         Qt::SmoothTransformation
85     );
86 
87     emit finished(key, m_image);
88 }
89 
90 // ----------------------------------------------------------------- //
91 
setImage(const QString & key,const QImage & image)92 void _Private::PictureShapeProxy::setImage(const QString &key, const QImage &image)
93 {
94     QPixmapCache::insert(key, QPixmap::fromImage(image));
95     m_pictureShape->update();
96 }
97 
98 // ----------------------------------------------------------------- //
99 
generateOutline(const QImage & imageIn,int threshold)100 QPainterPath _Private::generateOutline(const QImage &imageIn, int threshold)
101 {
102     int leftArray[100];
103     int rightArray[100];
104 
105     QImage image = imageIn.scaled(QSize(100, 100));
106 
107     QPainterPath path;
108 
109     for (int y = 0; y < 100; y++) {
110         leftArray[y] = -1;
111         for (int x = 0; x < 100; x++) {
112             int a = qAlpha(image.pixel(x,y));
113             if (a > threshold) {
114                 leftArray[y] = x;
115                 break;
116             }
117         }
118     }
119     for (int y = 0; y < 100; y++) {
120         rightArray[y] = -1;
121         if (leftArray[y] != -1) {
122             for (int x = 100-1; x >= 0; x--) {
123                 int a = qAlpha(image.pixel(x,y));
124                 if (a > threshold) {
125                     rightArray[y] = x;
126                     break;
127                 }
128             }
129         }
130     }
131 
132     // Now we know the outline let's make a path out of it
133     bool first = true;
134     for (int y = 0; y < 100; y++) {
135         if (rightArray[y] != -1) {
136             if (first) {
137                 path.moveTo(rightArray[y] / 99.0, y / 99.0);
138                 first = false;
139             } else {
140                 path.lineTo(rightArray[y] / 99.0, y / 99.0);
141             }
142         }
143     }
144     if (first) {
145         // Completely empty
146         return path;
147     }
148 
149     for (int y = 100-1; y >= 0; --y) {
150         if (leftArray[y] != -1) {
151             path.lineTo(leftArray[y] / 99.0, y / 99.0);
152         }
153     }
154     return path;
155 }
156 
157 // ----------------------------------------------------------------- //
158 
PictureShape()159 PictureShape::PictureShape()
160     : KoFrameShape(KoXmlNS::draw, "image")
161     , m_imageCollection(0)
162     , m_mirrorMode(MirrorNone)
163     , m_colorMode(Standard)
164     , m_proxy(this)
165 {
166     setKeepAspectRatio(true);
167     KoFilterEffectStack * effectStack = new KoFilterEffectStack();
168     effectStack->setClipRect(QRectF(0, 0, 1, 1));
169     setFilterEffectStack(effectStack);
170     filterEffectStack()->insertFilterEffect(0, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); // let's just set 3
171     filterEffectStack()->insertFilterEffect(1, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); // no-op filters
172     filterEffectStack()->insertFilterEffect(2, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect"));
173 }
174 
imageData() const175 KoImageData* PictureShape::imageData() const
176 {
177     return qobject_cast<KoImageData*>(userData());
178 }
179 
cropRect() const180 QRectF PictureShape::cropRect() const
181 {
182     return m_clippingRect.toRect();
183 }
184 
isPictureInProportion() const185 bool PictureShape::isPictureInProportion() const
186 {
187     QSizeF clippingRectSize(
188         imageData()->imageSize().width() * m_clippingRect.width(),
189         imageData()->imageSize().height() * m_clippingRect.height()
190     );
191 
192     qreal shapeAspect = size().width() / size().height();
193     qreal rectAspect = clippingRectSize.width() / clippingRectSize.height();
194 
195     return qAbs(shapeAspect - rectAspect) <= 0.025;
196 }
197 
setCropRect(const QRectF & rect)198 void PictureShape::setCropRect(const QRectF& rect)
199 {
200     m_clippingRect.setRect(rect, true);
201     update();
202 }
203 
calcOptimalPixmapSize(const QSizeF & shapeSize,const QSizeF & imageSize) const204 QSize PictureShape::calcOptimalPixmapSize(const QSizeF& shapeSize, const QSizeF& imageSize) const
205 {
206     qreal imageAspect = imageSize.width() / imageSize.height();
207     qreal shapeAspect = shapeSize.width() / shapeSize.height();
208     qreal scale = 1.0;
209 
210     if (shapeAspect > imageAspect) {
211         scale = shapeSize.width()  / imageSize.width()  / m_clippingRect.width();
212     }
213     else {
214         scale = shapeSize.height() / imageSize.height() / m_clippingRect.height();
215     }
216 
217     scale = qMin<qreal>(1.0, scale); // prevent upscaling
218     return (imageSize * scale).toSize();
219 }
220 
parseClippingRectString(const QString & originalString) const221 ClippingRect PictureShape::parseClippingRectString(const QString &originalString) const
222 {
223     ClippingRect rect;
224     QString string = originalString.trimmed();
225 
226     if (string.startsWith(QLatin1String("rect(")) &&
227         string.endsWith(QLatin1Char(')'))) {
228         // remove "rect(" & ")"
229         string.remove(0,5).chop(1);
230 
231 #ifndef NWORKAROUND_ODF_BUGS
232         KoOdfWorkaround::fixClipRectOffsetValuesString(string);
233 #endif
234         // split into the 4 values
235         const QStringList valueStrings = string.split(QLatin1Char(','));
236 
237         if (valueStrings.count() != 4) {
238             warnPicture << "Not exactly 4 values for attribute fo:clip=rect(...):" << originalString << ", please report.";
239             // hard to guess which value is for which offset, so just cancel parsing and return with the default rect
240             return rect;
241         }
242 
243         // default is 0.0 for all offsets
244         qreal values[4] = { 0, 0, 0, 0 };
245         const QLatin1String autoValueString("auto");
246 
247         for (int i=0; i<4; ++i) {
248             const QString valueString = valueStrings.at(i).trimmed();
249             // "auto" means: keep default 0.0
250             if (valueString != autoValueString) {
251                 values[i] = KoUnit::parseValue(valueString, 0.0);
252             }
253         }
254 
255         rect.top = values[0];
256         rect.right = values[1];
257         rect.bottom = values[2];
258         rect.left = values[3];
259         rect.uniform = false;
260         rect.inverted = true;
261     }
262 
263     return rect;
264 }
265 
shadowOutline() const266 QPainterPath PictureShape::shadowOutline() const
267 {
268     // Always return an outline for a shadow even if no fill is defined.
269     return outline();
270 }
271 
paint(QPainter & painter,const KoViewConverter & converter,KoShapePaintingContext & paintContext)272 void PictureShape::paint(QPainter &painter, const KoViewConverter &converter,
273                          KoShapePaintingContext &paintContext)
274 {
275     Q_UNUSED(paintContext);
276 
277     QRectF viewRect = converter.documentToView(QRectF(QPointF(0,0), size()));
278     if (imageData() == 0) {
279         painter.fillRect(viewRect, QColor(Qt::gray));
280         return;
281     }
282 
283     painter.save();
284     applyConversion(painter, converter);
285     paintBorder(painter, converter);
286     painter.restore();
287 
288     QSize pixmapSize = calcOptimalPixmapSize(viewRect.size(), imageData()->image().size());
289 
290     // Normalize the clipping rect if it isn't already done.
291     m_clippingRect.normalize(imageData()->imageSize());
292 
293     // Handle style:mirror, i.e. mirroring horizontally and/or vertically.
294     //
295     // NOTE: At this time we don't handle HorizontalOnEven
296     //       and HorizontalOnOdd, which have to know which
297     //       page they are on.  In those cases we treat it as
298     //       no horizontal mirroring at all.
299     bool   doFlip = false;
300     QSizeF shapeSize = size();
301     QSizeF viewSize = converter.documentToView(shapeSize);
302     qreal  midpointX = 0.0;
303     qreal  midpointY = 0.0;
304     qreal  scaleX = 1.0;
305     qreal  scaleY = 1.0;
306     if (m_mirrorMode & MirrorHorizontal) {
307         midpointX = viewSize.width() / qreal(2.0);
308         scaleX = -1.0;
309         doFlip = true;
310     }
311     if (m_mirrorMode & MirrorVertical) {
312         midpointY = viewSize.height() / qreal(2.0);
313         scaleY = -1.0;
314         doFlip = true;
315     }
316     if (doFlip) {
317         QTransform outputTransform = painter.transform();
318         QTransform worldTransform  = QTransform();
319 
320         //debugPicture << "Flipping" << midpointX << midpointY << scaleX << scaleY;
321         worldTransform.translate(midpointX, midpointY);
322         worldTransform.scale(scaleX, scaleY);
323         worldTransform.translate(-midpointX, -midpointY);
324         //debugPicture << "After flipping for window" << worldTransform;
325 
326         QTransform newTransform = worldTransform * outputTransform;
327         painter.setWorldTransform(newTransform);
328     }
329 
330     // Paint the image as prepared in waitUntilReady()
331     if (!m_printQualityImage.isNull() && pixmapSize != m_printQualityRequestedSize) {
332         QSizeF imageSize = m_printQualityImage.size();
333         QRectF cropRect(
334             imageSize.width()  * m_clippingRect.left,
335             imageSize.height() * m_clippingRect.top,
336             imageSize.width()  * m_clippingRect.width(),
337             imageSize.height() * m_clippingRect.height()
338         );
339 
340         painter.drawImage(viewRect, m_printQualityImage, cropRect);
341         m_printQualityImage = QImage(); // free memory
342     }
343     else {
344         QPixmap pixmap;
345         QString key(generate_key(imageData()->key(), pixmapSize));
346 
347         // If the required pixmap is not in the cache
348         // launch a task in a background thread that scales
349         // the source image to the required size
350         if (!QPixmapCache::find(key, &pixmap)) {
351             QThreadPool::globalInstance()->start(new _Private::PixmapScaler(this, pixmapSize));
352             painter.fillRect(viewRect, QColor(Qt::gray)); // just paint a gray rect as long as we don't have the required pixmap
353         }
354         else {
355             QRectF cropRect(
356                 pixmapSize.width()  * m_clippingRect.left,
357                 pixmapSize.height() * m_clippingRect.top,
358                 pixmapSize.width()  * m_clippingRect.width(),
359                 pixmapSize.height() * m_clippingRect.height()
360             );
361 
362             painter.drawPixmap(viewRect, pixmap, cropRect);
363         }
364     }
365 }
366 
waitUntilReady(const KoViewConverter & converter,bool asynchronous) const367 void PictureShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const
368 {
369     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
370     if (imageData == 0) {
371         return;
372     }
373 
374     if (asynchronous) {
375         // get pixmap and schedule it if not
376         QSize pixels = converter.documentToView(QRectF(QPointF(0,0), size())).size().toSize();
377         QImage image = imageData->image();
378         if (image.isNull()) {
379             return;
380         }
381         m_printQualityRequestedSize = pixels;
382         if (image.size().width() < pixels.width()) { // don't scale up.
383             pixels = image.size();
384         }
385         m_printQualityImage = image.scaled(pixels, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
386     }
387     else {
388         QSize pixmapSize = calcOptimalPixmapSize(converter.documentToView(QRectF(QPointF(0,0), size())).size(), imageData->image().size());
389         QString key(generate_key(imageData->key(), pixmapSize));
390         if (QPixmapCache::find(key) == 0) {
391             QPixmap pixmap = imageData->pixmap(pixmapSize);
392             QPixmapCache::insert(key, pixmap);
393         }
394     }
395 }
396 
saveOdf(KoShapeSavingContext & context) const397 void PictureShape::saveOdf(KoShapeSavingContext &context) const
398 {
399     // make sure we have a valid image data pointer before saving
400     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
401     if (imageData == 0) {
402         return;
403     }
404 
405     KoXmlWriter &writer = context.xmlWriter();
406 
407     writer.startElement("draw:frame");
408     saveOdfAttributes(context, OdfAllAttributes);
409     writer.startElement("draw:image");
410     // In the spec, only the xlink:href attribute is marked as mandatory, cool :)
411     QString name = context.imageHref(imageData);
412     writer.addAttribute("xlink:type", "simple");
413     writer.addAttribute("xlink:show", "embed");
414     writer.addAttribute("xlink:actuate", "onLoad");
415     writer.addAttribute("xlink:href", name);
416     saveText(context);
417     writer.endElement(); // draw:image
418     QSizeF scaleFactor(imageData->imageSize().width() / size().width(),
419                   imageData->imageSize().height() / size().height());
420     saveOdfClipContour(context, scaleFactor);
421     writer.endElement(); // draw:frame
422 
423     context.addDataCenter(m_imageCollection);
424 }
425 
loadOdf(const KoXmlElement & element,KoShapeLoadingContext & context)426 bool PictureShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
427 {
428     loadOdfAttributes(element, context, OdfAllAttributes);
429 
430     if (loadOdfFrame(element, context)) {
431         // load contour (clip)
432         KoImageData *imageData = qobject_cast<KoImageData*>(userData());
433         Q_ASSERT(imageData);
434 
435         QSizeF scaleFactor(size().width() / imageData->imageSize().width(),
436                  size().height() / imageData->imageSize().height());
437 
438         loadOdfClipContour(element, context, scaleFactor);
439         // this is needed so that the image is already normalized when calling waitUntilReady e.g. by cstester
440         m_clippingRect.normalize(imageData->imageSize());
441 
442         return true;
443     }
444     return false;
445 }
446 
447 static const char *s_emptyImageXpm[] = {
448     /* <Values> */
449     /* <width/columns> <height/rows> <colors> <chars per pixel>*/
450     "16 16 2 1",
451     /* <Colors> */
452     "a c #ffffff",
453     "b c #000000",
454     /* <Pixels> */
455     "bbbbbbbbbbbbbbbb",
456     "baaaaaaaaaaaaaab",
457     "baaaaaaaaaaaaaab",
458     "baaaaaaaaaaaaaab",
459     "baaaaaaaaaaaaaab",
460     "baaaaaaaaaaaaaab",
461     "baaaaaaaaaaaaaab",
462     "baaaaaaaaaaaaaab",
463     "baaaaaaaaaaaaaab",
464     "baaaaaaaaaaaaaab",
465     "baaaaaaaaaaaaaab",
466     "baaaaaaaaaaaaaab",
467     "baaaaaaaaaaaaaab",
468     "baaaaaaaaaaaaaab",
469     "baaaaaaaaaaaaaab",
470     "bbbbbbbbbbbbbbbb"
471 };
472 
473 // image formats (possibly) supported by Qt
474 // According to docs, only jpg and png are checked for by default
475 const int s_numImageFormats = 10;
476 const char *s_imageFormat[s_numImageFormats] = { "jpg", "jpeg", "png", "bmp", "gif", "pbm", "pgm", "ppm", "xbm", "xpm" };
477 
loadOdfFrameElement(const KoXmlElement & element,KoShapeLoadingContext & context)478 bool PictureShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context)
479 {
480     if (m_imageCollection) {
481         const QString href = element.attribute("href");
482         // this can happen in case it is a presentation:placeholder
483         if (!href.isEmpty()) {
484             KoStore *store = context.odfLoadingContext().store();
485             KoImageData *data = m_imageCollection->createImageData(href, store);
486             setUserData(data);
487         } else {
488             // check if we have an office:binary data element containing the image data
489             const KoXmlElement &binaryData(KoXml::namedItemNS(element, KoXmlNS::office, "binary-data"));
490             if (!binaryData.isNull()) {
491                 QImage image;
492                 for (int i = 0; i < s_numImageFormats; ++i) {
493                     if (image.loadFromData(QByteArray::fromBase64(binaryData.text().toLatin1()), s_imageFormat[i])) {
494                         KoImageData *data = m_imageCollection->createImageData(image);
495                         setUserData(data);
496                         debugPicture<<"Found image format:"<<s_imageFormat[i];
497                         break;
498                     }
499                 }
500             } else {
501                 debugPicture<<"No image binary data";
502             }
503         }
504         if (!userData()) {
505             // We must crate an image or else things crashes later on
506             warnPicture<<"Could not find an image, creating an empty one";
507             KoImageData *data = m_imageCollection->createImageData(QImage(s_emptyImageXpm));
508             setUserData(data);
509         }
510     }
511 
512     loadText(element, context);
513 
514     return true;
515 }
516 
imageCollection() const517 KoImageCollection *PictureShape::imageCollection() const
518 {
519     return m_imageCollection;
520 }
521 
saveStyle(KoGenStyle & style,KoShapeSavingContext & context) const522 QString PictureShape::saveStyle(KoGenStyle& style, KoShapeSavingContext& context) const
523 {
524     if(transparency() > 0.0) {
525         style.addProperty("draw:image-opacity", QString("%1%").arg((1.0 - transparency()) * 100.0));
526     }
527 
528     // this attribute is need to work around a bug in LO 3.4 to make it recognize us as an
529     // image and not just any shape. But we shouldn't produce illegal odf so: only for testing!
530     // style.addAttribute("style:parent-style-name", "dummy");
531 
532     // Mirroring
533     if (m_mirrorMode != MirrorNone) {
534         QString mode;
535 
536         if (m_mirrorMode & MirrorHorizontal)
537             mode = "horizontal";
538         else if (m_mirrorMode & MirrorHorizontalOnEven)
539             mode = "horizontal-on-even";
540         else if (m_mirrorMode & MirrorHorizontalOnOdd)
541             mode = "horizontal-on-odd";
542 
543         if (m_mirrorMode & MirrorVertical) {
544             if (!mode.isEmpty())
545                 mode += ' ';
546             mode += "vertical";
547         }
548 
549         style.addProperty("style:mirror", mode);
550     }
551 
552     switch(m_colorMode)
553     {
554     case Standard:
555         style.addProperty("draw:color-mode", "standard");
556         break;
557     case Greyscale:
558         style.addProperty("draw:color-mode", "greyscale");
559         break;
560     case Watermark:
561         style.addProperty("draw:color-mode", "watermark");
562         break;
563     case Mono:
564         style.addProperty("draw:color-mode", "mono");
565         break;
566     }
567 
568     if (ColoringFilterEffect *cEffect = dynamic_cast<ColoringFilterEffect *>(filterEffectStack()->filterEffects()[1])) {
569         style.addProperty("draw:red", QString("%1%").arg(100*cEffect->red()));
570         style.addProperty("draw:green", QString("%1%").arg(100*cEffect->green()));
571         style.addProperty("draw:blue", QString("%1%").arg(100*cEffect->blue()));
572         style.addProperty("draw:luminance", QString("%1%").arg(100*cEffect->luminance()));
573         style.addProperty("draw:contrast", QString("%1%").arg(100*cEffect->contrast()));
574     }
575 
576     if (GammaFilterEffect *gEffect = dynamic_cast<GammaFilterEffect *>(filterEffectStack()->filterEffects()[2])) {
577         style.addProperty("draw:gamma", QString("%1%").arg(100*gEffect->gamma()));
578     }
579 
580     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
581 
582     if (imageData != 0) {
583         QSizeF imageSize = imageData->imageSize();
584         ClippingRect rect = m_clippingRect;
585 
586         rect.normalize(imageSize);
587         rect.bottom = 1.0 - rect.bottom;
588         rect.right = 1.0 - rect.right;
589 
590         if (!qFuzzyCompare(rect.left + rect.right + rect.top + rect.bottom, qreal(0))) {
591             style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)")
592                 .arg(rect.top * imageSize.height())
593                 .arg(rect.right * imageSize.width())
594                 .arg(rect.bottom * imageSize.height())
595                 .arg(rect.left * imageSize.width())
596             );
597         }
598     }
599 
600     return KoTosContainer::saveStyle(style, context);
601 }
602 
loadStyle(const KoXmlElement & element,KoShapeLoadingContext & context)603 void PictureShape::loadStyle(const KoXmlElement& element, KoShapeLoadingContext& context)
604 {
605     // Load the common parts of the style.
606     KoTosContainer::loadStyle(element, context);
607 
608     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
609     styleStack.setTypeProperties("graphic");
610 
611     // Mirroring
612     if (styleStack.hasProperty(KoXmlNS::style, "mirror")) {
613         QString mirrorMode = styleStack.property(KoXmlNS::style, "mirror");
614 
615         QFlags<PictureShape::MirrorMode>  mode = 0;
616 
617         // Only one of the horizontal modes
618         if (mirrorMode.contains("horizontal-on-even")) {
619             mode |= MirrorHorizontalOnEven;
620         }
621         else if (mirrorMode.contains("horizontal-on-odd")) {
622             mode |= MirrorHorizontalOnOdd;
623         }
624         else if (mirrorMode.contains("horizontal")) {
625             mode |= MirrorHorizontal;
626         }
627 
628         if (mirrorMode.contains("vertical")) {
629             mode |= MirrorVertical;
630         }
631 
632         m_mirrorMode = mode;
633     }
634 
635     // Color-mode (effects)
636     if (styleStack.hasProperty(KoXmlNS::draw, "color-mode")) {
637         QString colorMode = styleStack.property(KoXmlNS::draw, "color-mode");
638         if (colorMode == "greyscale") {
639             setColorMode(Greyscale);
640         }
641         else if (colorMode == "mono") {
642             setColorMode(Mono);
643         }
644         else if (colorMode == "watermark") {
645             setColorMode(Watermark);
646         }
647     }
648 
649     QString red = styleStack.property(KoXmlNS::draw, "red");
650     QString green = styleStack.property(KoXmlNS::draw, "green");
651     QString blue = styleStack.property(KoXmlNS::draw, "blue");
652     QString luminance = styleStack.property(KoXmlNS::draw, "luminance");
653     QString contrast = styleStack.property(KoXmlNS::draw, "contrast");
654     setColoring(red.right(1) == "%" ? (red.left(red.length() - 1).toDouble() / 100.0) : 0.0
655               , green.right(1) == "%" ? (green.left(green.length() - 1).toDouble() / 100.0) : 0.0
656               , blue.right(1) == "%" ? (blue.left(blue.length() - 1).toDouble() / 100.0) : 0.0
657               , luminance.right(1) == "%" ? (luminance.left(luminance.length() - 1).toDouble() / 100.0) : 0.0
658               , contrast.right(1) == "%" ? (contrast.left(contrast.length() - 1).toDouble() / 100.0) : 0.0);
659 
660     QString gamma = styleStack.property(KoXmlNS::draw, "gamma");
661     setGamma(gamma.right(1) == "%" ? (gamma.left(gamma.length() - 1).toDouble() / 100.0) : 0.0);
662 
663     // image opacity
664     QString opacity(styleStack.property(KoXmlNS::draw, "image-opacity"));
665     if (! opacity.isEmpty() && opacity.right(1) == "%") {
666         setTransparency(1.0 - (opacity.left(opacity.length() - 1).toFloat() / 100.0));
667     }
668 
669     // clip rect
670     m_clippingRect = parseClippingRectString(styleStack.property(KoXmlNS::fo, "clip"));
671 }
672 
mirrorMode() const673 QFlags<PictureShape::MirrorMode> PictureShape::mirrorMode() const
674 {
675     return m_mirrorMode;
676 }
677 
colorMode() const678 PictureShape::ColorMode PictureShape::colorMode() const
679 {
680     return m_colorMode;
681 }
682 
setMirrorMode(QFlags<PictureShape::MirrorMode> mode)683 void PictureShape::setMirrorMode(QFlags<PictureShape::MirrorMode> mode)
684 {
685     // Sanity check
686     mode &= MirrorMask;
687 
688     // Make sure only one bit of the horizontal modes is set.
689     if (mode & MirrorHorizontal)
690         mode &= ~(MirrorHorizontalOnEven | MirrorHorizontalOnOdd);
691     else if (mode & MirrorHorizontalOnEven)
692         mode &= ~MirrorHorizontalOnOdd;
693 
694     // If the mode changes, redraw the image.
695     if (mode != m_mirrorMode) {
696         m_mirrorMode = mode;
697         update();
698     }
699 }
700 
setColorMode(PictureShape::ColorMode mode)701 void PictureShape::setColorMode(PictureShape::ColorMode mode)
702 {
703     if (mode != m_colorMode) {
704         filterEffectStack()->removeFilterEffect(0);
705 
706         switch(mode)
707         {
708         case Greyscale:
709             filterEffectStack()->insertFilterEffect(0, new GreyscaleFilterEffect());
710             break;
711         case Mono:
712             filterEffectStack()->insertFilterEffect(0, new MonoFilterEffect());
713             break;
714         case Watermark:
715             filterEffectStack()->insertFilterEffect(0, new WatermarkFilterEffect());
716             break;
717         case Standard:
718         default:
719             filterEffectStack()->insertFilterEffect(0, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect"));
720             break;
721         }
722 
723         m_colorMode = mode;
724         update();
725     }
726 }
727 
setColoring(qreal red,qreal green,qreal blue,qreal luminance,qreal contrast)728 void PictureShape::setColoring(qreal red, qreal green, qreal blue, qreal luminance, qreal contrast)
729 {
730     filterEffectStack()->removeFilterEffect(1);
731 
732     ColoringFilterEffect *cEffect = new ColoringFilterEffect();
733     cEffect->setColoring(red, green, blue, luminance, contrast);
734 
735     filterEffectStack()->insertFilterEffect(1, cEffect);
736 
737     update();
738 }
739 
setGamma(qreal gamma)740 void PictureShape::setGamma(qreal gamma)
741 {
742     filterEffectStack()->removeFilterEffect(2);
743 
744     GammaFilterEffect *gEffect = new GammaFilterEffect();
745     gEffect->setGamma(gamma);
746 
747     filterEffectStack()->insertFilterEffect(2, gEffect);
748 
749     update();
750 }
751 
generateClipPath()752 KoClipPath *PictureShape::generateClipPath()
753 {
754     QPainterPath path = _Private::generateOutline(imageData()->image());
755     path = path * QTransform().scale(size().width(), size().height());
756 
757     KoPathShape *pathShape = KoPathShape::createShapeFromPainterPath(path);
758 
759     //createShapeFromPainterPath converts the path topleft into a shape topleft
760     //and the pathShape needs to be on top of us. So to preserve both we do:
761     pathShape->setTransformation(pathShape->transformation() * transformation());
762 
763     return new KoClipPath(this, new KoClipData(pathShape));
764 }
765 
saveSvg(SvgSavingContext & context)766 bool PictureShape::saveSvg(SvgSavingContext &context)
767 {
768     KoImageData *imageData = qobject_cast<KoImageData*>(userData());
769     if (!imageData) {
770         warnPicture << "Picture has no image data. Omitting.";
771         return false;
772     }
773 
774     context.shapeWriter().startElement("image");
775     context.shapeWriter().addAttribute("id", context.getID(this));
776 
777     QTransform m = transformation();
778     if (m.type() == QTransform::TxTranslate) {
779         const QPointF pos = position();
780         context.shapeWriter().addAttributePt("x", pos.x());
781         context.shapeWriter().addAttributePt("y", pos.y());
782     } else {
783         context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(m));
784     }
785 
786     const QSizeF s = size();
787     context.shapeWriter().addAttributePt("width", s.width());
788     context.shapeWriter().addAttributePt("height", s.height());
789     context.shapeWriter().addAttribute("xlink:href", context.saveImage(imageData));
790     context.shapeWriter().endElement();
791 
792     return true;
793 }
794 
loadSvg(const KoXmlElement & element,SvgLoadingContext & context)795 bool PictureShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context)
796 {
797     const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x", "0"));
798     const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y", "0"));
799     const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width", "0"));
800     const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height", "0"));
801 
802     // zero width of height disables rendering this image (see svg spec)
803     if (w == 0.0 || h == 0.0)
804         return 0;
805 
806     const QString href = element.attribute("xlink:href");
807 
808     QImage image;
809 
810     if (href.startsWith(QLatin1String("data:"))) {
811         int start = href.indexOf("base64,");
812         if (start <= 0)
813             return false;
814         if(!image.loadFromData(QByteArray::fromBase64(href.mid(start + 7).toLatin1())))
815             return false;
816     } else if (!image.load(context.absoluteFilePath(href))) {
817         return false;
818     }
819 
820     KoImageCollection *imageCollection = context.imageCollection();
821     if (!imageCollection)
822         return false;
823 
824     // TODO use it already for loading
825     KoImageData *data = imageCollection->createImageData(image);
826 
827     setUserData(data);
828     setSize(QSizeF(w, h));
829     setPosition(QPointF(x, y));
830     return true;
831 }
832