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 }