Merge lp:~aacid/unity8/croppedImageMinimumSourceSizeProvider into lp:unity8

Proposed by Albert Astals Cid
Status: Work in progress
Proposed branch: lp:~aacid/unity8/croppedImageMinimumSourceSizeProvider
Merge into: lp:unity8
Diff against target: 431 lines (+346/-6)
6 files modified
plugins/Dash/CMakeLists.txt (+1/-0)
plugins/Dash/CardCreator.js (+6/-6)
plugins/Dash/croppedimageminimumsizeimageprovider.cpp (+294/-0)
plugins/Dash/croppedimageminimumsizeimageprovider.h (+36/-0)
plugins/Dash/plugin.cpp (+8/-0)
plugins/Dash/plugin.h (+1/-0)
To merge this branch: bzr merge lp:~aacid/unity8/croppedImageMinimumSourceSizeProvider
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+300176@code.launchpad.net

Description of the change

Image Provider for PreserveAspectCrop fillmode

To post a comment you must log in.

Unmerged revisions

2537. By Albert Astals Cid

First attempt at an image provider that does what CroppedImageMinimumSourceSize does

Saves double loading of files in the "bad" case

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/Dash/CMakeLists.txt'
2--- plugins/Dash/CMakeLists.txt 2016-06-02 09:32:33 +0000
3+++ plugins/Dash/CMakeLists.txt 2016-07-15 11:14:48 +0000
4@@ -28,6 +28,7 @@
5 verticaljournal.cpp
6 horizontaljournal.cpp
7 organicgrid.cpp
8+ croppedimageminimumsizeimageprovider.cpp
9 )
10
11 add_library(Dash-qml MODULE
12
13=== modified file 'plugins/Dash/CardCreator.js'
14--- plugins/Dash/CardCreator.js 2016-06-17 01:18:49 +0000
15+++ plugins/Dash/CardCreator.js 2016-07-15 11:14:48 +0000
16@@ -104,10 +104,10 @@
17 %6 \n\
18 width: root.fixedArtShapeSize.width; \n\
19 height: root.fixedArtShapeSize.height; \n\
20- CroppedImageMinimumSourceSize { \n\
21+ Image { \n\
22 id: artImage; \n\
23 objectName: "artImage"; \n\
24- source: artShapeLoader.cardArt; \n\
25+ source: "image://croppedImageMinimumSize/" + artShapeLoader.cardArt; \n\
26 asynchronous: %5; \n\
27 visible: %4; \n\
28 width: %2; \n\
29@@ -147,10 +147,10 @@
30 %6 \n\
31 width: image.status !== Image.Ready ? 0 : image.width; \n\
32 height: image.status !== Image.Ready ? 0 : image.height; \n\
33- CroppedImageMinimumSourceSize { \n\
34+ Image { \n\
35 id: artImage; \n\
36 objectName: "artImage"; \n\
37- source: artShapeLoader.cardArt; \n\
38+ source: "image://croppedImageMinimumSize/" + artShapeLoader.cardArt; \n\
39 asynchronous: %5; \n\
40 visible: %4; \n\
41 width: %2; \n\
42@@ -285,11 +285,11 @@
43 // %2 is used as visible of mascotImage
44 // %3 is injected as code to mascotImage
45 // %4 is used as fallback image
46-var kMascotImageCode = 'CroppedImageMinimumSourceSize { \n\
47+var kMascotImageCode = 'Image { \n\
48 id: mascotImage; \n\
49 objectName: "mascotImage"; \n\
50 anchors { %1 } \n\
51- source: cardData && cardData["mascot"] || %4; \n\
52+ source: "image://croppedImageMinimumSize/" + (cardData && cardData["mascot"] || %4); \n\
53 width: units.gu(6); \n\
54 height: units.gu(5.625); \n\
55 horizontalAlignment: Image.AlignHCenter; \n\
56
57=== added file 'plugins/Dash/croppedimageminimumsizeimageprovider.cpp'
58--- plugins/Dash/croppedimageminimumsizeimageprovider.cpp 1970-01-01 00:00:00 +0000
59+++ plugins/Dash/croppedimageminimumsizeimageprovider.cpp 2016-07-15 11:14:48 +0000
60@@ -0,0 +1,294 @@
61+/*
62+ * Copyright (C) 2016 Canonical, Ltd.
63+ *
64+ * This program is free software: you can redistribute it and/or modify it under
65+ * the terms of the GNU Lesser General Public License version 3, as published by
66+ * the Free Software Foundation.
67+ *
68+ * This program is distributed in the hope that it will be useful, but WITHOUT
69+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
70+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
71+ * Lesser General Public License for more details.
72+ *
73+ * You should have received a copy of the GNU Lesser General Public License
74+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
75+ */
76+
77+#include "croppedimageminimumsizeimageprovider.h"
78+
79+#include <QBuffer>
80+#include <QDebug>
81+#include <QDir>
82+#include <QImageReader>
83+#include <QNetworkReply>
84+#include <QNetworkRequest>
85+
86+#include <QtGui/private/qguiapplication_p.h>
87+#include <qpa/qplatformintegration.h>
88+
89+class AsyncImageResponse : public QQuickImageResponse, public QRunnable
90+{
91+ public:
92+ AsyncImageResponse(const QString &id, const QSize &requestedSize, QQmlEngine *engine)
93+ : m_id(id), m_requestedSize(requestedSize), m_engine(engine)
94+ {
95+ setAutoDelete(false);
96+ }
97+
98+ QQuickTextureFactory *textureFactory() const override
99+ {
100+ return QQuickTextureFactory::textureFactoryForImage(m_image);
101+ }
102+
103+ void readImage(const QUrl &u, QIODevice *dev)
104+ {
105+ QImageReader imgio(dev);
106+
107+ if (m_requestedSize.width() > 0 && m_requestedSize.height() > 0) {
108+ QSize s = imgio.size();
109+ const qreal imageRatio = s.width() / qreal(s.height());
110+ const qreal requestedRatio = m_requestedSize.width() / qreal(m_requestedSize.height());
111+ qreal ratio;
112+
113+ if (requestedRatio > imageRatio) {
114+ ratio = m_requestedSize.width() / qreal(s.width());
115+ } else {
116+ ratio = m_requestedSize.height() / qreal(s.height());
117+ }
118+
119+ qDebug() << "Original size" << s;
120+
121+ s.setHeight(qRound(s.height() * ratio));
122+ s.setWidth(qRound(s.width() * ratio));
123+ imgio.setScaledSize(s);
124+ }
125+
126+ if (!imgio.read(&m_image)) {
127+ m_errorString = QString("Error decoding: %1: %2").arg(u.toString()).arg(imgio.errorString());
128+ }
129+
130+ qDebug() << "ASYNCRESPONSE" << m_image;
131+ }
132+
133+ void run() override
134+ {
135+ const QUrl u = QUrl::fromUserInput(m_id, QDir::currentPath());
136+
137+ qDebug() << "run" << u << u.isLocalFile();
138+
139+ if (u.isLocalFile()) {
140+ QFile f(u.toLocalFile());
141+ if (f.open(QIODevice::ReadOnly)) {
142+ readImage(u, &f);
143+ } else {
144+ m_errorString = QString("Cannot open: %1").arg(u.toString());
145+ }
146+ emit finished();
147+ } else if (u.scheme() == QLatin1String("image")) {
148+ // Daisy chain :/
149+ QQuickImageProvider *provider = dynamic_cast<QQuickImageProvider *>(m_engine->imageProvider(u.host()));
150+
151+ if (!provider) {
152+ m_errorString = QString("No image provider for: %1").arg(u.toString());
153+ emit finished();
154+ return;
155+ }
156+
157+ const bool threadedPixmaps = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps);
158+ const QQuickImageProvider::ImageType imageType = provider->imageType();
159+ if (!threadedPixmaps && imageType == QQuickImageProvider::Pixmap) {
160+ m_errorString = QString("Can not daisy chain a pixmap provider, platform doesn't support threaded pixmaps: %1").arg(u.toString());
161+ emit finished();
162+ return;
163+ }
164+
165+ const QString imageId = u.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
166+
167+ switch (imageType) {
168+ case QQuickImageProvider::Invalid:
169+ {
170+ m_errorString = QString("Invalid image provider: %1").arg(u.toString());
171+ break;
172+ }
173+
174+ case QQuickImageProvider::Image:
175+ {
176+ QSize readSize;
177+ const QSize heightRequest = QSize(0, m_requestedSize.height());
178+ m_image = provider->requestImage(imageId, &readSize, heightRequest);
179+
180+ if ((readSize.height() > 0) &&
181+ (m_requestedSize.height() > 0) &&
182+ ((readSize.width() / readSize.height()) < (m_requestedSize.width() / m_requestedSize.height())))
183+ {
184+ qDebug() << "DOUBLE REQUEST :/" << u;
185+ // Doing double request :/
186+ const QSize widthRequest = QSize(m_requestedSize.width(), 0);
187+ m_image = provider->requestImage(imageId, &readSize, widthRequest);
188+ }
189+
190+ if (m_image.isNull()) {
191+ m_errorString = QString("Failed to get image from provider: %1").arg(u.toString());
192+ }
193+ emit finished();
194+ break;
195+ }
196+
197+ case QQuickImageProvider::Pixmap:
198+ {
199+ QSize readSize;
200+ const QSize heightRequest = QSize(0, m_requestedSize.height());
201+ QPixmap pixmap = provider->requestPixmap(imageId, &readSize, heightRequest);
202+
203+ if ((readSize.height() > 0) &&
204+ (m_requestedSize.height() > 0) &&
205+ ((readSize.width() / readSize.height()) < (m_requestedSize.width() / m_requestedSize.height())))
206+ {
207+ qDebug() << "DOUBLE REQUEST :/" << u;
208+ // Doing double request :/
209+ const QSize widthRequest = QSize(m_requestedSize.width(), 0);
210+ pixmap = provider->requestPixmap(imageId, &readSize, widthRequest);
211+ }
212+
213+ m_image = pixmap.toImage();
214+
215+ if (m_image.isNull()) {
216+ m_errorString = QString("Failed to get image from provider: %1").arg(u.toString());
217+ }
218+
219+ emit finished();
220+ break;
221+ }
222+
223+ case QQuickImageProvider::Texture:
224+ {
225+ QSize readSize;
226+ const QSize heightRequest = QSize(0, m_requestedSize.height());
227+ QQuickTextureFactory *t = provider->requestTexture(imageId, &readSize, heightRequest);
228+
229+ if ((readSize.height() > 0) &&
230+ (m_requestedSize.height() > 0) &&
231+ ((readSize.width() / readSize.height()) < (m_requestedSize.width() / m_requestedSize.height())))
232+ {
233+ delete t;
234+ qDebug() << "DOUBLE REQUEST :/" << u;
235+ // Doing double request :/
236+ const QSize widthRequest = QSize(m_requestedSize.width(), 0);
237+ t = provider->requestTexture(imageId, &readSize, widthRequest);
238+ }
239+
240+ if (!t) {
241+ m_errorString = QString("Failed to get texture from provider: %1").arg(u.toString());
242+ }
243+
244+ // TODO Do something with t
245+ break;
246+ }
247+
248+ case QQuickImageProvider::ImageResponse:
249+ {
250+ QQuickAsyncImageProvider *asyncProvider = static_cast<QQuickAsyncImageProvider*>(provider);
251+ const QSize heightRequest = QSize(0, m_requestedSize.height());
252+ QQuickImageResponse *response = asyncProvider->requestImageResponse(imageId, heightRequest);
253+
254+ connect(response, &QQuickImageResponse::finished, this, &AsyncImageResponse::asyncResponseFinished);
255+
256+ m_eventLoop = new QEventLoop();
257+ m_eventLoop->exec();
258+ delete m_eventLoop;
259+ m_eventLoop = 0;
260+
261+ // TODO Do the double request magic
262+ // TODO Do something with response::textureFactory
263+
264+
265+
266+ delete response;
267+
268+ break;
269+ }
270+ }
271+
272+ } else {
273+ QNetworkAccessManager nam; // TODO only one of these?
274+
275+ int redirectCount = 0;
276+
277+ QUrl imageUrl = u;
278+ bool needToGet = true;
279+ while (needToGet && redirectCount < 16) {
280+ QNetworkRequest req(imageUrl);
281+ req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
282+ QNetworkReply *reply = nam.get(req);
283+ connect(reply, &QNetworkReply::finished, this, &AsyncImageResponse::asyncResponseFinished);
284+
285+ m_eventLoop = new QEventLoop();
286+ m_eventLoop->exec();
287+ delete m_eventLoop;
288+ m_eventLoop = 0;
289+
290+ const QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
291+ if (redirect.isValid()) {
292+ imageUrl = reply->url().resolved(redirect.toUrl());
293+ } else {
294+ needToGet = false;
295+
296+ if (reply->error()) {
297+ m_errorString = reply->errorString();
298+ } else {
299+ QByteArray all = reply->readAll();
300+ QBuffer buff(&all);
301+ buff.open(QIODevice::ReadOnly);
302+ readImage(reply->url(), &buff);
303+ }
304+ }
305+ delete reply;
306+ }
307+
308+ emit finished();
309+ }
310+ }
311+
312+ QString errorString() const override
313+ {
314+ return m_errorString;
315+ }
316+
317+ void cancel() override
318+ {
319+ // TODO for QNAM and ImageResponse
320+ }
321+
322+ public slots:
323+ void asyncResponseFinished()
324+ {
325+ m_eventLoop->exit();
326+ }
327+
328+
329+ private:
330+ QString m_id;
331+ QSize m_requestedSize;
332+ QImage m_image;
333+ QString m_errorString;
334+ QQmlEngine *m_engine;
335+ QEventLoop *m_eventLoop;
336+};
337+
338+
339+CroppedImageMinimumSizeImageProvider::CroppedImageMinimumSizeImageProvider(QQmlEngine *engine)
340+ : QQuickAsyncImageProvider()
341+ , m_engine(engine)
342+{
343+}
344+
345+CroppedImageMinimumSizeImageProvider::~CroppedImageMinimumSizeImageProvider()
346+{
347+}
348+
349+QQuickImageResponse *CroppedImageMinimumSizeImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
350+{
351+ AsyncImageResponse *response = new AsyncImageResponse(id, requestedSize, m_engine);
352+ m_pool.start(response);
353+ return response;
354+}
355
356=== added file 'plugins/Dash/croppedimageminimumsizeimageprovider.h'
357--- plugins/Dash/croppedimageminimumsizeimageprovider.h 1970-01-01 00:00:00 +0000
358+++ plugins/Dash/croppedimageminimumsizeimageprovider.h 2016-07-15 11:14:48 +0000
359@@ -0,0 +1,36 @@
360+/*
361+ * Copyright (C) 2016 Canonical, Ltd.
362+ *
363+ * This program is free software: you can redistribute it and/or modify it under
364+ * the terms of the GNU Lesser General Public License version 3, as published by
365+ * the Free Software Foundation.
366+ *
367+ * This program is distributed in the hope that it will be useful, but WITHOUT
368+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
369+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
370+ * Lesser General Public License for more details.
371+ *
372+ * You should have received a copy of the GNU Lesser General Public License
373+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
374+ */
375+
376+#ifndef CROPPEDIMAGEMINIMUMSIZEIMAGEPROVIDER_H
377+#define CROPPEDIMAGEMINIMUMSIZEIMAGEPROVIDER_H
378+
379+#include <QQuickImageProvider>
380+#include <QThreadPool>
381+
382+class CroppedImageMinimumSizeImageProvider : public QQuickAsyncImageProvider
383+{
384+public:
385+ CroppedImageMinimumSizeImageProvider(QQmlEngine *engine);
386+ virtual ~CroppedImageMinimumSizeImageProvider();
387+
388+ QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
389+
390+private:
391+ QThreadPool m_pool;
392+ QQmlEngine *m_engine;
393+};
394+
395+#endif // CURSORIMAGEPROVIDER_H
396
397=== modified file 'plugins/Dash/plugin.cpp'
398--- plugins/Dash/plugin.cpp 2016-02-19 08:44:42 +0000
399+++ plugins/Dash/plugin.cpp 2016-07-15 11:14:48 +0000
400@@ -17,6 +17,7 @@
401
402 #include "plugin.h"
403
404+#include "croppedimageminimumsizeimageprovider.h"
405 #include "horizontaljournal.h"
406 #include "listviewwithpageheader.h"
407 #include "organicgrid.h"
408@@ -67,4 +68,11 @@
409 qmlRegisterSingletonType<AudioComparer>(uri, 0, 1, "AudioUrlComparer", audio_comparer_singleton_provider);
410 }
411
412+void DashPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
413+{
414+ QQmlExtensionPlugin::initializeEngine(engine, uri);
415+
416+ engine->addImageProvider(QStringLiteral("croppedImageMinimumSize"), new CroppedImageMinimumSizeImageProvider(engine));
417+}
418+
419 #include "plugin.moc"
420
421=== modified file 'plugins/Dash/plugin.h'
422--- plugins/Dash/plugin.h 2015-04-30 09:31:51 +0000
423+++ plugins/Dash/plugin.h 2016-07-15 11:14:48 +0000
424@@ -28,6 +28,7 @@
425
426 public:
427 void registerTypes(const char *uri) override;
428+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
429 };
430
431 #endif

Subscribers

People subscribed via source and target branches