Debarshi's den

Image wrangling with GEGL: an introduction

with 6 comments

One of the core dependencies of GNOME Photos, other than GTK+ and Tracker, is a library called GEGL. It is a GObject-based image processing library primarily developed for GIMP. GEGL is used by Photos to load pixels from files, create thumbnails, edit, share and export images.

Unfortunately, even though GEGL is a powerful and generic image processing framework, it can be hard to find documentation and code samples to refer to, and the pool of people who understand it well enough is relatively small. I am going to do a series of blog posts to address this by feeding the search engines. Hopefully this will be useful for new contributors to GIMP and GNOME Photos, and some of it can be folded back into the reference GEGL documentation; or maybe it will encourage adoption in new and interesting places.

Nodes and operations

Processing images with GEGL requires the creation of a graph, represented by a GeglNode. A GeglNode can either have a number of child nodes connected to each other forming a directed acyclic graph, or it can have a GeglOperation. An operation is where the actual image processing takes place. Multiple operations are chained together in a graph to obtain the desired outcome.

This is enough to get started with some basic effects and enhancements. Here is a snippet that takes a path to an input image, enhances the blacks and saves it as a PNG.

  #include <gegl.h>
  …
  g_autoptr (GeglNode) graph = NULL;
  GeglNode *exposure;
  GeglNode *load;
  GeglNode *sink;

  graph = gegl_node_new ();
  load = gegl_node_new_child (graph,
                              "operation", "gegl:load",
                              "path", /* input path as C string */,
                              NULL);
  exposure = gegl_node_new_child (graph,
                                  "operation", "gegl:exposure",
                                  "black-level", 0.03,
                                  NULL);
  sink = gegl_node_new_child (graph,
                              "operation", "gegl:png-save",
                              "bitdepth", 8,
                              "path", /* output path as C string */,
                              NULL);

  gegl_node_link_many (load, exposure, sink, NULL);
  gegl_node_process (sink);

Notice the many similarities with GStreamer.

There is a whole list of such filters to choose from. Such as gegl:cartoon to simulate a cartoon drawn with a black felt pen, gegl:mosaic to transform an image into a mosaic, gegl:saturation to change the colourfulness of the image, or gegl:posterize, which is used by the similarly named tool in GIMP.

example-00

Buffers

Image pixels are held in a GeglBuffer. Most applications would directly interact with a GeglBuffer at one point or the other. For example, to decode an image file and carry the pixels around instead of repeatedly decoding them off the storage. In the above code sample, the buffers were implicitly created by GEGL unbeknownst to us, but we can use a similar graph to load pixels off a file into a GeglBuffer.

  #include <gegl.h>
  …
  g_autoptr (GeglBuffer) buffer = NULL;
  g_autoptr (GeglNode) graph = NULL;
  GeglNode *load;
  GeglNode *sink;

  graph = gegl_node_new ();
  load = gegl_node_new_child (graph,
                              "operation", "gegl:load",
                              "path", /* input path as C string */,
                              NULL);
  sink = gegl_node_new_child (graph,
                              "operation", "gegl:buffer-sink",
                              "buffer", &buffer,
                              NULL);

  gegl_node_link_many (load, sink, NULL);
  gegl_node_process (sink);

A loaded buffer can be then fed into a graph using a gegl:buffer-source.

As the custodian of pixels, GeglBuffer is similar to the role played by GdkPixbuf, but it has some extra features that are handy for image processing.

Most notably, a GeglBuffer is designed to handle massive images that are larger than the amount of physical RAM available on the system. Instead of holding all the pixels in a linear sequence of bytes, it splits them up into small tiles that can be paged out into a file when not in use. However, if necessary, it is possible to optionally dumb down a GeglBuffer by setting it up to use a single array of bytes, or forcing all tiles to be held in RAM.

A GeglBuffer is not restricted to a single pixel format such as RGB with 8 bits per channel. It can transparently handle a horde of formats — monochrome, Lab, HSL, etc. with different degrees of precision per channel. Finally, it is mipmap-capable.

All these features make GeglBuffer a very sophisticated data structure for storing image pixels. However, they aren’t that important for an introduction to GEGL, so we will save them for a future article.

Happy hacking

This is enough to start playing with GEGL. Here is the code used to create the above image, and is proof that knowing just this much is enough to do practically useful things.

Written by Debarshi Ray

20 November, 2017 at 13:30

Posted in C, GEGL, GIMP, GNOME, Photography, Photos

6 Responses

Subscribe to comments with RSS.

  1. Nice introduction! I have a question though: How come gegl_node_new_child takes the operation as a string, and not e.g. an enum or something that would cause a compilation failure if mistyped? It seems very brittle to use strings as identifiers. Does the GEGL library have operation plugins, is that the reason?

    I tried to find docs on if there’s some way to check if an operation is available before going ahead and trying to use it, but couldn’t find any API docs at all. I guess you weren’t kidding when you said docs are sparse 🙂

    Elvis Stansvik

    20 November, 2017 at 21:04

  2. Ah, I found now that you can do gegl_operation_gtype_from_name (operation_type) != 0 to check if an operation with the given name exists. There’s even a gegl_has_operation(..) which does this in gegl-operations.c, but I can’t find it in any headers, so maybe it’s not public API.

    Elvis Stansvik

    20 November, 2017 at 21:15

    • Thanks for the comment.

      Yes, the operation is specified as a string because operations are implemented as extensions. They can be in separate shared objects loaded at run-time or built-in. As for documentation, the best option at the moment is to read gegl.git, gimp.git and gnome-photos.git. Both GIMP and GNOME Photos assert a list of operations at start-up, so you can see how they do that.

      I’ll cover GeglOperation in more detail in a following post.

      Debarshi Ray

      21 November, 2017 at 10:05

  3. Hi. Good tutorial ! Don’t see a rss or newsletter . Why ?

    Cătălin George Feștilă

    22 November, 2017 at 19:57


Leave a comment