blob: abc7cd86bd5e3431198570bbd3234a323366798d [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QTextDocument>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDir>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuick/qquickview.h>
#include <private/qquickimage_p.h>
#include <private/qquickimagebase_p.h>
#include <private/qquickloader_p.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlexpression.h>
#include <QtTest/QSignalSpy>
#include <QtGui/QPainter>
#include <QtGui/QImageReader>
#include <QQuickWindow>
#include <QQuickView>
#include <QQuickImageProvider>
#include <QQmlAbstractUrlInterceptor>
#include "../../shared/util.h"
#include "../../shared/testhttpserver.h"
#include "../shared/visualtestutil.h"
using namespace QQuickVisualTestUtil;
Q_DECLARE_METATYPE(QQuickImageBase::Status)
class tst_qquickimage : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquickimage();
private slots:
void initTestCase();
void cleanup();
void noSource();
void imageSource();
void imageSource_data();
void clearSource();
void resized();
void preserveAspectRatio();
void smooth();
void mirror();
void svg();
void svg_data();
void geometry();
void geometry_data();
void big();
void tiling_QTBUG_6716();
void tiling_QTBUG_6716_data();
void noLoading();
void paintedWidthHeight();
void sourceSize_QTBUG_14303();
void sourceSize_QTBUG_16389();
void nullPixmapPaint();
void imageCrash_QTBUG_22125();
void imageCrash_QTBUG_32513();
void sourceSize_data();
void sourceSize();
void progressAndStatusChanges();
void sourceSizeChanges();
void correctStatus();
void highdpi();
void highDpiFillModesAndSizes_data();
void highDpiFillModesAndSizes();
void hugeImages();
void urlInterceptor();
void multiFrame_data();
void multiFrame();
private:
QQmlEngine engine;
QSGRendererInterface::GraphicsApi graphicsApi = QSGRendererInterface::Unknown;
};
tst_qquickimage::tst_qquickimage()
{
}
void tst_qquickimage::initTestCase()
{
QQmlDataTest::initTestCase();
QScopedPointer<QQuickView> window(new QQuickView(0));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
graphicsApi = window->rendererInterface()->graphicsApi();
}
void tst_qquickimage::cleanup()
{
QQuickWindow window;
window.releaseResources();
engine.clearComponentCache();
}
void tst_qquickimage::noSource()
{
QString componentStr = "import QtQuick 2.0\nImage { source: \"\" }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->source(), QUrl());
QCOMPARE(obj->status(), QQuickImage::Null);
QCOMPARE(obj->width(), 0.);
QCOMPARE(obj->height(), 0.);
QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
QCOMPARE(obj->progress(), 0.0);
delete obj;
}
void tst_qquickimage::imageSource_data()
{
QTest::addColumn<QString>("source");
QTest::addColumn<double>("width");
QTest::addColumn<double>("height");
QTest::addColumn<bool>("remote");
QTest::addColumn<bool>("async");
QTest::addColumn<bool>("cache");
QTest::addColumn<QString>("error");
QTest::newRow("local") << testFileUrl("colors.png").toString() << 120.0 << 120.0 << false << false << true << "";
QTest::newRow("local no cache") << testFileUrl("colors.png").toString() << 120.0 << 120.0 << false << false << false << "";
QTest::newRow("local async") << testFileUrl("colors1.png").toString() << 120.0 << 120.0 << false << true << true << "";
QTest::newRow("local not found") << testFileUrl("no-such-file.png").toString() << 0.0 << 0.0 << false
<< false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file.png").toString();
QTest::newRow("local async not found") << testFileUrl("no-such-file-1.png").toString() << 0.0 << 0.0 << false
<< true << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file-1.png").toString();
QTest::newRow("remote") << "/colors.png" << 120.0 << 120.0 << true << false << true << "";
QTest::newRow("remote redirected") << "/oldcolors.png" << 120.0 << 120.0 << true << false << false << "";
if (QImageReader::supportedImageFormats().contains("svg"))
QTest::newRow("remote svg") << "/heart.svg" << 595.0 << 841.0 << true << false << false << "";
if (QImageReader::supportedImageFormats().contains("svgz"))
QTest::newRow("remote svgz") << "/heart.svgz" << 595.0 << 841.0 << true << false << false << "";
if (graphicsApi == QSGRendererInterface::OpenGL) {
QTest::newRow("texturefile pkm format") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << false << true << "";
QTest::newRow("texturefile ktx format") << testFileUrl("car.ktx").toString() << 146.0 << 80.0 << false << false << true << "";
QTest::newRow("texturefile async") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << true << true << "";
}
QTest::newRow("remote not found") << "/no-such-file.png" << 0.0 << 0.0 << true
<< false << true << "<Unknown File>:2:1: QML Image: Error transferring {{ServerBaseUrl}}/no-such-file.png - server replied: Not found";
QTest::newRow("extless") << testFileUrl("colors").toString() << 120.0 << 120.0 << false << false << true << "";
QTest::newRow("extless no cache") << testFileUrl("colors").toString() << 120.0 << 120.0 << false << false << false << "";
QTest::newRow("extless async") << testFileUrl("colors1").toString() << 120.0 << 120.0 << false << true << true << "";
QTest::newRow("extless not found") << testFileUrl("no-such-file").toString() << 0.0 << 0.0 << false
<< false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file").toString();
// Test that texture file is preferred over image file, when supported.
// Since pattern.pkm has different size than pattern.png, these tests verify that the right file has been loaded
if (graphicsApi == QSGRendererInterface::OpenGL) {
QTest::newRow("extless prefer-tex") << testFileUrl("pattern").toString() << 64.0 << 64.0 << false << false << true << "";
QTest::newRow("extless prefer-tex async") << testFileUrl("pattern").toString() << 64.0 << 64.0 << false << true << true << "";
} else {
QTest::newRow("extless ignore-tex") << testFileUrl("pattern").toString() << 200.0 << 200.0 << false << false << true << "";
QTest::newRow("extless ignore-tex async") << testFileUrl("pattern").toString() << 200.0 << 200.0 << false << true << true << "";
}
}
void tst_qquickimage::imageSource()
{
QFETCH(QString, source);
QFETCH(double, width);
QFETCH(double, height);
QFETCH(bool, remote);
QFETCH(bool, async);
QFETCH(bool, cache);
QFETCH(QString, error);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (qstrcmp(QTest::currentDataTag(), "remote") == 0
|| qstrcmp(QTest::currentDataTag(), "remote redirected") == 0
|| qstrcmp(QTest::currentDataTag(), "remote svg") == 0
|| qstrcmp(QTest::currentDataTag(), "remote svgz") == 0
|| qstrcmp(QTest::currentDataTag(), "remote not found") == 0
) {
QSKIP("Remote tests cause occasional hangs in the CI system -- QTBUG-45655");
}
#endif
TestHTTPServer server;
if (remote) {
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(dataDirectory());
server.addRedirect("oldcolors.png", server.urlString("/colors.png"));
source = server.urlString(source);
error.replace(QStringLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString());
}
if (!error.isEmpty())
QTest::ignoreMessage(QtWarningMsg, error.toUtf8());
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + source + "\"; asynchronous: "
+ (async ? QLatin1String("true") : QLatin1String("false")) + "; cache: "
+ (cache ? QLatin1String("true") : QLatin1String("false")) + " }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
if (async)
QVERIFY(obj->asynchronous());
else
QVERIFY(!obj->asynchronous());
if (cache)
QVERIFY(obj->cache());
else
QVERIFY(!obj->cache());
if (remote || async)
QTRY_COMPARE(obj->status(), QQuickImage::Loading);
QCOMPARE(obj->source(), remote ? source : QUrl(source));
if (error.isEmpty()) {
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QCOMPARE(obj->width(), qreal(width));
QCOMPARE(obj->height(), qreal(height));
QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
QCOMPARE(obj->progress(), 1.0);
} else {
QTRY_COMPARE(obj->status(), QQuickImage::Error);
}
delete obj;
}
void tst_qquickimage::clearSource()
{
QString componentStr = "import QtQuick 2.0\nImage { source: srcImage }";
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("srcImage", testFileUrl("colors.png"));
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->status(), QQuickImage::Ready);
QCOMPARE(obj->width(), 120.);
QCOMPARE(obj->height(), 120.);
QCOMPARE(obj->progress(), 1.0);
ctxt->setContextProperty("srcImage", "");
QVERIFY(obj->source().isEmpty());
QCOMPARE(obj->status(), QQuickImage::Null);
QCOMPARE(obj->width(), 0.);
QCOMPARE(obj->height(), 0.);
QCOMPARE(obj->progress(), 0.0);
delete obj;
}
void tst_qquickimage::resized()
{
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + testFile("colors.png") + "\"; width: 300; height: 300 }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 300.);
QCOMPARE(obj->height(), 300.);
QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
delete obj;
}
void tst_qquickimage::preserveAspectRatio()
{
QScopedPointer<QQuickView> window(new QQuickView(nullptr));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
window->setSource(testFileUrl("aspectratio.qml"));
QQuickImage *image = qobject_cast<QQuickImage*>(window->rootObject());
QVERIFY(image != nullptr);
image->setWidth(80.0);
QCOMPARE(image->width(), 80.);
QCOMPARE(image->height(), 80.);
window->setSource(testFileUrl("aspectratio.qml"));
image = qobject_cast<QQuickImage*>(window->rootObject());
image->setHeight(60.0);
QVERIFY(image != nullptr);
QCOMPARE(image->height(), 60.);
QCOMPARE(image->width(), 60.);
}
void tst_qquickimage::smooth()
{
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + testFile("colors.png") + "\"; smooth: true; width: 300; height: 300 }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 300.);
QCOMPARE(obj->height(), 300.);
QCOMPARE(obj->smooth(), true);
QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
delete obj;
}
void tst_qquickimage::mirror()
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QSKIP("Skipping due to grabWindow not functional on offscreen/minimimal platforms");
QMap<QQuickImage::FillMode, QImage> screenshots;
QList<QQuickImage::FillMode> fillModes;
fillModes << QQuickImage::Stretch << QQuickImage::PreserveAspectFit << QQuickImage::PreserveAspectCrop
<< QQuickImage::Tile << QQuickImage::TileVertically << QQuickImage::TileHorizontally << QQuickImage::Pad;
qreal width = 300;
qreal height = 250;
qreal devicePixelRatio = 1.0;
foreach (QQuickImage::FillMode fillMode, fillModes) {
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("mirror.qml"));
QQuickImage *obj = window->rootObject()->findChild<QQuickImage*>("image");
QVERIFY(obj != nullptr);
obj->setFillMode(fillMode);
obj->setProperty("mirror", true);
window->showNormal();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QImage screenshot = window->grabWindow();
screenshots[fillMode] = screenshot;
devicePixelRatio = window->devicePixelRatio();
}
foreach (QQuickImage::FillMode fillMode, fillModes) {
QPixmap srcPixmap;
QVERIFY(srcPixmap.load(testFile("pattern.png")));
QPixmap expected(width * (int)devicePixelRatio, height * (int)devicePixelRatio);
expected.setDevicePixelRatio(devicePixelRatio);
expected.fill();
QPainter p_e(&expected);
QTransform transform;
transform.translate(width, 0).scale(-1, 1.0);
p_e.setTransform(transform);
QPoint offset(width / 2 - srcPixmap.width() / 2, height / 2 - srcPixmap.height() / 2);
switch (fillMode) {
case QQuickImage::Stretch:
p_e.drawPixmap(QRect(0, 0, width, height), srcPixmap, QRect(0, 0, srcPixmap.width(), srcPixmap.height()));
break;
case QQuickImage::PreserveAspectFit:
p_e.drawPixmap(QRect(25, 0, height, height), srcPixmap, QRect(0, 0, srcPixmap.width(), srcPixmap.height()));
break;
case QQuickImage::PreserveAspectCrop:
{
qreal ratio = width/srcPixmap.width(); // width is the longer side
QRect rect(0, 0, srcPixmap.width()*ratio, srcPixmap.height()*ratio);
rect.moveCenter(QRect(0, 0, width, height).center());
p_e.drawPixmap(rect, srcPixmap, QRect(0, 0, srcPixmap.width(), srcPixmap.height()));
break;
}
case QQuickImage::Tile:
p_e.drawTiledPixmap(QRect(0, 0, width, height), srcPixmap, -offset);
break;
case QQuickImage::TileVertically:
transform.scale(width / srcPixmap.width(), 1.0);
p_e.setTransform(transform);
p_e.drawTiledPixmap(QRect(0, 0, width, height), srcPixmap, QPoint(0, -offset.y()));
break;
case QQuickImage::TileHorizontally:
transform.scale(1.0, height / srcPixmap.height());
p_e.setTransform(transform);
p_e.drawTiledPixmap(QRect(0, 0, width, height), srcPixmap, QPoint(-offset.x(), 0));
break;
case QQuickImage::Pad:
p_e.drawPixmap(offset, srcPixmap);
break;
}
QImage img = expected.toImage();
QCOMPARE(screenshots[fillMode].convertToFormat(img.format()), img);
}
}
void tst_qquickimage::svg_data()
{
QTest::addColumn<QString>("src");
QTest::addColumn<QByteArray>("format");
QTest::newRow("svg") << testFileUrl("heart.svg").toString() << QByteArray("svg");
QTest::newRow("svgz") << testFileUrl("heart.svgz").toString() << QByteArray("svgz");
}
void tst_qquickimage::svg()
{
QFETCH(QString, src);
QFETCH(QByteArray, format);
if (!QImageReader::supportedImageFormats().contains(format))
QSKIP("svg support not available");
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; sourceSize.width: 300; sourceSize.height: 300 }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 300.0);
QCOMPARE(obj->height(), 300.0);
obj->setSourceSize(QSize(200,200));
QCOMPARE(obj->width(), 200.0);
QCOMPARE(obj->height(), 200.0);
obj->setSourceSize(QSize(100,0));
QCOMPARE(obj->width(), 100.0);
// Due to aspect ratio calculations we can't get a precise
// check for all setups, so we allow a small margin of error
QVERIFY(qAbs(obj->height() - 141) < 1);
// Setting it to a size bigger than the actual file, SVG formats
// can scale up although other image formats cannot
obj->setSourceSize(QSize(800,0));
QCOMPARE(obj->width(), 800.0);
QVERIFY(qAbs(obj->height() - 1131) < 1);
delete obj;
}
void tst_qquickimage::geometry_data()
{
QTest::addColumn<QString>("fillMode");
QTest::addColumn<bool>("explicitWidth");
QTest::addColumn<bool>("explicitHeight");
QTest::addColumn<double>("itemWidth");
QTest::addColumn<double>("paintedWidth");
QTest::addColumn<double>("boundingWidth");
QTest::addColumn<double>("itemHeight");
QTest::addColumn<double>("paintedHeight");
QTest::addColumn<double>("boundingHeight");
// tested image has width 200, height 100
// bounding rect and item rect are equal with fillMode PreserveAspectFit, painted rect may be smaller if the aspect ratio doesn't match
QTest::newRow("PreserveAspectFit") << "PreserveAspectFit" << false << false << 200.0 << 200.0 << 200.0 << 100.0 << 100.0 << 100.0;
QTest::newRow("PreserveAspectFit explicit width 300") << "PreserveAspectFit" << true << false << 300.0 << 200.0 << 300.0 << 100.0 << 100.0 << 100.0;
QTest::newRow("PreserveAspectFit explicit height 400") << "PreserveAspectFit" << false << true << 200.0 << 200.0 << 200.0 << 400.0 << 100.0 << 400.0;
QTest::newRow("PreserveAspectFit explicit width 300, height 400") << "PreserveAspectFit" << true << true << 300.0 << 300.0 << 300.0 << 400.0 << 150.0 << 400.0;
// bounding rect and painted rect are equal with fillMode PreserveAspectCrop, item rect may be smaller if the aspect ratio doesn't match
QTest::newRow("PreserveAspectCrop") << "PreserveAspectCrop" << false << false << 200.0 << 200.0 << 200.0 << 100.0 << 100.0 << 100.0;
QTest::newRow("PreserveAspectCrop explicit width 300") << "PreserveAspectCrop" << true << false << 300.0 << 300.0 << 300.0 << 100.0 << 150.0 << 150.0;
QTest::newRow("PreserveAspectCrop explicit height 400") << "PreserveAspectCrop" << false << true << 200.0 << 800.0 << 800.0 << 400.0 << 400.0 << 400.0;
QTest::newRow("PreserveAspectCrop explicit width 300, height 400") << "PreserveAspectCrop" << true << true << 300.0 << 800.0 << 800.0 << 400.0 << 400.0 << 400.0;
// bounding rect, painted rect and item rect are equal in stretching and tiling images
QStringList fillModes;
fillModes << "Stretch" << "Tile" << "TileVertically" << "TileHorizontally";
foreach (QString fillMode, fillModes) {
QTest::newRow(fillMode.toLatin1()) << fillMode << false << false << 200.0 << 200.0 << 200.0 << 100.0 << 100.0 << 100.0;
QTest::newRow(QString(fillMode + " explicit width 300").toLatin1()) << fillMode << true << false << 300.0 << 300.0 << 300.0 << 100.0 << 100.0 << 100.0;
QTest::newRow(QString(fillMode + " explicit height 400").toLatin1()) << fillMode << false << true << 200.0 << 200.0 << 200.0 << 400.0 << 400.0 << 400.0;
QTest::newRow(QString(fillMode + " explicit width 300, height 400").toLatin1()) << fillMode << true << true << 300.0 << 300.0 << 300.0 << 400.0 << 400.0 << 400.0;
}
}
void tst_qquickimage::geometry()
{
QFETCH(QString, fillMode);
QFETCH(bool, explicitWidth);
QFETCH(bool, explicitHeight);
QFETCH(double, itemWidth);
QFETCH(double, itemHeight);
QFETCH(double, paintedWidth);
QFETCH(double, paintedHeight);
QFETCH(double, boundingWidth);
QFETCH(double, boundingHeight);
QString src = testFileUrl("rect.png").toString();
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; fillMode: Image." + fillMode + "; ";
if (explicitWidth)
componentStr.append("width: 300; ");
if (explicitHeight)
componentStr.append("height: 400; ");
componentStr.append("}");
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), itemWidth);
QCOMPARE(obj->paintedWidth(), paintedWidth);
QCOMPARE(obj->boundingRect().width(), boundingWidth);
QCOMPARE(obj->height(), itemHeight);
QCOMPARE(obj->paintedHeight(), paintedHeight);
QCOMPARE(obj->boundingRect().height(), boundingHeight);
delete obj;
}
void tst_qquickimage::big()
{
// If the JPEG loader does not implement scaling efficiently, it would
// have to build a 400 MB image. That would be a bug in the JPEG loader.
QString src = testFileUrl("big.jpeg").toString();
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; width: 100; sourceSize.height: 256 }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 100.0);
QCOMPARE(obj->height(), 256.0);
delete obj;
}
void tst_qquickimage::tiling_QTBUG_6716()
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QSKIP("Skipping due to grabWindow not functional on offscreen/minimimal platforms");
QFETCH(QString, source);
QQuickView view(testFileUrl(source));
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QQuickImage *tiling = findItem<QQuickImage>(view.rootObject(), "tiling");
QVERIFY(tiling != nullptr);
QImage img = view.grabWindow();
for (int x = 0; x < tiling->width(); ++x) {
for (int y = 0; y < tiling->height(); ++y) {
QVERIFY(img.pixel(x, y) == qRgb(0, 255, 0));
}
}
}
void tst_qquickimage::tiling_QTBUG_6716_data()
{
QTest::addColumn<QString>("source");
QTest::newRow("vertical_tiling") << "vtiling.qml";
QTest::newRow("horizontal_tiling") << "htiling.qml";
}
void tst_qquickimage::noLoading()
{
qRegisterMetaType<QQuickImageBase::Status>();
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(dataDirectory());
server.addRedirect("oldcolors.png", server.urlString("/colors.png"));
QString componentStr = "import QtQuick 2.0\nImage { source: srcImage; cache: true }";
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("srcImage", testFileUrl("heart.png"));
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->status(), QQuickImage::Ready);
QSignalSpy sourceSpy(obj, SIGNAL(sourceChanged(QUrl)));
QSignalSpy progressSpy(obj, SIGNAL(progressChanged(qreal)));
QSignalSpy statusSpy(obj, SIGNAL(statusChanged(QQuickImageBase::Status)));
// Loading local file
ctxt->setContextProperty("srcImage", testFileUrl("green.png"));
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
QTRY_COMPARE(sourceSpy.count(), 1);
QTRY_COMPARE(progressSpy.count(), 0);
QTRY_COMPARE(statusSpy.count(), 1);
// Loading remote file
ctxt->setContextProperty("srcImage", server.url("/rect.png"));
QTRY_COMPARE(obj->status(), QQuickImage::Loading);
QTRY_COMPARE(obj->progress(), 0.0);
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
QTRY_COMPARE(sourceSpy.count(), 2);
QTRY_VERIFY(progressSpy.count() >= 2);
QTRY_COMPARE(statusSpy.count(), 3);
// Loading remote file again - should not go through 'Loading' state.
progressSpy.clear();
ctxt->setContextProperty("srcImage", testFileUrl("green.png"));
ctxt->setContextProperty("srcImage", server.url("/rect.png"));
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
QTRY_COMPARE(sourceSpy.count(), 4);
QTRY_COMPARE(progressSpy.count(), 0);
QTRY_COMPARE(statusSpy.count(), 5);
delete obj;
}
void tst_qquickimage::paintedWidthHeight()
{
{
QString src = testFileUrl("heart.png").toString();
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; width: 200; height: 25; fillMode: Image.PreserveAspectFit }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 200.0);
QCOMPARE(obj->height(), 25.0);
QCOMPARE(obj->paintedWidth(), 25.0);
QCOMPARE(obj->paintedHeight(), 25.0);
delete obj;
}
{
QString src = testFileUrl("heart.png").toString();
QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; width: 26; height: 175; fillMode: Image.PreserveAspectFit }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 26.0);
QCOMPARE(obj->height(), 175.0);
QCOMPARE(obj->paintedWidth(), 26.0);
QCOMPARE(obj->paintedHeight(), 26.0);
delete obj;
}
}
void tst_qquickimage::sourceSize_QTBUG_14303()
{
QString componentStr = "import QtQuick 2.0\nImage { source: srcImage }";
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("srcImage", testFileUrl("heart200.png"));
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QSignalSpy sourceSizeSpy(obj, SIGNAL(sourceSizeChanged()));
QTRY_VERIFY(obj != nullptr);
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->sourceSize().width(), 200);
QTRY_COMPARE(obj->sourceSize().height(), 200);
QTRY_COMPARE(sourceSizeSpy.count(), 0);
ctxt->setContextProperty("srcImage", testFileUrl("colors.png"));
QTRY_COMPARE(obj->sourceSize().width(), 120);
QTRY_COMPARE(obj->sourceSize().height(), 120);
QTRY_COMPARE(sourceSizeSpy.count(), 1);
ctxt->setContextProperty("srcImage", testFileUrl("heart200.png"));
QTRY_COMPARE(obj->sourceSize().width(), 200);
QTRY_COMPARE(obj->sourceSize().height(), 200);
QTRY_COMPARE(sourceSizeSpy.count(), 2);
delete obj;
}
void tst_qquickimage::sourceSize_QTBUG_16389()
{
QScopedPointer<QQuickView> window(new QQuickView(nullptr));
window->setSource(testFileUrl("qtbug_16389.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickImage *image = findItem<QQuickImage>(window->rootObject(), "iconImage");
QQuickItem *handle = findItem<QQuickItem>(window->rootObject(), "blueHandle");
QCOMPARE(image->sourceSize().width(), 200);
QCOMPARE(image->sourceSize().height(), 200);
QCOMPARE(image->paintedWidth(), 0.0);
QCOMPARE(image->paintedHeight(), 0.0);
handle->setY(20);
QCOMPARE(image->sourceSize().width(), 200);
QCOMPARE(image->sourceSize().height(), 200);
QCOMPARE(image->paintedWidth(), 20.0);
QCOMPARE(image->paintedHeight(), 20.0);
}
// QTBUG-15690
void tst_qquickimage::nullPixmapPaint()
{
QScopedPointer<QQuickView> window(new QQuickView(nullptr));
window->setSource(testFileUrl("nullpixmap.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(dataDirectory(), TestHTTPServer::Delay);
QQuickImage *image = qobject_cast<QQuickImage*>(window->rootObject());
QTRY_VERIFY(image != nullptr);
image->setSource(server.url("/no-such-file.png"));
QQmlTestMessageHandler messageHandler;
// used to print "QTransform::translate with NaN called"
QPixmap pm = QPixmap::fromImage(window->grabWindow());
QVERIFY2(messageHandler.messages().size() == 0, qPrintable(messageHandler.messageString()));
delete image;
}
void tst_qquickimage::imageCrash_QTBUG_22125()
{
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(dataDirectory(), TestHTTPServer::Delay);
{
QQuickView view;
view.rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl());
view.setSource(testFileUrl("qtbug_22125.qml"));
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
qApp->processEvents();
// shouldn't crash when the view drops out of scope due to
// QQuickPixmapData attempting to dereference a pointer to
// the destroyed reader.
}
// shouldn't crash when deleting cancelled QQmlPixmapReplys.
server.sendDelayedItem();
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
QCoreApplication::processEvents();
}
void tst_qquickimage::imageCrash_QTBUG_32513()
{
QQuickView view(testFileUrl("qtbug_32513.qml"));
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QTest::qWait(1000);
// shouldn't crash when the image changes sources
}
void tst_qquickimage::sourceSize_data()
{
QTest::addColumn<int>("sourceWidth");
QTest::addColumn<int>("sourceHeight");
QTest::addColumn<qreal>("implicitWidth");
QTest::addColumn<qreal>("implicitHeight");
QTest::newRow("unscaled") << 0 << 0 << 300.0 << 300.0;
QTest::newRow("scale width") << 100 << 0 << 100.0 << 100.0;
QTest::newRow("scale height") << 0 << 150 << 150.0 << 150.0;
QTest::newRow("larger sourceSize") << 400 << 400 << 300.0 << 300.0;
}
void tst_qquickimage::sourceSize()
{
QFETCH(int, sourceWidth);
QFETCH(int, sourceHeight);
QFETCH(qreal, implicitWidth);
QFETCH(qreal, implicitHeight);
QScopedPointer<QQuickView> window(new QQuickView(nullptr));
QQmlContext *ctxt = window->rootContext();
ctxt->setContextProperty("srcWidth", sourceWidth);
ctxt->setContextProperty("srcHeight", sourceHeight);
window->setSource(testFileUrl("sourceSize.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickImage *image = qobject_cast<QQuickImage*>(window->rootObject());
QVERIFY(image);
QCOMPARE(image->sourceSize().width(), sourceWidth);
QCOMPARE(image->sourceSize().height(), sourceHeight);
QCOMPARE(image->implicitWidth(), implicitWidth);
QCOMPARE(image->implicitHeight(), implicitHeight);
}
void tst_qquickimage::sourceSizeChanges()
{
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(dataDirectory());
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\nImage { source: srcImage }", QUrl::fromLocalFile(""));
QTRY_VERIFY(component.isReady());
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("srcImage", "");
QQuickImage *img = qobject_cast<QQuickImage*>(component.create());
QVERIFY(img != nullptr);
QSignalSpy sourceSizeSpy(img, SIGNAL(sourceSizeChanged()));
// Local
ctxt->setContextProperty("srcImage", QUrl(""));
QTRY_COMPARE(img->status(), QQuickImage::Null);
QTRY_COMPARE(sourceSizeSpy.count(), 0);
ctxt->setContextProperty("srcImage", testFileUrl("heart.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 1);
ctxt->setContextProperty("srcImage", testFileUrl("heart.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 1);
ctxt->setContextProperty("srcImage", testFileUrl("heart_copy.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 1);
ctxt->setContextProperty("srcImage", testFileUrl("colors.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 2);
ctxt->setContextProperty("srcImage", QUrl(""));
QTRY_COMPARE(img->status(), QQuickImage::Null);
QTRY_COMPARE(sourceSizeSpy.count(), 3);
// Remote
ctxt->setContextProperty("srcImage", server.url("/heart.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 4);
ctxt->setContextProperty("srcImage", server.url("/heart.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 4);
ctxt->setContextProperty("srcImage", server.url("/heart_copy.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 4);
ctxt->setContextProperty("srcImage", server.url("/colors.png"));
QTRY_COMPARE(img->status(), QQuickImage::Ready);
QTRY_COMPARE(sourceSizeSpy.count(), 5);
ctxt->setContextProperty("srcImage", QUrl(""));
QTRY_COMPARE(img->status(), QQuickImage::Null);
QTRY_COMPARE(sourceSizeSpy.count(), 6);
delete img;
}
void tst_qquickimage::progressAndStatusChanges()
{
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(dataDirectory());
QQmlEngine engine;
QString componentStr = "import QtQuick 2.0\nImage { source: srcImage }";
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("srcImage", testFileUrl("heart.png"));
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
qRegisterMetaType<QQuickImageBase::Status>();
QSignalSpy sourceSpy(obj, SIGNAL(sourceChanged(QUrl)));
QSignalSpy progressSpy(obj, SIGNAL(progressChanged(qreal)));
QSignalSpy statusSpy(obj, SIGNAL(statusChanged(QQuickImageBase::Status)));
// Same image
ctxt->setContextProperty("srcImage", testFileUrl("heart.png"));
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
QTRY_COMPARE(sourceSpy.count(), 0);
QTRY_COMPARE(progressSpy.count(), 0);
QTRY_COMPARE(statusSpy.count(), 0);
// Loading local file
ctxt->setContextProperty("srcImage", testFileUrl("colors.png"));
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
QTRY_COMPARE(sourceSpy.count(), 1);
QTRY_COMPARE(progressSpy.count(), 0);
QTRY_COMPARE(statusSpy.count(), 1);
// Loading remote file
ctxt->setContextProperty("srcImage", server.url("/heart.png"));
QTRY_COMPARE(obj->status(), QQuickImage::Loading);
QTRY_COMPARE(obj->progress(), 0.0);
QTRY_COMPARE(obj->status(), QQuickImage::Ready);
QTRY_COMPARE(obj->progress(), 1.0);
QTRY_COMPARE(sourceSpy.count(), 2);
QTRY_VERIFY(progressSpy.count() > 1);
QTRY_COMPARE(statusSpy.count(), 3);
ctxt->setContextProperty("srcImage", "");
QTRY_COMPARE(obj->status(), QQuickImage::Null);
QTRY_COMPARE(obj->progress(), 0.0);
QTRY_COMPARE(sourceSpy.count(), 3);
QTRY_VERIFY(progressSpy.count() > 2);
QTRY_COMPARE(statusSpy.count(), 4);
delete obj;
}
class TestQImageProvider : public QQuickImageProvider
{
public:
TestQImageProvider() : QQuickImageProvider(Image) {}
QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize)
{
Q_UNUSED(requestedSize);
if (id == QLatin1String("first-image.png")) {
QTest::qWait(50);
int width = 100;
int height = 100;
QImage image(width, height, QImage::Format_RGB32);
image.fill(QColor("yellow").rgb());
if (size)
*size = QSize(width, height);
return image;
}
QTest::qWait(400);
int width = 100;
int height = 100;
QImage image(width, height, QImage::Format_RGB32);
image.fill(QColor("green").rgb());
if (size)
*size = QSize(width, height);
return image;
}
};
void tst_qquickimage::correctStatus()
{
QQmlEngine engine;
engine.addImageProvider(QLatin1String("test"), new TestQImageProvider());
QQmlComponent component(&engine, testFileUrl("correctStatus.qml"));
QObject *obj = component.create();
QVERIFY(obj);
QTest::qWait(200);
// at this point image1 should be attempting to load second-image.png,
// and should be in the loading state. Without a clear prior to that load,
// the status can mistakenly be in the ready state.
QCOMPARE(obj->property("status").toInt(), int(QQuickImage::Loading));
QTest::qWait(400);
delete obj;
}
void tst_qquickimage::highdpi()
{
QString componentStr = "import QtQuick 2.0\nImage { source: srcImage ; }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQmlContext *ctxt = engine.rootContext();
// Testing "@2x" high-dpi image loading:
// The basic case is as follows. Suppose you have foo.png,
// which is a 64x64 png that fits in a QML layout. Now,
// on a high-dpi system that pixmap would not provide
// enough pixels. To fix this the app developer provides
// a 128x128 foo@2x.png, which Qt automatically loads.
// The image continues to be referred to as "foo.png" in
// the QML sources, and reports a size of 64x64.
//
// Load "heart-highdpi@2x.png", which is a 300x300 png. As a 2x scale image it
// should render and report a geometry of 150x150.
ctxt->setContextProperty("srcImage", testFileUrl("heart-highdpi@2x.png"));
QQuickImage *obj = qobject_cast<QQuickImage*>(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->width(), 150.0);
QCOMPARE(obj->height(), 150.0);
QCOMPARE(obj->paintedWidth(), 150.0);
QCOMPARE(obj->paintedHeight(), 150.0);
// Load a normal 1x image.
ctxt->setContextProperty("srcImage", testFileUrl("heart.png"));
QCOMPARE(obj->width(), 300.0);
QCOMPARE(obj->height(), 300.0);
QCOMPARE(obj->paintedWidth(), 300.0);
QCOMPARE(obj->paintedHeight(), 300.0);
delete obj;
}
void tst_qquickimage::highDpiFillModesAndSizes_data()
{
QTest::addColumn<QQuickImage::FillMode>("fillMode");
QTest::addColumn<qreal>("expectedHeightAfterSettingWidthTo100");
QTest::addColumn<qreal>("expectedImplicitHeightAfterSettingWidthTo100");
QTest::addColumn<qreal>("expectedPaintedWidthAfterSettingWidthTo100");
QTest::addColumn<qreal>("expectedPaintedHeightAfterSettingWidthTo100");
QTest::addRow("Stretch") << QQuickImage::Stretch << 150.0 << 150.0 << 100.0 << 150.0;
QTest::addRow("PreserveAspectFit") << QQuickImage::PreserveAspectFit << 100.0 << 100.0 << 100.0 << 100.0;
QTest::addRow("PreserveAspectCrop") << QQuickImage::PreserveAspectCrop << 150.0 << 150.0 << 150.0 << 150.0;
QTest::addRow("Tile") << QQuickImage::Tile << 150.0 << 150.0 << 100.0 << 150.0;
QTest::addRow("TileVertically") << QQuickImage::TileVertically << 150.0 << 150.0 << 100.0 << 150.0;
QTest::addRow("TileHorizontally") << QQuickImage::TileHorizontally << 150.0 << 150.0 << 100.0 << 150.0;
QTest::addRow("Pad") << QQuickImage::Pad << 150.0 << 150.0 << 150.0 << 150.0;
}
void tst_qquickimage::highDpiFillModesAndSizes()
{
QFETCH(QQuickImage::FillMode, fillMode);
QFETCH(qreal, expectedHeightAfterSettingWidthTo100);
QFETCH(qreal, expectedImplicitHeightAfterSettingWidthTo100);
QFETCH(qreal, expectedPaintedWidthAfterSettingWidthTo100);
QFETCH(qreal, expectedPaintedHeightAfterSettingWidthTo100);
QString componentStr = "import QtQuick 2.0\nImage { source: srcImage; }";
QQmlComponent component(&engine);
component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
engine.rootContext()->setContextProperty("srcImage", testFileUrl("heart-highdpi@2x.png"));
QScopedPointer<QQuickImage> image(qobject_cast<QQuickImage*>(component.create()));
QVERIFY(image);
QCOMPARE(image->width(), 150.0);
QCOMPARE(image->height(), 150.0);
QCOMPARE(image->paintedWidth(), 150.0);
QCOMPARE(image->paintedHeight(), 150.0);
QCOMPARE(image->implicitWidth(), 150.0);
QCOMPARE(image->implicitHeight(), 150.0);
QCOMPARE(image->paintedWidth(), 150.0);
QCOMPARE(image->paintedHeight(), 150.0);
// The implicit size should not change when setting any fillMode here.
image->setFillMode(fillMode);
QCOMPARE(image->fillMode(), fillMode);
QCOMPARE(image->implicitWidth(), 150.0);
QCOMPARE(image->implicitHeight(), 150.0);
QCOMPARE(image->paintedWidth(), 150.0);
QCOMPARE(image->paintedHeight(), 150.0);
image->setWidth(100.0);
QCOMPARE(image->width(), 100.0);
QCOMPARE(image->height(), expectedHeightAfterSettingWidthTo100);
QCOMPARE(image->implicitWidth(), 150.0);
QCOMPARE(image->implicitHeight(), expectedImplicitHeightAfterSettingWidthTo100);
QCOMPARE(image->paintedWidth(), expectedPaintedWidthAfterSettingWidthTo100);
QCOMPARE(image->paintedHeight(), expectedPaintedHeightAfterSettingWidthTo100);
}
void tst_qquickimage::hugeImages()
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QSKIP("Skipping due to grabWindow not functional on offscreen/minimimal platforms");
QQuickView view;
view.setSource(testFileUrl("hugeImages.qml"));
view.setGeometry(0, 0, 200, 200);
view.create();
QImage contents = view.grabWindow();
QCOMPARE(contents.pixel(0, 0), qRgba(255, 0, 0, 255));
QCOMPARE(contents.pixel(99, 99), qRgba(255, 0, 0, 255));
QCOMPARE(contents.pixel(100, 0), qRgba(0, 0, 255, 255));
QCOMPARE(contents.pixel(199, 99), qRgba(0, 0, 255, 255));
}
class MyInterceptor : public QQmlAbstractUrlInterceptor
{
public:
MyInterceptor(QUrl url) : QQmlAbstractUrlInterceptor(), m_url(url) {}
QUrl intercept(const QUrl &url, QQmlAbstractUrlInterceptor::DataType)
{
if (url.scheme() == "interceptthis")
return m_url;
return url;
}
QUrl m_url;
};
void tst_qquickimage::urlInterceptor()
{
QQmlEngine engine;
MyInterceptor interceptor {testFileUrl("colors.png")};
engine.setUrlInterceptor(&interceptor);
QQmlComponent c(&engine);
c.setData("import QtQuick 2.12; Image { objectName: \"item\"; source: width == 0 ? \"interceptthis:doesNotExist\" : \"interceptthis:doesNotExist\"}", QUrl{});
QScopedPointer<QQuickImage> object { qobject_cast<QQuickImage*>(c.create())};
QVERIFY(object);
QTRY_COMPARE(object->status(), QQuickImage::Ready);
QTRY_COMPARE(object->progress(), 1.0);
}
void tst_qquickimage::multiFrame_data()
{
QTest::addColumn<QString>("qmlfile");
QTest::addColumn<bool>("asynchronous");
QTest::addRow("default") << "multiframe.qml" << false;
QTest::addRow("async") << "multiframeAsync.qml" << true;
}
void tst_qquickimage::multiFrame()
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QSKIP("Skipping due to grabWindow not functional on offscreen/minimimal platforms");
QFETCH(QString, qmlfile);
QFETCH(bool, asynchronous);
Q_UNUSED(asynchronous)
QQuickView view(testFileUrl(qmlfile));
QQuickImage *image = qobject_cast<QQuickImage*>(view.rootObject());
QVERIFY(image);
QSignalSpy countSpy(image, SIGNAL(frameCountChanged()));
QSignalSpy currentSpy(image, SIGNAL(currentFrameChanged()));
if (asynchronous) {
QCOMPARE(image->frameCount(), 0);
QTRY_COMPARE(image->frameCount(), 4);
QCOMPARE(countSpy.count(), 1);
} else {
QCOMPARE(image->frameCount(), 4);
}
QCOMPARE(image->currentFrame(), 0);
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QImage contents = view.grabWindow();
if (contents.width() < 40)
QSKIP("Skipping due to grabWindow not functional");
// The first frame is a blue ball, approximately qRgba(0x33, 0x6d, 0xcc, 0xff)
QRgb color = contents.pixel(16, 16);
QVERIFY(qRed(color) < 0xc0);
QVERIFY(qGreen(color) < 0xc0);
QVERIFY(qBlue(color) > 0xc0);
image->setCurrentFrame(1);
QTRY_COMPARE(image->status(), QQuickImageBase::Ready);
QCOMPARE(currentSpy.count(), 1);
QCOMPARE(image->currentFrame(), 1);
contents = view.grabWindow();
// The second frame is a green ball, approximately qRgba(0x27, 0xc8, 0x22, 0xff)
color = contents.pixel(16, 16);
QVERIFY(qRed(color) < 0xc0);
QVERIFY(qGreen(color) > 0xc0);
QVERIFY(qBlue(color) < 0xc0);
}
QTEST_MAIN(tst_qquickimage)
#include "tst_qquickimage.moc"