Visar inlägg med etikett GTKIMAGEVIEW. Visa alla inlägg
Visar inlägg med etikett GTKIMAGEVIEW. Visa alla inlägg

New release of GtkImageView coming

It will happen if I can keep the focus on the essentials for a day or so... Hopefully it will be the last and then someone who is more interested in the project than me can take over ownership of it. That's why I'm trying to move it to GNOME's hosting instead of my own.

GtkImageView 1.6.1

I probably should go to sleep but I have to blog about it. I just released GtkImageView 1.6.1 and PyGtkImageView 1.1.0. It almost was a perfect release except that Epydoc failed to generate a PDF for the Python documentation. I hope Jeffrey is about ready to release the Perl bindings and then hopefully Andreas will also make some nice gtkmm bindings.

All in all, GtkImageView is quite mature now. Time for me to think about the next step... I'd like to replace the GdkPixbuf backend with Cairo. Then I'd make a totally kickass painting application out of it.

Good job Björn! Now I'm done.

Interfaces uglier than C

I'm not to happy with what PyGTK does with my interfaces. In C, an implementation of GtkIImageTool looks like this. In Python, it looks like this:

import gobject import gtkimageview class MyTool(gobject.GObject, gtkimageview.IImageTool): def __init__(self): gobject.GObject.__init__(self) self.crosshair = gtk.gdk.Cursor(gtk.gdk.CROSSHAIR) self.cache = gtkimageview.PixbufDrawCache() def do_button_press(self, ev_button): pass def do_button_release(self, ev_button): pass def do_motion_notify(self, ev_motion): pass def do_pixbuf_changed(self, reset_fit, rect): print 'pixbuf changed' def do_paint_image(self, draw_opts, drawable): self.cache.draw(draw_opts, drawable) def do_cursor_at_point(self, x, y): return self.crosshair gobject.type_register(MyTool)

There are quite a few ugly warts here.

class MyTool(gobject.GObject, gtkimageview.IImageTool):

You have to subclass gobject.GObject, which I really would rather not. The whole point of writing programs in Python is to get rid of the stupid, annoying and complicated GObject system. Same for gtkimageview.IImageTool -- this is Python and you have duck-typing. You shouldn't need to subclass anything.

def do_cursor_at_point(self, x, y):

And why does PyGTK decide that all my interface methods should be prefixed with do_? I didn't tell it to do that. It is an extremely unpythonic convention.

gobject.type_register(MyTool)

Yet another GObject-ism. It would be plain simple to get rid of this step by making gobject.GObject a metaclass that registers the class when it is constructed. But no, PyGTK wants to remind you that you really are dealing with a C API and nothing more.

gobject.GObject.__init__(self)

As before, initialization like this shouldn't be needed for a plain Python class.

return self.crosshair

Don't try to return None, PyGTK:s code generator doesn't understand that an interface function can either return an object pointer or NULL.

But worst of all is probably that if you forget to do any of this, then IT WILL CRASH. Don't expect a nice exception, at best you'll get a cryptic GTK warning and a segfault with debug symbols.

PyGTK:s handling of GInterfaces is, as shown, currently very weak. But I expect it to be improved upon in the future. Meanwhile I'm thinking that PyGTK:s wrapping system with definition and override files isn't that good to begin with. It is fairly easy to whip something up fast, but when you want to make your binding pythonic, you run into problems.

I wonder if it wouldn't be possible to use ctypes to create more pythonic bindings with.

Efficient scrolling in X11

Lately, I've been trying to make scrolling as efficient as possible. On the surface, it is a simple problem, the algorithm is like this:

def scroll(surface, xscroll, yscroll): # Scroll it. Auto clip in case xscroll or yscroll is negative. surface.draw(surface, xscroll, yscroll) top = (0, 0, surface.width, yscroll) left = (0, 0, xscroll, surface.height) bottom = (0, surface.height + yscroll, surface.width, -yscroll) right = (surface.width + xscroll, 0, -xscroll, surface.height) # Filter out the ones to redraw. rects = [rect for rect in top, left, bottom, right if rect[2] * rect[3] >= 0] for x, y, width, height in rects: # Assume redraw is the expensive step. redraw(surface, x, y, width, height)

