Aurélien's room

Qt Image Decoders stepping on Each Others

A weird bug

In December last year, Falk Krönert filled a strange bug: Gwenview failed to load some PNG images, which other applications were able to display flawlessly. I couldn't reproduce it back then with Gwenview 2.7.3, so couldn't dig any further.

Falk did not give up so easily though. After further investigations, he discovered the bug only happened with images whose width would be 2560 or 2808. Since I still couldn't reproduce it, he provided me with a QEmu image of his openSUSE installation so that I could investigate (now that is one dedicated bug reporter!)

Investigating

Running the QEmu image, I was finally able to reproduce the bug. I started investigating, installing the necessary packages to build Gwenview and my beloved cgdb, learning how to use Zypper in the process.

Step one, an image format from the past...

I quickly discovered Qt was not able to discover the image format of the PNG. The code looked like this:

1
2
3
4
5
6
7
8
9
10
QBuffer buffer;
buffer.setBuffer(&mData); // mData contains the first 256 bytes of the image
buffer.open(QIODevice::ReadOnly);

QImageReader reader(&buffer);

mFormat = reader.format();
if (mFormat.isEmpty()) {
    return false;
}

In the case of this image, "mFormat" was supposed to contain "png", but it was empty instead.

In case you are not familiar with Qt image plugin code, it works like this: Qt provides a base class, QImageIOHandler, which image decoders must inherit from. so there is a QJpegHandler, a QPngHandler and so on. Some of these handlers are provided by Qt directly in QtGui.so, others are provided by Qt and kdelibs (and possibly others) as plugins installed in "plugins/imageformats".

Here is QImageReader::format() code:

1
2
3
4
5
6
7
8
9
10
QByteArray QImageReader::format() const
{
    if (d->format.isEmpty()) {
        if (!d->initHandler())
            return QByteArray();
        return d->handler->canRead() ? d->handler->format() : QByteArray();
    }

    return d->format;
}

Debugger was telling me d->handler was correctly created by initHandler(), but QPngHandler::canRead() was never called!

Quite puzzled by this, I finally resorted to place a breakpoint in QImageIOHandler constructor. Once I hit the breakpoints, I printed the backtrace, and was astonished to find myself inside the PCX handler provided by kdelibs! This explained why QPngHandler::canRead() was not called, QImageReader was calling PCXHandler::canRead(), which unsurprisedly returned false.

This does not explain how Qt decided the image was a PCX in the first place. To decide which handler to use, initHandler() calls all implementations of QImageIOHandler::canRead(), the first one to answer true wins. PCXHandler::canRead() implementation is not very selective: it returns true if the first byte of the image is 10 (According to Wikipedia it should be possible to check at least the first two bytes, need to look into this).

Here is an hex-dump of the first bytes of the PNG image:

89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00 00 0a 00 00 00 00 02  08 02 00 00 00 1b 8e 12  |................|

There are a few 10 (0a in the dump) values but the first byte is definitely not 10.

Maybe the PCX loader is looking at the wrong position? I decided to place a breakpoint in PCXHandler::canRead() and print device->pos(). Sure enough, it sayd the position was 18, instead of the expected 0... If you look at position 18 (starting from 0) in the dump there is indeed a 0a value.

It is interesting to look at what this 0a stands for: The PNG specification tells us a PNG starts with an 8 byte signature, followed by a series of chunks. The first chunk must be an IHDR chunk. This chunk starts with "IHDR", followed by four bytes representing the image width, in most significant byte order. This means the first four bytes in the second line of the dump (00 00 0a 00) are the image width, and indeed, 0a00 is the hexadecimal for 2560... Do you remember the bug was only happening for some image sizes? If the width had been 2559, the IHDR chunk would have started with 00 00 09 ff, and thus the PCX handler would not have identified this image as a PCX.

Another format gets in...

While the PCX handler could be improved to use more than one byte as a signature, it is not the real problem: had device->pos() been correctly set to 0, it wouldn't have identified the image as PCX. QImageIOHandler::canRead() documentation explicitly says:

When reimplementing canRead(), make sure that the I/O device (device()) is left in its original state (e.g., by using peek() rather than read()).

