From 2fe059c863377befdcf65084a07a7f4841beef0d Mon Sep 17 00:00:00 2001 From: aavit Date: Tue, 30 Mar 2010 13:41:18 +0200 Subject: Total makeover of SVG image reader Improved/fixed canRead(). Added svgz support. Implemented ClipRect, ScaledClipRect and BackgroundColor support. Avoid data copy when reading from memory. Improved support reading from sequential devices. Added svg and svgz files to the qimagereader autotests. Task-number: QTBUG-8227 and QTBUG-9053 Reviewed-by: Kim --- src/plugins/imageformats/svg/main.cpp | 16 +- src/plugins/imageformats/svg/qsvgiohandler.cpp | 157 ++++++--- tests/auto/qimagereader/images/corrupt.svg | 32 ++ tests/auto/qimagereader/images/corrupt.svgz | Bin 0 -> 407 bytes tests/auto/qimagereader/images/rect.svg | 462 +++++++++++++++++++++++++ tests/auto/qimagereader/images/rect.svgz | Bin 0 -> 5082 bytes tests/auto/qimagereader/qimagereader.pro | 1 + tests/auto/qimagereader/qimagereader.qrc | 4 + tests/auto/qimagereader/tst_qimagereader.cpp | 64 +++- 9 files changed, 667 insertions(+), 69 deletions(-) create mode 100644 tests/auto/qimagereader/images/corrupt.svg create mode 100644 tests/auto/qimagereader/images/corrupt.svgz create mode 100644 tests/auto/qimagereader/images/rect.svg create mode 100644 tests/auto/qimagereader/images/rect.svgz diff --git a/src/plugins/imageformats/svg/main.cpp b/src/plugins/imageformats/svg/main.cpp index dbbd3b7..329e9d4 100644 --- a/src/plugins/imageformats/svg/main.cpp +++ b/src/plugins/imageformats/svg/main.cpp @@ -62,26 +62,18 @@ public: QStringList QSvgPlugin::keys() const { - return QStringList() << QLatin1String("svg"); + return QStringList() << QLatin1String("svg") << QLatin1String("svgz"); } QImageIOPlugin::Capabilities QSvgPlugin::capabilities(QIODevice *device, const QByteArray &format) const { - //### canRead disabled for now because it's hard to detect - // whether the file is actually svg without parsing it - //if (device->isReadable() && QSvgIOHandler::canRead(device)) - - if (format == "svg") + if (format == "svg" || format == "svgz") return Capabilities(CanRead); - else - return 0; - - - if (!device->isOpen()) + if (!format.isEmpty()) return 0; Capabilities cap; - if (device->isReadable()) + if (device->isReadable() && QSvgIOHandler::canRead(device)) cap |= CanRead; return cap; } diff --git a/src/plugins/imageformats/svg/qsvgiohandler.cpp b/src/plugins/imageformats/svg/qsvgiohandler.cpp index f3670c6..8155569 100644 --- a/src/plugins/imageformats/svg/qsvgiohandler.cpp +++ b/src/plugins/imageformats/svg/qsvgiohandler.cpp @@ -48,6 +48,7 @@ #include "qpixmap.h" #include "qpainter.h" #include "qvariant.h" +#include "qbuffer.h" #include "qdebug.h" QT_BEGIN_NAMESPACE @@ -55,67 +56,54 @@ QT_BEGIN_NAMESPACE class QSvgIOHandlerPrivate { public: - QSvgIOHandlerPrivate() - : r(new QSvgRenderer()), loaded(false) + QSvgIOHandlerPrivate(QSvgIOHandler *qq) + : q(qq), loaded(false), readDone(false), backColor(Qt::transparent) {} - ~QSvgIOHandlerPrivate() - { - delete r; - } bool load(QIODevice *device); - static bool findSvgTag(QIODevice *device); - QSvgRenderer *r; - QSize defaultSize; - QSize currentSize; - bool loaded; + QSvgIOHandler *q; + QSvgRenderer r; + QXmlStreamReader xmlReader; + QSize defaultSize; + QRect clipRect; + QSize scaledSize; + QRect scaledClipRect; + bool loaded; + bool readDone; + QColor backColor; }; + bool QSvgIOHandlerPrivate::load(QIODevice *device) { if (loaded) return true; + if (q->format().isEmpty()) + q->canRead(); + + bool res = false; + QBuffer *buf = qobject_cast(device); + if (buf) { + res = r.load(buf->data()); + } else if (q->format() == "svgz") { + res = r.load(device->readAll()); // ### can't stream svgz + } else { + xmlReader.setDevice(device); + res = r.load(&xmlReader); //### doesn't leave pos() correctly + } - if (r->load(device->readAll())) { - defaultSize = QSize(r->viewBox().width(), r->viewBox().height()); - if (currentSize.isEmpty()) - currentSize = defaultSize; + if (res) { + defaultSize = QSize(r.viewBox().width(), r.viewBox().height()); + loaded = true; } - loaded = r->isValid(); return loaded; } -bool QSvgIOHandlerPrivate::findSvgTag(QIODevice *device) -{ - qint64 pos = device->pos(); - device->seek(0); - char buffer[256]; - const char svg_tag[] = "read(buffer, 256); - for (int i=0; iseek(pos); - return true; - } - } - } - if (device->atEnd()) - break; - device->seek(device->pos()-4); - } - device->seek(pos); - return false; -} QSvgIOHandler::QSvgIOHandler() - : d(new QSvgIOHandlerPrivate()) + : d(new QSvgIOHandlerPrivate(this)) { } @@ -129,7 +117,20 @@ QSvgIOHandler::~QSvgIOHandler() bool QSvgIOHandler::canRead() const { - return QSvgIOHandlerPrivate::findSvgTag(device()); + if (!device()) + return false; + if (d->loaded && !d->readDone) + return true; // Will happen if we have been asked for the size + + QByteArray buf = device()->peek(8); + if (buf.startsWith("\x1f\x8b")) { + setFormat("svgz"); + return true; + } else if (buf.contains("load(device())) { - *image = QImage(d->currentSize, QImage::Format_ARGB32_Premultiplied); - if (!d->currentSize.isEmpty()) { - image->fill(0x00000000); + if (!d->readDone && d->load(device())) { + bool xform = (d->clipRect.isValid() || d->scaledSize.isValid() || d->scaledClipRect.isValid()); + QSize finalSize = d->defaultSize; + QRectF bounds; + if (xform && !d->defaultSize.isEmpty()) { + bounds = QRectF(QPointF(0,0), QSizeF(d->defaultSize)); + QPoint tr1, tr2; + QSizeF sc(1, 1); + if (d->clipRect.isValid()) { + tr1 = -d->clipRect.topLeft(); + finalSize = d->clipRect.size(); + } + if (d->scaledSize.isValid()) { + sc = QSizeF(qreal(d->scaledSize.width()) / finalSize.width(), + qreal(d->scaledSize.height()) / finalSize.height()); + finalSize = d->scaledSize; + } + if (d->scaledClipRect.isValid()) { + tr2 = -d->scaledClipRect.topLeft(); + finalSize = d->scaledClipRect.size(); + } + QTransform t; + t.translate(tr2.x(), tr2.y()); + t.scale(sc.width(), sc.height()); + t.translate(tr1.x(), tr1.y()); + bounds = t.mapRect(bounds); + } + *image = QImage(finalSize, QImage::Format_ARGB32_Premultiplied); + if (!finalSize.isEmpty()) { + image->fill(d->backColor.rgba()); QPainter p(image); - d->r->render(&p); + d->r.render(&p, bounds); p.end(); } + d->readDone = true; return true; } @@ -166,8 +194,17 @@ QVariant QSvgIOHandler::option(ImageOption option) const d->load(device()); return d->defaultSize; break; + case ClipRect: + return d->clipRect; + break; case ScaledSize: - return d->currentSize; + return d->scaledSize; + break; + case ScaledClipRect: + return d->scaledClipRect; + break; + case BackgroundColor: + return d->backColor; break; default: break; @@ -179,12 +216,17 @@ QVariant QSvgIOHandler::option(ImageOption option) const void QSvgIOHandler::setOption(ImageOption option, const QVariant & value) { switch(option) { - case Size: - d->defaultSize = value.toSize(); - d->currentSize = value.toSize(); + case ClipRect: + d->clipRect = value.toRect(); break; case ScaledSize: - d->currentSize = value.toSize(); + d->scaledSize = value.toSize(); + break; + case ScaledClipRect: + d->scaledClipRect = value.toRect(); + break; + case BackgroundColor: + d->backColor = value.value(); break; default: break; @@ -198,7 +240,10 @@ bool QSvgIOHandler::supportsOption(ImageOption option) const { case ImageFormat: case Size: + case ClipRect: case ScaledSize: + case ScaledClipRect: + case BackgroundColor: return true; default: break; @@ -206,9 +251,11 @@ bool QSvgIOHandler::supportsOption(ImageOption option) const return false; } + bool QSvgIOHandler::canRead(QIODevice *device) { - return QSvgIOHandlerPrivate::findSvgTag(device); + QByteArray buf = device->peek(8); + return buf.startsWith("\x1f\x8b") || buf.contains(" + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/auto/qimagereader/images/rect.svgz b/tests/auto/qimagereader/images/rect.svgz new file mode 100644 index 0000000..c2e193b Binary files /dev/null and b/tests/auto/qimagereader/images/rect.svgz differ diff --git a/tests/auto/qimagereader/qimagereader.pro b/tests/auto/qimagereader/qimagereader.pro index 5b061b0..402e94b 100644 --- a/tests/auto/qimagereader/qimagereader.pro +++ b/tests/auto/qimagereader/qimagereader.pro @@ -9,6 +9,7 @@ RESOURCES += qimagereader.qrc !contains(QT_CONFIG, no-jpeg):DEFINES += QTEST_HAVE_JPEG !contains(QT_CONFIG, no-mng):DEFINES += QTEST_HAVE_MNG !contains(QT_CONFIG, no-tiff):DEFINES += QTEST_HAVE_TIFF +!contains(QT_CONFIG, no-svg):DEFINES += QTEST_HAVE_SVG win32-msvc:QMAKE_CXXFLAGS -= -Zm200 win32-msvc:QMAKE_CXXFLAGS += -Zm800 diff --git a/tests/auto/qimagereader/qimagereader.qrc b/tests/auto/qimagereader/qimagereader.qrc index bc48244..278427b 100644 --- a/tests/auto/qimagereader/qimagereader.qrc +++ b/tests/auto/qimagereader/qimagereader.qrc @@ -61,5 +61,9 @@ images/four-frames.gif images/qt-gif-anim.gif images/qt-gif-noanim.gif + images/rect.svg + images/rect.svgz + images/corrupt.svg + images/corrupt.svgz diff --git a/tests/auto/qimagereader/tst_qimagereader.cpp b/tests/auto/qimagereader/tst_qimagereader.cpp index 99244c2..1b4c502 100644 --- a/tests/auto/qimagereader/tst_qimagereader.cpp +++ b/tests/auto/qimagereader/tst_qimagereader.cpp @@ -245,6 +245,10 @@ void tst_QImageReader::readImage_data() QTest::newRow("MNG: ball") << QString("ball.mng") << true << QByteArray("mng"); QTest::newRow("MNG: fire") << QString("fire.mng") << true << QByteArray("mng"); #endif +#if defined QTEST_HAVE_SVG + QTest::newRow("SVG: rect") << QString("rect.svg") << true << QByteArray("svg"); + QTest::newRow("SVGZ: rect") << QString("rect.svgz") << true << QByteArray("svgz"); +#endif } void tst_QImageReader::readImage() @@ -294,7 +298,6 @@ void tst_QImageReader::readImage() QVERIFY(!image2Reader.format().isEmpty()); } QCOMPARE(image, image2); - do { QVERIFY2(!image.isNull(), io.errorString().toLatin1().constData()); } while (!(image = io.read()).isNull()); @@ -342,6 +345,10 @@ void tst_QImageReader::setScaledSize_data() QTest::newRow("MNG: ball") << "ball" << QSize(200, 200) << QByteArray("mng"); QTest::newRow("MNG: fire") << "fire" << QSize(200, 200) << QByteArray("mng"); #endif // QTEST_HAVE_MNG +#if defined QTEST_HAVE_SVG + QTest::newRow("SVG: rect") << "rect" << QSize(200, 200) << QByteArray("svg"); + QTest::newRow("SVGZ: rect") << "rect" << QSize(200, 200) << QByteArray("svgz"); +#endif } void tst_QImageReader::setScaledSize() @@ -409,6 +416,10 @@ void tst_QImageReader::setClipRect_data() QTest::newRow("MNG: ball") << "ball" << QRect(0, 0, 50, 50) << QByteArray("mng"); QTest::newRow("MNG: fire") << "fire" << QRect(0, 0, 50, 50) << QByteArray("mng"); #endif // QTEST_HAVE_MNG +#if defined QTEST_HAVE_SVG + QTest::newRow("SVG: rect") << "rect" << QRect(0, 0, 50, 50) << QByteArray("svg"); + QTest::newRow("SVGZ: rect") << "rect" << QRect(0, 0, 50, 50) << QByteArray("svgz"); +#endif } void tst_QImageReader::setClipRect() @@ -456,6 +467,10 @@ void tst_QImageReader::setScaledClipRect_data() QTest::newRow("MNG: ball") << "ball" << QRect(0, 0, 50, 50) << QByteArray("mng"); QTest::newRow("MNG: fire") << "fire" << QRect(0, 0, 50, 50) << QByteArray("mng"); #endif // QTEST_HAVE_MNG +#if defined QTEST_HAVE_SVG + QTest::newRow("SVG: rect") << "rect" << QRect(0, 0, 50, 50) << QByteArray("svg"); + QTest::newRow("SVGZ: rect") << "rect" << QRect(0, 0, 50, 50) << QByteArray("svgz"); +#endif } void tst_QImageReader::setScaledClipRect() @@ -509,6 +524,8 @@ void tst_QImageReader::imageFormat_data() QTest::newRow("png-2") << QString("YCbCr_cmyk.png") << QByteArray("png") << QImage::Format_RGB32; QTest::newRow("mng-1") << QString("ball.mng") << QByteArray("mng") << QImage::Format_Invalid; QTest::newRow("mng-2") << QString("fire.mng") << QByteArray("mng") << QImage::Format_Invalid; + QTest::newRow("svg") << QString("rect.svg") << QByteArray("svg") << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("svgz") << QString("rect.svgz") << QByteArray("svgz") << QImage::Format_ARGB32_Premultiplied; } void tst_QImageReader::imageFormat() @@ -530,6 +547,10 @@ void tst_QImageReader::imageFormat() #ifndef QTEST_HAVE_MNG return; #endif // !QTEST_HAVE_MNG + if (QByteArray("svg") == format || QByteArray("svgz") == format) +#ifndef QTEST_HAVE_SVG + return; +#endif // !QTEST_HAVE_SVG QSKIP(("Qt does not support the " + format + " format.").constData(), SkipSingle); } else { QCOMPARE(QImageReader::imageFormat(prefix + fileName), format); @@ -604,6 +625,10 @@ void tst_QImageReader::setBackgroundColor_data() QTest::newRow("MNG: ball") << QString("ball.mng") << QColor(Qt::yellow); QTest::newRow("MNG: fire") << QString("fire.mng") << QColor(Qt::gray); #endif +#if defined QTEST_HAVE_SVG + QTest::newRow("SVG: rect") << QString("rect.svg") << QColor(Qt::darkGreen); + QTest::newRow("SVGZ: rect") << QString("rect.svgz") << QColor(Qt::darkGreen); +#endif } void tst_QImageReader::setBackgroundColor() @@ -641,6 +666,10 @@ void tst_QImageReader::supportsAnimation_data() QTest::newRow("MNG: ball") << QString("ball.mng") << true; QTest::newRow("MNG: fire") << QString("fire.mng") << true; #endif +#if defined QTEST_HAVE_SVG + QTest::newRow("SVG: rect") << QString("rect.svg") << false; + QTest::newRow("SVGZ: rect") << QString("rect.svgz") << false; +#endif } void tst_QImageReader::supportsAnimation() @@ -979,6 +1008,10 @@ void tst_QImageReader::readFromDevice_data() QTest::newRow("mng-1") << QString("ball.mng") << QByteArray("mng"); QTest::newRow("mng-2") << QString("fire.mng") << QByteArray("mng"); #endif // QTEST_HAVE_MNG +#if defined QTEST_HAVE_SVG + QTest::newRow("svg") << QString("rect.svg") << QByteArray("svg"); + QTest::newRow("svgz") << QString("rect.svgz") << QByteArray("svgz"); +#endif } void tst_QImageReader::readFromDevice() @@ -1059,6 +1092,10 @@ void tst_QImageReader::readFromFileAfterJunk_data() QTest::newRow("png") << QString("kollada.png") << QByteArray("png"); // QTest::newRow("mng-1") << QString("images/ball.mng") << QByteArray("mng"); // QTest::newRow("mng-2") << QString("images/fire.mng") << QByteArray("mng"); +#if defined QTEST_HAVE_SVG + QTest::newRow("svg") << QString("rect.svg") << QByteArray("svg"); + QTest::newRow("svgz") << QString("rect.svgz") << QByteArray("svgz"); +#endif } void tst_QImageReader::readFromFileAfterJunk() @@ -1081,7 +1118,7 @@ void tst_QImageReader::readFromFileAfterJunk() QVERIFY(!imageData.isNull()); int iterations = 10; - if (format == "ppm" || format == "pbm" || format == "pgm") + if (format == "ppm" || format == "pbm" || format == "pgm" || format == "svg" || format == "svgz") iterations = 1; if (format == "mng" || !QImageWriter::supportedImageFormats().contains(format)) { @@ -1233,6 +1270,20 @@ void tst_QImageReader::readFromResources_data() << QByteArray("mng") << QSize(32, 32) << QString(""); #endif +#ifdef QTEST_HAVE_SVG + QTest::newRow("rect.svg") << QString("rect.svg") + << QByteArray("svg") << QSize(105, 137) + << QString(""); + QTest::newRow("rect.svgz") << QString("rect.svgz") + << QByteArray("svgz") << QSize(105, 137) + << QString(""); + QTest::newRow("corrupt.svg") << QString("corrupt.svg") + << QByteArray("svg") << QSize(0, 0) + << QString(""); + QTest::newRow("corrupt.svgz") << QString("corrupt.svgz") + << QByteArray("svgz") << QSize(0, 0) + << QString(""); +#endif QTest::newRow("image.pbm") << QString("image.pbm") << QByteArray("pbm") << QSize(16, 6) << QString(""); @@ -1405,6 +1456,10 @@ void tst_QImageReader::readCorruptImage_data() #if defined QTEST_HAVE_TIFF QTest::newRow("corrupt tiff") << QString("corrupt-data.tif") << true << QString(""); #endif +#if defined QTEST_HAVE_SVG + QTest::newRow("corrupt svg") << QString("corrupt.svg") << true << QString(""); + QTest::newRow("corrupt svgz") << QString("corrupt.svgz") << true << QString(""); +#endif } void tst_QImageReader::readCorruptImage() @@ -1753,6 +1808,11 @@ void tst_QImageReader::testIgnoresFormatAndExtension_data() #if defined QTEST_HAVE_TIFF QTest::newRow("image_100dpi.tif") << "image_100dpi" << "tif" << "tiff"; #endif + +#if defined QTEST_HAVE_SVG + QTest::newRow("rect.svg") << "rect" << "svg" << "svg"; + QTest::newRow("rect.svgz") << "rect" << "svgz" << "svgz"; +#endif } -- cgit v0.12