on xenial help sign in
Use QImageReader not to load the image into memory twice Fixes: #1384374

Approved by: Michał Sawicz

commit3b43143bb1ec207b7e9d2286c6f1a4b037ab2f2b authorAlbert Astals <albert.astals@canonical.com> dateTue 2 Dec 2014, 09:24 (12 years ago) parentbe59376d9bdf2c
14 files changed+324−45
plugins/Dash/CMakeLists.txt+1 plugins/Dash/CardCreator.js+6−5 plugins/Dash/CroppedImageMinimumSourceSize.qml+24−18 plugins/Dash/croppedimagesizer.cpp+138 plugins/Dash/croppedimagesizer.h+71 plugins/Dash/plugin.cpp+2 tests/plugins/Dash/CMakeLists.txt+1 tests/plugins/Dash/cardcreator/1.res+3−2 tests/plugins/Dash/cardcreator/2.res+1−1 tests/plugins/Dash/cardcreator/3.res+9−8 tests/plugins/Dash/cardcreator/4.res+2−2 tests/plugins/Dash/cardcreator/5.res+9−8 tests/plugins/Dash/cardcreator/7.res+1−1 tests/plugins/Dash/tst_CroppedImageMinimumSourceSize.qml+56
plugins/Dash/CMakeLists.txt+1
@@ -25,6 +25,7 @@ set(QMLPLUGIN_SRC
2525 verticaljournal.cpp
2626 horizontaljournal.cpp
2727 organicgrid.cpp
28+ croppedimagesizer.cpp
2829 )
2930
3031add_library(Dash-qml MODULE
plugins/Dash/CardCreator.js+6−5
@@ -82,16 +82,17 @@ var kArtShapeHolderCode = 'Item { \n\
8282 height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height }); \n\
8383 } \n\
8484 } \n\
85− image: CroppedImageMinimumSourceSize { \n\
85+ CroppedImageMinimumSourceSize { \n\
86+ id: artImage; \n\
8687 objectName: "artImage"; \n\
8788 property bool doLoadSource: !NetworkingStatus.limitedBandwith; \n\
8889 source: { if (root.visible) doLoadSource = true; return doLoadSource && cardData && cardData["art"] || ""; } \n\
89− cache: true; \n\
9090 asynchronous: root.asynchronous; \n\
9191 visible: false; \n\
9292 width: %2; \n\
9393 height: %3; \n\
9494 } \n\
95+ image: artImage.image; \n\
9596 } \n\
9697 } \n\
9798 }\n';
@@ -181,11 +182,11 @@ var kMascotShapeLoaderCode = 'Loader { \n\
181182 id: mascotShapeLoader; \n\
182183 objectName: "mascotShapeLoader"; \n\
183184 asynchronous: root.asynchronous; \n\
184− active: mascotImage.status === Image.Ready; \n\
185+ active: mascotImage.image.status === Image.Ready; \n\
185186 visible: showHeader && active && status == Loader.Ready; \n\
186187 width: units.gu(6); \n\
187188 height: units.gu(5.625); \n\
188− sourceComponent: UbuntuShape { image: mascotImage } \n\
189+ sourceComponent: UbuntuShape { image: mascotImage.image } \n\
189190 anchors { %1 } \n\
190191 }\n';
191192
@@ -435,7 +436,7 @@ function cardString(template, components) {
435436 mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors);
436437 }
437438
438− var mascotImageVisible = useMascotShape ? 'false' : 'showHeader && resized';
439+ var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
439440 mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible);
440441 }
441442
plugins/Dash/CroppedImageMinimumSourceSize.qml+24−18
@@ -15,24 +15,30 @@
1515 */
1616
1717import QtQuick 2.3
18+import Dash 0.1
1819
19−Image {
20− property bool resized: false
21− property bool resizing: false
22− fillMode: Image.PreserveAspectCrop
23− visible: resized
24− onSourceSizeChanged: {
25− if (!resized && !resizing) {
26− resizing = true;
27− var ar = width / height;
28− var ssar = sourceSize.width / sourceSize.height;
29− if (ar > ssar) {
30− sourceSize = Qt.size(width, 0);
31− } else {
32− sourceSize = Qt.size(0, height);
33− }
34− resizing = false;
35− resized = true;
36− }
20+Item {
21+ id: root
22+
23+ property string source
24+ property alias image: innerImage
25+ property alias asynchronous: innerImage.asynchronous
26+ property alias verticalAlignment: innerImage.verticalAlignment
27+ property alias horizontalAlignment: innerImage.horizontalAlignment
28+ property alias fillMode: innerImage.fillMode
29+
30+ CroppedImageSizer {
31+ id: sizer
32+ source: root.source
33+ width: root.width
34+ height: root.height
35+ }
36+
37+ Image {
38+ id: innerImage
39+ anchors.fill: parent
40+ fillMode: Image.PreserveAspectCrop
41+ sourceSize: sizer.sourceSize.width == 0 && sizer.sourceSize.height == 0 ? undefined : sizer.sourceSize
42+ source: sourceSize.width == -1 && sourceSize.height == -1 ? "" : root.source
3743 }
3844}
plugins/Dash/croppedimagesizer.cppnew file+138
@@ -0,0 +1,138 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "croppedimagesizer.h"
18+
19+#include <QDebug>
20+#include <QNetworkReply>
21+#include <QNetworkRequest>
22+#include <QQmlEngine>
23+#include <QQuickItem>
24+#include <QQuickView>
25+
26+CroppedImageSizer::CroppedImageSizer()
27+{
28+ connect(this, &CroppedImageSizer::inputParamsChanged, this, &CroppedImageSizer::calculateSourceSize);
29+ connect(this, &CroppedImageSizer::sourceChanged, this, &CroppedImageSizer::requestImage);
30+}
31+
32+CroppedImageSizer::~CroppedImageSizer()
33+{
34+ if (m_reply) {
35+ m_reply->deleteLater();
36+ }
37+}
38+
39+QUrl CroppedImageSizer::source() const
40+{
41+ return m_source;
42+}
43+
44+void CroppedImageSizer::setSource(const QUrl &source)
45+{
46+ if (source != m_source) {
47+ m_source = source;
48+ Q_EMIT sourceChanged();
49+ }
50+}
51+
52+qreal CroppedImageSizer::width() const
53+{
54+ return m_width;
55+}
56+
57+void CroppedImageSizer::setWidth(qreal width)
58+{
59+ if (width != m_width) {
60+ m_width = width;
61+ Q_EMIT inputParamsChanged();
62+ }
63+}
64+
65+qreal CroppedImageSizer::height() const
66+{
67+ return m_height;
68+}
69+
70+void CroppedImageSizer::setHeight(qreal height)
71+{
72+ if (height != m_height) {
73+ m_height = height;
74+ Q_EMIT inputParamsChanged();
75+ }
76+}
77+
78+QSize CroppedImageSizer::sourceSize() const
79+{
80+ return m_sourceSize;
81+}
82+
83+void CroppedImageSizer::setSourceSize(const QSize &sourceSize)
84+{
85+ if (sourceSize != m_sourceSize) {
86+ m_sourceSize = sourceSize;
87+ Q_EMIT sourceSizeChanged();
88+ }
89+}
90+
91+void CroppedImageSizer::requestImage()
92+{
93+ if (m_reply) {
94+ m_reply->disconnect();
95+ m_reply->abort();
96+ m_reply->deleteLater();
97+ m_reply = nullptr;
98+ }
99+
100+ if (m_source.isValid() && qmlEngine(this) && qmlEngine(this)->networkAccessManager()) {
101+ QNetworkRequest request(m_source);
102+ m_reply = qmlEngine(this)->networkAccessManager()->get(request);
103+ connect(m_reply, &QNetworkReply::finished, this, &CroppedImageSizer::requestFinished);
104+ } else {
105+ setSourceSize(QSize(-1, -1));
106+ }
107+}
108+
109+void CroppedImageSizer::requestFinished()
110+{
111+ QImageReader m_reader(m_reply);
112+ m_imageSize = m_reader.size();
113+
114+ m_reply->deleteLater();
115+ m_reply = nullptr;
116+
117+ calculateSourceSize();
118+}
119+
120+void CroppedImageSizer::calculateSourceSize()
121+{
122+ if (m_source.isValid() && m_width > 0 && m_height > 0 && !m_reply) {
123+ if (!m_imageSize.isEmpty()) {
124+ const qreal ar = m_width / m_height;
125+ const qreal ssar = m_imageSize.width() / (qreal)m_imageSize.height();
126+ if (ar > ssar) {
127+ setSourceSize(QSize(m_width, 0));
128+ } else {
129+ setSourceSize(QSize(0, m_height));
130+ }
131+ } else {
132+ qWarning() << "Invalid size for " << m_source << m_imageSize;
133+ setSourceSize(QSize(0, 0));
134+ }
135+ } else {
136+ setSourceSize(QSize(-1, -1));
137+ }
138+}
plugins/Dash/croppedimagesizer.hnew file+71
@@ -0,0 +1,71 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef CROPPEDIMAGESIZER_H
18+#define CROPPEDIMAGESIZER_H
19+
20+#include <QImageReader>
21+#include <QObject>
22+#include <QSize>
23+#include <QUrl>
24+
25+class QNetworkReply;
26+
27+class CroppedImageSizer : public QObject
28+{
29+ Q_OBJECT
30+
31+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
32+ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY inputParamsChanged)
33+ Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY inputParamsChanged)
34+ Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged)
35+
36+public:
37+ CroppedImageSizer();
38+ ~CroppedImageSizer();
39+
40+ QUrl source() const;
41+ void setSource(const QUrl &source);
42+
43+ qreal width() const;
44+ void setWidth(qreal width);
45+
46+ qreal height() const;
47+ void setHeight(qreal height);
48+
49+ QSize sourceSize() const;
50+ void setSourceSize(const QSize &sourceSize);
51+
52+Q_SIGNALS:
53+ void inputParamsChanged();
54+ void sourceChanged();
55+ void sourceSizeChanged();
56+
57+private Q_SLOT:
58+ void calculateSourceSize();
59+ void requestFinished();
60+ void requestImage();
61+
62+private:
63+ QUrl m_source;
64+ qreal m_width = 0;
65+ qreal m_height = 0;
66+ QSize m_sourceSize = QSize(-1, -1);
67+ QSize m_imageSize;
68+ QNetworkReply *m_reply = nullptr;
69+};
70+
71+#endif
plugins/Dash/plugin.cpp+2
@@ -17,6 +17,7 @@
1717
1818#include "plugin.h"
1919
20+#include "croppedimagesizer.h"
2021#include "horizontaljournal.h"
2122#include "listviewwithpageheader.h"
2223#include "organicgrid.h"
@@ -32,4 +33,5 @@ void DashPlugin::registerTypes(const char *uri)
3233 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
3334 qmlRegisterType<OrganicGrid>(uri, 0, 1, "OrganicGrid");
3435 qmlRegisterType<VerticalJournal>(uri, 0, 1, "VerticalJournal");
36+ qmlRegisterType<CroppedImageSizer>(uri, 0, 1, "CroppedImageSizer");
3537}
tests/plugins/Dash/CMakeLists.txt+1
@@ -80,3 +80,4 @@ set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
8080add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
8181add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
8282add_qml_test(. CardAttributes IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
83+add_qml_test(. CroppedImageMinimumSourceSize IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/1.res+3−2
@@ -46,16 +46,17 @@ Item {
4646 height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
4747 }
4848 }
49− image: CroppedImageMinimumSourceSize {
49+ CroppedImageMinimumSourceSize {
50+ id: artImage;
5051 objectName: "artImage";
5152 property bool doLoadSource: !NetworkingStatus.limitedBandwith;
5253 source: { if (root.visible) doLoadSource = true; return doLoadSource && cardData && cardData["art"] || ""; }
53− cache: true;
5454 asynchronous: root.asynchronous;
5555 visible: false;
5656 width: root.width;
5757 height: width / artShape.aspect;
5858 }
59+ image: artImage.image;
5960 }
6061 }
6162 }
tests/plugins/Dash/cardcreator/2.res+1−1
@@ -74,7 +74,7 @@ CroppedImageMinimumSourceSize {
7474 height: units.gu(5.625);
7575 horizontalAlignment: Image.AlignHCenter;
7676 verticalAlignment: Image.AlignVCenter;
77− visible: showHeader && resized;
77+ visible: showHeader;
7878 }
7979,Item {
8080 id: headerTitleContainer;
tests/plugins/Dash/cardcreator/3.res+9−8
@@ -46,16 +46,17 @@ Item {
4646 height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
4747 }
4848 }
49− image: CroppedImageMinimumSourceSize {
50− objectName: "artImage";
49+ CroppedImageMinimumSourceSize {
50+ id: artImage;
51+ objectName: "artImage";
5152 property bool doLoadSource: !NetworkingStatus.limitedBandwith;
5253 source: { if (root.visible) doLoadSource = true; return doLoadSource && cardData && cardData["art"] || ""; }
53− cache: true;
54− asynchronous: root.asynchronous;
55− visible: false;
56− width: root.width;
57− height: width / artShape.aspect;
58− }
54+ asynchronous: root.asynchronous;
55+ visible: false;
56+ width: root.width;
57+ height: width / artShape.aspect;
58+ }
59+ image: artImage.image;
5960 }
6061 }
6162 }
tests/plugins/Dash/cardcreator/4.res+2−2
@@ -35,11 +35,11 @@ Loader {
3535 id: mascotShapeLoader;
3636 objectName: "mascotShapeLoader";
3737 asynchronous: root.asynchronous;
38− active: mascotImage.status === Image.Ready;
38+ active: mascotImage.image.status === Image.Ready;
3939 visible: showHeader && active && status == Loader.Ready;
4040 width: units.gu(6);
4141 height: units.gu(5.625);
42− sourceComponent: UbuntuShape { image: mascotImage }
42+ sourceComponent: UbuntuShape { image: mascotImage.image }
4343 anchors { verticalCenter: parent.verticalCenter; }
4444 }
4545
tests/plugins/Dash/cardcreator/5.res+9−8
@@ -46,16 +46,17 @@ Item {
4646 height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
4747 }
4848 }
49− image: CroppedImageMinimumSourceSize {
50− objectName: "artImage";
49+ CroppedImageMinimumSourceSize {
50+ id: artImage;
51+ objectName: "artImage";
5152 property bool doLoadSource: !NetworkingStatus.limitedBandwith;
5253 source: { if (root.visible) doLoadSource = true; return doLoadSource && cardData && cardData["art"] || ""; }
53− cache: true;
54− asynchronous: root.asynchronous;
55− visible: false;
56− width: root.width;
57− height: width / artShape.aspect;
58− }
54+ asynchronous: root.asynchronous;
55+ visible: false;
56+ width: root.width;
57+ height: width / artShape.aspect;
58+ }
59+ image: artImage.image;
5960 }
6061 }
6162 }
tests/plugins/Dash/cardcreator/7.res+1−1
@@ -74,7 +74,7 @@ CroppedImageMinimumSourceSize {
7474 height: units.gu(5.625);
7575 horizontalAlignment: Image.AlignHCenter;
7676 verticalAlignment: Image.AlignVCenter;
77− visible: showHeader && resized;
77+ visible: showHeader;
7878 }
7979
8080,Item {
tests/plugins/Dash/tst_CroppedImageMinimumSourceSize.qmlnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import QtTest 1.0
19+import Ubuntu.Components 1.1
20+import Dash 0.1
21+
22+Rectangle {
23+ width: 500
24+ height: 300
25+
26+ color: "red"
27+
28+ CroppedImageMinimumSourceSize {
29+ id: cimss
30+ x: 100
31+ y: 100
32+ width: 100
33+ height: 100
34+ source: Qt.resolvedUrl("../../qmltests/Dash/artwork/music-player-design.png")
35+ asynchronous: true
36+ }
37+
38+ TestCase {
39+ id: testCase
40+ name: "ScopeStyle"
41+ when: windowShown
42+
43+ function test_croppedSource() {
44+ tryCompare(cimss.image.sourceSize, "width", 100);
45+ tryCompare(cimss.image.sourceSize, "height", 0);
46+
47+ cimss.width = 40;
48+ tryCompare(cimss.image.sourceSize, "width", 0);
49+ tryCompare(cimss.image.sourceSize, "height", 100);
50+
51+ cimss.width = 100;
52+ tryCompare(cimss.image.sourceSize, "width", 100);
53+ tryCompare(cimss.image.sourceSize, "height", 0);
54+ }
55+ }
56+}