BorderImage support for @2x assets
authorAndy Nichols <andy.nichols@digia.com>
Thu, 6 Nov 2014 19:30:45 +0000 (11:30 -0800)
committerGabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>
Wed, 6 May 2015 14:25:38 +0000 (14:25 +0000)
Despite being a subclass of QQuickImageBase, BorderImage components did
not support using @2x assets like Image components.  The 9 patch image
logic now accounts for device pixel ratio when needed.

Manual tests added for stretch, repeat and round tiling modes.

[ChangeLog][BorderImage] Add support for @2x HiDPI border images.
This means, no more need to multiply the border sizes by the device
pixel ratio.

Change-Id: I79958739929964c816ba5dacedd9eaf93a60a183
Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>
Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com>
src/quick/items/qquickborderimage.cpp
tests/manual/highdpi/BorderImage.png [new file with mode: 0644]
tests/manual/highdpi/BorderImage@2x.png [new file with mode: 0644]
tests/manual/highdpi/TiledBorderImage.png [new file with mode: 0644]
tests/manual/highdpi/TiledBorderImage@2x.png [new file with mode: 0644]
tests/manual/highdpi/borderimage.qml [new file with mode: 0644]

index fc35480..f345a77 100644 (file)
@@ -40,6 +40,7 @@
 #include <QtNetwork/qnetworkreply.h>
 #include <QtCore/qfile.h>
 #include <QtCore/qmath.h>
+#include <QtGui/qguiapplication.h>
 
 #include <private/qqmlglobal_p.h>
 
@@ -321,7 +322,13 @@ void QQuickBorderImage::load()
             if (d->cache)
                 options |= QQuickPixmap::Cache;
             d->pix.clear(this);
-            d->pix.load(qmlEngine(this), d->url, options);
+
+            const qreal targetDevicePixelRatio = (window() ? window()->effectiveDevicePixelRatio() : qGuiApp->devicePixelRatio());
+            d->devicePixelRatio = 1.0;
+
+            QUrl loadUrl = d->url;
+            resolve2xLocalFile(d->url, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
+            d->pix.load(qmlEngine(this), loadUrl, d->sourcesize * d->devicePixelRatio, options);
 
             if (d->pix.isLoading()) {
                 if (d->progress != 0.0) {
@@ -329,8 +336,19 @@ void QQuickBorderImage::load()
                     emit progressChanged(d->progress);
                 }
                 d->status = Loading;
-                d->pix.connectFinished(this, SLOT(requestFinished()));
-                d->pix.connectDownloadProgress(this, SLOT(requestProgress(qint64,qint64)));
+
+                static int thisRequestProgress = -1;
+                static int thisRequestFinished = -1;
+                if (thisRequestProgress == -1) {
+                    thisRequestProgress =
+                        QQuickImageBase::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)");
+                    thisRequestFinished =
+                        QQuickImageBase::staticMetaObject.indexOfSlot("requestFinished()");
+                }
+                d->pix.connectFinished(this, thisRequestFinished);
+                d->pix.connectDownloadProgress(this, thisRequestProgress);
+
+                update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint
             } else {
                 requestFinished();
                 return;
@@ -495,7 +513,7 @@ void QQuickBorderImage::requestFinished()
         }
     }
 
-    setImplicitSize(impsize.width(), impsize.height());
+    setImplicitSize(impsize.width() / d->devicePixelRatio, impsize.height() / d->devicePixelRatio);
     emit statusChanged(d->status);
     if (sourceSize() != d->oldSourceSize) {
         d->oldSourceSize = sourceSize();
@@ -569,40 +587,29 @@ QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat
     QRectF innerTargetRect = targetRect;
     if (d->border) {
         const QQuickScaleGrid *border = d->getScaleGrid();
-        innerSourceRect = QRectF(border->left() / qreal(d->pix.width()),
-                                 border->top() / qreal(d->pix.height()),
-                                 qMax<qreal>(0, d->pix.width() - border->right() - border->left()) / d->pix.width(),
-                                 qMax<qreal>(0, d->pix.height() - border->bottom() - border->top()) / d->pix.height());
+        innerSourceRect = QRectF(border->left() * d->devicePixelRatio / qreal(d->pix.width()),
+                                 border->top() * d->devicePixelRatio / qreal(d->pix.height()),
+                                 qMax<qreal>(0, d->pix.width() - (border->right() + border->left()) * d->devicePixelRatio) / d->pix.width(),
+                                 qMax<qreal>(0, d->pix.height() - (border->bottom() + border->top()) * d->devicePixelRatio) / d->pix.height());
         innerTargetRect = QRectF(border->left(),
                                  border->top(),
-                                 qMax<qreal>(0, width() - border->right() - border->left()),
-                                 qMax<qreal>(0, height() - border->bottom() - border->top()));
+                                 qMax<qreal>(0, width() - (border->right() + border->left())),
+                                 qMax<qreal>(0, height() - (border->bottom() + border->top())));
     }
     qreal hTiles = 1;
     qreal vTiles = 1;