So, if you scroll a surface of size 100x100 20 steps to the right and 20 steps down the rectangles become:

xscroll = 20
yscroll = 20

top = (0, 0, 100, 20)
left = (0, 0, 20, 100)
bottom = (0, 120, 100, -20)
right = (120, 0, -20, 100)

bottom and right whose area is negative becomes discarded and you are left with two rectangles totalling 4000 pixels (100 * 20 + 100 * 20) to redraw which is not so bad.

The problem is that this algorithm is freakishly hard to realize on X11 without theComposite extension. X11 doesn't store your surface, so how are you going to redraw it? You could store it yourself, in a GdkPixbuf for example. However, that would mean that the data is stored client side and you'll run into slowdowns when transferring it to the X11 server.

... Client-server is great for some things, but definitely not for computer graphics.

GdkPixbufs are drawn with gdk_draw_pixbuf() which should (or could) be a fast operation because it uses XShmPutImage(), but it is not. GdkPixbuf has a different format from the one that X11 want's so you run into a slowdown caused by the format mismatch.

Next attempt, use a GdkPixmap as the cache. surface.draw(surface, xscroll, yscroll) becomes one fast call to gdk_draw_drawable(). But it still isn't fast enough because pixmaps aren't easy to work with. There is no gdk_pixbuf_scale() equivalent for drawables so we still have to use a pixbuf to store the result of the scale operation. That pixbuf then has to be blitted to our pixmap cache which is then blitted, for real, to the XWindow we are drawing on. In effect, we are using triple buffering and redraws becomes to slow.

Third attempt, use the actual drawable we are drawing on as the cache. It would work except that it totally breaks down when the window is minimized, then your cache is gone.

GtkImageView uses a combination of the first and third method. It special cases scrolling operations and uses the third method to redraw them. By setting exposures to true, it detects when occluded areas becomes visible and then redraws them using the first method. That is far from ideal but is more or less the best thing you can do without composite.

Tools for GtkImageView

I have decided that I want to extend the GtkImageView widget to make it extensible. Right now, it handles showing images that you can zoom in on and drag around. It will be extended so that you can do the following things:

  • Drag a selection on the widget. Very similar to how gThumbs Image->Crop dialog works.
  • Various kinds of drawing operations.

I thought about implementing this as "tools":


+------------+
|            | 1     1 +-------------+
|GtkImageView| ------> |GtkIImageTool|
|            |         +-------------+
+------------+              |_____________________
                           /                      |
                 +-------------------+ +--------------------+
                 |GtkImageToolDragger| |GtkImageToolSelector|
                 +-------------------+ +--------------------+

In this diagram, GtkImageView has a reference to a GtkIImageTool which is an interface that abstracts out certain behaviour of GtkImageView. GtkImageToolDragger and GtkImageToolSelector are two concrete implementations of the GtkIImageTool interface. When GtkImageView references a GtkImageToolDragger, it behaves like normal. You have a hand cursor and can drag the image. When GtkImageView references a GtkImageToolSelector, it instead displays a selection cursor and you can make a rectangular selection on the image.

Using this arrangement, it is now possible to dynamically alter the behaviour of GtkImageView.

GtkImageView *view = GTK_IMAGE_VIEW (gtk_image_view_new ()); GtkImageToolSelector *selector = gtk_image_tool_selector_new (); gtk_image_view_set_tool (view, selector); /* The user can now make some selections on the image. We can query which area of the image that is selected. */ GdkRectangle rect; if (!gtk_image_tool_selector_get_selection (selector, &rect)) printf ("Nothing is selected!\n"); else printf ("You selected (%d, %d)-(%d, %d)\n", rect.x, rect.y, rect.width, rect.height);

