diff options
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 |