Someone was not playing by the rules and was altering the device position before PCXHandler::canRead() was reached.

I was starting to suspect one of the other image handlers was doing something nasty. To try to isolate the culprit, I first removed all KDE image plugins but the PCX handler. No luck, still buggy. I then removed all Qt image plugins (PNG was still there as it is one of those built into QtGui.so) Bingo! The image was now being correctly identified. Using a quick dichotomy, I found out the TGA plugin was the nasty boy.

QTgaHandler::canRead() looks like this:

1
2
3
4
5
6
7
8
9
bool QTgaHandler::canRead(QIODevice *device)
{
    if (!device) {
        qWarning("QTgaHandler::canRead() called with no device");
        return false;
    }
    QTgaFile tga(device);
    return tga.isValid();
}

Looking at the code you can probably guess it: QTgaFile constructor does not take care of not altering QIODevice state. Looking at the Git history for this handler shows it was copied from QtQuick3D to Qt before the 4.8.0 release, which explains why there was no problem when running Gwenview on top of Qt 4.7.

It is a bit sad however that this code was brought in instead of using KDE TGA handler, which has been around for a while, does not suffer from this bug and have (limited) write supports whereas the Qt handler is read-only.

Fixing time

I worked around this bug in Gwenview by taking advantage of the fact that QImageReader accept an optional "format" parameter, which can be used as a hint: QImageReader will first try to use an image handler which supports this format.

I was not using this feature until now because I thought passing a format would make QImageReader fail if the image was not of the specified format. It turns out I was wrong: the format parameter is just a hint, if the image cannot be loaded with a handler implementing this format, QImageReader will try the others. I should probably propose a patch for the documentation to reflect that.

The real fix however is to teach QTgaHandler proper manners. I just posted a fix on qt-project.org, hopefully it will get in.

Gwenview 2.8.2 (from upcoming KDE SC 4.8.2), has the work-around in but if you haven't upgraded yet, you can fix the bug by simply removing the TGA handler plugin. Look for a file named libqtga.so. It should be in:

  • openSUSE (64 bits): /usr/lib64/qt4/plugins/imageformats/
  • Ubuntu|Debian (64 bits): /usr/lib/x86_64-linux-gnu/qt4/plugins/imageformats/