It should be possible to achieve this, but the interface that GtkIImageTool will specify, might become to fat.

/* These are needed because the tool needs to be able to decide what happens when mouse events occur. */ gtk_iimage_tool_button_press () gtk_iimage_tool_button_release () gtk_iimage_tool_motion_notify () /* The tool needs to decide what the default cursor is. */ gtk_iimage_tool_get_default_cursor () /* The tool may need to do something when the pixbuf of the view is changed. */ gtk_iimage_tool_set_pixbuf () /* The tool will want to draw the image in a special way. */ gtk_iimage_tool_draw_pixbuf_data () /* The tool will want to tell GtkImageNav how the image data should be drawn as a thumbnail. */ gtk_iimage_tool_get_display_pixbuf ()

Tired of DocBook

Spent some time trying to find out how to write definition lists in DocBook. DocBook is the markup language used by gtk-doc and consequently the tool I am using for writing documentation for GtkImageView.

... And I am very, VERY sick of it. It turns out it was fairly simple (heh), all you have to do to create a list of directories and paragraphs is this:

<variablelist><title>Font Filename Extensions</title> <varlistentry><term><filename>TTF</filename></term> <listitem> <para> TrueType fonts. </para> </listitem> </varlistentry> <varlistentry><term><filename>PFA</filename></term> <term><filename>PFB</filename></term> <listitem> <para> PostScript fonts. <filename>PFA</filename> files are common on <acronym>UNIX</acronym> systems, <filename>PFB</filename> files are more common on Windows systems. </para> </listitem> </varlistentry> </variablelist>

The result is almost exactly what you could have accomplised using this HTML:

<dl> <lh>Font Filename Extension</lh> <dt><tt>TTF</tt></dt> <dd>TrueType fonts.</dd> <dt><tt>PFA, PFB</tt></dt> <dd>PostScript fonts. <tt>PFA</tt> files are common on UNIX systems, <tt>PFB</tt> files are more common on Windows systems.</dd> </dl>

And of course, even less markup is needed if you use reStructuredText.

My search for a decent documentation writing system continues.

It is official!

It is official! GtkImageView is now released!

Here is the whole release announcement reprinted in full, just for fun:

I'm pleased to finally announce GtkImageView 1.0.0:

Description

GtkImageView is a simple image viewer widget for GTK. Similar to the image viewer panes in gThumb or Eye of Gnome. It makes writing image viewing and editing applications easy. Among its features are:

  • Mouse and keyboard zooming.
  • Scrolling and dragging.
  • Adjustable interpolation.
  • Fullscreen mode.

Download

Check it out from Subversion:

svn co http://publicsvn.bjourne.webfactional.com/gtkimageview

Or download the latest release tarball:

http://trac.bjourne.webfactional.com/attachment/wiki/WikiStart/gtkimageview-1.0.0.tar.gz

API documentation can be found by browsing to the ./docs/reference/html/index.html file.

Project website: http://trac.bjourne.webfactiona.com

Examples

Here is the canonical example for using the widget:

#include <gtkimageview/gtkimagescrollwin.h>
#include <gtkimageview/gtkimageview.h>
...
GtkWidget *view = gtk_image_view_new ();
GtkWidget *scroll = gtk_image_scroll_win_new (GTK_IMAGE_VIEW (view));

/* Where "box" is a GtkBox already part of your layout. */
gtk_box_pack_start (GTK_BOX (box), scroll, TRUE, TRUE, 0);

GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file ("someimage.png", NULL);
gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (view), pixbuf);

Future

  • Python bindings.
  • Perl bindings.
  • Gtk# bindings.

The three classes in GtkImageView

The whole API for GtkImageView is not completed yet, but it is slowly getting there. Much of the design is borrowed (or almost stolen) from gThumb. Here is a birds eye view of the three main classes along with their counterparts in gThumb:

