diff --git a/plugins/pictureshape/PictureDebug.h b/plugins/pictureshape/PictureDebug.h index 1641b362f6b..c7ec64e63d1 100644 --- a/plugins/pictureshape/PictureDebug.h +++ b/plugins/pictureshape/PictureDebug.h @@ -1,33 +1,33 @@ /* * Copyright (c) 2015 Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PICTURE_DEBUG_H #define PICTURE_DEBUG_H #include #include extern const QLoggingCategory &PICTURE_LOG(); -#define debugPicture qCDebug(PICTURE_LOG) +#define debugPicture qCDebug(PICTURE_LOG)< * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2012 Inge Wallin * Copyright (C) 2012 C.Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "PictureShape.h" #include "filters/GreyscaleFilterEffect.h" #include "filters/MonoFilterEffect.h" #include "filters/WatermarkFilterEffect.h" #include "filters/ColoringFilterEffect.h" #include "filters/GammaFilterEffect.h" #include "PictureDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QString generate_key(qint64 key, const QSize & size) { return QString("%1-%2-%3").arg(key).arg(size.width()).arg(size.height()); } // ----------------------------------------------------------------- // _Private::PixmapScaler::PixmapScaler(PictureShape *pictureShape, const QSize &pixmapSize): m_size(pixmapSize) { m_image = pictureShape->imageData()->image(); m_imageKey = pictureShape->imageData()->key(); connect(this, SIGNAL(finished(QString,QImage)), &pictureShape->m_proxy, SLOT(setImage(QString,QImage))); } void _Private::PixmapScaler::run() { QString key = generate_key(m_imageKey, m_size); m_image = m_image.scaled( m_size.width(), m_size.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); emit finished(key, m_image); } // ----------------------------------------------------------------- // void _Private::PictureShapeProxy::setImage(const QString &key, const QImage &image) { QPixmapCache::insert(key, QPixmap::fromImage(image)); m_pictureShape->update(); } // ----------------------------------------------------------------- // QPainterPath _Private::generateOutline(const QImage &imageIn, int threshold) { int leftArray[100]; int rightArray[100]; QImage image = imageIn.scaled(QSize(100, 100)); QPainterPath path; for (int y = 0; y < 100; y++) { leftArray[y] = -1; for (int x = 0; x < 100; x++) { int a = qAlpha(image.pixel(x,y)); if (a > threshold) { leftArray[y] = x; break; } } } for (int y = 0; y < 100; y++) { rightArray[y] = -1; if (leftArray[y] != -1) { for (int x = 100-1; x >= 0; x--) { int a = qAlpha(image.pixel(x,y)); if (a > threshold) { rightArray[y] = x; break; } } } } // Now we know the outline let's make a path out of it bool first = true; for (int y = 0; y < 100; y++) { if (rightArray[y] != -1) { if (first) { path.moveTo(rightArray[y] / 99.0, y / 99.0); first = false; } else { path.lineTo(rightArray[y] / 99.0, y / 99.0); } } } if (first) { // Completely empty return path; } for (int y = 100-1; y >= 0; --y) { if (leftArray[y] != -1) { path.lineTo(leftArray[y] / 99.0, y / 99.0); } } return path; } // ----------------------------------------------------------------- // PictureShape::PictureShape() : KoFrameShape(KoXmlNS::draw, "image") , m_imageCollection(0) , m_mirrorMode(MirrorNone) , m_colorMode(Standard) , m_proxy(this) { setKeepAspectRatio(true); KoFilterEffectStack * effectStack = new KoFilterEffectStack(); effectStack->setClipRect(QRectF(0, 0, 1, 1)); setFilterEffectStack(effectStack); filterEffectStack()->insertFilterEffect(0, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); // let's just set 3 filterEffectStack()->insertFilterEffect(1, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); // no-op filters filterEffectStack()->insertFilterEffect(2, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); } KoImageData* PictureShape::imageData() const { return qobject_cast(userData()); } QRectF PictureShape::cropRect() const { return m_clippingRect.toRect(); } bool PictureShape::isPictureInProportion() const { QSizeF clippingRectSize( imageData()->imageSize().width() * m_clippingRect.width(), imageData()->imageSize().height() * m_clippingRect.height() ); qreal shapeAspect = size().width() / size().height(); qreal rectAspect = clippingRectSize.width() / clippingRectSize.height(); return qAbs(shapeAspect - rectAspect) <= 0.025; } void PictureShape::setCropRect(const QRectF& rect) { m_clippingRect.setRect(rect, true); update(); } QSize PictureShape::calcOptimalPixmapSize(const QSizeF& shapeSize, const QSizeF& imageSize) const { qreal imageAspect = imageSize.width() / imageSize.height(); qreal shapeAspect = shapeSize.width() / shapeSize.height(); qreal scale = 1.0; if (shapeAspect > imageAspect) { scale = shapeSize.width() / imageSize.width() / m_clippingRect.width(); } else { scale = shapeSize.height() / imageSize.height() / m_clippingRect.height(); } scale = qMin(1.0, scale); // prevent upscaling return (imageSize * scale).toSize(); } ClippingRect PictureShape::parseClippingRectString(const QString &originalString) const { ClippingRect rect; QString string = originalString.trimmed(); if (string.startsWith(QLatin1String("rect(")) && string.endsWith(QLatin1Char(')'))) { // remove "rect(" & ")" string.remove(0,5).chop(1); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixClipRectOffsetValuesString(string); #endif // split into the 4 values const QStringList valueStrings = string.split(QLatin1Char(',')); if (valueStrings.count() != 4) { warnPicture << "Not exactly 4 values for attribute fo:clip=rect(...):" << originalString << ", please report."; // hard to guess which value is for which offset, so just cancel parsing and return with the default rect return rect; } // default is 0.0 for all offsets qreal values[4] = { 0, 0, 0, 0 }; const QLatin1String autoValueString("auto"); for (int i=0; i<4; ++i) { const QString valueString = valueStrings.at(i).trimmed(); // "auto" means: keep default 0.0 if (valueString != autoValueString) { values[i] = KoUnit::parseValue(valueString, 0.0); } } rect.top = values[0]; rect.right = values[1]; rect.bottom = values[2]; rect.left = values[3]; rect.uniform = false; rect.inverted = true; } return rect; } QPainterPath PictureShape::shadowOutline() const { // Always return an outline for a shadow even if no fill is defined. return outline(); } void PictureShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(paintContext); QRectF viewRect = converter.documentToView(QRectF(QPointF(0,0), size())); if (imageData() == 0) { painter.fillRect(viewRect, QColor(Qt::gray)); return; } painter.save(); applyConversion(painter, converter); paintBorder(painter, converter); painter.restore(); QSize pixmapSize = calcOptimalPixmapSize(viewRect.size(), imageData()->image().size()); // Normalize the clipping rect if it isn't already done. m_clippingRect.normalize(imageData()->imageSize()); // Handle style:mirror, i.e. mirroring horizontally and/or vertically. // // NOTE: At this time we don't handle HorizontalOnEven // and HorizontalOnOdd, which have to know which // page they are on. In those cases we treat it as // no horizontal mirroring at all. bool doFlip = false; QSizeF shapeSize = size(); QSizeF viewSize = converter.documentToView(shapeSize); qreal midpointX = 0.0; qreal midpointY = 0.0; qreal scaleX = 1.0; qreal scaleY = 1.0; if (m_mirrorMode & MirrorHorizontal) { midpointX = viewSize.width() / qreal(2.0); scaleX = -1.0; doFlip = true; } if (m_mirrorMode & MirrorVertical) { midpointY = viewSize.height() / qreal(2.0); scaleY = -1.0; doFlip = true; } if (doFlip) { QTransform outputTransform = painter.transform(); QTransform worldTransform = QTransform(); //debugPicture << "Flipping" << midpointX << midpointY << scaleX << scaleY; worldTransform.translate(midpointX, midpointY); worldTransform.scale(scaleX, scaleY); worldTransform.translate(-midpointX, -midpointY); //debugPicture << "After flipping for window" << worldTransform; QTransform newTransform = worldTransform * outputTransform; painter.setWorldTransform(newTransform); } // Paint the image as prepared in waitUntilReady() if (!m_printQualityImage.isNull() && pixmapSize != m_printQualityRequestedSize) { QSizeF imageSize = m_printQualityImage.size(); QRectF cropRect( imageSize.width() * m_clippingRect.left, imageSize.height() * m_clippingRect.top, imageSize.width() * m_clippingRect.width(), imageSize.height() * m_clippingRect.height() ); painter.drawImage(viewRect, m_printQualityImage, cropRect); m_printQualityImage = QImage(); // free memory } else { QPixmap pixmap; QString key(generate_key(imageData()->key(), pixmapSize)); // If the required pixmap is not in the cache // launch a task in a background thread that scales // the source image to the required size if (!QPixmapCache::find(key, &pixmap)) { QThreadPool::globalInstance()->start(new _Private::PixmapScaler(this, pixmapSize)); painter.fillRect(viewRect, QColor(Qt::gray)); // just paint a gray rect as long as we don't have the required pixmap } else { QRectF cropRect( pixmapSize.width() * m_clippingRect.left, pixmapSize.height() * m_clippingRect.top, pixmapSize.width() * m_clippingRect.width(), pixmapSize.height() * m_clippingRect.height() ); painter.drawPixmap(viewRect, pixmap, cropRect); } } } void PictureShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { KoImageData *imageData = qobject_cast(userData()); if (imageData == 0) { return; } if (asynchronous) { // get pixmap and schedule it if not QSize pixels = converter.documentToView(QRectF(QPointF(0,0), size())).size().toSize(); QImage image = imageData->image(); if (image.isNull()) { return; } m_printQualityRequestedSize = pixels; if (image.size().width() < pixels.width()) { // don't scale up. pixels = image.size(); } m_printQualityImage = image.scaled(pixels, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } else { QSize pixmapSize = calcOptimalPixmapSize(converter.documentToView(QRectF(QPointF(0,0), size())).size(), imageData->image().size()); QString key(generate_key(imageData->key(), pixmapSize)); if (QPixmapCache::find(key) == 0) { QPixmap pixmap = imageData->pixmap(pixmapSize); QPixmapCache::insert(key, pixmap); } } } void PictureShape::saveOdf(KoShapeSavingContext &context) const { // make sure we have a valid image data pointer before saving KoImageData *imageData = qobject_cast(userData()); if (imageData == 0) { return; } KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); saveOdfAttributes(context, OdfAllAttributes); writer.startElement("draw:image"); // In the spec, only the xlink:href attribute is marked as mandatory, cool :) QString name = context.imageHref(imageData); writer.addAttribute("xlink:type", "simple"); writer.addAttribute("xlink:show", "embed"); writer.addAttribute("xlink:actuate", "onLoad"); writer.addAttribute("xlink:href", name); saveText(context); writer.endElement(); // draw:image QSizeF scaleFactor(imageData->imageSize().width() / size().width(), imageData->imageSize().height() / size().height()); saveOdfClipContour(context, scaleFactor); writer.endElement(); // draw:frame context.addDataCenter(m_imageCollection); } bool PictureShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfAllAttributes); if (loadOdfFrame(element, context)) { // load contour (clip) KoImageData *imageData = qobject_cast(userData()); + Q_ASSERT(imageData); QSizeF scaleFactor(size().width() / imageData->imageSize().width(), size().height() / imageData->imageSize().height()); loadOdfClipContour(element, context, scaleFactor); // this is needed so that the image is already normalized when calling waitUntilReady e.g. by cstester m_clippingRect.normalize(imageData->imageSize()); return true; } return false; } +static const char *s_emptyImageXpm[] = { + /* */ + /* */ + "16 16 2 1", + /* */ + "a c #ffffff", + "b c #000000", + /* */ + "bbbbbbbbbbbbbbbb", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "baaaaaaaaaaaaaab", + "bbbbbbbbbbbbbbbb" +}; + +// image formats (possibly) supported by Qt +// According to docs, only jpg and png are checked for by default +const int s_numImageFormats = 10; +const char *s_imageFormat[s_numImageFormats] = { "jpg", "jpeg", "png", "bmp", "gif", "pbm", "pgm", "ppm", "xbm", "xpm" }; + bool PictureShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { if (m_imageCollection) { const QString href = element.attribute("href"); // this can happen in case it is a presentation:placeholder if (!href.isEmpty()) { KoStore *store = context.odfLoadingContext().store(); KoImageData *data = m_imageCollection->createImageData(href, store); setUserData(data); } else { // check if we have an office:binary data element containing the image data const KoXmlElement &binaryData(KoXml::namedItemNS(element, KoXmlNS::office, "binary-data")); if (!binaryData.isNull()) { QImage image; - if (image.loadFromData(QByteArray::fromBase64(binaryData.text().toLatin1()))) { - KoImageData *data = m_imageCollection->createImageData(image); - setUserData(data); + for (int i = 0; i < s_numImageFormats; ++i) { + if (image.loadFromData(QByteArray::fromBase64(binaryData.text().toLatin1()), s_imageFormat[i])) { + KoImageData *data = m_imageCollection->createImageData(image); + setUserData(data); + debugPicture<<"Found image format:"<createImageData(QImage(s_emptyImageXpm)); + setUserData(data); + } } loadText(element, context); return true; } KoImageCollection *PictureShape::imageCollection() const { return m_imageCollection; } QString PictureShape::saveStyle(KoGenStyle& style, KoShapeSavingContext& context) const { if(transparency() > 0.0) { style.addProperty("draw:image-opacity", QString("%1%").arg((1.0 - transparency()) * 100.0)); } // this attribute is need to work around a bug in LO 3.4 to make it recognize us as an // image and not just any shape. But we shouldn't produce illegal odf so: only for testing! // style.addAttribute("style:parent-style-name", "dummy"); // Mirroring if (m_mirrorMode != MirrorNone) { QString mode; if (m_mirrorMode & MirrorHorizontal) mode = "horizontal"; else if (m_mirrorMode & MirrorHorizontalOnEven) mode = "horizontal-on-even"; else if (m_mirrorMode & MirrorHorizontalOnOdd) mode = "horizontal-on-odd"; if (m_mirrorMode & MirrorVertical) { if (!mode.isEmpty()) mode += ' '; mode += "vertical"; } style.addProperty("style:mirror", mode); } switch(m_colorMode) { case Standard: style.addProperty("draw:color-mode", "standard"); break; case Greyscale: style.addProperty("draw:color-mode", "greyscale"); break; case Watermark: style.addProperty("draw:color-mode", "watermark"); break; case Mono: style.addProperty("draw:color-mode", "mono"); break; } if (ColoringFilterEffect *cEffect = dynamic_cast(filterEffectStack()->filterEffects()[1])) { style.addProperty("draw:red", QString("%1%").arg(100*cEffect->red())); style.addProperty("draw:green", QString("%1%").arg(100*cEffect->green())); style.addProperty("draw:blue", QString("%1%").arg(100*cEffect->blue())); style.addProperty("draw:luminance", QString("%1%").arg(100*cEffect->luminance())); style.addProperty("draw:contrast", QString("%1%").arg(100*cEffect->contrast())); } if (GammaFilterEffect *gEffect = dynamic_cast(filterEffectStack()->filterEffects()[2])) { style.addProperty("draw:gamma", QString("%1%").arg(100*gEffect->gamma())); } KoImageData *imageData = qobject_cast(userData()); if (imageData != 0) { QSizeF imageSize = imageData->imageSize(); ClippingRect rect = m_clippingRect; rect.normalize(imageSize); rect.bottom = 1.0 - rect.bottom; rect.right = 1.0 - rect.right; if (!qFuzzyCompare(rect.left + rect.right + rect.top + rect.bottom, qreal(0))) { style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(rect.top * imageSize.height()) .arg(rect.right * imageSize.width()) .arg(rect.bottom * imageSize.height()) .arg(rect.left * imageSize.width()) ); } } return KoTosContainer::saveStyle(style, context); } void PictureShape::loadStyle(const KoXmlElement& element, KoShapeLoadingContext& context) { // Load the common parts of the style. KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); // Mirroring if (styleStack.hasProperty(KoXmlNS::style, "mirror")) { QString mirrorMode = styleStack.property(KoXmlNS::style, "mirror"); QFlags mode = 0; // Only one of the horizontal modes if (mirrorMode.contains("horizontal-on-even")) { mode |= MirrorHorizontalOnEven; } else if (mirrorMode.contains("horizontal-on-odd")) { mode |= MirrorHorizontalOnOdd; } else if (mirrorMode.contains("horizontal")) { mode |= MirrorHorizontal; } if (mirrorMode.contains("vertical")) { mode |= MirrorVertical; } m_mirrorMode = mode; } // Color-mode (effects) if (styleStack.hasProperty(KoXmlNS::draw, "color-mode")) { QString colorMode = styleStack.property(KoXmlNS::draw, "color-mode"); if (colorMode == "greyscale") { setColorMode(Greyscale); } else if (colorMode == "mono") { setColorMode(Mono); } else if (colorMode == "watermark") { setColorMode(Watermark); } } QString red = styleStack.property(KoXmlNS::draw, "red"); QString green = styleStack.property(KoXmlNS::draw, "green"); QString blue = styleStack.property(KoXmlNS::draw, "blue"); QString luminance = styleStack.property(KoXmlNS::draw, "luminance"); QString contrast = styleStack.property(KoXmlNS::draw, "contrast"); setColoring(red.right(1) == "%" ? (red.left(red.length() - 1).toDouble() / 100.0) : 0.0 , green.right(1) == "%" ? (green.left(green.length() - 1).toDouble() / 100.0) : 0.0 , blue.right(1) == "%" ? (blue.left(blue.length() - 1).toDouble() / 100.0) : 0.0 , luminance.right(1) == "%" ? (luminance.left(luminance.length() - 1).toDouble() / 100.0) : 0.0 , contrast.right(1) == "%" ? (contrast.left(contrast.length() - 1).toDouble() / 100.0) : 0.0); QString gamma = styleStack.property(KoXmlNS::draw, "gamma"); setGamma(gamma.right(1) == "%" ? (gamma.left(gamma.length() - 1).toDouble() / 100.0) : 0.0); // image opacity QString opacity(styleStack.property(KoXmlNS::draw, "image-opacity")); if (! opacity.isEmpty() && opacity.right(1) == "%") { setTransparency(1.0 - (opacity.left(opacity.length() - 1).toFloat() / 100.0)); } // clip rect m_clippingRect = parseClippingRectString(styleStack.property(KoXmlNS::fo, "clip")); } QFlags PictureShape::mirrorMode() const { return m_mirrorMode; } PictureShape::ColorMode PictureShape::colorMode() const { return m_colorMode; } void PictureShape::setMirrorMode(QFlags mode) { // Sanity check mode &= MirrorMask; // Make sure only one bit of the horizontal modes is set. if (mode & MirrorHorizontal) mode &= ~(MirrorHorizontalOnEven | MirrorHorizontalOnOdd); else if (mode & MirrorHorizontalOnEven) mode &= ~MirrorHorizontalOnOdd; // If the mode changes, redraw the image. if (mode != m_mirrorMode) { m_mirrorMode = mode; update(); } } void PictureShape::setColorMode(PictureShape::ColorMode mode) { if (mode != m_colorMode) { filterEffectStack()->removeFilterEffect(0); switch(mode) { case Greyscale: filterEffectStack()->insertFilterEffect(0, new GreyscaleFilterEffect()); break; case Mono: filterEffectStack()->insertFilterEffect(0, new MonoFilterEffect()); break; case Watermark: filterEffectStack()->insertFilterEffect(0, new WatermarkFilterEffect()); break; case Standard: default: filterEffectStack()->insertFilterEffect(0, new KoFilterEffect("NoOpFilterEffect", "NoOpFilterEffect")); break; } m_colorMode = mode; update(); } } void PictureShape::setColoring(qreal red, qreal green, qreal blue, qreal luminance, qreal contrast) { filterEffectStack()->removeFilterEffect(1); ColoringFilterEffect *cEffect = new ColoringFilterEffect(); cEffect->setColoring(red, green, blue, luminance, contrast); filterEffectStack()->insertFilterEffect(1, cEffect); update(); } void PictureShape::setGamma(qreal gamma) { filterEffectStack()->removeFilterEffect(2); GammaFilterEffect *gEffect = new GammaFilterEffect(); gEffect->setGamma(gamma); filterEffectStack()->insertFilterEffect(2, gEffect); update(); } KoClipPath *PictureShape::generateClipPath() { QPainterPath path = _Private::generateOutline(imageData()->image()); path = path * QTransform().scale(size().width(), size().height()); KoPathShape *pathShape = KoPathShape::createShapeFromPainterPath(path); //createShapeFromPainterPath converts the path topleft into a shape topleft //and the pathShape needs to be on top of us. So to preserve both we do: pathShape->setTransformation(pathShape->transformation() * transformation()); return new KoClipPath(this, new KoClipData(pathShape)); } bool PictureShape::saveSvg(SvgSavingContext &context) { KoImageData *imageData = qobject_cast(userData()); if (!imageData) { warnPicture << "Picture has no image data. Omitting."; return false; } context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", context.getID(this)); QTransform m = transformation(); if (m.type() == QTransform::TxTranslate) { const QPointF pos = position(); context.shapeWriter().addAttributePt("x", pos.x()); context.shapeWriter().addAttributePt("y", pos.y()); } else { context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(m)); } const QSizeF s = size(); context.shapeWriter().addAttributePt("width", s.width()); context.shapeWriter().addAttributePt("height", s.height()); context.shapeWriter().addAttribute("xlink:href", context.saveImage(imageData)); context.shapeWriter().endElement(); return true; } bool PictureShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x", "0")); const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y", "0")); const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width", "0")); const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height", "0")); // zero width of height disables rendering this image (see svg spec) if (w == 0.0 || h == 0.0) return 0; const QString href = element.attribute("xlink:href"); QImage image; if (href.startsWith(QLatin1String("data:"))) { int start = href.indexOf("base64,"); if (start <= 0) return false; if(!image.loadFromData(QByteArray::fromBase64(href.mid(start + 7).toLatin1()))) return false; } else if (!image.load(context.absoluteFilePath(href))) { return false; } KoImageCollection *imageCollection = context.imageCollection(); if (!imageCollection) return false; // TODO use it already for loading KoImageData *data = imageCollection->createImageData(image); setUserData(data); setSize(QSizeF(w, h)); setPosition(QPointF(x, y)); return true; }