1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <qtest.h>
29#include <QTextDocument>
30#include <QTcpServer>
31#include <QTcpSocket>
32#include <QDir>
33
34#include <QtQml/qqmlengine.h>
35#include <QtQml/qqmlcomponent.h>
36#include <QtQuick/qquickview.h>
37#include <private/qquickimage_p.h>
38#include <private/qquickimagebase_p.h>
39#include <private/qquickloader_p.h>
40#include <QtQml/qqmlcontext.h>
41#include <QtQml/qqmlexpression.h>
42#include <QtTest/QSignalSpy>
43#include <QtGui/QPainter>
44#include <QtGui/QImageReader>
45#include <QQuickWindow>
46#include <QQuickView>
47#include <QQuickImageProvider>
48#include <QQmlAbstractUrlInterceptor>
49
50#include "../../shared/util.h"
51#include "../../shared/testhttpserver.h"
52#include "../shared/visualtestutil.h"
53
54// #define DEBUG_WRITE_OUTPUT
55
56using namespace QQuickVisualTestUtil;
57
58Q_DECLARE_METATYPE(QQuickImageBase::Status)
59
60class tst_qquickimage : public QQmlDataTest
61{
62 Q_OBJECT
63public:
64 tst_qquickimage();
65
66private slots:
67 void initTestCase();
68 void cleanup();
69 void noSource();
70 void imageSource();
71 void imageSource_data();
72 void clearSource();
73 void resized();
74 void preserveAspectRatio();
75 void smooth();
76 void mirror();
77 void svg();
78 void svg_data();
79 void geometry();
80 void geometry_data();
81 void big();
82 void tiling_QTBUG_6716();
83 void tiling_QTBUG_6716_data();
84 void noLoading();
85 void paintedWidthHeight();
86 void sourceSize_QTBUG_14303();
87 void sourceSize_QTBUG_16389();
88 void nullPixmapPaint();
89 void imageCrash_QTBUG_22125();
90 void imageCrash_QTBUG_32513();
91 void sourceSize_data();
92 void sourceSize();
93 void sourceClipRect_data();
94 void sourceClipRect();
95 void progressAndStatusChanges();
96 void sourceSizeChanges();
97 void correctStatus();
98 void highdpi();
99 void highDpiFillModesAndSizes_data();
100 void highDpiFillModesAndSizes();
101 void hugeImages();
102 void urlInterceptor();
103 void multiFrame_data();
104 void multiFrame();
105 void colorSpace();
106
107private:
108 QQmlEngine engine;
109 QSGRendererInterface::GraphicsApi graphicsApi = QSGRendererInterface::Unknown;
110};
111
112tst_qquickimage::tst_qquickimage()
113{
114}
115
116void tst_qquickimage::initTestCase()
117{
118 QQmlDataTest::initTestCase();
119 QScopedPointer<QQuickView> window(new QQuickView(0));
120 window->show();
121 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
122 graphicsApi = window->rendererInterface()->graphicsApi();
123}
124
125void tst_qquickimage::cleanup()
126{
127 QQuickWindow window;
128 window.releaseResources();
129 engine.clearComponentCache();
130}
131
132void tst_qquickimage::noSource()
133{
134 QString componentStr = "import QtQuick 2.0\nImage { source: \"\" }";
135 QQmlComponent component(&engine);
136 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
137 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
138 QVERIFY(obj != nullptr);
139 QCOMPARE(obj->source(), QUrl());
140 QCOMPARE(obj->status(), QQuickImage::Null);
141 QCOMPARE(obj->width(), 0.);
142 QCOMPARE(obj->height(), 0.);
143 QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
144 QCOMPARE(obj->progress(), 0.0);
145
146 delete obj;
147}
148
149void tst_qquickimage::imageSource_data()
150{
151 QTest::addColumn<QString>(name: "source");
152 QTest::addColumn<double>(name: "width");
153 QTest::addColumn<double>(name: "height");
154 QTest::addColumn<bool>(name: "remote");
155 QTest::addColumn<bool>(name: "async");
156 QTest::addColumn<bool>(name: "cache");
157 QTest::addColumn<QString>(name: "error");
158
159 QTest::newRow(dataTag: "local") << testFileUrl(fileName: "colors.png").toString() << 120.0 << 120.0 << false << false << true << "";
160 QTest::newRow(dataTag: "local no cache") << testFileUrl(fileName: "colors.png").toString() << 120.0 << 120.0 << false << false << false << "";
161 QTest::newRow(dataTag: "local async") << testFileUrl(fileName: "colors1.png").toString() << 120.0 << 120.0 << false << true << true << "";
162 QTest::newRow(dataTag: "local not found") << testFileUrl(fileName: "no-such-file.png").toString() << 0.0 << 0.0 << false
163 << false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl(fileName: "no-such-file.png").toString();
164 QTest::newRow(dataTag: "local async not found") << testFileUrl(fileName: "no-such-file-1.png").toString() << 0.0 << 0.0 << false
165 << true << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl(fileName: "no-such-file-1.png").toString();
166 QTest::newRow(dataTag: "remote") << "/colors.png" << 120.0 << 120.0 << true << false << true << "";
167 QTest::newRow(dataTag: "remote redirected") << "/oldcolors.png" << 120.0 << 120.0 << true << false << false << "";
168 if (QImageReader::supportedImageFormats().contains(t: "svg"))
169 QTest::newRow(dataTag: "remote svg") << "/heart.svg" << 595.0 << 841.0 << true << false << false << "";
170 if (QImageReader::supportedImageFormats().contains(t: "svgz"))
171 QTest::newRow(dataTag: "remote svgz") << "/heart.svgz" << 595.0 << 841.0 << true << false << false << "";
172 if (graphicsApi == QSGRendererInterface::OpenGL) {
173 QTest::newRow(dataTag: "texturefile pkm format") << testFileUrl(fileName: "logo.pkm").toString() << 256.0 << 256.0 << false << false << true << "";
174 QTest::newRow(dataTag: "texturefile ktx format") << testFileUrl(fileName: "car.ktx").toString() << 146.0 << 80.0 << false << false << true << "";
175 QTest::newRow(dataTag: "texturefile async") << testFileUrl(fileName: "logo.pkm").toString() << 256.0 << 256.0 << false << true << true << "";
176 }
177 QTest::newRow(dataTag: "remote not found") << "/no-such-file.png" << 0.0 << 0.0 << true
178 << false << true << "<Unknown File>:2:1: QML Image: Error transferring {{ServerBaseUrl}}/no-such-file.png - server replied: Not found";
179 QTest::newRow(dataTag: "extless") << testFileUrl(fileName: "colors").toString() << 120.0 << 120.0 << false << false << true << "";
180 QTest::newRow(dataTag: "extless no cache") << testFileUrl(fileName: "colors").toString() << 120.0 << 120.0 << false << false << false << "";
181 QTest::newRow(dataTag: "extless async") << testFileUrl(fileName: "colors1").toString() << 120.0 << 120.0 << false << true << true << "";
182 QTest::newRow(dataTag: "extless not found") << testFileUrl(fileName: "no-such-file").toString() << 0.0 << 0.0 << false
183 << false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl(fileName: "no-such-file").toString();
184 // Test that texture file is preferred over image file, when supported.
185 // Since pattern.pkm has different size than pattern.png, these tests verify that the right file has been loaded
186 if (graphicsApi == QSGRendererInterface::OpenGL) {
187 QTest::newRow(dataTag: "extless prefer-tex") << testFileUrl(fileName: "pattern").toString() << 64.0 << 64.0 << false << false << true << "";
188 QTest::newRow(dataTag: "extless prefer-tex async") << testFileUrl(fileName: "pattern").toString() << 64.0 << 64.0 << false << true << true << "";
189 } else {
190 QTest::newRow(dataTag: "extless ignore-tex") << testFileUrl(fileName: "pattern").toString() << 200.0 << 200.0 << false << false << true << "";
191 QTest::newRow(dataTag: "extless ignore-tex async") << testFileUrl(fileName: "pattern").toString() << 200.0 << 200.0 << false << true << true << "";
192 }
193
194}
195
196void tst_qquickimage::imageSource()
197{
198 QFETCH(QString, source);
199 QFETCH(double, width);
200 QFETCH(double, height);
201 QFETCH(bool, remote);
202 QFETCH(bool, async);
203 QFETCH(bool, cache);
204 QFETCH(QString, error);
205
206
207#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
208 if (qstrcmp(str1: QTest::currentDataTag(), str2: "remote") == 0
209 || qstrcmp(str1: QTest::currentDataTag(), str2: "remote redirected") == 0
210 || qstrcmp(str1: QTest::currentDataTag(), str2: "remote svg") == 0
211 || qstrcmp(str1: QTest::currentDataTag(), str2: "remote svgz") == 0
212 || qstrcmp(str1: QTest::currentDataTag(), str2: "remote not found") == 0
213 ) {
214 QSKIP("Remote tests cause occasional hangs in the CI system -- QTBUG-45655");
215 }
216#endif
217
218 TestHTTPServer server;
219 if (remote) {
220 QVERIFY2(server.listen(), qPrintable(server.errorString()));
221 server.serveDirectory(dataDirectory());
222 server.addRedirect(filename: "oldcolors.png", redirectName: server.urlString(documentPath: "/colors.png"));
223 source = server.urlString(documentPath: source);
224 error.replace(QStringLiteral("{{ServerBaseUrl}}"), after: server.baseUrl().toString());
225 }
226
227 if (!error.isEmpty())
228 QTest::ignoreMessage(type: QtWarningMsg, message: error.toUtf8());
229
230 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + source + "\"; asynchronous: "
231 + (async ? QLatin1String("true") : QLatin1String("false")) + "; cache: "
232 + (cache ? QLatin1String("true") : QLatin1String("false")) + " }";
233 QQmlComponent component(&engine);
234 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
235 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
236 QVERIFY(obj != nullptr);
237
238 if (async)
239 QVERIFY(obj->asynchronous());
240 else
241 QVERIFY(!obj->asynchronous());
242
243 if (cache)
244 QVERIFY(obj->cache());
245 else
246 QVERIFY(!obj->cache());
247
248 if (remote || async)
249 QTRY_COMPARE(obj->status(), QQuickImage::Loading);
250
251 QCOMPARE(obj->source(), remote ? source : QUrl(source));
252
253 if (error.isEmpty()) {
254 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
255 QCOMPARE(obj->width(), qreal(width));
256 QCOMPARE(obj->height(), qreal(height));
257 QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
258 QCOMPARE(obj->progress(), 1.0);
259 } else {
260 QTRY_COMPARE(obj->status(), QQuickImage::Error);
261 }
262
263 delete obj;
264}
265
266void tst_qquickimage::clearSource()
267{
268 QString componentStr = "import QtQuick 2.0\nImage { source: srcImage }";
269 QQmlContext *ctxt = engine.rootContext();
270 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "colors.png"));
271 QQmlComponent component(&engine);
272 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
273 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
274 QVERIFY(obj != nullptr);
275 QCOMPARE(obj->status(), QQuickImage::Ready);
276 QCOMPARE(obj->width(), 120.);
277 QCOMPARE(obj->height(), 120.);
278 QCOMPARE(obj->progress(), 1.0);
279
280 ctxt->setContextProperty("srcImage", "");
281 QVERIFY(obj->source().isEmpty());
282 QCOMPARE(obj->status(), QQuickImage::Null);
283 QCOMPARE(obj->width(), 0.);
284 QCOMPARE(obj->height(), 0.);
285 QCOMPARE(obj->progress(), 0.0);
286
287 delete obj;
288}
289
290void tst_qquickimage::resized()
291{
292 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + testFile(fileName: "colors.png") + "\"; width: 300; height: 300 }";
293 QQmlComponent component(&engine);
294 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
295 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
296 QVERIFY(obj != nullptr);
297 QCOMPARE(obj->width(), 300.);
298 QCOMPARE(obj->height(), 300.);
299 QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
300 delete obj;
301}
302
303
304void tst_qquickimage::preserveAspectRatio()
305{
306 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
307 window->show();
308 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
309
310 window->setSource(testFileUrl(fileName: "aspectratio.qml"));
311 QQuickImage *image = qobject_cast<QQuickImage*>(object: window->rootObject());
312 QVERIFY(image != nullptr);
313 image->setWidth(80.0);
314 QCOMPARE(image->width(), 80.);
315 QCOMPARE(image->height(), 80.);
316
317 window->setSource(testFileUrl(fileName: "aspectratio.qml"));
318 image = qobject_cast<QQuickImage*>(object: window->rootObject());
319 image->setHeight(60.0);
320 QVERIFY(image != nullptr);
321 QCOMPARE(image->height(), 60.);
322 QCOMPARE(image->width(), 60.);
323}
324
325void tst_qquickimage::smooth()
326{
327 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + testFile(fileName: "colors.png") + "\"; smooth: true; width: 300; height: 300 }";
328 QQmlComponent component(&engine);
329 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
330 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
331 QVERIFY(obj != nullptr);
332 QCOMPARE(obj->width(), 300.);
333 QCOMPARE(obj->height(), 300.);
334 QCOMPARE(obj->smooth(), true);
335 QCOMPARE(obj->fillMode(), QQuickImage::Stretch);
336
337 delete obj;
338}
339
340void tst_qquickimage::mirror()
341{
342 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
343 || (QGuiApplication::platformName() == QLatin1String("minimal")))
344 QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
345
346 QMap<QQuickImage::FillMode, QImage> screenshots;
347 QList<QQuickImage::FillMode> fillModes;
348 fillModes << QQuickImage::Stretch << QQuickImage::PreserveAspectFit << QQuickImage::PreserveAspectCrop
349 << QQuickImage::Tile << QQuickImage::TileVertically << QQuickImage::TileHorizontally << QQuickImage::Pad;
350
351 qreal width = 300;
352 qreal height = 250;
353 qreal devicePixelRatio = 1.0;
354
355 foreach (QQuickImage::FillMode fillMode, fillModes) {
356 QScopedPointer<QQuickView> window(new QQuickView);
357 window->setSource(testFileUrl(fileName: "mirror.qml"));
358
359 QQuickImage *obj = window->rootObject()->findChild<QQuickImage*>(aName: "image");
360 QVERIFY(obj != nullptr);
361
362 obj->setFillMode(fillMode);
363 obj->setProperty(name: "mirror", value: true);
364 window->showNormal();
365 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
366
367 QImage screenshot = window->grabWindow();
368 screenshots[fillMode] = screenshot;
369 devicePixelRatio = window->devicePixelRatio();
370 }
371
372 foreach (QQuickImage::FillMode fillMode, fillModes) {
373 QPixmap srcPixmap;
374 QVERIFY(srcPixmap.load(testFile("pattern.png")));
375
376 QPixmap expected(width * (int)devicePixelRatio, height * (int)devicePixelRatio);
377 expected.setDevicePixelRatio(devicePixelRatio);
378 expected.fill();
379 QPainter p_e(&expected);
380 QTransform transform;
381 transform.translate(dx: width, dy: 0).scale(sx: -1, sy: 1.0);
382 p_e.setTransform(transform);
383
384 QPoint offset(width / 2 - srcPixmap.width() / 2, height / 2 - srcPixmap.height() / 2);
385
386 switch (fillMode) {
387 case QQuickImage::Stretch:
388 p_e.drawPixmap(targetRect: QRect(0, 0, width, height), pixmap: srcPixmap, sourceRect: QRect(0, 0, srcPixmap.width(), srcPixmap.height()));
389 break;
390 case QQuickImage::PreserveAspectFit:
391 p_e.drawPixmap(targetRect: QRect(25, 0, height, height), pixmap: srcPixmap, sourceRect: QRect(0, 0, srcPixmap.width(), srcPixmap.height()));
392 break;
393 case QQuickImage::PreserveAspectCrop:
394 {
395 qreal ratio = width/srcPixmap.width(); // width is the longer side
396 QRect rect(0, 0, srcPixmap.width()*ratio, srcPixmap.height()*ratio);
397 rect.moveCenter(p: QRect(0, 0, width, height).center());
398 p_e.drawPixmap(targetRect: rect, pixmap: srcPixmap, sourceRect: QRect(0, 0, srcPixmap.width(), srcPixmap.height()));
399 break;
400 }
401 case QQuickImage::Tile:
402 p_e.drawTiledPixmap(rect: QRect(0, 0, width, height), pm: srcPixmap, offset: -offset);
403 break;
404 case QQuickImage::TileVertically:
405 transform.scale(sx: width / srcPixmap.width(), sy: 1.0);
406 p_e.setTransform(transform);
407 p_e.drawTiledPixmap(rect: QRect(0, 0, width, height), pm: srcPixmap, offset: QPoint(0, -offset.y()));
408 break;
409 case QQuickImage::TileHorizontally:
410 transform.scale(sx: 1.0, sy: height / srcPixmap.height());
411 p_e.setTransform(transform);
412 p_e.drawTiledPixmap(rect: QRect(0, 0, width, height), pm: srcPixmap, offset: QPoint(-offset.x(), 0));
413 break;
414 case QQuickImage::Pad:
415 p_e.drawPixmap(p: offset, pm: srcPixmap);
416 break;
417 }
418
419 QImage img = expected.toImage();
420 QCOMPARE(screenshots[fillMode].convertToFormat(img.format()), img);
421 }
422}
423
424void tst_qquickimage::svg_data()
425{
426 QTest::addColumn<QString>(name: "src");
427 QTest::addColumn<QByteArray>(name: "format");
428
429 QTest::newRow(dataTag: "svg") << testFileUrl(fileName: "heart.svg").toString() << QByteArray("svg");
430 QTest::newRow(dataTag: "svgz") << testFileUrl(fileName: "heart.svgz").toString() << QByteArray("svgz");
431}
432
433void tst_qquickimage::svg()
434{
435 QFETCH(QString, src);
436 QFETCH(QByteArray, format);
437 if (!QImageReader::supportedImageFormats().contains(t: format))
438 QSKIP("svg support not available");
439
440 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; sourceSize.width: 300; sourceSize.height: 300 }";
441 QQmlComponent component(&engine);
442 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
443 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
444 QVERIFY(obj != nullptr);
445 QCOMPARE(obj->width(), 300.0);
446 QCOMPARE(obj->height(), 300.0);
447 obj->setSourceSize(QSize(200,200));
448
449 QCOMPARE(obj->width(), 200.0);
450 QCOMPARE(obj->height(), 200.0);
451 obj->setSourceSize(QSize(100,0));
452 QCOMPARE(obj->width(), 100.0);
453 // Due to aspect ratio calculations we can't get a precise
454 // check for all setups, so we allow a small margin of error
455 QVERIFY(qAbs(obj->height() - 141) < 1);
456
457 // Setting it to a size bigger than the actual file, SVG formats
458 // can scale up although other image formats cannot
459 obj->setSourceSize(QSize(800,0));
460 QCOMPARE(obj->width(), 800.0);
461 QVERIFY(qAbs(obj->height() - 1131) < 1);
462 delete obj;
463}
464
465void tst_qquickimage::geometry_data()
466{
467 QTest::addColumn<QString>(name: "fillMode");
468 QTest::addColumn<bool>(name: "explicitWidth");
469 QTest::addColumn<bool>(name: "explicitHeight");
470 QTest::addColumn<double>(name: "itemWidth");
471 QTest::addColumn<double>(name: "paintedWidth");
472 QTest::addColumn<double>(name: "boundingWidth");
473 QTest::addColumn<double>(name: "itemHeight");
474 QTest::addColumn<double>(name: "paintedHeight");
475 QTest::addColumn<double>(name: "boundingHeight");
476
477 // tested image has width 200, height 100
478
479 // bounding rect and item rect are equal with fillMode PreserveAspectFit, painted rect may be smaller if the aspect ratio doesn't match
480 QTest::newRow(dataTag: "PreserveAspectFit") << "PreserveAspectFit" << false << false << 200.0 << 200.0 << 200.0 << 100.0 << 100.0 << 100.0;
481 QTest::newRow(dataTag: "PreserveAspectFit explicit width 300") << "PreserveAspectFit" << true << false << 300.0 << 200.0 << 300.0 << 100.0 << 100.0 << 100.0;
482 QTest::newRow(dataTag: "PreserveAspectFit explicit height 400") << "PreserveAspectFit" << false << true << 200.0 << 200.0 << 200.0 << 400.0 << 100.0 << 400.0;
483 QTest::newRow(dataTag: "PreserveAspectFit explicit width 300, height 400") << "PreserveAspectFit" << true << true << 300.0 << 300.0 << 300.0 << 400.0 << 150.0 << 400.0;
484
485 // bounding rect and painted rect are equal with fillMode PreserveAspectCrop, item rect may be smaller if the aspect ratio doesn't match
486 QTest::newRow(dataTag: "PreserveAspectCrop") << "PreserveAspectCrop" << false << false << 200.0 << 200.0 << 200.0 << 100.0 << 100.0 << 100.0;
487 QTest::newRow(dataTag: "PreserveAspectCrop explicit width 300") << "PreserveAspectCrop" << true << false << 300.0 << 300.0 << 300.0 << 100.0 << 150.0 << 150.0;
488 QTest::newRow(dataTag: "PreserveAspectCrop explicit height 400") << "PreserveAspectCrop" << false << true << 200.0 << 800.0 << 800.0 << 400.0 << 400.0 << 400.0;
489 QTest::newRow(dataTag: "PreserveAspectCrop explicit width 300, height 400") << "PreserveAspectCrop" << true << true << 300.0 << 800.0 << 800.0 << 400.0 << 400.0 << 400.0;
490
491 // bounding rect, painted rect and item rect are equal in stretching and tiling images
492 QStringList fillModes;
493 fillModes << "Stretch" << "Tile" << "TileVertically" << "TileHorizontally";
494 foreach (QString fillMode, fillModes) {
495 QTest::newRow(dataTag: fillMode.toLatin1()) << fillMode << false << false << 200.0 << 200.0 << 200.0 << 100.0 << 100.0 << 100.0;
496 QTest::newRow(dataTag: QString(fillMode + " explicit width 300").toLatin1()) << fillMode << true << false << 300.0 << 300.0 << 300.0 << 100.0 << 100.0 << 100.0;
497 QTest::newRow(dataTag: QString(fillMode + " explicit height 400").toLatin1()) << fillMode << false << true << 200.0 << 200.0 << 200.0 << 400.0 << 400.0 << 400.0;
498 QTest::newRow(dataTag: QString(fillMode + " explicit width 300, height 400").toLatin1()) << fillMode << true << true << 300.0 << 300.0 << 300.0 << 400.0 << 400.0 << 400.0;
499 }
500}
501
502void tst_qquickimage::geometry()
503{
504 QFETCH(QString, fillMode);
505 QFETCH(bool, explicitWidth);
506 QFETCH(bool, explicitHeight);
507 QFETCH(double, itemWidth);
508 QFETCH(double, itemHeight);
509 QFETCH(double, paintedWidth);
510 QFETCH(double, paintedHeight);
511 QFETCH(double, boundingWidth);
512 QFETCH(double, boundingHeight);
513
514 QString src = testFileUrl(fileName: "rect.png").toString();
515 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; fillMode: Image." + fillMode + "; ";
516
517 if (explicitWidth)
518 componentStr.append(s: "width: 300; ");
519 if (explicitHeight)
520 componentStr.append(s: "height: 400; ");
521 componentStr.append(s: "}");
522 QQmlComponent component(&engine);
523 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
524 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
525 QVERIFY(obj != nullptr);
526
527 QCOMPARE(obj->width(), itemWidth);
528 QCOMPARE(obj->paintedWidth(), paintedWidth);
529 QCOMPARE(obj->boundingRect().width(), boundingWidth);
530
531 QCOMPARE(obj->height(), itemHeight);
532 QCOMPARE(obj->paintedHeight(), paintedHeight);
533 QCOMPARE(obj->boundingRect().height(), boundingHeight);
534 delete obj;
535}
536
537void tst_qquickimage::big()
538{
539 // If the JPEG loader does not implement scaling efficiently, it would
540 // have to build a 400 MB image. That would be a bug in the JPEG loader.
541
542 QString src = testFileUrl(fileName: "big.jpeg").toString();
543 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; width: 100; sourceSize.height: 256 }";
544
545 QQmlComponent component(&engine);
546 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
547 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
548 QVERIFY(obj != nullptr);
549 QCOMPARE(obj->width(), 100.0);
550 QCOMPARE(obj->height(), 256.0);
551
552 delete obj;
553}
554
555void tst_qquickimage::tiling_QTBUG_6716()
556{
557 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
558 || (QGuiApplication::platformName() == QLatin1String("minimal")))
559 QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
560
561 QFETCH(QString, source);
562
563 QQuickView view(testFileUrl(fileName: source));
564 view.show();
565 QVERIFY(QTest::qWaitForWindowExposed(&view));
566
567 QQuickImage *tiling = findItem<QQuickImage>(parent: view.rootObject(), objectName: "tiling");
568
569 QVERIFY(tiling != nullptr);
570 QImage img = view.grabWindow();
571 for (int x = 0; x < tiling->width(); ++x) {
572 for (int y = 0; y < tiling->height(); ++y) {
573 QVERIFY(img.pixel(x, y) == qRgb(0, 255, 0));
574 }
575 }
576}
577
578void tst_qquickimage::tiling_QTBUG_6716_data()
579{
580 QTest::addColumn<QString>(name: "source");
581 QTest::newRow(dataTag: "vertical_tiling") << "vtiling.qml";
582 QTest::newRow(dataTag: "horizontal_tiling") << "htiling.qml";
583}
584
585void tst_qquickimage::noLoading()
586{
587 qRegisterMetaType<QQuickImageBase::Status>();
588
589 TestHTTPServer server;
590 QVERIFY2(server.listen(), qPrintable(server.errorString()));
591 server.serveDirectory(dataDirectory());
592 server.addRedirect(filename: "oldcolors.png", redirectName: server.urlString(documentPath: "/colors.png"));
593
594 QString componentStr = "import QtQuick 2.0\nImage { source: srcImage; cache: true }";
595 QQmlContext *ctxt = engine.rootContext();
596 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart.png"));
597 QQmlComponent component(&engine);
598 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
599 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
600 QVERIFY(obj != nullptr);
601 QCOMPARE(obj->status(), QQuickImage::Ready);
602
603 QSignalSpy sourceSpy(obj, SIGNAL(sourceChanged(QUrl)));
604 QSignalSpy progressSpy(obj, SIGNAL(progressChanged(qreal)));
605 QSignalSpy statusSpy(obj, SIGNAL(statusChanged(QQuickImageBase::Status)));
606
607 // Loading local file
608 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "green.png"));
609 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
610 QTRY_COMPARE(obj->progress(), 1.0);
611 QTRY_COMPARE(sourceSpy.count(), 1);
612 QTRY_COMPARE(progressSpy.count(), 0);
613 QTRY_COMPARE(statusSpy.count(), 1);
614
615 // Loading remote file
616 ctxt->setContextProperty("srcImage", server.url(documentPath: "/rect.png"));
617 QTRY_COMPARE(obj->status(), QQuickImage::Loading);
618 QTRY_COMPARE(obj->progress(), 0.0);
619 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
620 QTRY_COMPARE(obj->progress(), 1.0);
621 QTRY_COMPARE(sourceSpy.count(), 2);
622 QTRY_VERIFY(progressSpy.count() >= 2);
623 QTRY_COMPARE(statusSpy.count(), 3);
624
625 // Loading remote file again - should not go through 'Loading' state.
626 progressSpy.clear();
627 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "green.png"));
628 ctxt->setContextProperty("srcImage", server.url(documentPath: "/rect.png"));
629 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
630 QTRY_COMPARE(obj->progress(), 1.0);
631 QTRY_COMPARE(sourceSpy.count(), 4);
632 QTRY_COMPARE(progressSpy.count(), 0);
633 QTRY_COMPARE(statusSpy.count(), 5);
634
635 delete obj;
636}
637
638void tst_qquickimage::paintedWidthHeight()
639{
640 {
641 QString src = testFileUrl(fileName: "heart.png").toString();
642 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; width: 200; height: 25; fillMode: Image.PreserveAspectFit }";
643
644 QQmlComponent component(&engine);
645 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
646 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
647 QVERIFY(obj != nullptr);
648 QCOMPARE(obj->width(), 200.0);
649 QCOMPARE(obj->height(), 25.0);
650 QCOMPARE(obj->paintedWidth(), 25.0);
651 QCOMPARE(obj->paintedHeight(), 25.0);
652
653 delete obj;
654 }
655
656 {
657 QString src = testFileUrl(fileName: "heart.png").toString();
658 QString componentStr = "import QtQuick 2.0\nImage { source: \"" + src + "\"; width: 26; height: 175; fillMode: Image.PreserveAspectFit }";
659 QQmlComponent component(&engine);
660 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
661 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
662 QVERIFY(obj != nullptr);
663 QCOMPARE(obj->width(), 26.0);
664 QCOMPARE(obj->height(), 175.0);
665 QCOMPARE(obj->paintedWidth(), 26.0);
666 QCOMPARE(obj->paintedHeight(), 26.0);
667
668 delete obj;
669 }
670}
671
672void tst_qquickimage::sourceSize_QTBUG_14303()
673{
674 QString componentStr = "import QtQuick 2.0\nImage { source: srcImage }";
675 QQmlContext *ctxt = engine.rootContext();
676 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart200.png"));
677 QQmlComponent component(&engine);
678 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
679 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
680
681 QSignalSpy sourceSizeSpy(obj, SIGNAL(sourceSizeChanged()));
682
683 QTRY_VERIFY(obj != nullptr);
684 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
685
686 QTRY_COMPARE(obj->sourceSize().width(), 200);
687 QTRY_COMPARE(obj->sourceSize().height(), 200);
688 QTRY_COMPARE(sourceSizeSpy.count(), 0);
689
690 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "colors.png"));
691 QTRY_COMPARE(obj->sourceSize().width(), 120);
692 QTRY_COMPARE(obj->sourceSize().height(), 120);
693 QTRY_COMPARE(sourceSizeSpy.count(), 1);
694
695 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart200.png"));
696 QTRY_COMPARE(obj->sourceSize().width(), 200);
697 QTRY_COMPARE(obj->sourceSize().height(), 200);
698 QTRY_COMPARE(sourceSizeSpy.count(), 2);
699
700 delete obj;
701}
702
703void tst_qquickimage::sourceSize_QTBUG_16389()
704{
705 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
706 window->setSource(testFileUrl(fileName: "qtbug_16389.qml"));
707 window->show();
708 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
709
710 QQuickImage *image = findItem<QQuickImage>(parent: window->rootObject(), objectName: "iconImage");
711 QQuickItem *handle = findItem<QQuickItem>(parent: window->rootObject(), objectName: "blueHandle");
712
713 QCOMPARE(image->sourceSize().width(), 200);
714 QCOMPARE(image->sourceSize().height(), 200);
715 QCOMPARE(image->paintedWidth(), 0.0);
716 QCOMPARE(image->paintedHeight(), 0.0);
717
718 handle->setY(20);
719
720 QCOMPARE(image->sourceSize().width(), 200);
721 QCOMPARE(image->sourceSize().height(), 200);
722 QCOMPARE(image->paintedWidth(), 20.0);
723 QCOMPARE(image->paintedHeight(), 20.0);
724}
725
726// QTBUG-15690
727void tst_qquickimage::nullPixmapPaint()
728{
729 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
730 window->setSource(testFileUrl(fileName: "nullpixmap.qml"));
731 window->show();
732 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
733
734 TestHTTPServer server;
735 QVERIFY2(server.listen(), qPrintable(server.errorString()));
736 server.serveDirectory(dataDirectory(), TestHTTPServer::Delay);
737
738 QQuickImage *image = qobject_cast<QQuickImage*>(object: window->rootObject());
739 QTRY_VERIFY(image != nullptr);
740 image->setSource(server.url(documentPath: "/no-such-file.png"));
741
742 QQmlTestMessageHandler messageHandler;
743 // used to print "QTransform::translate with NaN called"
744 QPixmap pm = QPixmap::fromImage(image: window->grabWindow());
745 QVERIFY2(messageHandler.messages().size() == 0, qPrintable(messageHandler.messageString()));
746 delete image;
747}
748
749void tst_qquickimage::imageCrash_QTBUG_22125()
750{
751 TestHTTPServer server;
752 QVERIFY2(server.listen(), qPrintable(server.errorString()));
753 server.serveDirectory(dataDirectory(), TestHTTPServer::Delay);
754
755 {
756 QQuickView view;
757 view.rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl());
758 view.setSource(testFileUrl(fileName: "qtbug_22125.qml"));
759 view.show();
760 QVERIFY(QTest::qWaitForWindowExposed(&view));
761 qApp->processEvents();
762 // shouldn't crash when the view drops out of scope due to
763 // QQuickPixmapData attempting to dereference a pointer to
764 // the destroyed reader.
765 }
766
767 // shouldn't crash when deleting cancelled QQmlPixmapReplys.
768 server.sendDelayedItem();
769 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
770 QCoreApplication::processEvents();
771}
772
773void tst_qquickimage::imageCrash_QTBUG_32513()
774{
775 QQuickView view(testFileUrl(fileName: "qtbug_32513.qml"));
776 view.show();
777 QVERIFY(QTest::qWaitForWindowExposed(&view));
778 QTest::qWait(ms: 1000);
779 // shouldn't crash when the image changes sources
780}
781
782void tst_qquickimage::sourceSize_data()
783{
784 QTest::addColumn<int>(name: "sourceWidth");
785 QTest::addColumn<int>(name: "sourceHeight");
786 QTest::addColumn<qreal>(name: "implicitWidth");
787 QTest::addColumn<qreal>(name: "implicitHeight");
788
789 QTest::newRow(dataTag: "unscaled") << 0 << 0 << 300.0 << 300.0;
790 QTest::newRow(dataTag: "scale width") << 100 << 0 << 100.0 << 100.0;
791 QTest::newRow(dataTag: "scale height") << 0 << 150 << 150.0 << 150.0;
792 QTest::newRow(dataTag: "larger sourceSize") << 400 << 400 << 300.0 << 300.0;
793}
794
795void tst_qquickimage::sourceSize()
796{
797 QFETCH(int, sourceWidth);
798 QFETCH(int, sourceHeight);
799 QFETCH(qreal, implicitWidth);
800 QFETCH(qreal, implicitHeight);
801
802 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
803 QQmlContext *ctxt = window->rootContext();
804 ctxt->setContextProperty("srcWidth", sourceWidth);
805 ctxt->setContextProperty("srcHeight", sourceHeight);
806
807 window->setSource(testFileUrl(fileName: "sourceSize.qml"));
808 window->show();
809 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
810
811 QQuickImage *image = qobject_cast<QQuickImage*>(object: window->rootObject());
812 QVERIFY(image);
813
814 QCOMPARE(image->sourceSize().width(), sourceWidth);
815 QCOMPARE(image->sourceSize().height(), sourceHeight);
816 QCOMPARE(image->implicitWidth(), implicitWidth);
817 QCOMPARE(image->implicitHeight(), implicitHeight);
818}
819
820void tst_qquickimage::sourceSizeChanges()
821{
822 TestHTTPServer server;
823 QVERIFY2(server.listen(), qPrintable(server.errorString()));
824 server.serveDirectory(dataDirectory());
825
826 QQmlEngine engine;
827 QQmlComponent component(&engine);
828 component.setData("import QtQuick 2.0\nImage { source: srcImage }", baseUrl: QUrl::fromLocalFile(localfile: ""));
829 QTRY_VERIFY(component.isReady());
830 QQmlContext *ctxt = engine.rootContext();
831 ctxt->setContextProperty("srcImage", "");
832 QQuickImage *img = qobject_cast<QQuickImage*>(object: component.create());
833 QVERIFY(img != nullptr);
834
835 QSignalSpy sourceSizeSpy(img, SIGNAL(sourceSizeChanged()));
836
837 // Local
838 ctxt->setContextProperty("srcImage", QUrl(""));
839 QTRY_COMPARE(img->status(), QQuickImage::Null);
840 QTRY_COMPARE(sourceSizeSpy.count(), 0);
841
842 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart.png"));
843 QTRY_COMPARE(img->status(), QQuickImage::Ready);
844 QTRY_COMPARE(sourceSizeSpy.count(), 1);
845
846 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart.png"));
847 QTRY_COMPARE(img->status(), QQuickImage::Ready);
848 QTRY_COMPARE(sourceSizeSpy.count(), 1);
849
850 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart_copy.png"));
851 QTRY_COMPARE(img->status(), QQuickImage::Ready);
852 QTRY_COMPARE(sourceSizeSpy.count(), 1);
853
854 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "colors.png"));
855 QTRY_COMPARE(img->status(), QQuickImage::Ready);
856 QTRY_COMPARE(sourceSizeSpy.count(), 2);
857
858 ctxt->setContextProperty("srcImage", QUrl(""));
859 QTRY_COMPARE(img->status(), QQuickImage::Null);
860 QTRY_COMPARE(sourceSizeSpy.count(), 3);
861
862 // Remote
863 ctxt->setContextProperty("srcImage", server.url(documentPath: "/heart.png"));
864 QTRY_COMPARE(img->status(), QQuickImage::Ready);
865 QTRY_COMPARE(sourceSizeSpy.count(), 4);
866
867 ctxt->setContextProperty("srcImage", server.url(documentPath: "/heart.png"));
868 QTRY_COMPARE(img->status(), QQuickImage::Ready);
869 QTRY_COMPARE(sourceSizeSpy.count(), 4);
870
871 ctxt->setContextProperty("srcImage", server.url(documentPath: "/heart_copy.png"));
872 QTRY_COMPARE(img->status(), QQuickImage::Ready);
873 QTRY_COMPARE(sourceSizeSpy.count(), 4);
874
875 ctxt->setContextProperty("srcImage", server.url(documentPath: "/colors.png"));
876 QTRY_COMPARE(img->status(), QQuickImage::Ready);
877 QTRY_COMPARE(sourceSizeSpy.count(), 5);
878
879 ctxt->setContextProperty("srcImage", QUrl(""));
880 QTRY_COMPARE(img->status(), QQuickImage::Null);
881 QTRY_COMPARE(sourceSizeSpy.count(), 6);
882
883 delete img;
884}
885
886void tst_qquickimage::sourceClipRect_data()
887{
888 QTest::addColumn<QRectF>(name: "sourceClipRect");
889 QTest::addColumn<QSize>(name: "sourceSize");
890 QTest::addColumn<QList<QPoint>>(name: "redPixelLocations");
891 QTest::addColumn<QList<QPoint>>(name: "bluePixelLocations");
892
893 QTest::newRow(dataTag: "unclipped") << QRectF() << QSize()
894 << (QList<QPoint>() << QPoint(80, 80) << QPoint(150, 256))
895 << (QList<QPoint>() << QPoint(28, 28) << QPoint(215, 215));
896 QTest::newRow(dataTag: "upperLeft") << QRectF(10, 10, 100, 100) << QSize()
897 << (QList<QPoint>() << QPoint(99, 99))
898 << (QList<QPoint>() << QPoint(100, 100) << QPoint(28, 28));
899 QTest::newRow(dataTag: "lowerRight") << QRectF(200, 200, 20, 20) << QSize()
900 << (QList<QPoint>() << QPoint(0, 0))
901 << (QList<QPoint>() << QPoint(14, 14));
902 QTest::newRow(dataTag: "miniMiddle") << QRectF(20, 20, 60, 60) << QSize(100, 100)
903 << (QList<QPoint>() << QPoint(59, 0) << QPoint(6, 12) << QPoint(42, 42))
904 << (QList<QPoint>() << QPoint(54, 54) << QPoint(15, 59));
905}
906
907void tst_qquickimage::sourceClipRect()
908{
909 QFETCH(QRectF, sourceClipRect);
910 QFETCH(QSize, sourceSize);
911 QFETCH(QList<QPoint>, redPixelLocations);
912 QFETCH(QList<QPoint>, bluePixelLocations);
913
914 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
915
916 window->setColor(Qt::blue);
917 window->setSource(testFileUrl(fileName: "image.qml"));
918 window->show();
919 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
920
921 QQuickImage *image = qobject_cast<QQuickImage*>(object: window->rootObject());
922 QVERIFY(image);
923
924 image->setSourceSize(sourceSize);
925 QCOMPARE(image->implicitWidth(), sourceSize.isValid() ? sourceSize.width() : 300);
926 QCOMPARE(image->implicitHeight(), sourceSize.isValid() ? sourceSize.height() : 300);
927 image->setSourceClipRect(sourceClipRect);
928 QCOMPARE(image->implicitWidth(), sourceClipRect.isNull() ? 300 : sourceClipRect.width());
929 QCOMPARE(image->implicitHeight(), sourceClipRect.isNull() ? 300 : sourceClipRect.height());
930
931 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
932 || (QGuiApplication::platformName() == QLatin1String("minimal")))
933 QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
934 QImage contents = window->grabWindow();
935 if (contents.width() < sourceClipRect.width())
936 QSKIP("Skipping due to grabWindow not functional");
937#ifdef DEBUG_WRITE_OUTPUT
938 contents.save("/tmp/sourceClipRect_" + QLatin1String(QTest::currentDataTag()) + ".png");
939#endif
940 for (auto p : redPixelLocations) {
941 QRgb color = contents.pixel(pt: p);
942 QVERIFY(qRed(color) > 0xc0);
943 QVERIFY(qBlue(color) < 0x0f);
944 }
945 for (auto p : bluePixelLocations){
946 QRgb color = contents.pixel(pt: p);
947 QVERIFY(qBlue(color) > 0xc0);
948 QVERIFY(qRed(color) < 0x0f);
949 }
950}
951
952void tst_qquickimage::progressAndStatusChanges()
953{
954 TestHTTPServer server;
955 QVERIFY2(server.listen(), qPrintable(server.errorString()));
956 server.serveDirectory(dataDirectory());
957
958 QQmlEngine engine;
959 QString componentStr = "import QtQuick 2.0\nImage { source: srcImage }";
960 QQmlContext *ctxt = engine.rootContext();
961 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart.png"));
962 QQmlComponent component(&engine);
963 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
964 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
965 QVERIFY(obj != nullptr);
966 QCOMPARE(obj->status(), QQuickImage::Ready);
967 QTRY_COMPARE(obj->progress(), 1.0);
968
969 qRegisterMetaType<QQuickImageBase::Status>();
970 QSignalSpy sourceSpy(obj, SIGNAL(sourceChanged(QUrl)));
971 QSignalSpy progressSpy(obj, SIGNAL(progressChanged(qreal)));
972 QSignalSpy statusSpy(obj, SIGNAL(statusChanged(QQuickImageBase::Status)));
973
974 // Same image
975 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart.png"));
976 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
977 QTRY_COMPARE(obj->progress(), 1.0);
978 QTRY_COMPARE(sourceSpy.count(), 0);
979 QTRY_COMPARE(progressSpy.count(), 0);
980 QTRY_COMPARE(statusSpy.count(), 0);
981
982 // Loading local file
983 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "colors.png"));
984 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
985 QTRY_COMPARE(obj->progress(), 1.0);
986 QTRY_COMPARE(sourceSpy.count(), 1);
987 QTRY_COMPARE(progressSpy.count(), 0);
988 QTRY_COMPARE(statusSpy.count(), 1);
989
990 // Loading remote file
991 ctxt->setContextProperty("srcImage", server.url(documentPath: "/heart.png"));
992 QTRY_COMPARE(obj->status(), QQuickImage::Loading);
993 QTRY_COMPARE(obj->progress(), 0.0);
994 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
995 QTRY_COMPARE(obj->progress(), 1.0);
996 QTRY_COMPARE(sourceSpy.count(), 2);
997 QTRY_VERIFY(progressSpy.count() > 1);
998 QTRY_COMPARE(statusSpy.count(), 3);
999
1000 ctxt->setContextProperty("srcImage", "");
1001 QTRY_COMPARE(obj->status(), QQuickImage::Null);
1002 QTRY_COMPARE(obj->progress(), 0.0);
1003 QTRY_COMPARE(sourceSpy.count(), 3);
1004 QTRY_VERIFY(progressSpy.count() > 2);
1005 QTRY_COMPARE(statusSpy.count(), 4);
1006
1007 delete obj;
1008}
1009
1010class TestQImageProvider : public QQuickImageProvider
1011{
1012public:
1013 TestQImageProvider() : QQuickImageProvider(Image) {}
1014
1015 QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize)
1016 {
1017 Q_UNUSED(requestedSize);
1018 if (id == QLatin1String("first-image.png")) {
1019 QTest::qWait(ms: 50);
1020 int width = 100;
1021 int height = 100;
1022 QImage image(width, height, QImage::Format_RGB32);
1023 image.fill(pixel: QColor("yellow").rgb());
1024 if (size)
1025 *size = QSize(width, height);
1026 return image;
1027 }
1028
1029 QTest::qWait(ms: 400);
1030 int width = 100;
1031 int height = 100;
1032 QImage image(width, height, QImage::Format_RGB32);
1033 image.fill(pixel: QColor("green").rgb());
1034 if (size)
1035 *size = QSize(width, height);
1036 return image;
1037 }
1038};
1039
1040void tst_qquickimage::correctStatus()
1041{
1042 QQmlEngine engine;
1043 engine.addImageProvider(id: QLatin1String("test"), new TestQImageProvider());
1044
1045 QQmlComponent component(&engine, testFileUrl(fileName: "correctStatus.qml"));
1046 QObject *obj = component.create();
1047 QVERIFY(obj);
1048
1049 QTest::qWait(ms: 200);
1050
1051 // at this point image1 should be attempting to load second-image.png,
1052 // and should be in the loading state. Without a clear prior to that load,
1053 // the status can mistakenly be in the ready state.
1054 QCOMPARE(obj->property("status").toInt(), int(QQuickImage::Loading));
1055
1056 QTest::qWait(ms: 400);
1057 delete obj;
1058}
1059
1060void tst_qquickimage::highdpi()
1061{
1062 QString componentStr = "import QtQuick 2.0\nImage { source: srcImage ; }";
1063 QQmlComponent component(&engine);
1064 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1065 QQmlContext *ctxt = engine.rootContext();
1066
1067 // Testing "@2x" high-dpi image loading:
1068 // The basic case is as follows. Suppose you have foo.png,
1069 // which is a 64x64 png that fits in a QML layout. Now,
1070 // on a high-dpi system that pixmap would not provide
1071 // enough pixels. To fix this the app developer provides
1072 // a 128x128 foo@2x.png, which Qt automatically loads.
1073 // The image continues to be referred to as "foo.png" in
1074 // the QML sources, and reports a size of 64x64.
1075 //
1076
1077 // Load "heart-highdpi@2x.png", which is a 300x300 png. As a 2x scale image it
1078 // should render and report a geometry of 150x150.
1079 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart-highdpi@2x.png"));
1080
1081 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
1082 QVERIFY(obj != nullptr);
1083
1084 QCOMPARE(obj->width(), 150.0);
1085 QCOMPARE(obj->height(), 150.0);
1086 QCOMPARE(obj->paintedWidth(), 150.0);
1087 QCOMPARE(obj->paintedHeight(), 150.0);
1088
1089 // Load a normal 1x image.
1090 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "heart.png"));
1091 QCOMPARE(obj->width(), 300.0);
1092 QCOMPARE(obj->height(), 300.0);
1093 QCOMPARE(obj->paintedWidth(), 300.0);
1094 QCOMPARE(obj->paintedHeight(), 300.0);
1095
1096 delete obj;
1097}
1098
1099void tst_qquickimage::highDpiFillModesAndSizes_data()
1100{
1101 QTest::addColumn<QQuickImage::FillMode>(name: "fillMode");
1102 QTest::addColumn<qreal>(name: "expectedHeightAfterSettingWidthTo100");
1103 QTest::addColumn<qreal>(name: "expectedImplicitHeightAfterSettingWidthTo100");
1104 QTest::addColumn<qreal>(name: "expectedPaintedWidthAfterSettingWidthTo100");
1105 QTest::addColumn<qreal>(name: "expectedPaintedHeightAfterSettingWidthTo100");
1106
1107 QTest::addRow(format: "Stretch") << QQuickImage::Stretch << 150.0 << 150.0 << 100.0 << 150.0;
1108 QTest::addRow(format: "PreserveAspectFit") << QQuickImage::PreserveAspectFit << 100.0 << 100.0 << 100.0 << 100.0;
1109 QTest::addRow(format: "PreserveAspectCrop") << QQuickImage::PreserveAspectCrop << 150.0 << 150.0 << 150.0 << 150.0;
1110 QTest::addRow(format: "Tile") << QQuickImage::Tile << 150.0 << 150.0 << 100.0 << 150.0;
1111 QTest::addRow(format: "TileVertically") << QQuickImage::TileVertically << 150.0 << 150.0 << 100.0 << 150.0;
1112 QTest::addRow(format: "TileHorizontally") << QQuickImage::TileHorizontally << 150.0 << 150.0 << 100.0 << 150.0;
1113 QTest::addRow(format: "Pad") << QQuickImage::Pad << 150.0 << 150.0 << 150.0 << 150.0;
1114}
1115
1116void tst_qquickimage::highDpiFillModesAndSizes()
1117{
1118 QFETCH(QQuickImage::FillMode, fillMode);
1119 QFETCH(qreal, expectedHeightAfterSettingWidthTo100);
1120 QFETCH(qreal, expectedImplicitHeightAfterSettingWidthTo100);
1121 QFETCH(qreal, expectedPaintedWidthAfterSettingWidthTo100);
1122 QFETCH(qreal, expectedPaintedHeightAfterSettingWidthTo100);
1123
1124 QString componentStr = "import QtQuick 2.0\nImage { source: srcImage; }";
1125 QQmlComponent component(&engine);
1126 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1127
1128 engine.rootContext()->setContextProperty("srcImage", testFileUrl(fileName: "heart-highdpi@2x.png"));
1129
1130 QScopedPointer<QQuickImage> image(qobject_cast<QQuickImage*>(object: component.create()));
1131 QVERIFY(image);
1132 QCOMPARE(image->width(), 150.0);
1133 QCOMPARE(image->height(), 150.0);
1134 QCOMPARE(image->paintedWidth(), 150.0);
1135 QCOMPARE(image->paintedHeight(), 150.0);
1136 QCOMPARE(image->implicitWidth(), 150.0);
1137 QCOMPARE(image->implicitHeight(), 150.0);
1138 QCOMPARE(image->paintedWidth(), 150.0);
1139 QCOMPARE(image->paintedHeight(), 150.0);
1140
1141 // The implicit size should not change when setting any fillMode here.
1142 image->setFillMode(fillMode);
1143 QCOMPARE(image->fillMode(), fillMode);
1144 QCOMPARE(image->implicitWidth(), 150.0);
1145 QCOMPARE(image->implicitHeight(), 150.0);
1146 QCOMPARE(image->paintedWidth(), 150.0);
1147 QCOMPARE(image->paintedHeight(), 150.0);
1148
1149 image->setWidth(100.0);
1150 QCOMPARE(image->width(), 100.0);
1151 QCOMPARE(image->height(), expectedHeightAfterSettingWidthTo100);
1152 QCOMPARE(image->implicitWidth(), 150.0);
1153 QCOMPARE(image->implicitHeight(), expectedImplicitHeightAfterSettingWidthTo100);
1154 QCOMPARE(image->paintedWidth(), expectedPaintedWidthAfterSettingWidthTo100);
1155 QCOMPARE(image->paintedHeight(), expectedPaintedHeightAfterSettingWidthTo100);
1156}
1157
1158void tst_qquickimage::hugeImages()
1159{
1160 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1161 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1162 QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
1163
1164 QQuickView view;
1165 view.setSource(testFileUrl(fileName: "hugeImages.qml"));
1166 view.setGeometry(posx: 0, posy: 0, w: 200, h: 200);
1167 view.create();
1168
1169 QImage contents = view.grabWindow();
1170
1171 QCOMPARE(contents.pixel(0, 0), qRgba(255, 0, 0, 255));
1172 QCOMPARE(contents.pixel(99, 99), qRgba(255, 0, 0, 255));
1173 QCOMPARE(contents.pixel(100, 0), qRgba(0, 0, 255, 255));
1174 QCOMPARE(contents.pixel(199, 99), qRgba(0, 0, 255, 255));
1175}
1176
1177
1178class MyInterceptor : public QQmlAbstractUrlInterceptor
1179{
1180public:
1181 MyInterceptor(QUrl url) : QQmlAbstractUrlInterceptor(), m_url(url) {}
1182 QUrl intercept(const QUrl &url, QQmlAbstractUrlInterceptor::DataType)
1183 {
1184 if (url.scheme() == "interceptthis")
1185 return m_url;
1186 return url;
1187 }
1188
1189 QUrl m_url;
1190};
1191
1192void tst_qquickimage::urlInterceptor()
1193{
1194 QQmlEngine engine;
1195 MyInterceptor interceptor {testFileUrl(fileName: "colors.png")};
1196 engine.setUrlInterceptor(&interceptor);
1197
1198 QQmlComponent c(&engine);
1199
1200 c.setData("import QtQuick 2.12; Image { objectName: \"item\"; source: width == 0 ? \"interceptthis:doesNotExist\" : \"interceptthis:doesNotExist\"}", baseUrl: QUrl{});
1201 QScopedPointer<QQuickImage> object { qobject_cast<QQuickImage*>(object: c.create())};
1202 QVERIFY(object);
1203 QTRY_COMPARE(object->status(), QQuickImage::Ready);
1204 QTRY_COMPARE(object->progress(), 1.0);
1205}
1206
1207void tst_qquickimage::multiFrame_data()
1208{
1209 QTest::addColumn<QString>(name: "qmlfile");
1210 QTest::addColumn<bool>(name: "asynchronous");
1211
1212 QTest::addRow(format: "default") << "multiframe.qml" << false;
1213 QTest::addRow(format: "async") << "multiframeAsync.qml" << true;
1214}
1215
1216void tst_qquickimage::multiFrame()
1217{
1218 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1219 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1220 QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
1221
1222 QFETCH(QString, qmlfile);
1223 QFETCH(bool, asynchronous);
1224 Q_UNUSED(asynchronous)
1225
1226 QQuickView view(testFileUrl(fileName: qmlfile));
1227 QQuickImage *image = qobject_cast<QQuickImage*>(object: view.rootObject());
1228 QVERIFY(image);
1229 QSignalSpy countSpy(image, SIGNAL(frameCountChanged()));
1230 QSignalSpy currentSpy(image, SIGNAL(currentFrameChanged()));
1231 if (asynchronous) {
1232 QCOMPARE(image->frameCount(), 0);
1233 QTRY_COMPARE(image->frameCount(), 4);
1234 QCOMPARE(countSpy.count(), 1);
1235 } else {
1236 QCOMPARE(image->frameCount(), 4);
1237 }
1238 QCOMPARE(image->currentFrame(), 0);
1239 view.show();
1240 QVERIFY(QTest::qWaitForWindowExposed(&view));
1241
1242 QImage contents = view.grabWindow();
1243 if (contents.width() < 40)
1244 QSKIP("Skipping due to grabWindow not functional");
1245 // The first frame is a blue ball, approximately qRgba(0x33, 0x6d, 0xcc, 0xff)
1246 QRgb color = contents.pixel(x: 16, y: 16);
1247 QVERIFY(qRed(color) < 0xc0);
1248 QVERIFY(qGreen(color) < 0xc0);
1249 QVERIFY(qBlue(color) > 0xc0);
1250
1251 image->setCurrentFrame(1);
1252 QTRY_COMPARE(image->status(), QQuickImageBase::Ready);
1253 QCOMPARE(currentSpy.count(), 1);
1254 QCOMPARE(image->currentFrame(), 1);
1255 contents = view.grabWindow();
1256 // The second frame is a green ball, approximately qRgba(0x27, 0xc8, 0x22, 0xff)
1257 color = contents.pixel(x: 16, y: 16);
1258 QVERIFY(qRed(color) < 0xc0);
1259 QVERIFY(qGreen(color) > 0xc0);
1260 QVERIFY(qBlue(color) < 0xc0);
1261}
1262
1263void tst_qquickimage::colorSpace()
1264{
1265 QString componentStr1 = "import QtQuick 2.15\n"
1266 "Image { source: srcImage; }";
1267 QQmlComponent component1(&engine);
1268 component1.setData(componentStr1.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1269 engine.rootContext()->setContextProperty("srcImage", testFileUrl(fileName: "ProPhoto.jpg"));
1270
1271 QScopedPointer<QQuickImage> object1 { qobject_cast<QQuickImage*>(object: component1.create())};
1272 QVERIFY(object1);
1273 QTRY_COMPARE(object1->status(), QQuickImageBase::Ready);
1274 QCOMPARE(object1->colorSpace(), QColorSpace(QColorSpace::ProPhotoRgb));
1275
1276 QString componentStr2 = "import QtQuick 2.15\n"
1277 "Image {\n"
1278 " source: srcImage;\n"
1279 " colorSpace.namedColorSpace: ColorSpace.SRgb;\n"
1280 "}";
1281
1282 QQmlComponent component2(&engine);
1283 component2.setData(componentStr2.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1284
1285 QScopedPointer<QQuickImage> object2 { qobject_cast<QQuickImage*>(object: component2.create())};
1286 QVERIFY(object2);
1287 QTRY_COMPARE(object2->status(), QQuickImageBase::Ready);
1288 QCOMPARE(object2->colorSpace(), QColorSpace(QColorSpace::SRgb));
1289}
1290
1291QTEST_MAIN(tst_qquickimage)
1292
1293#include "tst_qquickimage.moc"
1294

source code of qtdeclarative/tests/auto/quick/qquickimage/tst_qquickimage.cpp