Haskell, GTK4 and pictures on the fly

As part of my further experiments with GTK4 and Haskell, I wanted to vary gtk-picture by creating the Picture programatically. I named the alternative gtk-dynamic-picture.

Diagrams

I created the picture using the Diagrams project. I looked at various backends that support that project:

  • the diagrams-gtk package, based on the Cairo C library, renders to values provided by the gtk Haskell package, not those provided by the gi-gtk Haskell package;
  • the diagrams-cairo package, also based on Cairo, can render to a buffer in memory with the pixel format known as CAIRO_FORMAT_ARGB32. That format has alpha in the upper byte, then red, then green, then blue. The pixels are, however, stored native-endian; that is, on little endian x86_64 machine architecture, the order of the bytes is B G R A. Also, pre-multiplied alpha is used. (For example, 50% transparent red is 0x80800000, not 0x80ff0000.); and
  • the diagrams-rasterific package, based on the Rasterific Haskell package, renders to values of type Image PixelRGBA8 (provided by the JuicyPixels package). Pixels are stored in the order R G B A.

Ultimately, given the pixel format of a Pixbuf value (R G B A), I used diagrams-rasterific as the backend.

The first step was to yield an Image PixelRGBA8 from a Diagram B. That was straightforward, given the renderDia function:

Picture

The gi-gtk package provides:

and the gi-gdk package provides Texture, which satisfies the IsPaintable constraint, and:

So, the final step was to yield a Picture value from a Texture value and, in turn, a Texture value from a Pixbuf:

imagePixelRGBA8ToPixbuf

The missing link was a function that would yield a Pixbuf value given an Image PixelRGBA8 value. I bridged that link with:

imageData image has type Data.Vector.Storable.Vector Word8. A buffer is allocated (mallocBytes n) and the bytes copied into it (copyBytes pixbufPtr ptr n). free is specified as the destroy function.

Mutable application state

Following an example presented by Mark Karpov and Jorge Galarze, I realised I would need a mutable state of the application. I used an IORef, as follows:

onMouseClick

The onMouseClick action needed to update the application state and the paintable attribute of the Picture value. In that regard, I had a couple of false starts.

First, I thought I needed to use getEventControllerWidget. The auto-generated Haddock documentation for package gi-gtk-4.0.9 has:

However, the Haddock documentation for module Data.GI.Base.Attributes has:

Whenever the attribute is represented as a pointer in the C side, it is often the case that the underlying C representation admits or returns NULL as a valid value for the property. In these cases the get operation may return Maybe value, with Nothing representing the NULL pointer value (notable exceptions are GList and GSList, for which NULL is represented simply as the empty list). This can be overridden in the introspection data, since sometimes attributes are non-nullable, even if the type would allow for NULL.

and the GTK4 documentation for gtk_event_controller_get_widget states that the return value can be NULL.

I found on one machine that get gestureClick #widget was an action yielding a Maybe Widget and on another machine, with the same dependencies on Haskell packages, that get gestureClick #widget was an action yielding a Widget. I concluded that something had been corrupted on the former machine. I solved the problem by starting from scratch on that machine: I deleted the Stack-supplied MSYS2 and the snapshot directory in the Stack root. I had Stack re-install the Stack-supplied MSYS2, updated it, and installed the necessary MSYS2 packages. I then re-built the Haskell package dependencies that depended on the C libraries provided by those MSYS2 packages.

Then, I realised that I could could sequence actions so that the Picture value had been specified before the onMouseClick action needed to refer to it:

gtk-dynamic-picture.exe

The resulting executable behaved as expected: