Merge lp:~jamesh/thumbnailer/dbus-service into lp:thumbnailer

Proposed by James Henstridge
Status: Merged
Approved by: Jussi Pakkanen
Approved revision: 107
Merged at revision: 79
Proposed branch: lp:~jamesh/thumbnailer/dbus-service
Merge into: lp:thumbnailer
Prerequisite: lp:~jpakkane/thumbnailer/lastfmtest
Diff against target: 992 lines (+772/-31)
22 files modified
CMakeLists.txt (+3/-1)
debian/changelog (+6/-9)
debian/control (+31/-4)
debian/qtdeclarative5-ubuntu-thumbnailer0.1.install (+1/-0)
debian/thumbnailer-service.install (+2/-0)
plugins/Ubuntu/Thumbnailer/CMakeLists.txt (+22/-0)
plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp (+90/-0)
plugins/Ubuntu/Thumbnailer/albumartgenerator.h (+36/-0)
plugins/Ubuntu/Thumbnailer/plugin.cpp (+47/-0)
plugins/Ubuntu/Thumbnailer/plugin.h (+34/-0)
plugins/Ubuntu/Thumbnailer/qmldir (+2/-0)
plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp (+87/-0)
plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h (+36/-0)
src/CMakeLists.txt (+2/-0)
src/service/CMakeLists.txt (+34/-0)
src/service/com.canonical.Thumbnailer.service.in (+4/-0)
src/service/dbus-interface.xml (+17/-0)
src/service/dbusinterface.cpp (+168/-0)
src/service/dbusinterface.h (+40/-0)
src/service/main.cpp (+44/-0)
src/thumbnailer.cpp (+4/-17)
tests/qml/tst_image_provider.qml (+62/-0)
To merge this branch: bzr merge lp:~jamesh/thumbnailer/dbus-service
Reviewer Review Type Date Requested Status
Jussi Pakkanen (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+212347@code.launchpad.net

Commit message

Add a D-Bus service that uses libthumbnailer to download cover art and then provides that art to the client via a read only file descriptor. This allows confined applications to safely access a shared media art cache.

Description of the change

Add a D-Bus service that uses libthumbnailer to download cover art and then provides that art to the client via a read only file descriptor. This allows confined applications to safely access a shared media art cache.

I've put the service in its own binary package marked "Multi-Arch: foreign", since we only need one copy installed on multi-arch systems, and the D-Bus interface should be usable by all architectures on the system.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~jamesh/thumbnailer/dbus-service updated
98. By James Henstridge

Fix up installation of service file.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

> catch (std::exception &e)

The recommended practice is to catch exceptions by const ref rather than plain ref.

> std::thread t(&getCoverArt,

This may throw, please wrap in try/catch.

> // This should be replaced with actual lookup code.

I think it has been. :)

> /**
> + * A class that sends a broadcast signal that the state of media
> + * files has changed.
> + */

This is still from InvalidationSender and thus not accurate.

review: Needs Fixing
Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

The code talks about album art but the method is called GetCoverArt. We should use one term consistently here, I vote for AlbumArt.

Revision history for this message
James Henstridge (jamesh) wrote :

Good catch. It looks like I introduced that discrepancy when cleaning up the ubuntu-ui-toolkit branch (looking at the strings in the .so I was testing shows GetCoverArt). I've updated this branch in favour of GetAlbumArt, to match the C++ API name.

lp:~jamesh/thumbnailer/dbus-service updated
99. By James Henstridge

Update per Jussi's review comments.

Revision history for this message
James Henstridge (jamesh) wrote :

I've done fresh builds of thumbnailer and ubuntu-ui-toolkit packages, and verified that they talk to each other correctly with these changes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

Here's something that bothers me:

static void getAlbumArt(unique_gobj<TNThumbnailer> iface,
  unique_gobj<GDBusMethodInvocation> invocation,

This function takes a unique_gobj by value but unique_gobj does not have a copy constructor or operator=. Maybe it invokes the move constructor because it is called with a temporary object?

Anyhow, it works so if no-one can give a feasible case why this would break I'm fine with merging this.

review: Approve
Revision history for this message
James Henstridge (jamesh) wrote :

The move constructor is the only way this could work, yes.

That said, there isn't much reason the type couldn't have a copy constructor: it's holding a reference counted object after all.

lp:~jamesh/thumbnailer/dbus-service updated
100. By James Henstridge

Copy over the libthumbnailer QML image providers over from
ubuntu-ui-toolkit.

101. By James Henstridge

Add packaging for thumbnailer QML plugin.

102. By James Henstridge

Register a bogus type so the QML module can be imported.

Revision history for this message
James Henstridge (jamesh) wrote :

MP updated with QML plugin code. Note that the bogus qmlRegisterType call is necessary for the plugin to be recognised: plugins that don't register any types are considered broken.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~jamesh/thumbnailer/dbus-service updated
103. By James Henstridge

Merge from trunk, fixing conflicts.

104. By James Henstridge

Merge lp:~jpakkane/thumbnailer/lastfmtest

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

The main issue seems to be that the service is called thumbnailer-service. It implies that it would provide thumbnailing services for all files. However it must not do that because that would open a way for confined applications to access files outside its sandbox. For album art this is not an issue because album art images are fully public data. Maybe we should call this albumart-service or alternatively develop a confinement security policy (which is a lot of work).

That being said, here are a few minor things:

"__ThumbnailerIngoreMe" has a typo.

#ifndef PLUGIN_H is a bit too generic and may clash, should be THUMBNAILERPLUGIN_H or somesuch.

review: Needs Information
lp:~jamesh/thumbnailer/dbus-service updated
105. By James Henstridge

Add a test for the image provider. Not currently hooked up as an
automated test, since it can't run headless.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~jamesh/thumbnailer/dbus-service updated
106. By James Henstridge

Add a note to the D-Bus interface definition file warning about security
considerations when adding new methods to the service.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~jamesh/thumbnailer/dbus-service updated
107. By James Henstridge

Other fixes from Jussi's review comments.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-03-28 10:11:10 +0000
3+++ CMakeLists.txt 2014-03-28 10:11:10 +0000
4@@ -29,9 +29,10 @@
5 set(SHARE_PRIV_ABS ${CMAKE_INSTALL_PREFIX}/${SHARE_PRIV_DIR})
6
7 find_package(Threads REQUIRED)
8+find_package(Qt5Core REQUIRED)
9 include(FindPkgConfig)
10 pkg_check_modules(GST_DEPS REQUIRED gstreamer-1.0 gstreamer-plugins-base-1.0 gstreamer-pbutils-1.0 gstreamer-app-1.0)
11-pkg_check_modules(GIO_DEPS REQUIRED gio-2.0)
12+pkg_check_modules(GIO_DEPS REQUIRED gio-2.0 gio-unix-2.0)
13 pkg_check_modules(IMG_DEPS REQUIRED gdk-pixbuf-2.0 libexif)
14 pkg_check_modules(SOUP_DEPS REQUIRED libsoup-2.4)
15 pkg_check_modules(XML_DEPS REQUIRED libxml-2.0)
16@@ -46,6 +47,7 @@
17
18 enable_testing()
19 add_subdirectory(src)
20+add_subdirectory(plugins/Ubuntu/Thumbnailer)
21 add_subdirectory(tests)
22 add_subdirectory(tools)
23 add_subdirectory(include)
24
25=== modified file 'debian/changelog'
26--- debian/changelog 2014-03-28 10:11:10 +0000
27+++ debian/changelog 2014-03-28 10:11:10 +0000
28@@ -1,4 +1,9 @@
29-<<<<<<< TREE
30+thumbnailer (1.1-0ubuntu1) UNRELEASED; urgency=medium
31+
32+ * New minor release.
33+
34+ -- Jussi Pakkanen <jussi.pakkanen@ubuntu.com> Mon, 10 Mar 2014 15:15:28 +0200
35+
36 thumbnailer (1.0+14.04.20140327-0ubuntu1) trusty; urgency=low
37
38 [ Jussi Pakkanen ]
39@@ -13,14 +18,6 @@
40
41 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 19 Mar 2014 20:16:40 +0000
42
43-=======
44-thumbnailer (1.1-0ubuntu1) UNRELEASED; urgency=medium
45-
46- * New minor release.
47-
48- -- Jussi Pakkanen <jussi.pakkanen@ubuntu.com> Mon, 10 Mar 2014 15:15:28 +0200
49-
50->>>>>>> MERGE-SOURCE
51 thumbnailer (1.0+14.04.20140307-0ubuntu1) trusty; urgency=low
52
53 [ Jussi Pakkanen ]
54
55=== modified file 'debian/control'
56--- debian/control 2014-03-28 10:11:10 +0000
57+++ debian/control 2014-03-28 10:11:10 +0000
58@@ -6,14 +6,18 @@
59 Build-Depends: cmake,
60 debhelper (>= 9),
61 gstreamer1.0-plugins-good,
62+ libexif-dev,
63 libgdk-pixbuf2.0-dev,
64+ libgstreamer1.0-dev,
65 libgstreamer-plugins-base1.0-dev,
66- libgstreamer1.0-dev,
67- shared-mime-info,
68 libgtest-dev,
69+ libsoup2.4-dev,
70 libxml2-dev,
71- libsoup2.4-dev,
72- libexif-dev,
73+ qt5-default,
74+ qtbase5-dev,
75+ qtbase5-dev-tools,
76+ qtdeclarative5-dev,
77+ shared-mime-info,
78 Homepage: https://launchpad.net/thumbnailer
79 # if you don't have have commit access to this branch but would like to upload
80 # directly to Ubuntu, don't worry: your changes will be merged back into the
81@@ -43,3 +47,26 @@
82 Description: development files for thumbnailer
83 This package contains development files
84 for the thumbnailer package.
85+
86+Package: thumbnailer-service
87+Architecture: any
88+Multi-Arch: foreign
89+Pre-Depends: ${misc:Pre-Depends},
90+Depends: ${misc:Depends},
91+ ${shlibs:Depends},
92+ libthumbnailer0 (= ${binary:Version}),
93+Description: D-Bus service for out of process thumbnailing
94+ This package provides a D-Bus service that can provide thumbnails on
95+ behalf of another process.
96+
97+Package: qtdeclarative5-ubuntu-thumbnailer0.1
98+Architecture: any
99+Multi-Arch: same
100+Pre-Depends: ${misc:Pre-Depends},
101+Depends: ${misc:Depends},
102+ ${shlibs:Depends},
103+ libthumbnailer0 (= ${binary:Version}),
104+Recommends: thumbnailer-service (= ${binary:Version}),
105+Description: QML interface for the thumbnailer.
106+ This package provides image providers that allow access to the
107+ thumbnailer from Qt Quick 2 / QML applications.
108
109=== added file 'debian/qtdeclarative5-ubuntu-thumbnailer0.1.install'
110--- debian/qtdeclarative5-ubuntu-thumbnailer0.1.install 1970-01-01 00:00:00 +0000
111+++ debian/qtdeclarative5-ubuntu-thumbnailer0.1.install 2014-03-28 10:11:10 +0000
112@@ -0,0 +1,1 @@
113+usr/lib/*/qt5/qml/Ubuntu/Thumbnailer.0.1/*
114
115=== added file 'debian/thumbnailer-service.install'
116--- debian/thumbnailer-service.install 1970-01-01 00:00:00 +0000
117+++ debian/thumbnailer-service.install 2014-03-28 10:11:10 +0000
118@@ -0,0 +1,2 @@
119+usr/lib/*/thumbnailer/thumbnailer-service
120+usr/share/dbus-1/services/com.canonical.Thumbnailer.service
121
122=== added directory 'plugins'
123=== added directory 'plugins/Ubuntu'
124=== added directory 'plugins/Ubuntu/Thumbnailer'
125=== added file 'plugins/Ubuntu/Thumbnailer/CMakeLists.txt'
126--- plugins/Ubuntu/Thumbnailer/CMakeLists.txt 1970-01-01 00:00:00 +0000
127+++ plugins/Ubuntu/Thumbnailer/CMakeLists.txt 2014-03-28 10:11:10 +0000
128@@ -0,0 +1,22 @@
129+
130+set(QML_PLUGIN_DIR "${CMAKE_INSTALL_LIBDIR}/qt5/qml/Ubuntu/Thumbnailer.0.1")
131+
132+add_library(thumbnailer-qml MODULE
133+ plugin.cpp
134+ albumartgenerator.cpp
135+ thumbnailgenerator.cpp
136+)
137+
138+set_target_properties(thumbnailer-qml PROPERTIES AUTOMOC TRUE)
139+qt5_use_modules(thumbnailer-qml Qml Quick DBus)
140+target_link_libraries(thumbnailer-qml thumbnailer)
141+
142+install(
143+ TARGETS thumbnailer-qml
144+ LIBRARY DESTINATION ${QML_PLUGIN_DIR}
145+)
146+
147+install(
148+ FILES qmldir
149+ DESTINATION ${QML_PLUGIN_DIR}
150+)
151
152=== added file 'plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp'
153--- plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp 1970-01-01 00:00:00 +0000
154+++ plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp 2014-03-28 10:11:10 +0000
155@@ -0,0 +1,90 @@
156+/*
157+ * Copyright 2014 Canonical Ltd.
158+ *
159+ * This program is free software; you can redistribute it and/or modify
160+ * it under the terms of the GNU Lesser General Public License as published by
161+ * the Free Software Foundation; version 3.
162+ *
163+ * This program is distributed in the hope that it will be useful,
164+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
165+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
166+ * GNU Lesser General Public License for more details.
167+ *
168+ * You should have received a copy of the GNU Lesser General Public License
169+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
170+ *
171+ * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
172+ * James Henstridge <james.henstridge@canonical.com>
173+*/
174+
175+#include "albumartgenerator.h"
176+#include <stdexcept>
177+#include <QDebug>
178+#include <QFile>
179+#include <QUrlQuery>
180+#include <QDBusUnixFileDescriptor>
181+#include <QDBusReply>
182+
183+static const char DEFAULT_ALBUM_ART[] = "/usr/share/unity/icons/album_missing.png";
184+
185+static const char BUS_NAME[] = "com.canonical.Thumbnailer";
186+static const char BUS_PATH[] = "/com/canonical/Thumbnailer";
187+static const char THUMBNAILER_IFACE[] = "com.canonical.Thumbnailer";
188+static const char GET_ALBUM_ART[] = "GetAlbumArt";
189+
190+AlbumArtGenerator::AlbumArtGenerator()
191+ : QQuickImageProvider(QQuickImageProvider::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading),
192+ iface(BUS_NAME, BUS_PATH, THUMBNAILER_IFACE) {
193+}
194+
195+static QImage fallbackImage(QSize *realSize) {
196+ QImage fallback;
197+ fallback.load(DEFAULT_ALBUM_ART);
198+ *realSize = fallback.size();
199+ return fallback;
200+}
201+
202+QImage AlbumArtGenerator::requestImage(const QString &id, QSize *realSize,
203+ const QSize &requestedSize) {
204+ QUrlQuery query(id);
205+ if (!query.hasQueryItem("artist") || !query.hasQueryItem("album")) {
206+ qWarning() << "Invalid albumart uri:" << id;
207+ return fallbackImage(realSize);
208+ }
209+
210+ const QString artist = query.queryItemValue("artist", QUrl::FullyDecoded);
211+ const QString album = query.queryItemValue("album", QUrl::FullyDecoded);
212+
213+ QString desiredSize = "original";
214+ int size = requestedSize.width() > requestedSize.height() ? requestedSize.width() : requestedSize.height();
215+ if (size < 128) {
216+ desiredSize = "small";
217+ } else if (size < 256) {
218+ desiredSize = "large";
219+ } else if (size < 512) {
220+ desiredSize = "xlarge";
221+ }
222+
223+ // perform dbus call
224+ QDBusReply<QDBusUnixFileDescriptor> reply = iface.call(
225+ GET_ALBUM_ART, artist, album, desiredSize);
226+ if (!reply.isValid()) {
227+ qWarning() << "D-Bus error: " << reply.error().message();
228+ return fallbackImage(realSize);
229+ }
230+
231+ try {
232+ QFile file;
233+ file.open(reply.value().fileDescriptor(), QIODevice::ReadOnly);
234+ QImage image;
235+ image.load(&file, NULL);
236+ *realSize = image.size();
237+ return image;
238+ } catch (const std::exception &e) {
239+ qDebug() << "Album art loader failed: " << e.what();
240+ } catch (...) {
241+ qDebug() << "Unknown error when generating image.";
242+ }
243+
244+ return fallbackImage(realSize);
245+}
246
247=== added file 'plugins/Ubuntu/Thumbnailer/albumartgenerator.h'
248--- plugins/Ubuntu/Thumbnailer/albumartgenerator.h 1970-01-01 00:00:00 +0000
249+++ plugins/Ubuntu/Thumbnailer/albumartgenerator.h 2014-03-28 10:11:10 +0000
250@@ -0,0 +1,36 @@
251+/*
252+ * Copyright 2014 Canonical Ltd.
253+ *
254+ * This program is free software; you can redistribute it and/or modify
255+ * it under the terms of the GNU Lesser General Public License as published by
256+ * the Free Software Foundation; version 3.
257+ *
258+ * This program is distributed in the hope that it will be useful,
259+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
260+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
261+ * GNU Lesser General Public License for more details.
262+ *
263+ * You should have received a copy of the GNU Lesser General Public License
264+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
265+ *
266+ * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
267+ * James Henstridge <james.henstridge@canonical.com>
268+*/
269+
270+#ifndef ALBUMART_GENERATOR_H
271+#define ALBUMART_GENERATOR_H
272+
273+#include <QDBusInterface>
274+#include <QQuickImageProvider>
275+
276+class AlbumArtGenerator: public QQuickImageProvider
277+{
278+private:
279+ QDBusInterface iface;
280+
281+public:
282+ AlbumArtGenerator();
283+ QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
284+};
285+
286+#endif
287
288=== added file 'plugins/Ubuntu/Thumbnailer/plugin.cpp'
289--- plugins/Ubuntu/Thumbnailer/plugin.cpp 1970-01-01 00:00:00 +0000
290+++ plugins/Ubuntu/Thumbnailer/plugin.cpp 2014-03-28 10:11:10 +0000
291@@ -0,0 +1,47 @@
292+/*
293+ * Copyright 2014 Canonical Ltd.
294+ *
295+ * This program is free software; you can redistribute it and/or modify
296+ * it under the terms of the GNU Lesser General Public License as published by
297+ * the Free Software Foundation; version 3.
298+ *
299+ * This program is distributed in the hope that it will be useful,
300+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
301+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
302+ * GNU Lesser General Public License for more details.
303+ *
304+ * You should have received a copy of the GNU Lesser General Public License
305+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
306+ *
307+ * Authors: James Henstridge <james.henstridge@canonical.com>
308+*/
309+
310+#include "plugin.h"
311+#include "albumartgenerator.h"
312+#include "thumbnailgenerator.h"
313+
314+void ThumbnailerPlugin::registerTypes(const char *uri) {
315+ qmlRegisterTypeNotAvailable(
316+ uri, 0, 1, "__ThumbnailerIgnoreMe",
317+ "Ignore this: QML plugins must contain at least one type");
318+}
319+
320+void ThumbnailerPlugin::initializeEngine(QQmlEngine *engine, const char *uri) {
321+ QQmlExtensionPlugin::initializeEngine(engine, uri);
322+
323+ try {
324+ engine->addImageProvider("albumart", new AlbumArtGenerator());
325+ } catch (const std::exception &e) {
326+ qWarning() << "Failed to register albumart image provider:" << e.what();
327+ } catch (...) {
328+ qWarning() << "Failed to register albumart image provider.";
329+ }
330+
331+ try {
332+ engine->addImageProvider("thumbnailer", new ThumbnailGenerator());
333+ } catch (const std::exception &e) {
334+ qWarning() << "Failed to register thumbnailer image provider:" << e.what();
335+ } catch (...) {
336+ qWarning() << "Failed to register thumbnailer image provider.";
337+ }
338+}
339
340=== added file 'plugins/Ubuntu/Thumbnailer/plugin.h'
341--- plugins/Ubuntu/Thumbnailer/plugin.h 1970-01-01 00:00:00 +0000
342+++ plugins/Ubuntu/Thumbnailer/plugin.h 2014-03-28 10:11:10 +0000
343@@ -0,0 +1,34 @@
344+/*
345+ * Copyright 2014 Canonical Ltd.
346+ *
347+ * This program is free software; you can redistribute it and/or modify
348+ * it under the terms of the GNU Lesser General Public License as published by
349+ * the Free Software Foundation; version 3.
350+ *
351+ * This program is distributed in the hope that it will be useful,
352+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
353+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
354+ * GNU Lesser General Public License for more details.
355+ *
356+ * You should have received a copy of the GNU Lesser General Public License
357+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
358+ *
359+ * Authors: James Henstridge <james.henstridge@canonical.com>
360+*/
361+
362+#ifndef THUMBNAILER_PLUGIN_H
363+#define THUMBNAILER_PLUGIN_H
364+
365+#include <QtQml>
366+
367+class ThumbnailerPlugin : public QQmlExtensionPlugin
368+{
369+ Q_OBJECT
370+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
371+
372+public:
373+ virtual void registerTypes(const char *uri) override;
374+ virtual void initializeEngine(QQmlEngine *engine, const char *uri) override;
375+};
376+
377+#endif
378
379=== added file 'plugins/Ubuntu/Thumbnailer/qmldir'
380--- plugins/Ubuntu/Thumbnailer/qmldir 1970-01-01 00:00:00 +0000
381+++ plugins/Ubuntu/Thumbnailer/qmldir 2014-03-28 10:11:10 +0000
382@@ -0,0 +1,2 @@
383+module Ubuntu.Thumbnailer
384+plugin thumbnailer-qml
385
386=== added file 'plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp'
387--- plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp 1970-01-01 00:00:00 +0000
388+++ plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp 2014-03-28 10:11:10 +0000
389@@ -0,0 +1,87 @@
390+/*
391+ * Copyright 2013 Canonical Ltd.
392+ *
393+ * This program is free software; you can redistribute it and/or modify
394+ * it under the terms of the GNU Lesser General Public License as published by
395+ * the Free Software Foundation; version 3.
396+ *
397+ * This program is distributed in the hope that it will be useful,
398+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
399+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
400+ * GNU Lesser General Public License for more details.
401+ *
402+ * You should have received a copy of the GNU Lesser General Public License
403+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
404+ *
405+ * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
406+*/
407+
408+#include "thumbnailgenerator.h"
409+#include <stdexcept>
410+#include <QDebug>
411+#include <QMimeDatabase>
412+#include <QUrl>
413+
414+static const char *DEFAULT_VIDEO_ART = "/usr/share/unity/icons/video_missing.png";
415+static const char *DEFAULT_ALBUM_ART = "/usr/share/unity/icons/album_missing.png";
416+
417+ThumbnailGenerator::ThumbnailGenerator() : QQuickImageProvider(QQuickImageProvider::Image,
418+ QQmlImageProviderBase::ForceAsynchronousImageLoading) {
419+
420+}
421+
422+QImage ThumbnailGenerator::requestImage(const QString &id, QSize *realSize,
423+ const QSize &requestedSize) {
424+ /* Allow appending a query string (e.g. ?something=timestamp)
425+ * to the id and then ignore it.
426+ * This is workaround to force reloading a thumbnail when it has
427+ * the same file name on disk but we know the content has changed.
428+ * It is necessary because in such a situation the QML image cache
429+ * will kick in and this ImageProvider will never get called.
430+ * The only "solution" is setting Image.cache = false, but in some
431+ * cases we don't want to do that for performance reasons, so this
432+ * is the only way around the issue for now. */
433+ std::string src_path(QUrl(id).path().toUtf8().data());
434+ std::string tgt_path;
435+ try {
436+ ThumbnailSize desiredSize;
437+ const int xlarge_cutoff = 512;
438+ const int large_cutoff = 256;
439+ const int small_cutoff = 128;
440+ if(requestedSize.width() > xlarge_cutoff || requestedSize.height() > xlarge_cutoff) {
441+ desiredSize = TN_SIZE_ORIGINAL;
442+ } if(requestedSize.width() > large_cutoff || requestedSize.height() > large_cutoff) {
443+ desiredSize = TN_SIZE_XLARGE;
444+ } else if(requestedSize.width() > small_cutoff || requestedSize.height() > small_cutoff) {
445+ desiredSize = TN_SIZE_LARGE;
446+ } else {
447+ desiredSize = TN_SIZE_SMALL;
448+ }
449+ tgt_path = tn.get_thumbnail(src_path, desiredSize);
450+ if(!tgt_path.empty()) {
451+ QString tgt(tgt_path.c_str());
452+ QImage image;
453+ image.load(tgt);
454+ *realSize = image.size();
455+ return image;
456+ }
457+ } catch(std::runtime_error &e) {
458+ qDebug() << "Thumbnail generator failed: " << e.what();
459+ }
460+ return getFallbackImage(id, realSize, requestedSize);
461+}
462+
463+QImage ThumbnailGenerator::getFallbackImage(const QString &id, QSize *size,
464+ const QSize &requestedSize) {
465+ Q_UNUSED(requestedSize);
466+ QMimeDatabase db;
467+ QMimeType mime = db.mimeTypeForFile(id);
468+ QImage result;
469+ if(mime.name().contains("audio")) {
470+ result.load(DEFAULT_ALBUM_ART);
471+ } else if(mime.name().contains("video")) {
472+ result.load(DEFAULT_VIDEO_ART);
473+ }
474+ *size = result.size();
475+ return result;
476+}
477
478=== added file 'plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h'
479--- plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h 1970-01-01 00:00:00 +0000
480+++ plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h 2014-03-28 10:11:10 +0000
481@@ -0,0 +1,36 @@
482+/*
483+ * Copyright 2013 Canonical Ltd.
484+ *
485+ * This program is free software; you can redistribute it and/or modify
486+ * it under the terms of the GNU Lesser General Public License as published by
487+ * the Free Software Foundation; version 3.
488+ *
489+ * This program is distributed in the hope that it will be useful,
490+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
491+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
492+ * GNU Lesser General Public License for more details.
493+ *
494+ * You should have received a copy of the GNU Lesser General Public License
495+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
496+ *
497+ * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
498+*/
499+
500+#ifndef THUMBNAIL_GENERATOR_H
501+#define THUMBNAIL_GENERATOR_H
502+
503+#include <QQuickImageProvider>
504+#include <thumbnailer.h>
505+
506+class ThumbnailGenerator: public QQuickImageProvider
507+{
508+private:
509+ Thumbnailer tn;
510+
511+public:
512+ ThumbnailGenerator();
513+ QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
514+ QImage getFallbackImage(const QString &id, QSize *size, const QSize &requestedSize);
515+};
516+
517+#endif
518
519=== modified file 'src/CMakeLists.txt'
520--- src/CMakeLists.txt 2014-03-28 10:11:10 +0000
521+++ src/CMakeLists.txt 2014-03-28 10:11:10 +0000
522@@ -37,3 +37,5 @@
523 RUNTIME DESTINATION ${SHARE_PRIV_DIR}
524 LIBRARY DESTINATION ${SHARE_PRIV_DIR}
525 )
526+
527+add_subdirectory(service)
528
529=== added directory 'src/service'
530=== added file 'src/service/CMakeLists.txt'
531--- src/service/CMakeLists.txt 1970-01-01 00:00:00 +0000
532+++ src/service/CMakeLists.txt 2014-03-28 10:11:10 +0000
533@@ -0,0 +1,34 @@
534+add_definitions(${THUMBNAILER_CFLAGS})
535+include_directories(${CMAKE_CURRENT_BINARY_DIR})
536+
537+add_executable(thumbnailer-service
538+ main.cpp
539+ dbusinterface.cpp
540+ dbus-generated.c
541+)
542+
543+target_link_libraries(thumbnailer-service thumbnailer)
544+
545+install(
546+ TARGETS thumbnailer-service
547+ RUNTIME DESTINATION ${SHARE_PRIV_DIR}
548+)
549+
550+find_program(gdbus_codegen gdbus-codegen)
551+if(NOT gdbus_codegen)
552+ msg(FATAL_ERROR "Could not locate gdbus-codegen")
553+endif()
554+
555+add_custom_command(
556+ OUTPUT dbus-generated.c dbus-generated.h
557+ COMMAND ${gdbus_codegen} --interface-prefix=com.canonical. --generate-c-code dbus-generated --c-namespace TN ${CMAKE_CURRENT_SOURCE_DIR}/dbus-interface.xml
558+ MAIN_DEPENDENCY dbus-interface.xml
559+)
560+
561+# Install the service file.
562+configure_file(com.canonical.Thumbnailer.service.in com.canonical.Thumbnailer.service)
563+
564+install(
565+ FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.Thumbnailer.service
566+ DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services
567+)
568
569=== added file 'src/service/com.canonical.Thumbnailer.service.in'
570--- src/service/com.canonical.Thumbnailer.service.in 1970-01-01 00:00:00 +0000
571+++ src/service/com.canonical.Thumbnailer.service.in 2014-03-28 10:11:10 +0000
572@@ -0,0 +1,4 @@
573+[D-BUS Service]
574+Name=com.canonical.Thumbnailer
575+Exec=@SHARE_PRIV_ABS@/thumbnailer-service
576+
577
578=== added file 'src/service/dbus-interface.xml'
579--- src/service/dbus-interface.xml 1970-01-01 00:00:00 +0000
580+++ src/service/dbus-interface.xml 2014-03-28 10:11:10 +0000
581@@ -0,0 +1,17 @@
582+<node>
583+ <interface name="com.canonical.Thumbnailer">
584+ <method name="GetAlbumArt">
585+ <arg direction="in" type="s" name="artist" />
586+ <arg direction="in" type="s" name="album" />
587+ <!-- possible values are "small", "large", "xlarge" and "original" -->
588+ <arg direction="in" type="s" name="desiredSize" />
589+ <arg direction="out" type="h" name="fd" />
590+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true" />
591+ </method>
592+
593+ <!-- Note: if adding additional thumbnailing methods that take a
594+ file name as input, ensure that the calling process has
595+ permission to read said file. Otherwise we will leak
596+ information to confined applications. -->
597+ </interface>
598+</node>
599
600=== added file 'src/service/dbusinterface.cpp'
601--- src/service/dbusinterface.cpp 1970-01-01 00:00:00 +0000
602+++ src/service/dbusinterface.cpp 2014-03-28 10:11:10 +0000
603@@ -0,0 +1,168 @@
604+/*
605+ * Copyright (C) 2014 Canonical, Ltd.
606+ *
607+ * Authors:
608+ * James Henstridge <james.henstridge@canonical.com>
609+ *
610+ * This library is free software; you can redistribute it and/or modify it under
611+ * the terms of version 3 of the GNU General Public License as published
612+ * by the Free Software Foundation.
613+ *
614+ * This library is distributed in the hope that it will be useful, but WITHOUT
615+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
616+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
617+ * details.
618+ *
619+ * You should have received a copy of the GNU General Public License
620+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
621+ */
622+
623+#include "dbusinterface.h"
624+
625+#include <cstdio>
626+#include <cstring>
627+#include <memory>
628+#include <stdexcept>
629+#include <string>
630+#include <thread>
631+#include <fcntl.h>
632+#include <unistd.h>
633+
634+#include <glib.h>
635+#include <glib-object.h>
636+#include <gio/gio.h>
637+#include <gio/gunixfdlist.h>
638+#include <thumbnailer.h>
639+
640+#include <internal/gobj_memory.h>
641+#include "dbus-generated.h"
642+
643+using namespace std;
644+
645+static const char BUS_NAME[] = "com.canonical.MediaScanner2";
646+static const char ART_ERROR[] = "com.canonical.MediaScanner2.Error.Failed";
647+
648+static const char MISSING_ALBUM_ART[] = "/usr/share/unity/icons/album_missing.png";
649+
650+struct DBusInterfacePrivate {
651+ const std::string bus_path;
652+ unique_gobj<GDBusConnection> bus;
653+ unique_gobj<TNThumbnailer> iface;
654+ unsigned int handler_id;
655+ std::shared_ptr<Thumbnailer> thumbnailer;
656+
657+ DBusInterfacePrivate(GDBusConnection *g_bus, const std::string& bus_path)
658+ : bus_path(bus_path),
659+ bus(static_cast<GDBusConnection*>(g_object_ref(g_bus))),
660+ iface(tn_thumbnailer_skeleton_new()),
661+ handler_id(0),
662+ thumbnailer(std::make_shared<Thumbnailer>()) {
663+ handler_id = g_signal_connect(
664+ iface.get(), "handle-get-album-art",
665+ G_CALLBACK(&DBusInterfacePrivate::handleGetAlbumArt), this);
666+
667+ GError *error = nullptr;
668+ if (!g_dbus_interface_skeleton_export(
669+ G_DBUS_INTERFACE_SKELETON(iface.get()), bus.get(),
670+ bus_path.c_str(), &error)) {
671+ string errortxt(error->message);
672+ g_error_free(error);
673+
674+ string msg = "Failed to export interface: ";
675+ msg += errortxt;
676+ throw runtime_error(msg);
677+ }
678+ }
679+
680+ ~DBusInterfacePrivate() {
681+ g_dbus_interface_skeleton_unexport(
682+ G_DBUS_INTERFACE_SKELETON(iface.get()));
683+ g_signal_handler_disconnect(iface.get(), handler_id);
684+ }
685+
686+ static gboolean handleGetAlbumArt(TNThumbnailer *iface, GDBusMethodInvocation *invocation, GUnixFDList *, const char *artist, const char *album, const char *size, void *user_data) {
687+ auto p = static_cast<DBusInterfacePrivate*>(user_data);
688+ fprintf(stderr, "Look up cover art for %s/%s at size %s\n", artist, album, size);
689+
690+ ThumbnailSize desiredSize;
691+ if (!strcmp(size, "small")) {
692+ desiredSize = TN_SIZE_SMALL;
693+ } else if (!strcmp(size, "large")) {
694+ desiredSize = TN_SIZE_LARGE;
695+ } else if (!strcmp(size, "xlarge")) {
696+ desiredSize = TN_SIZE_XLARGE;
697+ } else if (!strcmp(size, "original")) {
698+ desiredSize = TN_SIZE_ORIGINAL;
699+ } else {
700+ std::string error("Unknown size: ");
701+ error += size;
702+ g_dbus_method_invocation_return_dbus_error(
703+ invocation, ART_ERROR, error.c_str());
704+ return TRUE;
705+ }
706+
707+ try {
708+ std::thread t(&getAlbumArt,
709+ unique_gobj<TNThumbnailer>(static_cast<TNThumbnailer*>(g_object_ref(iface))),
710+ unique_gobj<GDBusMethodInvocation>(static_cast<GDBusMethodInvocation*>(g_object_ref(invocation))),
711+ p->thumbnailer,
712+ std::string(artist), std::string(album), desiredSize);
713+ t.detach();
714+ } catch (const std::exception &e) {
715+ g_dbus_method_invocation_return_dbus_error(
716+ invocation, ART_ERROR, e.what());
717+ }
718+ return TRUE;
719+ }
720+
721+ static void getAlbumArt(unique_gobj<TNThumbnailer> iface,
722+ unique_gobj<GDBusMethodInvocation> invocation,
723+ std::shared_ptr<Thumbnailer> thumbnailer,
724+ const std::string artist, const std::string album,
725+ ThumbnailSize desiredSize) {
726+ std::string art;
727+ try {
728+ art = thumbnailer->get_album_art(
729+ artist, album, desiredSize, TN_REMOTE);
730+ } catch (const std::exception &e) {
731+ g_dbus_method_invocation_return_dbus_error(
732+ invocation.get(), ART_ERROR, e.what());
733+ return;
734+ }
735+
736+ if (art.empty()) {
737+ g_dbus_method_invocation_return_dbus_error(
738+ invocation.get(), ART_ERROR, "Could not get thumbnail");
739+ return;
740+ }
741+ int fd = open(art.c_str(), O_RDONLY);
742+ if (fd < 0) {
743+ g_dbus_method_invocation_return_dbus_error(
744+ invocation.get(), ART_ERROR, strerror(errno));
745+ return;
746+ }
747+
748+ unique_gobj<GUnixFDList> fd_list(g_unix_fd_list_new());
749+ GError *error = nullptr;
750+ g_unix_fd_list_append(fd_list.get(), fd, &error);
751+ close(fd);
752+ if (error != nullptr) {
753+ g_dbus_method_invocation_return_dbus_error(
754+ invocation.get(), ART_ERROR, error->message);
755+ g_error_free(error);
756+ return;
757+ }
758+
759+ tn_thumbnailer_complete_get_album_art(
760+ iface.get(), invocation.get(), fd_list.get(), g_variant_new_handle(0));
761+ }
762+
763+};
764+
765+DBusInterface::DBusInterface(GDBusConnection *bus, const std::string& bus_path)
766+ : p(new DBusInterfacePrivate(bus, bus_path)) {
767+}
768+
769+DBusInterface::~DBusInterface() {
770+ delete p;
771+}
772
773=== added file 'src/service/dbusinterface.h'
774--- src/service/dbusinterface.h 1970-01-01 00:00:00 +0000
775+++ src/service/dbusinterface.h 2014-03-28 10:11:10 +0000
776@@ -0,0 +1,40 @@
777+/*
778+ * Copyright (C) 2014 Canonical, Ltd.
779+ *
780+ * Authors:
781+ * James Henstridge <james.henstridge@canonical.com>
782+ *
783+ * This library is free software; you can redistribute it and/or modify it under
784+ * the terms of version 3 of the GNU General Public License as published
785+ * by the Free Software Foundation.
786+ *
787+ * This library is distributed in the hope that it will be useful, but WITHOUT
788+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
789+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
790+ * details.
791+ *
792+ * You should have received a copy of the GNU General Public License
793+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
794+ */
795+
796+#ifndef DBUSINTERFACE_H
797+#define DBUSINTERFACE_H
798+
799+#include <string>
800+
801+struct DBusInterfacePrivate;
802+typedef struct _GDBusConnection GDBusConnection;
803+
804+class DBusInterface final {
805+public:
806+ DBusInterface(GDBusConnection *bus, const std::string& bus_path);
807+ ~DBusInterface();
808+
809+ DBusInterface(const DBusInterface&) = delete;
810+ DBusInterface& operator=(DBusInterface&) = delete;
811+
812+private:
813+ DBusInterfacePrivate *p;
814+};
815+
816+#endif
817
818=== added file 'src/service/main.cpp'
819--- src/service/main.cpp 1970-01-01 00:00:00 +0000
820+++ src/service/main.cpp 2014-03-28 10:11:10 +0000
821@@ -0,0 +1,44 @@
822+#include <cstdio>
823+#include <memory>
824+#include <stdexcept>
825+#include <glib.h>
826+#include <gio/gio.h>
827+
828+#include "dbusinterface.h"
829+
830+static const char BUS_NAME[] = "com.canonical.Thumbnailer";
831+static const char BUS_PATH[] = "/com/canonical/Thumbnailer";
832+
833+static std::unique_ptr<GMainLoop,void(*)(GMainLoop*)> main_loop(
834+ g_main_loop_new(nullptr, FALSE), g_main_loop_unref);
835+
836+static void nameLost(GDBusConnection *, const char *, void *) {
837+ fprintf(stderr, "Could no acquire D-Bus name %s. Quitting.\n", BUS_NAME);
838+ g_main_loop_quit(main_loop.get());
839+}
840+
841+int main(int argc, char **argv) {
842+ GError *error = nullptr;
843+ std::unique_ptr<GDBusConnection, void(*)(void*)> bus(
844+ g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error),
845+ g_object_unref);
846+ if (error != nullptr) {
847+ fprintf(stderr, "Failed to connect to session bus: %s\n", error->message);
848+ g_error_free(error);
849+ return 1;
850+ }
851+
852+ DBusInterface dbus(bus.get(), BUS_PATH);
853+
854+ unsigned int name_id = g_bus_own_name_on_connection(
855+ bus.get(), BUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE,
856+ nullptr, &nameLost, nullptr, nullptr);
857+
858+ g_main_loop_run(main_loop.get());
859+
860+ if (name_id != 0) {
861+ g_bus_unown_name(name_id);
862+ }
863+
864+ return 0;
865+}
866
867=== modified file 'src/thumbnailer.cpp'
868--- src/thumbnailer.cpp 2014-03-28 10:11:10 +0000
869+++ src/thumbnailer.cpp 2014-03-28 10:11:10 +0000
870@@ -53,16 +53,12 @@
871
872 ThumbnailerPrivate() {};
873
874-<<<<<<< TREE
875- string create_thumbnail(const string &abspath, ThumbnailSize desired_size);
876+ string create_thumbnail(const string &abspath, ThumbnailSize desired_size,
877+ ThumbnailPolicy policy);
878 string create_random_filename();
879-=======
880- string create_thumbnail(const string &abspath, ThumbnailSize desired_size,
881- ThumbnailPolicy policy);
882->>>>>>> MERGE-SOURCE
883 };
884
885-<<<<<<< TREE
886+
887 string ThumbnailerPrivate::create_random_filename() {
888 string fname;
889 char *dirbase = getenv("TMPDIR"); // Set when in a confined application.
890@@ -77,8 +73,6 @@
891 return fname;
892 }
893
894-string ThumbnailerPrivate::create_thumbnail(const string &abspath, ThumbnailSize desired_size) {
895-=======
896 string ThumbnailerPrivate::create_audio_thumbnail(const string &/*abspath*/,
897 ThumbnailSize /*desired_size*/, ThumbnailPolicy /*policy*/) {
898 // There was a symbol clash between 1.0 and 0.10 versions of
899@@ -101,14 +95,8 @@
900 return "";
901 }
902 string ThumbnailerPrivate::create_generic_thumbnail(const string &abspath, ThumbnailSize desired_size) {
903->>>>>>> MERGE-SOURCE
904 int tmpw, tmph;
905 string tnfile = cache.get_cache_file_name(abspath, desired_size);
906-<<<<<<< TREE
907- string tmpname = create_random_filename();
908-
909-=======
910->>>>>>> MERGE-SOURCE
911 // Special case: full size image files are their own preview.
912 if(desired_size == TN_SIZE_ORIGINAL &&
913 gdk_pixbuf_get_file_info(abspath.c_str(), &tmpw, &tmph)) {
914@@ -123,9 +111,8 @@
915 }
916
917 string ThumbnailerPrivate::create_video_thumbnail(const string &abspath, ThumbnailSize desired_size) {
918- char filebuf[] = "/tmp/some/long/text/here/so/path/will/fit";
919 string tnfile = cache.get_cache_file_name(abspath, desired_size);
920- string tmpname = tmpnam(filebuf);
921+ string tmpname = create_random_filename();
922 if(video.extract(abspath, tmpname)) {
923 scaler.scale(tmpname, tnfile, desired_size, abspath);
924 unlink(tmpname.c_str());
925
926=== added directory 'tests/qml'
927=== added file 'tests/qml/tst_image_provider.qml'
928--- tests/qml/tst_image_provider.qml 1970-01-01 00:00:00 +0000
929+++ tests/qml/tst_image_provider.qml 2014-03-28 10:11:10 +0000
930@@ -0,0 +1,62 @@
931+import QtQuick 2.0
932+import QtTest 1.0
933+import Ubuntu.Thumbnailer 0.1
934+
935+Item {
936+ Image {
937+ id: image
938+ width: 200
939+ height: 200
940+
941+ SignalSpy {
942+ id: spy
943+ target: image
944+ signalName: "statusChanged"
945+ }
946+ }
947+
948+ Canvas {
949+ id: canvas
950+ width: 200
951+ height: 200
952+ renderStrategy: Canvas.Immediate
953+ renderTarget: Canvas.Image
954+ }
955+
956+ TestCase {
957+ name: "ThumbnailerProviderTests"
958+ when: windowShown
959+
960+ function test_albumart() {
961+ var ctx = loadImage(
962+ "image://albumart/artist=Gotye&album=Making%20Mirrors");
963+ comparePixel(ctx, 0, 0, 242, 228, 209, 255);
964+ }
965+
966+ function loadImage(uri) {
967+ image.source = uri
968+ while (image.status == Image.Loading) {
969+ spy.wait();
970+ }
971+ compare(image.status, Image.Ready);
972+
973+ var ctx = canvas.getContext("2d");
974+ ctx.drawImage(image, 0, 0);
975+ return ctx;
976+ }
977+
978+ function comparePixel(ctx,x,y,r,g,b,a, d) {
979+ var c = ctx.getImageData(x,y,1,1).data;
980+ if (d === undefined)
981+ d = 0;
982+ r = Math.round(r);
983+ g = Math.round(g);
984+ b = Math.round(b);
985+ a = Math.round(a);
986+ var notSame = Math.abs(c[0]-r)>d || Math.abs(c[1]-g)>d || Math.abs(c[2]-b)>d || Math.abs(c[3]-a)>d;
987+ if (notSame)
988+ qtest_fail('Pixel compare fail:\nactual :[' + c[0]+','+c[1]+','+c[2]+','+c[3] + ']\nexpected:['+r+','+g+','+b+','+a+'] +/- '+d, 1);
989+
990+ }
991+ }
992+}

Subscribers

People subscribed via source and target branches

to all changes: