diff options
| author | Mitch Curtis <mitch.curtis@qt.io> | 2026-09-14 14:06:57 +0800 |
|---|---|---|
| committer | Mitch Curtis <mitch.curtis@qt.io> | 2026-09-14 14:27:26 +0000 |
| commit | e8d5cd542f3ffcc7597c4a46804edb3b5f415180 (patch) | |
| tree | 6781f6670b75317f0a956da7a046499f9e83354e | |
| parent | b464941cabc6e392214bc4076415f4b23b19eee3 (diff) | |
QQuickImage: fix devicePixelRatio being lost for grabToImage() results
QQuickItemGrabResult::render() correctly records the window's
devicePixelRatio on the grabbed QImage. But when that image is exposed
under an "itemgrabber:" URL via QQuickPixmap::QQuickPixmap(const QUrl &,
const QImage &), the ratio was discarded: QQuickPixmapData's
devicePixelRatio was left at its default of 1, and
QQuickImageBase::loadPixmap() unconditionally resets its own
devicePixelRatio to 1.0, only restoring a real value for scalable image
formats (SVG/PDF) or "@2x" local files. An itemgrabber: URL, being a
raster in-memory image, matches neither.
As a result, an Image loaded from a grabToImage() result via "Image {
source: result.url }" (with no explicit width/height/sourceSize) ended
up devicePixelRatio times too big in device-independent pixels, even
though the underlying pixel data and DPR metadata were both already
correct.
Fix this by propagating image.devicePixelRatio() into QQuickPixmapData
when QQuickPixmap wraps a raw QImage, exposing it via a new
QQuickPixmap::devicePixelRatio() accessor, and having
QQuickImageBase::requestFinished() adopt that ratio for "itemgrabber:"
URLs specifically, once the pixmap has actually loaded (the ratio isn't
knowable ahead of load the way it is for "@2x"/scalable formats).
Add tst_qquickimage::grabToImageDevicePixelRatio() to cover this.
Fixes: QTBUG-150289
Change-Id: Iaf72a76c779ee208fac45e539543f9c6cb9738f7
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
| -rw-r--r-- | src/quick/items/qquickimagebase.cpp | 5 | ||||
| -rw-r--r-- | src/quick/util/qquickpixmap_p.h | 1 | ||||
| -rw-r--r-- | src/quick/util/qquickpixmapcache.cpp | 8 | ||||
| -rw-r--r-- | tests/auto/quick/qquickimage/data/grabToImageDevicePixelRatio.qml | 12 | ||||
| -rw-r--r-- | tests/auto/quick/qquickimage/tst_qquickimage.cpp | 27 |
5 files changed, 52 insertions, 1 deletions
diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index 592d9dec1a..17850a29fd 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -377,6 +377,11 @@ void QQuickImageBase::requestFinished() d->status = Error; d->setProgress(0); } else { + // itemgrabber: images carry their own devicePixelRatio (set by whatever grabbed + // them), which isn't known ahead of load the way "@2x" and scalable-image-format + // ratios are, so pick it up here once the pixmap has actually loaded. + if (d->url.scheme() == QQuickPixmap::itemGrabberScheme) + d->devicePixelRatio = d->currentPix->devicePixelRatio(); d->status = Ready; // do not emit statusChanged until after setImplicitSize d->setProgress(1); } diff --git a/src/quick/util/qquickpixmap_p.h b/src/quick/util/qquickpixmap_p.h index 972235f172..ad66e014b0 100644 --- a/src/quick/util/qquickpixmap_p.h +++ b/src/quick/util/qquickpixmap_p.h @@ -144,6 +144,7 @@ public: QRect rect() const; int width() const; int height() const; + qreal devicePixelRatio() const; void load(QQmlEngine *, const QUrl &); void load(QQmlEngine *, const QUrl &, QQuickPixmap::Options options); diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index 288d7afbce..7e693a6587 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -1709,7 +1709,8 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect ® QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image) { d = new QQuickPixmapData(url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(), - QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform); + QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform, + 0, 1, image.devicePixelRatio()); d->addToCache(); } @@ -1860,6 +1861,11 @@ int QQuickPixmap::height() const return 0; } +qreal QQuickPixmap::devicePixelRatio() const +{ + return d ? d->devicePixelRatio : 1.0; +} + QRect QQuickPixmap::rect() const { if (d && d->textureFactory) diff --git a/tests/auto/quick/qquickimage/data/grabToImageDevicePixelRatio.qml b/tests/auto/quick/qquickimage/data/grabToImageDevicePixelRatio.qml new file mode 100644 index 0000000000..4a7f9ad79c --- /dev/null +++ b/tests/auto/quick/qquickimage/data/grabToImageDevicePixelRatio.qml @@ -0,0 +1,12 @@ +import QtQuick + +Image { + id: sourceImage + objectName: "sourceImage" + source: "rect.png" + + Image { + id: grabbedImage + objectName: "grabbedImage" + } +} diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index cbd6282e2b..2bdad33777 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -21,6 +21,7 @@ #include <QQuickView> #include <QQuickImageProvider> #include <QQmlAbstractUrlInterceptor> +#include <QtQuick/QQuickItemGrabResult> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/testhttpserver_p.h> @@ -83,6 +84,7 @@ private slots: void multiFrame(); void colorSpace(); void devicePixelRatio(); + void grabToImageDevicePixelRatio(); private: QQmlEngine engine; @@ -1344,6 +1346,31 @@ void tst_qquickimage::devicePixelRatio() QCOMPARE(obj2->sourceSize(), (devicePixelRatio * origSize).toSize()); } +void tst_qquickimage::grabToImageDevicePixelRatio() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("grabToImageDevicePixelRatio.qml"))); + + if (window.effectiveDevicePixelRatio() <= 1) + QSKIP("This test only demonstrates the bug on a HiDPI (devicePixelRatio > 1) screen"); + + QQuickImage *sourceImage = qmlobject_cast<QQuickImage *>(window.rootObject()); + QVERIFY(sourceImage); + QQuickImage *grabbedImage = sourceImage->findChild<QQuickImage *>("grabbedImage"); + QVERIFY(grabbedImage); + + auto grabResult = sourceImage->grabToImage(); + QVERIFY(grabResult); + QSignalSpy readySpy(grabResult.data(), &QQuickItemGrabResult::ready); + QVERIFY(readySpy.wait()); + grabbedImage->setSource(grabResult->url()); + QTRY_COMPARE(grabbedImage->status(), QQuickImageBase::Ready); + + // grabToImage() results loaded via Image.source ignore devicePixelRatio: on a HiDPI screen, + // grabbedImage ends up devicePixelRatio times too big instead of matching sourceImage. + QCOMPARE(grabbedImage->size(), sourceImage->size()); +} + QTEST_MAIN(tst_qquickimage) #include "tst_qquickimage.moc" |
