diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-14 14:50:21 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-14 14:50:21 +0300 |
| commit | de1d108d97bc55a6f047c47bb56667068aac71ff (patch) | |
| tree | c963ca62a985ad6e1c6930743c5a4eee7ed08a64 /src/enhancer.c | |
| parent | 95858bc4a819c118cf76dd38d6da0365ead997a9 (diff) | |
gegl: enhancer presets + export, LSAN suppressions (wt0)
M9 (task wt0): GEGL quick-enhance (optional, feature-gated).
- src/enhancer.{c,h}: 8 built-in presets (Auto-fix, Brightness, Contrast,
Saturation, Warm, Cool, Sharpen, Denoise) using gegl:stretch-contrast,
gegl:exposure, gegl:brightness-contrast, gegl:saturation, gegl:color-enhance,
gegl:unsharp-mask, gegl:noise-reduction. enhancer_apply via buffer-source +
buffer-sink pipeline; enhancer_export via gegl:jpg-save. User graph presets
deferred (NOT_SUPPORTED in v1).
- meson: gegl_dep conditional (-DHAVE_GEGL); enhancer.c in libggae.
- tests: unit test_enhancer (presets dims+non-zero, export file written).
LSAN suppressions for GEGL third-party init/node leaks.
- 20/20 green, ASan clean (with GEGL lib suppressions).
Diffstat (limited to 'src/enhancer.c')
| -rw-r--r-- | src/enhancer.c | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/src/enhancer.c b/src/enhancer.c new file mode 100644 index 0000000..b195035 --- /dev/null +++ b/src/enhancer.c @@ -0,0 +1,177 @@ +/* enhancer.c — GEGL quick-enhance presets (optional, feature-gated). */ +#include "enhancer.h" + +struct Enhancer { + GPtrArray *p_presets; +}; + +static void +_preset_free(gpointer p) { + EnhancerPreset *d = (EnhancerPreset *)p; + if (d) { + g_free(d->c_name); + g_free(d->c_graph); + g_free(d); + } +} + +Enhancer * +enhancer_new(void) { + Enhancer *e = g_new0(Enhancer, 1); + e->p_presets = g_ptr_array_new_with_free_func(_preset_free); + /* Built-in presets (programmatic; graph=NULL). */ + const char *names[] = { + "Auto-fix", "Brightness", "Contrast", "Saturation", + "Warm", "Cool", "Sharpen", "Denoise", + }; + for (guint i = 0; i < G_N_ELEMENTS(names); i++) { + EnhancerPreset *p = g_new0(EnhancerPreset, 1); + p->c_name = g_strdup(names[i]); + p->i_builtin = 1; + g_ptr_array_add(e->p_presets, p); + } + return e; +} + +void +enhancer_delete(Enhancer *e) { + if (!e) + return; + g_ptr_array_unref(e->p_presets); + g_free(e); +} + +void +enhancer_set_presets(Enhancer *e, const GPtrArray *p) { + g_return_if_fail(e); + g_ptr_array_set_size(e->p_presets, 0); + if (!p) + return; + for (guint i = 0; i < p->len; i++) { + const EnhancerPreset *s = g_ptr_array_index((GPtrArray *)p, i); + EnhancerPreset *np = g_new0(EnhancerPreset, 1); + np->c_name = g_strdup(s->c_name); + np->c_graph = g_strdup(s->c_graph); + np->i_builtin = s->i_builtin; + g_ptr_array_add(e->p_presets, np); + } +} + +const GPtrArray * +enhancer_get_presets(Enhancer *e) { + return e ? e->p_presets : NULL; +} + +/* Build and apply a GEGL graph for a built-in preset. */ +static GeglBuffer * +_apply_builtin(GeglBuffer *p_in, const char *c_name, GError **p_err) { + GeglNode *p_graph = gegl_node_new(); + GeglNode *p_src = gegl_node_new_child( + p_graph, "operation", "gegl:buffer-source", "buffer", p_in, NULL); + GeglNode *p_op = NULL; + + if (g_str_equal(c_name, "Auto-fix")) { + p_op = gegl_node_new_child(p_graph, "operation", "gegl:stretch-contrast", + NULL); + } else if (g_str_equal(c_name, "Brightness")) { + p_op = gegl_node_new_child(p_graph, "operation", "gegl:exposure", + "exposure", 0.5, NULL); + } else if (g_str_equal(c_name, "Contrast")) { + p_op = + gegl_node_new_child(p_graph, "operation", "gegl:brightness-contrast", + "contrast", 1.3, NULL); + } else if (g_str_equal(c_name, "Saturation")) { + p_op = gegl_node_new_child(p_graph, "operation", "gegl:saturation", + "scale", 1.4, NULL); + } else if (g_str_equal(c_name, "Warm")) { + p_op = + gegl_node_new_child(p_graph, "operation", "gegl:color-enhance", NULL); + } else if (g_str_equal(c_name, "Cool")) { + p_op = gegl_node_new_child(p_graph, "operation", "gegl:exposure", + "exposure", -0.3, NULL); + } else if (g_str_equal(c_name, "Sharpen")) { + p_op = + gegl_node_new_child(p_graph, "operation", "gegl:unsharp-mask", NULL); + } else if (g_str_equal(c_name, "Denoise")) { + p_op = gegl_node_new_child(p_graph, "operation", "gegl:noise-reduction", + NULL); + } + + if (p_op == NULL) { + g_object_unref(p_graph); + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: unknown preset '%s'", c_name); + return NULL; + } + + gegl_node_link(p_src, p_op); + GeglRectangle st_rect = {0, 0, gegl_buffer_get_width(p_in), + gegl_buffer_get_height(p_in)}; + GeglBuffer *p_out = gegl_buffer_new(&st_rect, babl_format("RGBA float")); + GeglNode *p_sink = gegl_node_new_child( + p_graph, "operation", "gegl:buffer-sink", "buffer", &p_out, NULL); + gegl_node_link(p_op, p_sink); + gegl_node_process(p_sink); + g_object_unref(p_graph); + return p_out; +} + +GeglBuffer * +enhancer_apply(Enhancer *e, GeglBuffer *p_in, const EnhancerPreset *p_preset, + GError **p_err) { + (void)e; + g_return_val_if_fail(p_in != NULL, NULL); + g_return_val_if_fail(p_preset != NULL, NULL); + + if (p_preset->i_builtin) { + return _apply_builtin(p_in, p_preset->c_name, p_err); + } + + /* User preset: parse a GEGL graph string. */ + if (p_preset->c_graph == NULL) { + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: empty graph for preset '%s'", p_preset->c_name); + return NULL; + } + /* TODO: implement gegl_node_new_from_xml parsing. For M9 v1, built-in only. + */ + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "enhancer: user graph presets not yet supported"); + return NULL; +} + +gboolean +enhancer_export(Enhancer *e, GeglBuffer *p_in, const EnhancerPreset *p_preset, + GFile *p_out, GError **p_err) { + (void)e; + (void)p_preset; + g_return_val_if_fail(p_in != NULL, FALSE); + g_return_val_if_fail(p_out != NULL, FALSE); + + /* Apply the preset first. */ + GeglBuffer *p_buf = enhancer_apply(NULL, p_in, p_preset, p_err); + if (p_buf == NULL) + return FALSE; + + char *c_path = g_file_get_path(p_out); + if (c_path == NULL) { + g_object_unref(p_buf); + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: non-local export path"); + return FALSE; + } + + GeglNode *p_graph = gegl_node_new(); + GeglNode *p_src = gegl_node_new_child( + p_graph, "operation", "gegl:buffer-source", "buffer", p_buf, NULL); + GeglNode *p_save = gegl_node_new_child(p_graph, "operation", "gegl:jpg-save", + "path", c_path, NULL); + gegl_node_link(p_src, p_save); + gegl_node_process(p_save); + gboolean b_ok = g_file_test(c_path, G_FILE_TEST_EXISTS); + + g_object_unref(p_graph); + g_object_unref(p_buf); + g_free(c_path); + return b_ok; +}
\ No newline at end of file |