GtkImageView (gtkimageview.c, gtkimageview.h)

This is the main class of the package. It provides a draggable and zoomable pane on which images can be displayed. The user of the class can customize how images are displayed and whether high or low quality scaling should be used. It also implements a number of useful keybindings for manipulating the image view.

Much code and ideas was borrowed from the ImageViewer widget in gThumb in the files /libgthumb/image-viewer.{c,h}.

GtkImageScrollWin (gtkimagescrollwin.c, gtkimagescrollwin.h)

This class implements a kind of GtkScrolledWindow which is more suitable to use in conjuction with GtkImageView. A GtkImageView embedded inside a GtkImageScrollWin will have auto-hiding horizontal and vertical scrollbars. Additionally, in the bottom right corner it has a button which brings up a GtkImageNavigor for the image.

This class corresponds to the GthNavWindow class in /libgthumb/gth-nav-window.{c,h}.

GtkImageNavigator (gtkimagenavigator.c, gtkimagenavigator.h)

The GtkImageNavigator provides a small popup window showing a preview of the GdkPixbuf it references. It contains a rectangle which the user can drag which causes the GtkImageNavigator to emit a certain signal, signalling to possibly a GtkImageView that it should change its scroll.

It is fairly similar to the NavWindow class in /libgthumb/nav-window.{c,h}.

And here follows a small example showing how the library is supposed to be used:

#include <gtkimageview/gtkimageview.h> ... GtkWidget *image_view; GtkWidget *scroll_win; GdkPixbuf *pixbuf; image_view = gtk_image_view_new (); scroll_win = gtk_image_scroll_win_new (GTK_IMAGE_VIEW (image_view)); gtk_box_pack_start (GTK_BOX (box), scroll_win, TRUE, TRUE, 0); ... pixbuf = gdk_pixbuf_new_from_file ("animage.png", NULL); gtk_image_view_set_pixbuf (image_view, pixbuf);

GtkImageView is now online

The code for GtkImageView is now publicly released. You can check it out using Subversion:

svn co http://publicsvn.bjourne.webfactional.com/gtkimageview gtkimageview

You can also browse to the project site at trac.bjourne.webfactional.com where you can view the code online. It is still very basic.

The GtkImageView widget

The project I have been working on lately is to create a general purpose image viewer widget for GTK . For my personal project, which will be a commercial product to be sold, I needed such a widget.

After searching around a bit, I concluded that there was no such widget [ 1 ] .

So, what I have created is such a general purpouse widget named GtkImageView. I think that there is a great use for such a widget in GNOME and that it will be just as useful as the GtkSourceView widget has been.

There are quite a few programs in GNOME that display an image inside a pane which you can zoom and drag around. Atleast both gThumb , Eye of GNOME and GIMP has such widgets. But the image display widget in these three programs is implemented independently and are incompatible to each other. Each behave slightly differently from the other. Different keybindings, different zoom factor levels and different graphic adornments.

This is where GtkImageView comes into the picture. It is an attempt to unify these three different widget implementations into one that they all can use. The gains are obvious:

less code duplication
Less code for each project to maintain means less bugs.
Uniform UI
The same set of keybindings in all programs means that a user does not have to relearn each program.
Usable by third parties
If you today want to create an application that has a widget that can pane and zoom on an image you have to create it from scratch. You would probably look at, for example, gThumb's source and then cut and paste that. Then you would have to spend some time integrating their widget in your own programs. Time that would be better spent doing something else.

Right now, GtkImageView is almost fully coded. What remains is to upload it to bjourne.webfactional.com and make it world-readable. The following tasks are not yet done:

  • API documentation needs to be written.
  • There needs to be example programs.
  • Patches must be written for EOG and gThumb.
  • Must announce intention to the GNOME community and get buy-in from EOG and gThumb's maintainers.
  • Need to upload code and publish documentation online.
[ 1 ] Which was wrong, as I found out later.

Bloggarkiv