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