(I don't know the location for 32 bits systems)

So to summarize, a rogue TGA handler was tricking a sloppy PCX handler into identifying a PNG file into a PCX file, what a mess!

PyQt+WebKit experiments part 2: debugging

(This is part 2 of the PyQt+WebKit experiments series)

In Part 1 I described how to embed WebKit in a PyQt application and how to expose PyQt objects in WebKit and manipulate them with JavaScript.

Even if you are a great JavaScript master, you can't avoid the occasional typo while writing JavaScript code in your application. This can be quite frustrating with QtWebKit because it likes to stay quiet: it won't tell you about any error.

Let's have a look at an example.

First here is loader.py, a simple Python script which loads a block of HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.view = QWebView(self)

        layout = QVBoxLayout(self)
        layout.setMargin(0)
        layout.addWidget(self.view)

def main():
    app = QApplication(sys.argv)
    window = Window()
    html = open(sys.argv[1]).read()
    window.show()
    window.view.setHtml(html)
    app.exec_()

if __name__ == "__main__":
    main()

And here is "broken.html", our broken HTML code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script>
function brokenFunction(arg1, arg2) {
    var result;
    result = arg1 * 2;
    result += arg2;
    resul /= 4;
    return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(brokenFunction(2, 3));
</script>
</body>
</html>

Notice the missing 't' in "resul /= 4"?

The last-resort, grandpa-debugged-js-this-way, debugging tool is still there: the mighty alert() function. Just stuff your code with calls to alert() and be happy... Anyone ever wrote code like that?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<script>
function brokenFunction(arg1, arg2) {
    var result;
    result = arg1 * 2;
    alert("1");
    result += arg2;
    alert("2");
    resul /= 4;
    alert("3");
    return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(brokenFunction(2, 3));
</script>
</body>
</html>

Easy enough, no? With the great alert() function we can quickly pinpoint the bug in our brokenFunction() is between alert("2") and alert("3").

Can we do better?

alert()-style debugging gets old very fast. Clicking that "OK" button is a pain. Fortunately, there is a way to get more useful feedback from our PyQt application.

The job of the QWebView class is to show the content of a QWebPage instance. By default QWebView creates its own instance of QWebPage, but it is possible to replace this instance with our own QWebPage. The QWebPage class has a few virtual methods. Among them, the javaScriptConsoleMessage() method is the one we are looking for: it is called every time console.log() is called from JavaScript.

Here is an implementation of WebPage which uses Python logging module to get JavaScript console messages out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import logging

from PyQt4.QtWebKit import *

class WebPage(QWebPage):
    """
    Makes it possible to use a Python logger to print javascript console messages
    """
    def __init__(self, logger=None, parent=None):
        super(WebPage, self).__init__(parent)
        if not logger:
            logger = logging
        self.logger = logger

    def javaScriptConsoleMessage(self, msg, lineNumber, sourceID):
        self.logger.warning("JsConsole(%s:%d): %s" % (sourceID, lineNumber, msg))

And here is "loader-log.py", a loader which uses this class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import os
import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *

from webpage import WebPage

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.view = QWebView(self)
        self.view.setPage(WebPage())

        layout = QVBoxLayout(self)
        layout.setMargin(0)
        layout.addWidget(self.view)

def main():
    app = QApplication(sys.argv)
    window = Window()
    html = open(sys.argv[1]).read()
    window.show()
    window.view.setHtml(html)
    app.exec_()

if __name__ == "__main__":
    main()

If we load "broken.html" with "loader-log.py" we get the following on stderr:

$ python loader-log.py broken.html
WARNING:root:JsConsole(undefined:0): ReferenceError: Can't find variable: resul

That should make it easier to find and fix our bug, even if we don't get very useful file names or line numbers.

javaScriptConsoleMessage() receives all console messages. This means our logger will also print out calls to console.log(). Here is "console-log.html":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<script>
function chattyFunction(arg1, arg2) {
    var result;
    result = arg1 * 2;
    console.log("result" + result);
    result += arg2;
    console.log("result" + result);
    result /= 4;
    console.log("result" + result);
    return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(chattyFunction(2, 3));
</script>
</body>
</html>

When loaded with "loader-log.py", we get this output:

$ python loader-log.py console-log.html
WARNING:root:JsConsole(about:blank:5): result: 4
WARNING:root:JsConsole(about:blank:5): result: 7
WARNING:root:JsConsole(about:blank:5): result: 1.75

Not good enough?

Getting the output of console.log() is nice, but modern browsers have much more efficient tools: if you open "broken.html" with Rekonq and look at the output in the Web Inspector Console, you can not only see console output, but you can also easily inspect your HTML tree and many other things.

Like us, Rekonq uses QtWebKit, so is there a way to get a similar tool?

It is actually possible. Rekonq uses a class named QWebInspector. All that is necessary to get a nice inspector tool for our application is to:

  1. Get the QWebView page

  2. set the QWebSettings.DeveloperExtrasEnabled attribute on this page

  3. Instantiate a QWebInspector

  4. Pass the view page to the inspector

Here is "loader-webinspector.py", a new HTML loader which can show a web inspector when one presses F12:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import os
import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *

from webpage import WebPage

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.view = QWebView(self)

        self.setupInspector()

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)

        layout = QVBoxLayout(self)
        layout.setMargin(0)
        layout.addWidget(self.splitter)

        self.splitter.addWidget(self.view)
        self.splitter.addWidget(self.webInspector)

    def setupInspector(self):
        page = self.view.page()
        page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
        self.webInspector = QWebInspector(self)
        self.webInspector.setPage(page)

        shortcut = QShortcut(self)
        shortcut.setKey(Qt.Key_F12)
        shortcut.activated.connect(self.toggleInspector)
        self.webInspector.setVisible(False)

    def toggleInspector(self):
        self.webInspector.setVisible(not self.webInspector.isVisible())

def main():
    app = QApplication(sys.argv)
    window = Window()
    html = open(sys.argv[1]).read()
    window.show()
    window.view.setHtml(html)
    app.exec_()

if __name__ == "__main__":
    main()

The four steps I described are done in the "setupInspector()" method.

And here is "console-webinspector.html":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<script>
function chattyFunction(arg1, arg2) {
    var result;
    result = arg1 * 2;
    console.log("result: %d", result);
    result += arg2;
    console.warn("result: %d", result);
    result /= 4;
    console.error("result: %d", result);
    return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(chattyFunction(2, 3));
</script>
</body>
</html>

It is similar to "console-log.html" but takes advantage of two new features which do not work with the previous approach:

  • printf-style formatting: that is, printing the value of result with "result: %d", result, not "result: " + result

  • log categorization: you can use console.warn() and console.error() to get different type of output. These methods worked with the previous approach, but the categorization was lost.

Loading "console-webinspector.html" with "loader-webinspector.py" and pressing F12 we get this:

Closing words

These two approaches should help you track down the nastiest bugs in your embedded JavaScript code. The Web Inspector approach is probably the most powerful one, but the Python logging approach can also be useful when tracking down bugs where it is more practical to have one single log output for both the PyQt and the JavaScript sides.

Flattr this

What's new in Gwenview from KDE 4.8

Now that KDE 4.8 has been released, it's time to recap all changes you will find in Gwenview.

The main change is the addition of animations when viewing images: crossfading between images and nicer-to-use comparisons. You can learn more from this previous blog article.

This change was not nice for users of some graphic cards whose OpenGL drivers do not support what Gwenview tries to do. I decided to play it safe for now: animations in Gwenview now use software rendering by default. For better performance, you can enable OpenGL rendering in the configuration dialog.

This new version of Gwenview also comes with a lot of smaller changes, some of them caused by the limitations which were introduced by the new animation system.

Scrolling and Zooming

  • No more scrollbars: A bird-eye view lets you scroll the image.

  • Nicer zoom cursor. I realized Qt now supports truecolor cursors, so I drew a nicer magnifying glass cursor instead of the black+white+1bit-alpha-channel version. Holding down Ctrl to zoom won't bring you back to the 90s anymore!

  • Pressing 'F' toggles zoom-to-fit on and off.

  • More consistent behavior: SVG images can now be scrolled using the same shortcuts as scrolling raster images.

Global User interface changes

  • The "sidebar collapser", the little arrow on the left of the view which let you hide the sidebar is gone. It has been replaced by a button in the statusbar.

  • Labels for some of the toolbar buttons have been removed, reducing its width. It should now be more usable on small netbook-like screens.

Tools

  • The red-eye reduction and crop tools no longer show floating widgets over the image, a thin bar slides from the bottom of the window instead.

Behavior

  • Compared images follow thumbnail view order: previously when one selected two or more images to compare them, they would not necessarily appear in the same order as in the thumbnail bar.

  • Arrow-key navigation in zoom-to-fit mode. This one has been requested by quite a few people. When an image is in zoom-to-fit mode, you can go to the previous and next image with the arrow keys. When you zoom in, arrow keys are used to scroll the image. This is very similar to the behavior provided by phones or digital cameras.

Video support

  • The on-screen-display is now transparent.

  • One can use the left and right arrow keys to seek

  • A late addition: an undocumented shortcut (P) to toggle playback

  • Note that video support is a bit fishy right now: it seems Phonon does not always play well with its video widget being embedded in a QGraphicsView, known symptoms are wrong colors or wrong aspect ratio. Hopefully this will improve in the next releases.

4.8.1 should bring you its usual series of bug fixes, among them is generating thumbnails for all images of the current folder, not only the currently visible ones. This fix is a bit bigger than usual *.1 fixes, so testers are welcome: the code lives in Gwenview git repository, in the "gen-all-thumbnails" branch.

I hope you enjoy this new Gwenview!

Flattr this

Swimming against the stream or preparing for next stream change? PyQt+WebKit experiments

Introduction

(This is part 1 of the PyQt+WebKit experiments series)

It is an interesting time to be a Qt developer: new ways to develop applications are emerging, one can either ignore them or experiment... I like experimentations.

In one corner we have traditional, QWidget-based Qt development.

In another corner we have QML, a declarative language coupled with JavaScript, which brings a whole new way to think about application development. Qt developers are betting a lot on QML, planning for it to be the preferred way to develop new applications with Qt 5.

In yet another corner is the Web, a perfect demonstration of technology hijacking: what was once designed to present structured documents has become one of the most powerful way to create applications.

Of those three technologies, I believe QML is the most innovative and powerful one, and I had high hopes it could secure a significant market share when Nokia and Intel were still planning to deliver it on their upcoming smart-phones and tablets. Now that Nokia switched to WP7 and Intel switched to EFL, I am a lot less confident QML will succeed. Experience shows that when two technologies are in competition, the best one does not automatically wins, parameters like market share and learning curve are as important as, if not more important than, technical excellence.

Not willing to ignore the behemoth that is the Web, I started an experiment: how would it feel to develop an application which would mix Qt and HTML rendering? Thanks to QtWebKit, this kind of integration is easy to achieve. My experimentation subject is based on Yokadi, a command-line based TODO list I work on. I decided to create a graphical frontend for it. The result is this:

This application, named QYok (yes, I suck at naming applications, suggestions for better names are welcome!), is an interesting mix of technologies. It uses:

  • PyQt, for the main window and most of the widgets

  • QtWebKit, to display the task lists

  • jQuery, to make it easier to manipulate the HTML and provide nice animations

  • Jinja2, a Python-based template system, using a syntax similar to Django (and thus Grantlee)

  • and of course, Yokadi itself, to provide access to the TODO database

In this series of articles, I am going to describe how one can mix PyQt and WebKit together, based on my learnings from the QYok project. In particular I want to show ways to generate native-looking HTML code, to ensure your application does not look alien on your desktop.

Getting a Qt application to show HTML code

This is the first, very easy, step. Here is a complete example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        view = QWebView(self)
        layout = QVBoxLayout(self)
        layout.addWidget(view)

        html = """
            <html><body>
            Hello World!
            </body></html>
            """
        view.setHtml(html)

def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    app.exec_()

if __name__ == "__main__":
    main()

(Source code)

What all this does is create a window, create a webview in it and set some HTML. Nothing fancy.

HTML is nicer with images

One of the main point of using HTML is that it makes it reasonably easy to create complex documents which include images. So let's add a folder named "static" to our code folder, with an image in it. Since we are feeding QWebView with HTML, it has no way to know where to look for our images. Luckily, setHtml() accepts a second parameter: the base url of the document. Any relative url contained in our HTML will be resolved using this url as a base.

Here is the modified call to setHtml():

1
2
3
4
5
6
7
8
9
pyDir = os.path.abspath(os.path.dirname(__file__))
baseUrl = QUrl.fromLocalFile(os.path.join(pyDir, "static/"))
html = """
    <html><body>
    <div>Hello World!</div>
    <img src="test.png"/>
    </body></html>
    """
view.setHtml(html, baseUrl)

(Source code)

Be careful: always ensure baseUrl ends up with a trailing slash! If there is no trailing slash, QWebView will resolve "test.png" as "/path/to/tut1/statictest.png" instead of "/path/to/tut1/static/test.png". I learnt that the hard way...

Calling Qt code from JavaScript

QWebView.setHtml() makes it easy for Qt code to provide HTML content to WebKit, but it is a one-way only setup: it doesn't provide a way for JavaScript code to call Qt code. The way to do this is by exposing objects created on the Qt side to QWebView (or more precisely, to the main frame of the QWebPage inside QWebView).

QtWebKit takes advantage of Qt introspection features to access object methods and properties. To be exposed to JavaScript, an object must thus inherit from QObject and expose itself through Qt properties, Qt signals and Qt slots. Here is an example. First lets define a class named Foo which implements a compute() slot, performing complicated computations and a quit() slot:

1
2
3
4
5
6
7
8
class Foo(QObject):
    @pyqtSlot(int, result=int)
    def compute(self, value):
        return value * 2

    @pyqtSlot()
    def quit(self):
        QApplication.quit()

The important part here are the "@pyqtSlot()" decorators, which take care of turning our methods into slots. As you may have guessed, the decorator expects you to describe the type of the parameters accepted by your method, as well as the type of the returned value, if any.

Now that this done, lets create a Window class which will instantiate our Foo class and pass it to JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        view = QWebView(self)
        layout = QVBoxLayout(self)
        layout.addWidget(view)

        self.foo = Foo(self)
        view.page().mainFrame().addToJavaScriptWindowObject("foo", self.foo)

        html = """
            <html>
            <head>
            <script>
            function updateEntry() {
                var element = document.getElementById("entry");
                var result = foo.compute(element.value);
                element.value = result;
            }
            </script>
            </head>

            <body>
            <div>
                <input type="text" id="entry" value="1"/>
                <input type="button" value="Compute" onclick="updateEntry()"/>
            </div>
            <div>
                <input type="button" value="Quit" onclick="foo.quit()"/>
            </div>
            </body>
            </html>
            """
        view.setHtml(html)

(Source code)

Clicking the "Compute" button doubles the value in the input widget, clicking the "Quit" button, quits the application.

In this example, we create self.foo, an instance of the Foo class, then expose it to our QWebView with the line:

1
view.page().mainFrame().addToJavaScriptWindowObject("foo", self.foo)

We then feed our QWebView with some HTML code with view.setHtml(). Note how JavaScript code can now refer to our foo object as if it was a native JavaScript object.

Conclusion

That's it for now. Stay tuned for the next article.

PS: All examples are available from github: git clone git://github.com/agateau/pyqt-webkit-tutorial.git to get it.

Flattr this

Oups, my burst shots are all shuffled!

At the end of December my wife and I were invited with other parents to my daughter last dance lesson of the year.

Of course, I brought my camera with me. I shot a little bit more than 160 pictures from the one hour lesson (if that sounds like a crazy number, then you probably don't have kids...).

Some of them were shot in burst mode, where the camera continuously takes a few pictures per second, increasing the chances the wannabe photographer gets at least one decent picture (and partially explaining the embarrassing large number of shots...)

Importing, aka, the mistake

Back home, I imported the pictures with Gwenview importer and started to comb through them to get rid of the cruft.

Burst mode is fun, but Gwenview importer does not play nice with it: by default it renames imported pictures using the shot date found in the image EXIF information. I like this feature because I find it more expressive to have a picture named "2011-12-17_12-47-47.jpg" than "pict0234.jpg". It has one big drawback though: the precision of time information stored in EXIF is one second. When one shoots in burst mode, more than one picture per second is produced... In such a situation, Gwenview importer inserts a "_n" suffix just before the extension dot, where "n" starts at "1" and is increased until the importer can create a file name which does not exist. It gets even a bit nastier when you realize Gwenview importer does not necessarily import pictures in file order, meaning the "n" values do not necessarily match the order in which the pictures were taken... not good.

Workarounds

I want to address this issue correctly in KDE SC 4.9, but meanwhile here are some workarounds.

If you haven't yet imported your burst-mode pictures, you can either disable image renaming altogether or change the "Rename Format" from "{date}{time}.{ext.lower}" to "{date}{time}_{name.lower}.{ext.lower}". This way imported images will still carry the original image name and will be properly ordered. File names will be a bit ugly, but at least you will be safe.

Configuring Gwenview importer to append the original file name at the end of imported pictures Configuring Gwenview importer to append the original file name at the end of imported pictures

If you have already imported the pictures it gets a bit more tricky. I looked at the EXIF information my camera recorded and noticed pictures have a "Exif.Panasonic.SequenceNumber" tag (yes, that sounds vendor-specific :/). This tag is set to 0 for normal pictures, but starts at 1 and goes up for burst-mode pictures. I thus put together "pict-exif-rename", a quick Python script using pyexiv2 to rename all images, appending a suffix based on the sequence number if it is greater than 0.

I pushed "pict-exif-rename" as a github gist, maybe you will find it useful.

It would be interesting to hear from different camera owners about the presence of the "Exif.Panasonic.SequenceNumber" tag (or an equivalent). You can quickly check its presence by running "exiv2 -Pkv a_picture.jpg | grep SequenceNumber". All pictures taken with my camera (Panasonic DMC-FS16) have this tag, it is set to 0 for non burst-mode pictures.

Flattr this

Announcing Gwenview Forum

Gwenview has had a mailing-list for a very long time now, but time changes. Nowadays I believe forums are more appropriate for end-users discussions than mailing-lists for a few reasons:

  • Forum do not require setting up individual passwords for every topic you are interested in.

  • It is easier to join an existing conversation on a forum you just discovered than on a mailing list you have not subscribed yet.

  • It is easier for non-technical users to read and join a forum.

Therefore I asked for a Gwenview forum to be created on forum.kde.org. You can find it forum.kde.org/viewforum.php?f=213 (too bad forums do not get nicer urls :/). Come and ask your questions here!

I plan to phase out the mailing-list. I haven't yet decided of a day when it will be turned off but it's probably going to happen in a month or two. After that the only remaining bit of SourceForge infrastructure used for Gwenview will be the (outdated) website, maybe I'll get around to create a site for Gwenview under kde.org domain...

Flattr this

Akademy FR at Capitole du Libre

Last weekend I went back to the beautiful city of Toulouse for Capitole du Libre, a free software event made of three co-hosted events: Akademy FR, Ubuntu Party and Drupal Camp. It was great to meet the usual gang of Toulibre KDE people and put faces on people I only knew from #kde-fr, the friendly French-speaking Freenode IRC channel. We even had Aleix Pol, of KDevelop and KAlgebra fame, coming from Barcelona to help us.

Saturday

Saturday, was the busiest day: we had a KDE booth and ran two conference tracks: users and contributors.

Being co-hosted with the Ubuntu Party brought in a different population from previous KDE Release Parties. We had nice conversations on the KDE booth with Ubuntu users who heard about KDE but did not really know what it looked like or even what it really was. We did our best to sell our beautiful products. The Plasma Active powered Wetab tablet made a very good impression. It is interesting how people had a much easier time understanding the concept of activities on the tablet than on traditional desktops.

Unfortunately I didn't take any picture of the booth except for this one, our DIY KDE logo. Yes, it could have used an extra row at the bottom, but we were running out of "K"!

The conference tracks were quite packed as well. I gave a presentation on QML development, which went OK I think. My slides should be online soon. I wanted to attend the MyPaint presentation but it was unfortunately scheduled at the same time as mine. Will look at the video.

Sunday

Sunday was dedicated to workshops. It was a lot less crowded: the workshops mostly attracted people who were already familiar with free software. I attended Zanshin workshop. One of my many side-projects is a todo list system so I have to keep an eye on the competition :) Since I am a nice guy, I put together Ubuntu 11.10 packages for Zanshin 2.0 in my PPA.

After a nice pizza-powered lunch, I spent some time chatting with various people before leaving to Toulouse airport. The event was great, I hope there will be another one next year!

Flattr this

One year after

It has been a bit more than one year since I announced I was reducing my work hours to 4 days per week so that I would get one day to work on what I want to work on. One year after, it is time to look back how it went.

Donations

I was curious to find out how much money could be generated by asking for donations. To this end I wrote monthly wrap-ups, with the idea that people would be more motivated to donate if they knew what would be done. It started out reasonably well, but quickly slowed down. At some point I decided writing those wrap-ups was not worth the pain: They did not generate much, were getting tedious to write and made me look like I was begging for money when there are charities which are much more in need of money than me. I also experimented with Flattr. I like the idea behind Flattr and intend to continue using it.

A book, or not

Another project I worked on for a few months was a Qt book. Writing a book is a challenge I have been willing to take for a long time. A French editor was looking for an author to write a book on Qt, so I contacted them and we started to work together. It did not turn out that well unfortunately and I decided to abort the project for two reasons: the book was supposed to target Qt 5, but the lack of a firm release date made it impossible for me to sign myself for a date when I could hand out a final script. More importantly, the editor wanted the book to be written following a specific organization which did not fit with what I had in mind: after two dozen pages, writing no longer fun, and a book which is not pleasant to write cannot be pleasant to read, in my opinion.

tropmignon.com

In my last year post, I also mentioned a web-based project. The idea is to provide a place for parents to share amusing quotes from their children... I assume if you don't have kids it sounds a bit silly :) On the other hand, if you do have kids (and speak French) you should check out tropmignon.com! I didn't get as much as I expected done on it, because I decided to put tropmignon.com on hold when I started working on the Qt book. Now that the book project has been canceled I resumed working on the site. It still lacks important features like Facebook login or comments, but they will come. This project is also a nice way for me to brush up my dusty web development skills.

KDE

Finally I have been busy working on KDE this year and intend to continue doing so. Until 4.8 is out, I am concentrating on Gwenview: the QGraphicsView port was quite involved and there are quite a few rough areas I want to get polished before it can be considered good enough.

That's it for this year, we'll see how it feels in November 2012!

Flattr this

Crossfade!

I have been a bit quiet lately, but haven't been slacking :) Among other things, I started working on implementing one of the oldest most-wanted feature of Gwenview: crossfade transitions between images. I have pushed the code to Gwenview git repository. It still lives in topic branches for now though.

I first worked with an evolution of the QWidget-based code. This is quite stable but it is too slow at HD (1920x1080) resolution. You can find this work in the "agateau/xfade" branch.

I thus decided to bite the bullet and start a massive refactor of the code, rebasing it on QGraphicsView with an OpenGL viewport. This is the much-faster-but-currently-full-of-regressions "agateau/xfade-qgv" branch.

This change makes Gwenview much smoother to use when going through images but it also helps quite a bit in comparison mode: images move around when selected and unselected, making it easier to track where they go.

I recorded a short video to demonstrate the current state of the xfade-qgv branch. It demonstrates both going through images and image comparison. In this video Gwenview runs on a HD monitor. Note that I had to record it with a digital camera, desktop recorders did not produce a smooth animation, so the video quality is not exactly optimal.

[blip.tv http://blip.tv/play/AYLZlHAC width="550" height="357"] (Watch on blip.tv)

I hope I can bring the xfade-qgv branch back to feature-parity with master so that it can be merged in before Soft Feature Freeze (October 27th!).

Flattr this

Make Skype a bit less shy

I already wrote some time ago about sni-qt, a Qt plugin which turns old-school systemtray icons from Qt applications into shiny StatusNotifierItem | AppIndicators.

Last week I was busy debugging a problem with Skype and sni-qt. To do so I added a runtime config option to enable debugging in sni-qt. It is quite efficient at flooding stderr with debug messages for all applications I have tried it on, except for Skype:

Disappointing! Come on Skype, talk to me!

I don't give up that easily, so I decided to fiddle around with gdb.

I first tried to start Skype from within gdb with the "start" function. This function causes gdb to set a temporary breakpoint on main() and start the program. This is quite handy because when your program reachs the breakpoint the application has not started yet, but most libraries (except dlopen-ed ones) are already loaded, making it easier to set breakpoints. No such luck with Skype: for some reason gdb didn't find the main() function and did not stop Skype at startup. Bummer.

Then I remembered I recently had a discussion about "qt_startup_hook()", a function which is called at the end of the QCoreApplication constructor. This is close enough from startup to me. I set up a breakpoint on "qt_startup_hook" and said gdb to run Skype. That worked! Cool.

Now, I am stopped inside Skype, what to do next? A not so well-known feature of Qt logging system lets you install your own message handler for logging functions with the qInstallMsgHandler(yourHandler) function. Your message handler then receives all the output from qDebug(), qWarn() and friends. To go back to the default message handler, just call "qInstallMsgHandler(0)". Not really confident in it, I tried to call qInstallMsgHandler(0) from within gdb. It turns out that did the trick: after calling resetting the message handler and asking gdb to continue, I got my error messages (plus Skype own debug messages, probably not a major achievement in reverse-engineering, but that was fun :))

Oh and by the way, if you run {,K}Ubuntu Oneiric on a 64bit machine and want to get your Skype icon turned into a StatusNotifierItem, be sure to install the i386 version of sni-qt (you can do so with "sudo apt-get install sni-qt:i386"). Skype still does not provide 64bit binaries :(

Flattr this