-    if (innerSourceRect.width() != 0) {
-        switch (d->horizontalTileMode) {
-        case QQuickBorderImage::Repeat:
-            hTiles = innerTargetRect.width() / qreal(innerSourceRect.width() * d->pix.width());
-            break;
-        case QQuickBorderImage::Round:
-            hTiles = qCeil(innerTargetRect.width() / qreal(innerSourceRect.width() * d->pix.width()));
-            break;
-        default:
-            break;
-        }
+    const QSizeF innerTargetSize = innerTargetRect.size() * d->devicePixelRatio;
+    if (innerSourceRect.width() != 0
+        && d->horizontalTileMode != QQuickBorderImage::Stretch) {
+        hTiles = innerTargetSize.width() / qreal(innerSourceRect.width() * d->pix.width());
+        if (d->horizontalTileMode == QQuickBorderImage::Round)
+            hTiles = qCeil(hTiles);
     }
-    if (innerSourceRect.height() != 0) {
-        switch (d->verticalTileMode) {
-        case QQuickBorderImage::Repeat:
-            vTiles = innerTargetRect.height() / qreal(innerSourceRect.height() * d->pix.height());
-            break;
-        case QQuickBorderImage::Round:
-            vTiles = qCeil(innerTargetRect.height() / qreal(innerSourceRect.height() * d->pix.height()));
-            break;
-        default:
-            break;
-        }
+    if (innerSourceRect.height() != 0
+        && d->verticalTileMode != QQuickBorderImage::Stretch) {
+        vTiles = innerTargetSize.height() / qreal(innerSourceRect.height() * d->pix.height());
+        if (d->verticalTileMode == QQuickBorderImage::Round)
+            vTiles = qCeil(vTiles);
     }
 
     node->setTargetRect(targetRect);
diff --git a/tests/manual/highdpi/BorderImage.png b/tests/manual/highdpi/BorderImage.png
new file mode 100644 (file)
index 0000000..8fa6b40
Binary files /dev/null and b/tests/manual/highdpi/BorderImage.png differ
diff --git a/tests/manual/highdpi/BorderImage@2x.png b/tests/manual/highdpi/BorderImage@2x.png
new file mode 100644 (file)
index 0000000..1bbc527
Binary files /dev/null and b/tests/manual/highdpi/BorderImage@2x.png differ
diff --git a/tests/manual/highdpi/TiledBorderImage.png b/tests/manual/highdpi/TiledBorderImage.png
new file mode 100644 (file)
index 0000000..b17288e
Binary files /dev/null and b/tests/manual/highdpi/TiledBorderImage.png differ
diff --git a/tests/manual/highdpi/TiledBorderImage@2x.png b/tests/manual/highdpi/TiledBorderImage@2x.png
new file mode 100644 (file)
index 0000000..85c7c4b
Binary files /dev/null and b/tests/manual/highdpi/TiledBorderImage@2x.png differ
diff --git a/tests/manual/highdpi/borderimage.qml b/tests/manual/highdpi/borderimage.qml
new file mode 100644 (file)
index 0000000..902d4e5
--- /dev/null
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Rectangle {
+    width: 900
+    height: 400
+
+    readonly property real imageBorder: 32
+    readonly property real animDuration: 3000
+    readonly property real animMin: 2 * imageBorder
+    readonly property real animMax: 280
+
+    Text {
+        anchors.bottom: row.top
+        anchors.horizontalCenter: parent.horizontalCenter
+        text: "Green => standard DPI; purple => @2x"
+    }
+
+    Row {
+        id: row
+        anchors.centerIn: parent
+        spacing: 10
+        Repeater {
+            model: 3
+            delegate: Item {
+                width: animMax
+                height: animMax
+                BorderImage {
+                    source : index === 0 ? "BorderImage.png" : "TiledBorderImage.png"
+                    anchors.centerIn: parent
+
+                    border {
+                        left: imageBorder; right: imageBorder
+                        top: imageBorder; bottom: imageBorder
+                    }
+
+                    horizontalTileMode: index === 0 ? BorderImage.Stretch :
+                                        index === 1 ? BorderImage.Repeat : BorderImage.Round
+                    verticalTileMode: index === 0 ? BorderImage.Stretch :
+                                      index === 1 ? BorderImage.Repeat : BorderImage.Round
+
+                    width: animMin
+                    SequentialAnimation on width {
+                        NumberAnimation { to: animMax; duration: animDuration }
+                        NumberAnimation { to: animMin; duration: animDuration }
+                        loops: Animation.Infinite
+                    }
+
+                    height: animMax
+                    SequentialAnimation on height {
+                        NumberAnimation { to: animMin; duration: animDuration }
+                        NumberAnimation { to: animMax; duration: animDuration }
+                        loops: Animation.Infinite
+                    }
+                }
+
+                Text {
+                    anchors.top: parent.bottom
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    font.pixelSize: 18
+                    text: index === 0 ? "Stretch" :
+                          index === 1 ? "Repeat" : "Round"
+                }
+            }
+        }
+    }
+}