How to Test GEGL Operations in GIMP
This article provides a practical guide for developers looking to write unit tests for GEGL (Generic Graphics Library) operations integrated into GIMP. It covers the setup of the testing environment using GLib's testing framework and Meson, constructing graph-based image processing pipelines, feeding test buffers into GEGL nodes, and asserting expected pixel and metadata outputs within both isolated and GIMP-dependent environments.
Understanding the Testing Scope
When testing GEGL operations within GIMP, tests fall into two categories:
- Pure GEGL Unit Tests: Isolated tests verifying mathematical and color transformation logic of custom GEGL nodes without launching GIMP core components.
- GIMP-Integrated Tests: Tests that verify how GIMP handles the operation through its internal wrappers, tool interfaces, or the Procedural Database (PDB).
For pure node logic, write tests using the GEGL API directly. For
integration verification, write tests within GIMP's test directory
(app/tests/).
Setting Up the Test Runner
GIMP and GEGL utilize the standard GLib Testing Framework
(gtest). A minimal test file initializes the GLib test
harness and the GEGL runtime environment.
#include <glib.h>
#include <gegl.h>
static void
test_gegl_operation_basic (void)
{
/* Test logic goes here */
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
gegl_init (&argc, &argv);
g_test_add_func ("/gegl-ops/custom-filter/basic", test_gegl_operation_basic);
int result = g_test_run ();
gegl_exit ();
return result;
}Constructing the Test Pipeline
To test an operation, construct a Directed Acyclic Graph (DAG) consisting of a source node, the operation under test, and a sink node.
- Source Node: Provides synthetic input data (such as a solid color, pattern, or known pixel buffer).
- Operation Node: The specific
gegl:operationbeing tested. - Sink Node: Captures the processed output into a
GeglBufferfor inspection.
static void
test_brightness_contrast_operation (void)
{
GeglNode *graph = gegl_node_new ();
GeglColor *color = gegl_color_new ("rgb(0.5, 0.5, 0.5)");
/* Create Nodes */
GeglNode *source = gegl_node_new_child (graph,
"operation", "gegl:color",
"value", color,
NULL);
GeglNode *filter = gegl_node_new_child (graph,
"operation", "gegl:brightness-contrast",
"brightness", 0.2,
"contrast", 0.0,
NULL);
GeglNode *sink = gegl_node_new_child (graph,
"operation", "gegl:buffer-sink",
NULL);
/* Connect Nodes */
gegl_node_link_many (source, filter, sink, NULL);
/* Process Graph */
GeglBuffer *output_buffer = NULL;
gegl_node_process (sink);
gegl_node_get (sink, "buffer", &output_buffer, NULL);
g_assert_nonnull (output_buffer);
/* Validate output here */
g_object_unref (output_buffer);
g_object_unref (color);
g_object_unref (graph);
}Validating Buffer Outputs
To ensure the GEGL operation functions correctly, inspect the pixel
values stored in the resulting GeglBuffer. Read the buffer
back using a defined format (e.g., Babl format
"RGBA float") to compare against expected values.
static void
assert_pixel_values (GeglBuffer *buffer, gfloat expected_r, gfloat expected_g, gfloat expected_b)
{
GeglRectangle rect = { 0, 0, 1, 1 };
gfloat pixel[4];
gegl_buffer_get (buffer, &rect, 1.0, babl_format ("RGBA float"),
pixel, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
/* Use a delta for floating-point comparisons */
const gfloat epsilon = 0.001f;
g_assert_cmpfloat_with_epsilon (pixel[0], expected_r, epsilon);
g_assert_cmpfloat_with_epsilon (pixel[1], expected_g, epsilon);
g_assert_cmpfloat_with_epsilon (pixel[2], expected_b, epsilon);
}Testing GIMP-Specific Integrations
When an operation is wired directly into GIMP (e.g., using
GimpGeglProcedure or GIMP tools), initialize GIMP's
subsystem within the test:
- Use
gimp_init_test ()or link againstlibgimpbaseandlibgimpcore. - Instantiate a
GimpImageandGimpLayer. - Apply the operation via GIMP's internal API
(
gimp_drawable_apply_operation ()or corresponding procedural functions). - Assert that the underlying projection updates properly and undo/redo stacks maintain integrity.
/* Example: Verifying an operation applied to a GimpDrawable */
GimpDrawable *drawable = GIMP_DRAWABLE (layer);
gimp_gegl_apply_operation (drawable, NULL, "Processing", filter_node);
gimp_drawable_update (drawable, 0, 0, width, height);
/* Verify layer buffer changes match expected result */
GeglBuffer *layer_buffer = gimp_drawable_get_buffer (drawable);
assert_pixel_values (layer_buffer, 0.7f, 0.7f, 0.7f);Configuring the Build System (Meson)
Register the test in the relevant meson.build file to
integrate it with continuous integration and local testing
workflows:
test_gegl_operation = executable(
'test-gegl-operation',
'test-gegl-operation.c',
dependencies: [gegl_dep, glib_dep, babl_dep],
include_directories: [include_directories('.')],
)
test('gegl-operation-test', test_gegl_operation)
Run the compiled test suite from the build directory using Meson:
meson test -C build --verbose gegl-operation-test