From 50457678278f81d6dc07b8396dfb235fd9acf557 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 31 Jan 2020 11:18:31 +0100 Subject: Add support for 'image-rendering' attribute Write and parse the 'image-rendering' attibute. The value is mapped to QPainter::SmoothPixmapTransform render hint. [ChangeLog] Add support for 'image-rendering' attribute Task-number: QTBUG-4145 Change-Id: I5268eac73b234cd195adade502ab9945a89f3ff6 Reviewed-by: Eirik Aavitsland --- src/svg/qsvggenerator.cpp | 9 +++++- src/svg/qsvghandler.cpp | 28 ++++++++++++++++ src/svg/qsvgstyle.cpp | 39 +++++++++++++++++++--- src/svg/qsvgstyle_p.h | 13 +++++++- tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp | 48 ++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/svg/qsvggenerator.cpp b/src/svg/qsvggenerator.cpp index f69e420..7306551 100644 --- a/src/svg/qsvggenerator.cpp +++ b/src/svg/qsvggenerator.cpp @@ -951,12 +951,19 @@ void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image, Q_UNUSED(sr); Q_UNUSED(flags); + QString quality; + if (state->renderHints() & QPainter::SmoothPixmapTransform) { + quality = QLatin1String("optimizeQuality"); + } else { + quality = QLatin1String("optimizeSpeed"); + } stream() << " m_cssAttributes; @@ -274,6 +275,10 @@ QSvgAttributes::QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHa else if (name == QLatin1String("font-variant")) fontVariant = value; break; + case 'i': + if (name == QLatin1String("image-rendering")) + imageRendering = value; + break; case 'o': if (name == QLatin1String("opacity")) @@ -376,6 +381,8 @@ QSvgAttributes::QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHa case 'i': if (name == QLatin1String("id")) id = value.toString(); + else if (name == QLatin1String("image-rendering")) + imageRendering = value; break; case 'o': @@ -2251,6 +2258,25 @@ static void parseOthers(QSvgNode *node, } } +static void parseRenderingHints(QSvgNode *node, + const QSvgAttributes &attributes, + QSvgHandler *) +{ + if (attributes.imageRendering.isEmpty()) + return; + + QString ir = attributes.imageRendering.toString().trimmed(); + QSvgQualityStyle *p = new QSvgQualityStyle(0); + if (ir == QLatin1String("auto")) + p->setImageRendering(QSvgQualityStyle::ImageRenderingAuto); + else if (ir == QLatin1String("optimizeSpeed")) + p->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeSpeed); + else if (ir == QLatin1String("optimizeQuality")) + p->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeQuality); + node->appendStyleProperty(p, attributes.id); +} + + static bool parseStyle(QSvgNode *node, const QSvgAttributes &attributes, QSvgHandler *handler) @@ -2263,7 +2289,9 @@ static bool parseStyle(QSvgNode *node, parseVisibility(node, attributes, handler); parseOpacity(node, attributes, handler); parseCompOp(node, attributes, handler); + parseRenderingHints(node, attributes, handler); parseOthers(node, attributes, handler); + #if 0 value = attributes.value("audio-level"); diff --git a/src/svg/qsvgstyle.cpp b/src/svg/qsvgstyle.cpp index d425923..cf17944 100644 --- a/src/svg/qsvgstyle.cpp +++ b/src/svg/qsvgstyle.cpp @@ -61,7 +61,8 @@ QSvgExtraStates::QSvgExtraStates() fontWeight(QFont::Normal), fillRule(Qt::WindingFill), strokeDashOffset(0), - vectorEffect(false) + vectorEffect(false), + imageRendering(QSvgQualityStyle::ImageRenderingAuto) { } @@ -81,16 +82,46 @@ void QSvgFillStyleProperty::revert(QPainter *, QSvgExtraStates &) QSvgQualityStyle::QSvgQualityStyle(int color) + : m_imageRendering(QSvgQualityStyle::ImageRenderingAuto) + , m_oldImageRendering(QSvgQualityStyle::ImageRenderingAuto) + , m_imageRenderingSet(0) { Q_UNUSED(color); } -void QSvgQualityStyle::apply(QPainter *, const QSvgNode *, QSvgExtraStates &) -{ +void QSvgQualityStyle::setImageRendering(ImageRendering hint) { + m_imageRendering = hint; + m_imageRenderingSet = 1; } -void QSvgQualityStyle::revert(QPainter *, QSvgExtraStates &) + +void QSvgQualityStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states) { + m_oldImageRendering = states.imageRendering; + if (m_imageRenderingSet) { + states.imageRendering = m_imageRendering; + } + if (m_imageRenderingSet) { + bool smooth = false; + if (m_imageRendering == ImageRenderingAuto) + // auto (the spec says to prefer quality) + smooth = true; + else + smooth = (m_imageRendering == ImageRenderingOptimizeQuality); + p->setRenderHint(QPainter::SmoothPixmapTransform, smooth); + } +} +void QSvgQualityStyle::revert(QPainter *p, QSvgExtraStates &states) +{ + if (m_imageRenderingSet) { + states.imageRendering = m_oldImageRendering; + bool smooth = false; + if (m_oldImageRendering == ImageRenderingAuto) + smooth = true; + else + smooth = (m_oldImageRendering == ImageRenderingOptimizeQuality); + p->setRenderHint(QPainter::SmoothPixmapTransform, smooth); + } } QSvgFillStyle::QSvgFillStyle() diff --git a/src/svg/qsvgstyle_p.h b/src/svg/qsvgstyle_p.h index 420dd67..8664f7a 100644 --- a/src/svg/qsvgstyle_p.h +++ b/src/svg/qsvgstyle_p.h @@ -148,6 +148,7 @@ struct Q_SVG_PRIVATE_EXPORT QSvgExtraStates int nestedUseLevel = 0; int nestedUseCount = 0; bool vectorEffect; // true if pen is cosmetic + qint8 imageRendering; // QSvgQualityStyle::ImageRendering }; class Q_SVG_PRIVATE_EXPORT QSvgStyleProperty : public QSvgRefCounted @@ -186,10 +187,18 @@ public: class Q_SVG_PRIVATE_EXPORT QSvgQualityStyle : public QSvgStyleProperty { public: + enum ImageRendering: qint8 { + ImageRenderingAuto = 0, + ImageRenderingOptimizeSpeed = 1, + ImageRenderingOptimizeQuality = 2, + }; + QSvgQualityStyle(int color); void apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states) override; void revert(QPainter *p, QSvgExtraStates &states) override; Type type() const override; + + void setImageRendering(ImageRendering); private: // color-render ing v v 'auto' | 'optimizeSpeed' | // 'optimizeQuality' | 'inherit' @@ -210,7 +219,9 @@ private: // image-rendering v v 'auto' | 'optimizeSpeed' | 'optimizeQuality' | // 'inherit' - //QSvgImageRendering m_imageRendering; + qint32 m_imageRendering: 4; + qint32 m_oldImageRendering: 4; + qint32 m_imageRenderingSet: 1; }; diff --git a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp index ea23d2d..2f83301 100644 --- a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp +++ b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp @@ -85,6 +85,7 @@ private slots: void oss_fuzz_23731(); void oss_fuzz_24131(); void oss_fuzz_24738(); + void imageRendering(); #ifndef QT_NO_COMPRESS void testGzLoading(); @@ -1632,5 +1633,52 @@ void tst_QSvgRenderer::oss_fuzz_24738() QSvgRenderer().load(QByteArray("")); } +QByteArray image_data_url(QImage &image) { + QByteArray data; + QBuffer buffer(&data); + buffer.open(QBuffer::ReadWrite); + image.save(&buffer, "PNG"); + buffer.close(); + QByteArray url("data:image/png;base64,"); + url.append(data.toBase64()); + return url; +} + +void tst_QSvgRenderer::imageRendering() { + QImage img(2, 2, QImage::Format_ARGB32_Premultiplied); + img.fill(Qt::green); + img.setPixel(0, 0, qRgb(255, 0, 0)); + img.setPixel(1, 1, qRgb(255, 0, 0)); + QByteArray imgurl(image_data_url(img)); + QString svgtemplate( + "" + "" + "" + ); + const char *cases[] = {"optimizeQuality", "optimizeSpeed"}; + for (auto ir: cases) { + QString svg = svgtemplate.arg(QLatin1String(ir)).arg(QLatin1String(imgurl)); + QImage img1(4, 4, QImage::Format_ARGB32); + QPainter p1; + p1.begin(&img1); + QSvgRenderer renderer(svg.toLatin1()); + Q_ASSERT(renderer.isValid()); + renderer.render(&p1); + p1.end(); + + QImage img2(4, 4, QImage::Format_ARGB32); + QPainter p2(&img2); + p2.scale(2, 2); + if (QLatin1String(ir) == QLatin1String("optimizeSpeed")) + p2.setRenderHint(QPainter::SmoothPixmapTransform, false); + else if (QLatin1String(ir) == QLatin1String("optimizeQuality")) + p2.setRenderHint(QPainter::SmoothPixmapTransform, true); + p2.drawImage(0, 0, img); + p2.end(); + QCOMPARE(img1, img2); + } +} + + QTEST_MAIN(tst_QSvgRenderer) #include "tst_qsvgrenderer.moc" -- cgit v1.2.1