diff options
author | Oyvind Kolas <pippin@gimp.org> | 2015-07-26 13:58:43 +0200 |
---|---|---|
committer | Oyvind Kolas <pippin@gimp.org> | 2015-07-26 21:30:25 +0200 |
commit | 4bda0124f8387ce493e1ff6752f562b048cfaff0 (patch) | |
tree | 3aabe94499fb141d14fed26fb40ad4218fda92b3 | |
parent | 7381f3177dc916bb13f745ea41502ff554a2710e (diff) |
build: add a microraptor gui based ui to gegl binary
If the new dependencies gexiv2 and microraptor gui (mrg)
http://github.com/hodefoting/mrg/
are found, the start of an image viewer/editor based on microraptor gui and GEGL
has taken over the interactive/display run mode of the installed gegl binary.
-rw-r--r-- | bin/Makefile.am | 11 | ||||
-rw-r--r-- | bin/gegl.c | 8 | ||||
-rw-r--r-- | bin/mrg-ui.c | 1230 | ||||
-rw-r--r-- | configure.ac | 42 |
4 files changed, 1288 insertions, 3 deletions
diff --git a/bin/Makefile.am b/bin/Makefile.am index 24e77b41..91fc956a 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -22,12 +22,13 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/gegl/property-types AM_CFLAGS = \ - $(DEP_CFLAGS) $(BABL_CFLAGS) $(PNG_CFLAGS) - + $(DEP_CFLAGS) $(BABL_CFLAGS) $(PNG_CFLAGS) \ + $(MRG_CFLAGS) $(GEXIV2_CFLAGS) AM_LDFLAGS = \ $(no_undefined) ../gegl/libgegl-$(GEGL_API_VERSION).la \ - $(DEP_LIBS) $(BABL_LIBS) $(PNG_LIBS) $(LIBSPIRO) $(MATH_LIB) + $(DEP_LIBS) $(BABL_LIBS) $(PNG_LIBS) $(LIBSPIRO) $(MATH_LIB) \ + $(MRG_LIBS) $(GEXIV2_LIBS) bin_PROGRAMS = gegl gegl-tester @@ -41,6 +42,10 @@ gegl_SOURCES = \ gegl_tester_SOURCES = \ gegl-tester.c +if HAVE_MRG +gegl_SOURCES += mrg-ui.c +endif + if HAVE_SPIRO gegl_SOURCES += gegl-path-spiro.h gegl-path-spiro.c endif @@ -76,6 +76,8 @@ static gboolean file_is_gegl_xml (const gchar *path) return FALSE; } +int mrg_ui_main (int argc, char **argv); + gint main (gint argc, gchar **argv) @@ -171,6 +173,12 @@ main (gint argc, script = g_strdup (DEFAULT_COMPOSITION); } } + + if (o->mode == GEGL_RUN_MODE_DISPLAY) + { + mrg_ui_main (argc, argv); + return 0; + } gegl = gegl_node_new_from_xml (script, path_root); diff --git a/bin/mrg-ui.c b/bin/mrg-ui.c new file mode 100644 index 00000000..3efbf1c4 --- /dev/null +++ b/bin/mrg-ui.c @@ -0,0 +1,1230 @@ +/* This file is part of GEGL editor -- an mrg frontend for GEGL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2015 Øyvind Kolås + */ + +#define _BSD_SOURCE +#define _DEFAULT_SOURCE + +#include "config.h" + +#if HAVE_MRG + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <dirent.h> +#include <mrg.h> +#include <gegl.h> +#include <gexiv2/gexiv2.h> +#include <gegl-paramspecs.h> + +typedef struct ActionData { + const char *label; + float priority; + const char *op_name; +} ActionData; + +ActionData actions[]={ + {"rotate", 10, "gegl:rotate"}, + {"crop", 20, "gegl:crop"}, + {"color temperature", 50, "gegl:color-temperature"}, + {"exposure", 60, "gegl:exposure"}, + {"levels", 60, "gegl:levels"}, + {"threshold", 70, "gegl:threshold"}, + {NULL, 0, NULL}, /* sentinel */ +}; + +static char *suffix = "-giev"; + + +#define USE_MIPMAPS 1 +#define DEBUG_OP_LIST 1 + +typedef struct _State State; +struct _State { + void (*ui) (Mrg *mrg, void *state); + Mrg *mrg; + + char *path; + char *giev_path; + GeglBuffer *buffer; + GeglNode *gegl; + GeglNode *sink; + GeglNode *source; + GeglNode *load; + GeglNode *save; + GeglNode *active; + int rev; + float u, v; + float scale; + int show_actions; + int show_controls; + + float render_quality; + float preview_quality; +}; + +void gegl_meta_set (const char *path, const char *meta_data); +char * gegl_meta_get (const char *path); + +static char *suffix_path (const char *path) +{ + char *ret, *last_dot; + + if (!path) + return NULL; + ret = malloc (strlen (path) + strlen (suffix) + 3); + strcpy (ret, path); + last_dot = strrchr (ret, '.'); + if (last_dot) + { + char *extension = strdup (last_dot + 1); + sprintf (last_dot, "%s.%s", suffix, extension); + free (extension); + } + else + { + sprintf (ret, "%s%s", path, suffix); + } + return ret; +} + +static void contrasty_stroke (cairo_t *cr) +{ + double x0 = 6.0, y0 = 6.0; + double x1 = 4.0, y1 = 4.0; + + cairo_device_to_user_distance (cr, &x0, &y0); + cairo_device_to_user_distance (cr, &x1, &y1); + cairo_set_source_rgba (cr, 0,0,0,0.5); + cairo_set_line_width (cr, y0); + cairo_stroke_preserve (cr); + cairo_set_source_rgba (cr, 1,1,1,0.5); + cairo_set_line_width (cr, y1); + cairo_stroke (cr); +} + +static char *unsuffix_path (const char *path) +{ + char *ret = NULL, *last_dot, *extension; + char *suf; + + if (!path) + return NULL; + ret = malloc (strlen (path) + 4); + strcpy (ret, path); + last_dot = strrchr (ret, '.'); + extension = strdup (last_dot + 1); + + suf = strstr(ret, suffix); + sprintf (suf, ".%s", extension); + free (extension); + return ret; +} + +static int is_giev_path (const char *path) +{ + if (strstr (path, suffix)) return 1; + return 0; +} + +static unsigned char *copy_buf = NULL; +static int copy_buf_len = 0; + +static void mrg_gegl_blit (Mrg *mrg, + float x0, float y0, + float width, float height, + GeglNode *node, + float u, float v, + float scale, + float preview_multiplier) +{ + float fake_factor = preview_multiplier; + GeglRectangle bounds; + + cairo_t *cr = mrg_cr (mrg); + cairo_surface_t *surface = NULL; + + if (!node) + return; + + bounds = gegl_node_get_bounding_box (node); + + if (width == -1 && height == -1) + { + width = bounds.width; + height = bounds.height; + } + + if (width == -1) + width = bounds.width * height / bounds.height; + if (height == -1) + height = bounds.height * width / bounds.width; + + width /= fake_factor; + height /= fake_factor; + u /= fake_factor; + v /= fake_factor; + + if (copy_buf_len < width * height * 4) + { + if (copy_buf) + free (copy_buf); + copy_buf_len = width * height * 4; + copy_buf = malloc (copy_buf_len); + } + { + unsigned char *buf = copy_buf; + GeglRectangle roi = {u, v, width, height}; + static const Babl *fmt = NULL; + if (!fmt) fmt = babl_format ("cairo-ARGB32"); + gegl_node_blit (node, scale / fake_factor, &roi, fmt, buf, width * 4, + GEGL_BLIT_DEFAULT); + surface = cairo_image_surface_create_for_data (buf, CAIRO_FORMAT_ARGB32, width, height, width * 4); + } + + cairo_save (cr); + cairo_surface_set_device_scale (surface, 1.0/fake_factor, 1.0/fake_factor); + + width *= fake_factor; + height *= fake_factor; + u *= fake_factor; + v *= fake_factor; + + cairo_rectangle (cr, x0, y0, width, height); + + cairo_clip (cr); + cairo_translate (cr, x0 * fake_factor, y0 * fake_factor); + cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_NEAREST); + cairo_set_source_surface (cr, surface, 0, 0); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + cairo_surface_destroy (surface); + cairo_restore (cr); +} + +static void load_path (State *o); + +static void go_next (State *o) +{ + char *lastslash = strrchr (o->path, '/'); + if (lastslash) + { + struct dirent **namelist; + int n; + + if (lastslash == o->path) + lastslash[1] = '\0'; + else + lastslash[0] = '\0'; + + n = scandir (o->path, &namelist, NULL, alphasort); + if (n) + { + int i; + int done = 0; + for (i = 0; i < n; i ++) + { + if (!done && !strcmp (namelist[i]->d_name, lastslash+1) && i + 1 < n) + { + char *tmp = malloc (strlen (o->path) + 2 + strlen (namelist[i+1]->d_name)); + sprintf (tmp, "%s/%s", o->path, namelist[i+1]->d_name); + free (o->path); + o->path = tmp; + load_path (o); + done = 1; + } + free (namelist[i]); + } + free (namelist); + + if (!done) + lastslash[0] = '/'; + } + mrg_queue_draw (o->mrg, NULL); + } +} + +static void go_prev (State *o) +{ + char *lastslash; + if (access (o->giev_path, F_OK) != -1) + { + /* we need to skip from the -giev one, when walking the dir alphabetically backwards */ + char *tmp = o->path; + o->path = o->giev_path; + o->giev_path = tmp; + } + lastslash = strrchr (o->path, '/'); + if (lastslash) + { + struct dirent **namelist; + int n; + + if (lastslash == o->path) + lastslash[1] = '\0'; + else + lastslash[0] = '\0'; + + n = scandir (o->path, &namelist, NULL, alphasort); + if (n) + { + int i; + int done = 0; + for (i = 0; i < n; i ++) + { + if (!done && i > 0 && + (namelist[i]->d_name[0] != '.') && + (namelist[i-1]->d_name[0] != '.') && + !strcmp (namelist[i]->d_name, lastslash+1)) + { + char *tmp = malloc (strlen (o->path) + 2 + strlen (namelist[i-1]->d_name)); + sprintf (tmp, "%s/%s", o->path, namelist[i-1]->d_name); + free (o->path); + o->path = tmp; + load_path (o); + done = 1; + } + } + for (i = 0; i < n; i ++) + free (namelist[i]); + free (namelist); + if (!done) + lastslash[0] = '/'; + } + } +} + +static void save_cb (MrgEvent *event, void *data1, void *data2); + +static void go_next_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + if (o->rev) + save_cb (event, data1, data2); + go_next (data1); + o->active = NULL; + mrg_event_stop_propagate (event); +} + +static void go_prev_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + if (o->rev) + save_cb (event, data1, data2); + go_prev (data1); + o->active = NULL; + mrg_event_stop_propagate (event); +} + +static void on_pan_drag (MrgEvent *e, void *data1, void *data2) +{ + State *o = data1; + + static float old_factor = 1; + + switch (e->type) + { + case MRG_DRAG_PRESS: + old_factor = o->render_quality; + if (o->render_quality < o->preview_quality) + o->render_quality = o->preview_quality; + break; + case MRG_DRAG_RELEASE: + o->render_quality = old_factor; + mrg_queue_draw (e->mrg, NULL); + break; + case MRG_DRAG_MOTION: + o->u -= (e->delta_x ); + o->v -= (e->delta_y ); + mrg_queue_draw (e->mrg, NULL); + break; + default: + break; + } +} + +static void load_into_buffer (State *o, const char *path) +{ + GeglNode *gegl, *load, *sink; + GeglBuffer *tempbuf; + + if (o->buffer) + { + g_object_unref (o->buffer); + o->buffer = NULL; + } + + gegl = gegl_node_new (); + load = gegl_node_new_child (gegl, "operation", "gegl:load", + "path", path, + NULL); + sink = gegl_node_new_child (gegl, "operation", "gegl:buffer-sink", + "buffer", &(o->buffer), + NULL); + gegl_node_link_many (load, sink, NULL); + gegl_node_process (sink); + g_object_unref (gegl); + + tempbuf = gegl_buffer_new (gegl_buffer_get_extent (o->buffer), + babl_format ("RGBA float")); + + gegl_buffer_copy (o->buffer, NULL, GEGL_ABYSS_NONE, tempbuf, NULL); + g_object_unref (o->buffer); + o->buffer = tempbuf; +} + +static void zoom_to_fit (State *o) +{ + Mrg *mrg = o->mrg; + GeglRectangle rect = gegl_node_get_bounding_box (o->sink); + float scale, scale2; + + scale = 1.0 * mrg_width (mrg) / rect.width; + scale2 = 1.0 * mrg_height (mrg) / rect.height; + + if (scale2 < scale) scale = scale2; + + o->scale = scale; + + o->u = -(mrg_width (mrg) - rect.width * scale) / 2; + o->v = -(mrg_height (mrg) - rect.height * scale) / 2; + + o->u += rect.x * scale; + o->v += rect.y * scale; + + mrg_queue_draw (mrg, NULL); +} + +static void zoom_fit_cb (MrgEvent *e, void *data1, void *data2) +{ + zoom_to_fit (data1); +} + +static int deferred_zoom_to_fit (Mrg *mrg, void *data) +{ + zoom_to_fit (data); + return 0; +} + +static void pan_left_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float amount = mrg_width (event->mrg) * 0.1; + o->u = o->u - amount; + mrg_queue_draw (o->mrg, NULL); +} + +static void pan_right_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float amount = mrg_width (event->mrg) * 0.1; + + o->u = o->u + amount; + mrg_queue_draw (o->mrg, NULL); +} + +static void pan_down_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float amount = mrg_width (event->mrg) * 0.1; + + o->v = o->v + amount; + mrg_queue_draw (o->mrg, NULL); +} + +static void pan_up_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float amount = mrg_width (event->mrg) * 0.1; + + o->v = o->v - amount; + + mrg_queue_draw (o->mrg, NULL); +} + +static void get_coords (State *o, float screen_x, float screen_y, float *gegl_x, float *gegl_y) +{ + float scale = o->scale; + *gegl_x = (o->u + screen_x) / scale; + *gegl_y = (o->v + screen_y) / scale; +} + +static void preview_more_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + o->render_quality *= 2; + mrg_queue_draw (o->mrg, NULL); +} + +static void preview_less_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + o->render_quality /= 2; + if (o->render_quality <= 1.0) + o->render_quality = 1.0; + + mrg_queue_draw (o->mrg, NULL); +} + +static void zoom_1_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float x, y; + get_coords (o, mrg_width(o->mrg)/2, mrg_height(o->mrg)/2, &x, &y); + o->scale = 1.0; + o->u = x * o->scale - mrg_width(o->mrg)/2; + o->v = y * o->scale - mrg_height(o->mrg)/2; + mrg_queue_draw (o->mrg, NULL); +} + +static void zoom_in_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float x, y; + get_coords (o, mrg_width(o->mrg)/2, mrg_height(o->mrg)/2, &x, &y); + o->scale = o->scale * 1.1; + o->u = x * o->scale - mrg_width(o->mrg)/2; + o->v = y * o->scale - mrg_height(o->mrg)/2; + mrg_queue_draw (o->mrg, NULL); +} + +static void zoom_out_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + float x, y; + get_coords (o, mrg_width(o->mrg)/2, mrg_height(o->mrg)/2, &x, &y); + o->scale = o->scale / 1.1; + o->u = x * o->scale - mrg_width(o->mrg)/2; + o->v = y * o->scale - mrg_height(o->mrg)/2; + mrg_queue_draw (o->mrg, NULL); +} + +static void toggle_actions_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + o->show_actions = !o->show_actions; + mrg_queue_draw (o->mrg, NULL); +} + +static void toggle_fullscreen_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + mrg_set_fullscreen (event->mrg, !mrg_is_fullscreen (event->mrg)); + mrg_event_stop_propagate (event); + mrg_add_timeout (event->mrg, 250, deferred_zoom_to_fit, o); +} + +static GeglNode *locate_node (State *o, const char *op_name) +{ + GeglNode *iter = o->sink; + while (iter) + { + char *opname = NULL; + g_object_get (iter, "operation", &opname, NULL); + if (!strcmp (opname, op_name)) + return iter; + g_free (opname); + iter = gegl_node_get_producer (iter, "input", NULL); + } + return NULL; +} + +static void activate_op_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + GeglNode *found; + ActionData *ad = data2; + + o->show_actions = 0; + o->rev ++; + + found = locate_node (o, ad->op_name); + if (found) + { + o->active = found; + } + else + { + if (!strcmp (ad->op_name, "gegl:rotate")) + { + const GeglRectangle *extent = gegl_buffer_get_extent (o->buffer); + o->active = gegl_node_new_child (o->gegl, + "operation", ad->op_name, NULL); + gegl_node_set (o->active, "origin-x", extent->width * 0.5, + "origin-y", extent->height * 0.5, + "degrees", 0.0, + NULL); + } else if (!strcmp (ad->op_name, "gegl:crop")) + { + const GeglRectangle *extent = gegl_buffer_get_extent (o->buffer); + o->active = gegl_node_new_child (o->gegl, + "operation", ad->op_name, NULL); + gegl_node_set (o->active, "x", 0.0, + "y", 0.0, + "width", extent->width * 1.0, + "height", extent->height * 1.0, + NULL); + } else + { + o->active = gegl_node_new_child (o->gegl, "operation", ad->op_name, NULL); + } + + gegl_node_link_many (gegl_node_get_producer (o->sink, "input", NULL), + o->active, + o->sink, + NULL); + } + mrg_queue_draw (o->mrg, NULL); +} + +static void disable_filter_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + GeglNode *iter = o->sink; + GeglNode *prev = NULL; + GeglNode *next = NULL; + while (iter) + { + prev = iter; + iter = gegl_node_get_producer (iter, "input", NULL); + if (o->active == iter) + { + next = gegl_node_get_producer (iter, "input", NULL); + break; + } + } + gegl_node_link_many (next, prev, NULL); + gegl_node_remove_child (o->gegl, o->active); + o->active = NULL; + mrg_queue_draw (o->mrg, NULL); +} + +static void apply_filter_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + o->active = NULL; + mrg_queue_draw (o->mrg, NULL); +} + +static void discard_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + char *old_path = strdup (o->path); + char *tmp; + char *lastslash; + go_next_cb (event, data1, data2); + if (!strcmp (old_path, o->path)) + { + go_prev_cb (event, data1, data2); + } + tmp = strdup (old_path); + lastslash = strrchr (tmp, '/'); + if (lastslash) + { + char command[2048]; + if (lastslash == tmp) + lastslash[1] = '\0'; + else + lastslash[0] = '\0'; + + sprintf (command, "mkdir %s/.discard > /dev/null 2>&1", tmp); + system (command); + sprintf (command, "mv %s %s/.discard", old_path, tmp); + system (command); + } + free (tmp); + free (old_path); +} + +static State *hack_state = NULL; +static void prop_double_drag_cb (MrgEvent *e, void *data1, void *data2) +{ + GeglNode *node = data1; + GParamSpec *pspec = data2; + State *o = hack_state; + GeglParamSpecDouble *gspec = data2; + gdouble value = 0.0; + float range = gspec->ui_maximum - gspec->ui_minimum; + static float old_factor = 1; + + value = e->x / mrg_width (e->mrg); + value = value * range + gspec->ui_minimum; + gegl_node_set (node, pspec->name, value, NULL); + + switch (e->type) + { + case MRG_DRAG_PRESS: + old_factor = o->render_quality; + if (o->render_quality < o->preview_quality) + o->render_quality = o->preview_quality; + break; + case MRG_DRAG_RELEASE: + o->render_quality = old_factor; + mrg_queue_draw (e->mrg, NULL); + break; + default: + break; + } + + mrg_queue_draw (e->mrg, NULL); +} + +static void draw_gegl_generic (State *state, Mrg *mrg, cairo_t *cr, GeglNode *node) +{ + const gchar* op_name = gegl_node_get_operation (node); + mrg_set_edge_left (mrg, mrg_width (mrg) * 0.1); + mrg_set_edge_top (mrg, mrg_height (mrg) * 0.1); + mrg_set_font_size (mrg, mrg_height (mrg) * 0.07); + mrg_set_style (mrg, "color:white; background-color: transparent"); + + cairo_save (cr); + mrg_printf (mrg, "%s\n", op_name); + { + guint n_props; + GParamSpec **pspecs = gegl_operation_list_properties (op_name, &n_props); + + if (pspecs) + { + int tot_pos = 0; + int pos_no = 0; + + for (gint i = 0; i < n_props; i++) + { + if (g_type_is_a (pspecs[i]->value_type, G_TYPE_DOUBLE) || + g_type_is_a (pspecs[i]->value_type, G_TYPE_INT) || + g_type_is_a (pspecs[i]->value_type, G_TYPE_STRING) || + g_type_is_a (pspecs[i]->value_type, G_TYPE_BOOLEAN)) + tot_pos ++; + } + + for (gint i = 0; i < n_props; i++) + { + mrg_set_xy (mrg, mrg_em(mrg), mrg_height (mrg) - mrg_em (mrg) * ((tot_pos-pos_no))); + + + if (g_type_is_a (pspecs[i]->value_type, G_TYPE_DOUBLE)) + { + float xpos; + GeglParamSpecDouble *geglspec = (void*)pspecs[i]; + gdouble value; + gegl_node_get (node, pspecs[i]->name, &value, NULL); + + cairo_rectangle (cr, 0, + mrg_height (mrg) - mrg_em (mrg) * ((tot_pos-pos_no+1)), + mrg_width (mrg), mrg_em(mrg)); + cairo_set_source_rgba (cr, 0,0,0, 0.5); + + mrg_listen (mrg, MRG_DRAG, prop_double_drag_cb, node,(void*)pspecs[i]); + + cairo_fill (cr); + xpos = (value - geglspec->ui_minimum) / (geglspec->ui_maximum - geglspec->ui_minimum); + cairo_rectangle (cr, xpos * mrg_width(mrg) - mrg_em(mrg)/4, + mrg_height (mrg) - mrg_em (mrg) * ((tot_pos-pos_no+1)), + mrg_em(mrg)/2, mrg_em(mrg)); + cairo_set_source_rgba (cr, 1,1,1, 0.5); + cairo_fill (cr); + + mrg_printf (mrg, "%s:%f\n", pspecs[i]->name, value); + pos_no ++; + } + else if (g_type_is_a (pspecs[i]->value_type, G_TYPE_INT)) + { + gint value; + gegl_node_get (node, pspecs[i]->name, &value, NULL); + mrg_printf (mrg, "%s:%i\n", pspecs[i]->name, value); + pos_no ++; + } + else if (g_type_is_a (pspecs[i]->value_type, G_TYPE_STRING)) + { + char *value = NULL; + gegl_node_get (node, pspecs[i]->name, &value, NULL); + pos_no ++; + mrg_printf (mrg, "%s:%s\n", pspecs[i]->name, value); + if (value) g_free (value); + } + else if (g_type_is_a (pspecs[i]->value_type, G_TYPE_BOOLEAN)) + { + gboolean value = FALSE; + gegl_node_get (node, pspecs[i]->name, &value, NULL); + pos_no ++; + mrg_printf (mrg, "%s:%i\n", pspecs[i]->name, value); + } + + } + g_free (pspecs); + } + } + + cairo_restore (cr); + mrg_set_style (mrg, "color:yellow; background-color: transparent"); +} + +static void draw_gegl_crop (State *o, Mrg *mrg, cairo_t *cr, GeglNode *node) +{ + const gchar* op_name = gegl_node_get_operation (node); + float dim = mrg_height (mrg) * 0.1 / o->scale; + double x,y,width,height; + double x0, y0, x1, y1; + + mrg_set_edge_left (mrg, mrg_width (mrg) * 0.1); + mrg_set_edge_top (mrg, mrg_height (mrg) * 0.1); + mrg_set_font_size (mrg, mrg_height (mrg) * 0.07); + mrg_set_style (mrg, "color:white; background-color: transparent"); + + cairo_save (cr); + mrg_printf (mrg, "%s\n", op_name); + + cairo_translate (cr, -o->u, -o->v); + cairo_scale (cr, o->scale, o->scale); + + gegl_node_get (node, "x", &x, "y", &y, "width", &width, "height", &height, NULL); + x0 = x; y0 = y; x1 = x0 + width; y1 = y + height; + + cairo_rectangle (cr, x0, y0, dim, dim); + contrasty_stroke (cr); + + cairo_rectangle (cr, x1-dim, y0, dim, dim); + contrasty_stroke (cr); + + cairo_rectangle (cr, x0, y1-dim, dim, dim); + contrasty_stroke (cr); + + cairo_rectangle (cr, x1-dim, y1-dim, dim, dim); + contrasty_stroke (cr); + + cairo_restore (cr); + mrg_set_style (mrg, "color:yellow; background-color: transparent"); +} + +static void ui_op_draw_apply_disable (State *o) +{ + Mrg *mrg = o->mrg; + mrg_set_font_size (mrg, mrg_height (mrg) * 0.1); + mrg_set_xy (mrg, 0, mrg_em(mrg)); + mrg_text_listen (mrg, MRG_PRESS, disable_filter_cb, o, NULL); + mrg_printf (mrg, "X"); + mrg_text_listen_done (mrg); + mrg_set_xy (mrg, mrg_width(mrg) - mrg_em (mrg), mrg_em(mrg)); + mrg_text_listen (mrg, MRG_PRESS, apply_filter_cb, o, NULL); + mrg_printf (mrg, "O"); + mrg_text_listen_done (mrg); +} + +static void ui_active_op (State *o) +{ + Mrg *mrg = o->mrg; + cairo_t *cr = mrg_cr (mrg); + char *opname = NULL; + g_object_get (o->active, "operation", &opname, NULL); + + if (!strcmp (opname, "gegl:crop")) { + draw_gegl_crop (o, mrg, cr, o->active); + } + else + { + draw_gegl_generic (o, mrg, cr, o->active); + ui_op_draw_apply_disable (o); + } + +} + +static void save_cb (MrgEvent *event, void *data1, void *data2) +{ + GeglNode *load; + State *o = data1; + gchar *path; + char *xml; + + gegl_node_link_many (o->sink, o->save, NULL); + gegl_node_process (o->save); + gegl_node_get (o->save, "path", &path, NULL); + fprintf (stderr, "saved to %s\n", path); + + load = gegl_node_new_child (o->gegl, "operation", "gegl:load", + "path", o->path, + NULL); + gegl_node_link_many (load, o->source, NULL); + xml = gegl_node_to_xml (o->sink, NULL); + gegl_node_remove_child (o->gegl, load); + gegl_node_link_many (o->load, o->source, NULL); + gegl_meta_set (path, xml); + g_free (xml); + o->rev = 0; +} + +static void ui_debug_op_chain (State *o) +{ + Mrg *mrg = o->mrg; + GeglNode *iter; + mrg_set_edge_top (mrg, mrg_height (mrg) * 0.2); + iter = o->sink; + while (iter) + { + char *opname = NULL; + g_object_get (iter, "operation", &opname, NULL); + if (iter == o->active) + mrg_printf (mrg, "[%s]", opname); + else + mrg_printf (mrg, "%s", opname); + mrg_printf (mrg, "\n"); + + g_free (opname); + iter = gegl_node_get_producer (iter, "input", NULL); + } +} + +static void ui_actions (State *o) +{ + Mrg *mrg = o->mrg; + int i; + cairo_t *cr = mrg_cr (mrg); + mrg_set_edge_left (mrg, mrg_width (mrg) * 0.25); + mrg_set_edge_top (mrg, mrg_height (mrg) * 0.1); + mrg_set_font_size (mrg, mrg_height (mrg) * 0.08); + + for (i = 0; actions[i].label; i ++) + { + mrg_text_listen (mrg, MRG_PRESS, activate_op_cb, o, &actions[i]); + if (locate_node (o, actions[i].op_name)) + mrg_printf (mrg, "-"); + mrg_printf (mrg, "%s\n", actions[i].label); + mrg_text_listen_done (mrg); + } + + mrg_print (mrg, "\n"); + mrg_printf (mrg, "existing\n"); + mrg_text_listen (mrg, MRG_PRESS, discard_cb, o, NULL); + mrg_printf (mrg, "discard\n"); + mrg_text_listen_done (mrg); + cairo_scale (cr, mrg_width(mrg), mrg_height(mrg)); + cairo_new_path (cr); + cairo_arc (cr, 0.9, 0.1, 0.1, 0.0, G_PI * 2); + mrg_listen (mrg, MRG_PRESS, toggle_actions_cb, o, NULL); + contrasty_stroke (cr); +} + +static void ui_viewer (State *o) +{ + Mrg *mrg = o->mrg; + cairo_t *cr = mrg_cr (mrg); + cairo_rectangle (cr, 0,0, mrg_width(mrg), mrg_height(mrg)); + mrg_listen (mrg, MRG_DRAG, on_pan_drag, o, NULL); + cairo_new_path (cr); + cairo_scale (cr, mrg_width(mrg), mrg_height(mrg)); + cairo_move_to (cr, 0.2, 0.8); + cairo_line_to (cr, 0.2, 1.0); + cairo_line_to (cr, 0.0, 0.9); + cairo_close_path (cr); + mrg_listen (mrg, MRG_PRESS, go_prev_cb, o, NULL); + if (o->show_controls) + contrasty_stroke (cr); + else + cairo_new_path (cr); + + cairo_move_to (cr, 0.8, 0.8); + cairo_line_to (cr, 0.8, 1.0); + cairo_line_to (cr, 1.0, 0.9); + cairo_close_path (cr); + + mrg_listen (mrg, MRG_PRESS, go_next_cb, o, NULL); + if (o->show_controls) + contrasty_stroke (cr); + else + cairo_new_path (cr); + + cairo_arc (cr, 0.9, 0.1, 0.1, 0.0, G_PI * 2); + mrg_listen (mrg, MRG_PRESS, toggle_actions_cb, o, NULL); + + if (o->show_controls) + contrasty_stroke (cr); + else + cairo_new_path (cr); +} + +static void toggle_show_controls_cb (MrgEvent *event, void *data1, void *data2) +{ + State *o = data1; + o->show_controls = !o->show_controls; + mrg_queue_draw (o->mrg, NULL); +} + +static void ui (Mrg *mrg, void *data) +{ + State *o = data; + mrg_gegl_blit (mrg, + 0, 0, + mrg_width (mrg), mrg_height (mrg), + o->sink, + o->u, o->v, + o->scale, + o->render_quality); + + if (o->show_controls) + { + mrg_printf (mrg, "%s\n", o->path); +#if DEBUG_OP_LIST + ui_debug_op_chain (o); +#endif + } + + if (o->show_actions) + { + ui_actions (o); + mrg_add_binding (mrg, "return", NULL, NULL, toggle_actions_cb, o); + } + else if (o->active) + { + ui_active_op (o); + mrg_add_binding (mrg, "return", NULL, NULL, apply_filter_cb, o); + mrg_add_binding (mrg, "escape", NULL, NULL, disable_filter_cb, o); + } + else + { + ui_viewer (o); + mrg_add_binding (mrg, "return", NULL, NULL, toggle_actions_cb, o); + } + + mrg_add_binding (mrg, "left", NULL, NULL, pan_left_cb, o); + mrg_add_binding (mrg, "right", NULL, NULL, pan_right_cb, o); + mrg_add_binding (mrg, "up", NULL, NULL, pan_up_cb, o); + mrg_add_binding (mrg, "down", NULL, NULL, pan_down_cb, o); + mrg_add_binding (mrg, "+", NULL, NULL, zoom_in_cb, o); + mrg_add_binding (mrg, "=", NULL, NULL, zoom_in_cb, o); + mrg_add_binding (mrg, "-", NULL, NULL, zoom_out_cb, o); + mrg_add_binding (mrg, "1", NULL, NULL, zoom_1_cb, o); + mrg_add_binding (mrg, "m", NULL, NULL, zoom_fit_cb, o); + + mrg_add_binding (mrg, "control-q", NULL, NULL, mrg_quit_cb, o); + mrg_add_binding (mrg, "m", NULL, NULL, zoom_fit_cb, o); + mrg_add_binding (mrg, "q", NULL, NULL, mrg_quit_cb, o); + mrg_add_binding (mrg, "x", NULL, NULL, discard_cb, o); + mrg_add_binding (mrg, "f", NULL, NULL, toggle_fullscreen_cb, o); + mrg_add_binding (mrg, "F11", NULL, NULL, toggle_fullscreen_cb, o); + mrg_add_binding (mrg, "tab", NULL, NULL, toggle_show_controls_cb, o); + mrg_add_binding (mrg, "space", NULL, NULL, go_next_cb , o); + mrg_add_binding (mrg, "n", NULL, NULL, go_next_cb, o); + mrg_add_binding (mrg, "p", NULL, NULL, go_prev_cb, o); + mrg_add_binding (mrg, "backspace", NULL, NULL, go_prev_cb, o); + + + mrg_add_binding (mrg, ",", NULL, NULL, preview_less_cb, o); + mrg_add_binding (mrg, ".", NULL, NULL, preview_more_cb, o); +} + +#if 0 +void gegl_node_defaults (GeglNode *node) +{ + const gchar* op_name = gegl_node_get_operation (node); + { + guint n_props; + GParamSpec **pspecs = gegl_operation_list_properties (op_name, &n_props); + if (pspecs) + { + for (gint i = 0; i < n_props; i++) + { + if (g_type_is_a (pspecs[i]->value_type, G_TYPE_DOUBLE)) + { + GParamSpecDouble *pspec = (void*)pspecs[i]; + gegl_node_set (node, pspecs[i]->name, pspec->default_value, NULL); + } + else if (g_type_is_a (pspecs[i]->value_type, G_TYPE_INT)) + { + GParamSpecInt *pspec = (void*)pspecs[i]; + gegl_node_set (node, pspecs[i]->name, pspec->default_value, NULL); + } + else if (g_type_is_a (pspecs[i]->value_type, G_TYPE_STRING)) + { + GParamSpecString *pspec = (void*)pspecs[i]; + gegl_node_set (node, pspecs[i]->name, pspec->default_value, NULL); + } + } + g_free (pspecs); + } + } +} +#endif + +/* loads the source image corresponding to o->path into o->buffer and + * creates live gegl pipeline, or nops.. rigs up o->giev_path to be + * the location where default saves ends up. + */ +static void load_path (State *o) +{ + char *path; + char *meta; + if (is_giev_path (o->path)) + { + if (o->giev_path) + free (o->giev_path); + o->giev_path = o->path; + o->path = unsuffix_path (o->giev_path); + } + else + { + if (o->giev_path) + free (o->giev_path); + o->giev_path = suffix_path (o->path); + } + path = o->path; + + if (access (o->giev_path, F_OK) != -1) + path = o->giev_path; + + g_object_unref (o->gegl); + o->gegl = NULL; + + meta = gegl_meta_get (path); + if (meta) + { + GSList *nodes, *n; + o->gegl = gegl_node_new_from_xml (meta, NULL); + o->sink = gegl_node_new_child (o->gegl, + "operation", "gegl:nop", NULL); + o->source = NULL; + gegl_node_link_many ( + gegl_node_get_producer (o->gegl, "input", NULL), o->sink, NULL); + nodes = gegl_node_get_children (o->gegl); + for (n = nodes; n; n=n->next) + { + const char *op_name = gegl_node_get_operation (n->data); + if (!strcmp (op_name, "gegl:load")) + { + GeglNode *load; + gchar *path; + gegl_node_get (n->data, "path", &path, NULL); + load_into_buffer (o, path); + gegl_node_set (n->data, "operation", "gegl:nop", NULL); + o->source = n->data; + load = gegl_node_new_child (o->gegl, "operation", "gegl:buffer-source", + "buffer", o->buffer, NULL); + gegl_node_link_many (load, o->source, NULL); + g_free (path); + break; + } + } + o->save = gegl_node_new_child (o->gegl, + "operation", "gegl:save", + "path", path, + NULL); + } + else + { + o->gegl = gegl_node_new (); + o->sink = gegl_node_new_child (o->gegl, + "operation", "gegl:nop", NULL); + o->source = gegl_node_new_child (o->gegl, + "operation", "gegl:nop", NULL); + load_into_buffer (o, path); + o->load = gegl_node_new_child (o->gegl, + "operation", "gegl:buffer-source", + NULL); + o->save = gegl_node_new_child (o->gegl, + "operation", "gegl:save", + "path", o->giev_path, + NULL); + gegl_node_link_many (o->load, o->source, o->sink, NULL); + gegl_node_set (o->load, "buffer", o->buffer, NULL); + } + zoom_to_fit (o); + + mrg_queue_draw (o->mrg, NULL); + o->rev = 0; +} + +int mrg_ui_main (int argc, char **argv); +int mrg_ui_main (int argc, char **argv) +{ + Mrg *mrg = mrg_new (1024, 768, NULL); + State o = {NULL,}; +#ifdef USE_MIPMAPS + g_setenv ("GEGL_MIPMAP_RENDERING", "1", TRUE); +#endif + g_setenv ("BABL_TOLERANCE", "0.1", TRUE); + + gegl_init (&argc, &argv); + o.gegl = gegl_node_new (); + o.mrg = mrg; + o.scale = 1.0; + o.render_quality = 1.0; + o.preview_quality = 4.0; + + if (access (argv[1], F_OK) != -1) + o.path = strdup (argv[1]); + else + { + printf ("usage: %s <full-path-to-image>\n", argv[0]); + return -1; + } + + load_path (&o); + mrg_set_ui (mrg, ui, &o); + hack_state = &o; + mrg_main (mrg); + + g_object_unref (o.gegl); + if (o.buffer) + { + g_object_unref (o.buffer); + o.buffer = NULL; + } + gegl_exit (); + return 0; +} + +void +gegl_meta_set (const char *path, + const char *meta_data) +{ + GError *error = NULL; + GExiv2Metadata *e2m = gexiv2_metadata_new (); + gexiv2_metadata_open_path (e2m, path, &error); + if (error) + { + g_warning ("%s", error->message); + } + else + { + if (gexiv2_metadata_has_tag (e2m, "Xmp.xmp.GEGL")) + gexiv2_metadata_clear_tag (e2m, "Xmp.xmp.GEGL"); + + gexiv2_metadata_set_tag_string (e2m, "Xmp.xmp.GEGL", meta_data); + gexiv2_metadata_save_file (e2m, path, &error); + if (error) + g_warning ("%s", error->message); + } + gexiv2_metadata_free (e2m); +} + +char * +gegl_meta_get (const char *path) +{ + gchar *ret = NULL; + GError *error = NULL; + GExiv2Metadata *e2m = gexiv2_metadata_new (); + gexiv2_metadata_open_path (e2m, path, &error); + if (error) + g_warning ("%s", error->message); + else + ret = gexiv2_metadata_get_tag_string (e2m, "Xmp.xmp.GEGL"); + gexiv2_metadata_free (e2m); + return ret; +} + +#endif diff --git a/configure.ac b/configure.ac index ae31682a..949ecaba 100644 --- a/configure.ac +++ b/configure.ac @@ -646,6 +646,46 @@ AM_CONDITIONAL(HAVE_DOT, test "x$have_dot" = "xyes") AM_PATH_PYTHON([2.5.0],, [:]) AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != :]) +############### +# Check for mrg +############### + +AC_ARG_WITH(mrg, [ --without-mrg build without mrg support]) + +have_mrg="no" +if test "x$with_mrg" != "xno"; then + PKG_CHECK_MODULES(MRG, mrg, + AC_DEFINE(HAVE_MRG, 1, + [Define to 1 if the mrg library is available]) + have_mrg="yes", + have_mrg="no (mrg not found)") +fi + +AM_CONDITIONAL(HAVE_MRG, test "$have_mrg" = "yes") + +AC_SUBST(MRG_CFLAGS) +AC_SUBST(MRG_LIBS) + +################## +# Check for gexiv2 +################## + +AC_ARG_WITH(gexiv2, [ --without-gexiv2 build without gexiv2 support]) + +have_gexiv2="no" +if test "x$with_gexiv2" != "xno"; then + PKG_CHECK_MODULES(GEXIV2, gexiv2, + AC_DEFINE(HAVE_GEXIV2, 1, + [Define to 1 if the gexiv2 library is available]) + have_gexiv2="yes", + have_gexiv2="no (gexiv2 not found)") +fi + +AM_CONDITIONAL(HAVE_GEXIV2, test "$have_gexiv2" = "yes") + +AC_SUBST(GEXIV2_CFLAGS) +AC_SUBST(GEXIV2_LIBS) + ################# # Check for Cairo ################# @@ -1284,6 +1324,7 @@ Optional features: Optional dependencies: asciidoc: $have_asciidoc enscript: $have_enscript + mrg: $have_mrg Ruby: $have_ruby Lua: $have_lua Cairo: $have_cairo @@ -1303,6 +1344,7 @@ Optional dependencies: V4L2: $have_libv4l2 spiro: $spiro_ok EXIV: $have_exiv2 + gexiv2: $have_gexiv2 umfpack: $have_umfpack webp: $have_webp poly2tri-c: $have_p2tc |