20080923

Again with the N800 :-)

Well, I'm at it again. I wanted to read mangas on the N800, but the only working application, Comix (a Python application) was somewhat slow. Not the Python code itself, which was actually quite fast, but rather the overall approach. You see, to speed up processing, Comix would always unzip the whole comic archive file (cbz or cbr) in a temporary directory and read from there. It does make page flipping faster.

It also fills the N800's main drive as fast as you can say, "out of disk space" :-)

The approach is acceptable on the desktop, where at worse, we're talking about 50 MB of temp disk space for really large archives. Not an issue, even on my relatively small (by today's standards) 80 GB drive.

On the tablet, though, not only is it problematic (you get 256 MB of drive space and that's it!), but even writing to external memory cards is really, really slow. Expect 2-5 minutes before you see the first page, and meanwhile Comix is hung. And woe to you if it crashes–you'll have to recover all this space at the command line.

The crash thing didn't bother me too much, but the slow boot time did. I read fast, especially manga which tend to have relatively little dialog per image. So I started looking for an alternate solution that ran on GTK+ and that I could potentially port.

After swimming through numerous Java applications (useless on the tablet), there were two candidates: cbrPager and Comical.

Choosing the One

Comical initially looked more interesting. It embeds libunzip, unrar (through a special exception to the GPL in the license) and did all image decoding in memory, which I thought could be faster. cbrPager, in contrast, had a similar approach to Comix (unzip to a temporary directory), except that it unzipped only one image at a time. I worried that this would be slow. Also, cbrPager used the Gnome libraries, some of which are not available on Maemo, and I didn't really expect to have to rewrite parts of the UI layer. But unfortunately, Comical is written on top of wxGTK, which is a bit problematic on Maemo (there's a port, but it's hard to get from a repository). So I decided to take a look at cbrPager and do an initial "straight port" using the Gnome canvas and disabling parts of the interface, just to see whether it was fast enough to be a workable base and not force me to deal with wxGTK.

It was quite fast, thank you very much.

I immediately knew I had a winner.

Modifications: Front end

I Hildonized the toolbar and main window, which was relatively quick. Unfortunately, one of the features that's really useful for tablet-based comic book readers is rotation. Comix had it, together with very high quality antialiasing. A small modification to the cbrPager source gave me antialiasing and rotation, but not both at the same time; rotated images looked really, really bad.

Since I wasn't too happy with using the Gnome canvas anyways, and that it was the last Gnome dependency left (I had had to remove all others), I decided to try to find a nice way to replace it. Initially, I was quite ambitious, thinking I'd do raw updates to the main widget. Looking over in the gdk-pixbuf library, it looked possible, as that library had all the bells and whistles I needed: antialiasing with selectable algorithm, automatic rotation, image scaling, etc.

But then I started to think about how to embed the image render in the main view, and my head started hurting, thinking that I'd have to write a GTK+ widget from scratch (it's not hard, exactly, as I've learned later, but I had never done something like that before...). In the end, I decided to look for a plain image viewer (initially I though gqview, since it has the features I'm looking for) and see if I could scavenge their widget.

In the end, I found GtkImageView, which had a ready-made widget and was only lacking rotation. I added rotation and was a happy camper.

Well, almost :-) I had introduced a memory leak that ate all the RAM of the tablet really, really quickly. Fixing it was really simple, but that didn't happen before I crashed my tablet several times in an effort to figure out what exactly was going on...

From the front-end point of view, I then added a few tidbits: scrolling through direct pen usage, auto-rotate, bookmarks (my wife asked for that one really fast...), persistent zoom settings, fullscreen... All of those were pretty easy to add, which speaks volumes about the initial cbrPager code's organisation (hint: it's pretty nice).

Modifications: back end

I then got annoyed that users were forced to install unzip. One nice feature of Comical is that it embeds the unzip code. So I wanted to do the same. For RAR files, I chose not to do it right away because of licensing problems.

To do that, I needed to extract the archive reading logic. It was embedded in command line use. So I chose to encapsulate it in a gobject. This was probably a bit overkill, but it made sense with respect to the use of the GtkImageView gobject.

I first moved the code of the fork/exec logic into a gobject and got it to work that way. Once that was done, it was really easy to get it to work with direct calls to the unzip library.

I had wanted to extract the image bits in memory and decode completely in memory, thus removing the need for a temporary file. I didn't do it that way right then because there were reports of really slow decoding when doing that through pixbuf loaders. Since image decoding was actually slower than archive reading, I decided to pass for now.

Writing a gobject was kinda cool, but those things are really quite heavy... there's a lot of macros and presumably some overhead. Since the same 3 objects are reused throughout the application (one object per supported archive type), the overhead is negligible, but if I had to do it again I'd just go with a struct and function pointers, although it feels kind of silly to do that given that there's already gobjects in the application.

The result

Well, just go download it at https://garage.maemo.org/frs/?group_id=777&release_id=2075.

You can see the announcement on the Internet Tablet Talk forum. There's screenshots.

Just beware, you need a memory card in the internal slot for this to work... I'll work on a fix tomorrow.

Credits, thanks, etc.

Many thanks to the original author of cbrPager for writing the initial code, and for putting up with me :-)

And thanks to the ITT community, who will be my beta testers, as usual ;-)