diff options
Diffstat (limited to 'plug-ins/ifs-compose/ifs-compose.c')
-rw-r--r-- | plug-ins/ifs-compose/ifs-compose.c | 2799 |
1 files changed, 2799 insertions, 0 deletions
diff --git a/plug-ins/ifs-compose/ifs-compose.c b/plug-ins/ifs-compose/ifs-compose.c new file mode 100644 index 0000000..27cad9c --- /dev/null +++ b/plug-ins/ifs-compose/ifs-compose.c @@ -0,0 +1,2799 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * IfsCompose is a interface for creating IFS fractals by + * direct manipulation. + * Copyright (C) 1997 Owen Taylor + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* TODO + * ---- + * + * 1. Run in non-interactive mode (need to figure out useful way for a + * script to give the 19N parameters for an image). Perhaps just + * support saving parameters to a file, script passes file name. + * 2. Figure out if we need multiple phases for supersampled brushes. + */ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <glib/gstdio.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "ifs-compose.h" + +#include "libgimp/stdplugins-intl.h" + + +#define RESPONSE_RESET 1 +#define RESPONSE_OPEN 2 +#define RESPONSE_SAVE 3 + +#define DESIGN_AREA_MAX_SIZE 300 + +#define PREVIEW_RENDER_CHUNK 10000 + +#define UNDO_LEVELS 24 + +#define PLUG_IN_PARASITE "ifscompose-parasite" +#define PLUG_IN_PROC "plug-in-ifscompose" +#define PLUG_IN_BINARY "ifs-compose" +#define PLUG_IN_ROLE "gimp-ifs-compose" + +typedef enum +{ + OP_TRANSLATE, + OP_ROTATE, /* or scale */ + OP_STRETCH +} DesignOp; + +typedef enum +{ + VALUE_PAIR_INT, + VALUE_PAIR_DOUBLE +} ValuePairType; + +typedef struct +{ + GtkAdjustment *adjustment; + GtkWidget *scale; + GtkWidget *spin; + + ValuePairType type; + guint timeout_id; + + union + { + gdouble *d; + gint *i; + } data; +} ValuePair; + +typedef struct +{ + IfsComposeVals ifsvals; + AffElement **elements; + gboolean *element_selected; + gint current_element; +} UndoItem; + +typedef struct +{ + GimpRGB *color; + GtkWidget *hbox; + GtkWidget *orig_preview; + GtkWidget *button; + gboolean fixed_point; +} ColorMap; + +typedef struct +{ + GtkWidget *dialog; + + ValuePair *iterations_pair; + ValuePair *subdivide_pair; + ValuePair *radius_pair; + ValuePair *memory_pair; +} IfsOptionsDialog; + +typedef struct +{ + GtkWidget *area; + GtkUIManager *ui_manager; + GdkPixmap *pixmap; + + DesignOp op; + gdouble op_x; + gdouble op_y; + gdouble op_xcenter; + gdouble op_ycenter; + gdouble op_center_x; + gdouble op_center_y; + guint button_state; + gint num_selected; +} IfsDesignArea; + +typedef struct +{ + ValuePair *prob_pair; + ValuePair *x_pair; + ValuePair *y_pair; + ValuePair *scale_pair; + ValuePair *angle_pair; + ValuePair *asym_pair; + ValuePair *shear_pair; + GtkWidget *flip_check_button; + + ColorMap *red_cmap; + ColorMap *green_cmap; + ColorMap *blue_cmap; + ColorMap *black_cmap; + ColorMap *target_cmap; + ValuePair *hue_scale_pair; + ValuePair *value_scale_pair; + GtkWidget *simple_button; + GtkWidget *full_button; + GtkWidget *current_frame; + + GtkWidget *preview; + guchar *preview_data; + gint preview_iterations; + + gint drawable_width; + gint drawable_height; + gint preview_width; + gint preview_height; + + AffElement *selected_orig; + gint current_element; + AffElementVals current_vals; + + gboolean in_update; /* true if we're currently in + update_values() - don't do anything + on updates */ +} IfsDialog; + +typedef struct +{ + gboolean run; +} IfsComposeInterface; + +/* Declare local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +/* user interface functions */ +static gint ifs_compose_dialog (gint32 drawable_id); +static void ifs_options_dialog (GtkWidget *parent); +static GtkWidget * ifs_compose_trans_page (void); +static GtkWidget * ifs_compose_color_page (void); +static GtkUIManager * design_op_menu_create (GtkWidget *window); +static void design_op_actions_update (void); +static void design_area_create (GtkWidget *window, + gint design_width, + gint design_height); + +/* functions for drawing design window */ +static void update_values (void); +static void set_current_element (gint index); +static void design_area_realize (GtkWidget *widget); +static gint design_area_expose (GtkWidget *widget, + GdkEventExpose *event); +static gint design_area_button_press (GtkWidget *widget, + GdkEventButton *event); +static gint design_area_button_release (GtkWidget *widget, + GdkEventButton *event); +static void design_area_select_all_callback (GtkWidget *widget, + gpointer data); +static gint design_area_configure (GtkWidget *widget, + GdkEventConfigure *event); +static gint design_area_motion (GtkWidget *widget, + GdkEventMotion *event); +static void design_area_redraw (void); + +/* Undo ring functions */ +static void undo_begin (void); +static void undo_update (gint element); +static void undo_exchange (gint el); +static void undo (void); +static void redo (void); + +static void recompute_center (gboolean save_undo); +static void recompute_center_cb (GtkWidget *widget, + gpointer data); + +static void ifs_compose (gint32 drawable_id); + +static ColorMap *color_map_create (const gchar *name, + GimpRGB *orig_color, + GimpRGB *data, + gboolean fixed_point); +static void color_map_color_changed_cb (GtkWidget *widget, + ColorMap *color_map); +static void color_map_update (ColorMap *color_map); + +/* interface functions */ +static void simple_color_toggled (GtkWidget *widget, gpointer data); +static void simple_color_set_sensitive (void); +static void val_changed_update (void); +static ValuePair *value_pair_create (gpointer data, + gdouble lower, + gdouble upper, + gboolean create_scale, + ValuePairType type); +static void value_pair_update (ValuePair *value_pair); +static void value_pair_scale_callback (GtkAdjustment *adjustment, + ValuePair *value_pair); + +static void design_op_update_callback (GtkRadioAction *action, + GtkRadioAction *current, + gpointer data); +static void flip_check_button_callback (GtkWidget *widget, gpointer data); +static gint preview_idle_render (gpointer data); + +static void ifs_compose_preview (void); +static void ifs_compose_set_defaults (void); +static void ifs_compose_new_callback (GtkAction *action, + gpointer data); +static void ifs_compose_delete_callback (GtkAction *action, + gpointer data); +static void ifs_compose_options_callback (GtkAction *action, + gpointer data); +static void ifs_compose_load (GtkWidget *parent); +static void ifs_compose_save (GtkWidget *parent); +static void ifs_compose_response (GtkWidget *widget, + gint response_id, + gpointer data); + +/* + * Some static variables + */ + +static IfsDialog *ifsD = NULL; +static IfsOptionsDialog *ifsOptD = NULL; +static IfsDesignArea *ifsDesign = NULL; + + +static AffElement **elements = NULL; +static gint *element_selected = NULL; +/* labels are generated by printing this int */ +static gint count_for_naming = 0; + +static UndoItem undo_ring[UNDO_LEVELS]; +static gint undo_cur = -1; +static gint undo_num = 0; +static gint undo_start = 0; + + +/* num_elements = 0, signals not inited */ +static IfsComposeVals ifsvals = +{ + 0, /* num_elements */ + 50000, /* iterations */ + 4096, /* max_memory */ + 4, /* subdivide */ + 0.75, /* radius */ + 1.0, /* aspect ratio */ + 0.5, /* center_x */ + 0.5, /* center_y */ +}; + +static IfsComposeInterface ifscint = +{ + FALSE, /* run */ +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, + }; + + static const GimpParamDef *return_vals = NULL; + static int nreturn_vals = 0; + + gimp_install_procedure (PLUG_IN_PROC, + N_("Create an Iterated Function System (IFS) fractal"), + "Interactively create an Iterated Function System " + "fractal. Use the window on the upper left to adjust " + "the component transformations of the fractal. The " + "operation that is performed is selected by the " + "buttons underneath the window, or from a menu " + "popped up by the right mouse button. The fractal " + "will be rendered with a transparent background if " + "the current image has an alpha channel.", + "Owen Taylor", + "Owen Taylor", + "1997", + N_("_IFS Fractal..."), + "*", + GIMP_PLUGIN, + G_N_ELEMENTS (args), nreturn_vals, + args, return_vals); + + gimp_plugin_menu_register (PLUG_IN_PROC, + "<Image>/Filters/Render/Fractals"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GimpParasite *parasite = NULL; + gint32 image_id; + gint32 drawable_id; + gboolean found_parasite = FALSE; + + INIT_I18N (); + gegl_init (NULL, NULL); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = status; + + image_id = param[1].data.d_image; + drawable_id = param[2].data.d_drawable; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data; first look for a parasite - + * if not found, fall back to global values + */ + parasite = gimp_item_get_parasite (drawable_id, + PLUG_IN_PARASITE); + if (parasite) + { + found_parasite = ifsvals_parse_string (gimp_parasite_data (parasite), + &ifsvals, &elements); + gimp_parasite_free (parasite); + } + + if (!found_parasite) + { + gint length = gimp_get_data_size (PLUG_IN_PROC); + + if (length > 0) + { + gchar *data = g_new (gchar, length); + + gimp_get_data (PLUG_IN_PROC, data); + ifsvals_parse_string (data, &ifsvals, &elements); + g_free (data); + } + } + + /* after ifsvals_parse_string, need to set up naming */ + count_for_naming = ifsvals.num_elements; + + /* First acquire information with a dialog */ + if (! ifs_compose_dialog (drawable_id)) + return; + break; + + case GIMP_RUN_NONINTERACTIVE: + status = GIMP_PDB_CALLING_ERROR; + break; + + case GIMP_RUN_WITH_LAST_VALS: + { + gint length = gimp_get_data_size (PLUG_IN_PROC); + + if (length > 0) + { + gchar *data = g_new (gchar, length); + + gimp_get_data (PLUG_IN_PROC, data); + ifsvals_parse_string (data, &ifsvals, &elements); + g_free (data); + } + else + { + ifs_compose_set_defaults (); + } + } + break; + + default: + break; + } + + /* Render the fractal */ + if (status == GIMP_PDB_SUCCESS) + { + if (run_mode == GIMP_RUN_INTERACTIVE) + { + gchar *str; + GimpParasite *parasite; + + gimp_image_undo_group_start (image_id); + + /* run the effect */ + ifs_compose (drawable_id); + + /* Store data for next invocation - both globally and + * as a parasite on this layer + */ + str = ifsvals_stringify (&ifsvals, elements); + + gimp_set_data (PLUG_IN_PROC, str, strlen (str) + 1); + + parasite = gimp_parasite_new (PLUG_IN_PARASITE, + GIMP_PARASITE_PERSISTENT | + GIMP_PARASITE_UNDOABLE, + strlen (str) + 1, str); + gimp_item_attach_parasite (drawable_id, parasite); + gimp_parasite_free (parasite); + + g_free (str); + + gimp_image_undo_group_end (image_id); + + gimp_displays_flush (); + } + else + { + /* run the effect */ + ifs_compose (drawable_id); + } + } + + values[0].data.d_status = status; +} + +static GtkWidget * +ifs_compose_trans_page (void) +{ + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + + table = gtk_table_new (3, 6, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + gtk_table_set_col_spacing (GTK_TABLE (table), 2, 6); + gtk_table_set_col_spacing (GTK_TABLE (table), 4, 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 12); + gtk_table_set_row_spacing (GTK_TABLE (table), 2, 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* X */ + + label = gtk_label_new (_("X:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->x_pair = value_pair_create (&ifsD->current_vals.x, 0.0, 1.0, FALSE, + VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->x_pair->spin, 1, 2, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->x_pair->spin); + + /* Y */ + + label = gtk_label_new (_("Y:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->y_pair = value_pair_create (&ifsD->current_vals.y, 0.0, 1.0, FALSE, + VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->y_pair->spin, 1, 2, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->y_pair->spin); + + /* Scale */ + + label = gtk_label_new (_("Scale:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->scale_pair = value_pair_create (&ifsD->current_vals.scale, 0.0, 1.0, + FALSE, VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->scale_pair->spin, 3, 4, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->scale_pair->spin); + + /* Angle */ + + label = gtk_label_new (_("Angle:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->angle_pair = value_pair_create (&ifsD->current_vals.theta, -180, 180, + FALSE, VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->angle_pair->spin, 3, 4, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->angle_pair->spin); + + /* Asym */ + + label = gtk_label_new (_("Asymmetry:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 4, 5, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->asym_pair = value_pair_create (&ifsD->current_vals.asym, 0.10, 10.0, + FALSE, VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->asym_pair->spin, 5, 6, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->asym_pair->spin); + + /* Shear */ + + label = gtk_label_new (_("Shear:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 4, 5, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->shear_pair = value_pair_create (&ifsD->current_vals.shear, -10.0, 10.0, + FALSE, VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->shear_pair->spin, 5, 6, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->shear_pair->spin); + + /* Flip */ + + ifsD->flip_check_button = gtk_check_button_new_with_label (_("Flip")); + gtk_table_attach (GTK_TABLE (table), ifsD->flip_check_button, 0, 6, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + g_signal_connect (ifsD->flip_check_button, "toggled", + G_CALLBACK (flip_check_button_callback), + NULL); + gtk_widget_show (ifsD->flip_check_button); + + return vbox; +} + +static GtkWidget * +ifs_compose_color_page (void) +{ + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GSList *group = NULL; + GimpRGB color; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + + table = gtk_table_new (3, 5, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + /* Simple color control section */ + + ifsD->simple_button = gtk_radio_button_new_with_label (group, _("Simple")); + gtk_table_attach (GTK_TABLE (table), ifsD->simple_button, 0, 1, 0, 2, + GTK_FILL, GTK_FILL, 0, 0); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (ifsD->simple_button)); + g_signal_connect (ifsD->simple_button, "toggled", + G_CALLBACK (simple_color_toggled), + NULL); + gtk_widget_show (ifsD->simple_button); + + ifsD->target_cmap = color_map_create (_("IFS Fractal: Target"), NULL, + &ifsD->current_vals.target_color, TRUE); + gtk_table_attach (GTK_TABLE (table), ifsD->target_cmap->hbox, 1, 2, 0, 2, + GTK_FILL, 0, 0, 0); + gtk_widget_show (ifsD->target_cmap->hbox); + + label = gtk_label_new (_("Scale hue by:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->hue_scale_pair = value_pair_create (&ifsD->current_vals.hue_scale, + 0.0, 1.0, TRUE, VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->hue_scale_pair->scale, 3, 4, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->hue_scale_pair->scale); + gtk_table_attach (GTK_TABLE (table), ifsD->hue_scale_pair->spin, 4, 5, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->hue_scale_pair->spin); + + label = gtk_label_new (_("Scale value by:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsD->value_scale_pair = value_pair_create (&ifsD->current_vals.value_scale, + 0.0, 1.0, TRUE, VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsD->value_scale_pair->scale, + 3, 4, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->value_scale_pair->scale); + gtk_table_attach (GTK_TABLE (table), ifsD->value_scale_pair->spin, + 4, 5, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->value_scale_pair->spin); + + /* Full color control section */ + + ifsD->full_button = gtk_radio_button_new_with_label (group, _("Full")); + gtk_table_attach (GTK_TABLE (table), ifsD->full_button, 0, 1, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (ifsD->full_button)); + gtk_widget_show (ifsD->full_button); + + gimp_rgb_parse_name (&color, "red", -1); + gimp_rgb_set_alpha (&color, 1.0); + ifsD->red_cmap = color_map_create (_("IFS Fractal: Red"), &color, + &ifsD->current_vals.red_color, FALSE); + gtk_table_attach (GTK_TABLE (table), ifsD->red_cmap->hbox, 1, 2, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->red_cmap->hbox); + + gimp_rgb_parse_name (&color, "green", -1); + gimp_rgb_set_alpha (&color, 1.0); + ifsD->green_cmap = color_map_create (_("IFS Fractal: Green"), &color, + &ifsD->current_vals.green_color, FALSE); + gtk_table_attach (GTK_TABLE (table), ifsD->green_cmap->hbox, 2, 3, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->green_cmap->hbox); + + gimp_rgb_parse_name (&color, "blue", -1); + gimp_rgb_set_alpha (&color, 1.0); + ifsD->blue_cmap = color_map_create (_("IFS Fractal: Blue"), &color, + &ifsD->current_vals.blue_color, FALSE); + gtk_table_attach (GTK_TABLE (table), ifsD->blue_cmap->hbox, 3, 4, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->blue_cmap->hbox); + + gimp_rgb_parse_name (&color, "black", -1); + gimp_rgb_set_alpha (&color, 1.0); + ifsD->black_cmap = color_map_create (_("IFS Fractal: Black"), &color, + &ifsD->current_vals.black_color, FALSE); + gtk_table_attach (GTK_TABLE (table), ifsD->black_cmap->hbox, 4, 5, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsD->black_cmap->hbox); + + return vbox; +} + +static gint +ifs_compose_dialog (gint32 drawable_id) +{ + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *main_vbox; + GtkWidget *toolbar; + GtkWidget *aspect_frame; + GtkWidget *notebook; + GtkWidget *page; + gint design_width = gimp_drawable_width (drawable_id); + gint design_height = gimp_drawable_height (drawable_id); + + if (design_width > design_height) + { + if (design_width > DESIGN_AREA_MAX_SIZE) + { + design_height = design_height * DESIGN_AREA_MAX_SIZE / design_width; + design_width = DESIGN_AREA_MAX_SIZE; + } + } + else + { + if (design_height > DESIGN_AREA_MAX_SIZE) + { + design_width = design_width * DESIGN_AREA_MAX_SIZE / design_height; + design_height = DESIGN_AREA_MAX_SIZE; + } + } + + ifsD = g_new0 (IfsDialog, 1); + + ifsD->drawable_width = gimp_drawable_width (drawable_id); + ifsD->drawable_height = gimp_drawable_height (drawable_id); + ifsD->preview_width = design_width; + ifsD->preview_height = design_height; + + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + dialog = gimp_dialog_new (_("IFS Fractal"), PLUG_IN_ROLE, + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Open"), RESPONSE_OPEN, + _("_Save"), RESPONSE_SAVE, + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_OPEN, + RESPONSE_SAVE, + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_window_set_transient (GTK_WINDOW (dialog)); + + g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer) &dialog); + + g_signal_connect (dialog, "response", + G_CALLBACK (ifs_compose_response), + NULL); + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_main_quit), + NULL); + + design_area_create (dialog, design_width, design_height); + + toolbar = gtk_ui_manager_get_widget (ifsDesign->ui_manager, + "/ifs-compose-toolbar"); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + toolbar, FALSE, FALSE, 0); + gtk_widget_show (toolbar); + + /* The main vbox */ + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + + /* The design area */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + + aspect_frame = gtk_aspect_frame_new (NULL, + 0.5, 0.5, + (gdouble) design_width / design_height, + 0); + gtk_frame_set_shadow_type (GTK_FRAME (aspect_frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), aspect_frame, TRUE, TRUE, 0); + gtk_widget_show (aspect_frame); + + gtk_container_add (GTK_CONTAINER (aspect_frame), ifsDesign->area); + gtk_widget_show (ifsDesign->area); + + /* The Preview */ + + aspect_frame = gtk_aspect_frame_new (NULL, + 0.5, 0.5, + (gdouble) design_width / design_height, + 0); + gtk_frame_set_shadow_type (GTK_FRAME (aspect_frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), aspect_frame, TRUE, TRUE, 0); + + ifsD->preview = gimp_preview_area_new (); + gtk_widget_set_size_request (ifsD->preview, + ifsD->preview_width, + ifsD->preview_height); + gtk_container_add (GTK_CONTAINER (aspect_frame), ifsD->preview); + gtk_widget_show (ifsD->preview); + + gtk_widget_show (aspect_frame); + + gtk_widget_show (hbox); + + /* The current transformation frame */ + + ifsD->current_frame = gimp_frame_new (NULL); + gtk_box_pack_start (GTK_BOX (main_vbox), ifsD->current_frame, + FALSE, FALSE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (ifsD->current_frame), vbox); + + /* The notebook */ + + notebook = gtk_notebook_new (); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + gtk_box_pack_start (GTK_BOX (vbox), notebook, FALSE, FALSE, 0); + gtk_widget_show (notebook); + + page = ifs_compose_trans_page (); + label = gtk_label_new (_("Spatial Transformation")); + gtk_label_set_xalign (GTK_LABEL (label), 0.5); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label); + gtk_widget_show (page); + + page = ifs_compose_color_page (); + label = gtk_label_new (_("Color Transformation")); + gtk_label_set_xalign (GTK_LABEL (label), 0.5); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label); + gtk_widget_show (page); + + /* The probability entry */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Relative probability:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + ifsD->prob_pair = value_pair_create (&ifsD->current_vals.prob, 0.0, 5.0, TRUE, + VALUE_PAIR_DOUBLE); + gtk_box_pack_start (GTK_BOX (hbox), ifsD->prob_pair->scale, TRUE, TRUE, 0); + gtk_widget_show (ifsD->prob_pair->scale); + gtk_box_pack_start (GTK_BOX (hbox), ifsD->prob_pair->spin, FALSE, TRUE, 0); + gtk_widget_show (ifsD->prob_pair->spin); + + gtk_widget_show (hbox); + gtk_widget_show (vbox); + gtk_widget_show (ifsD->current_frame); + + gtk_widget_show (main_vbox); + + if (ifsvals.num_elements == 0) + { + ifs_compose_set_defaults (); + } + else + { + gint i; + gdouble ratio = (gdouble) ifsD->drawable_height / ifsD->drawable_width; + + element_selected = g_new (gint, ifsvals.num_elements); + element_selected[0] = TRUE; + for (i = 1; i < ifsvals.num_elements; i++) + element_selected[i] = FALSE; + + if (ratio != ifsvals.aspect_ratio) + { + /* Adjust things so that what fit onto the old image, fits + onto the new image */ + Aff2 t1, t2, t3; + gdouble x_offset, y_offset; + gdouble center_x, center_y; + gdouble scale; + + if (ratio < ifsvals.aspect_ratio) + { + scale = ratio/ifsvals.aspect_ratio; + x_offset = (1-scale)/2; + y_offset = 0; + } + else + { + scale = 1; + x_offset = 0; + y_offset = (ratio - ifsvals.aspect_ratio)/2; + } + aff2_scale (&t1, scale, 0); + aff2_translate (&t2, x_offset, y_offset); + aff2_compose (&t3, &t2, &t1); + aff2_invert (&t1, &t3); + + aff2_apply (&t3, ifsvals.center_x, ifsvals.center_y, ¢er_x, + ¢er_y); + + for (i = 0; i < ifsvals.num_elements; i++) + { + aff_element_compute_trans (elements[i],1, ifsvals.aspect_ratio, + ifsvals.center_x, ifsvals.center_y); + aff2_compose (&t2, &elements[i]->trans, &t1); + aff2_compose (&elements[i]->trans, &t3, &t2); + aff_element_decompose_trans (elements[i],&elements[i]->trans, + 1, ifsvals.aspect_ratio, + center_x, center_y); + } + ifsvals.center_x = center_x; + ifsvals.center_y = center_y; + + ifsvals.aspect_ratio = ratio; + } + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_color_trans (elements[i]); + /* boundary and spatial transformations will be computed + when the design_area gets a ConfigureNotify event */ + + set_current_element (0); + + ifsD->selected_orig = g_new (AffElement, ifsvals.num_elements); + } + + gtk_widget_show (dialog); + + ifs_compose_preview (); + + gtk_main (); + + g_object_unref (ifsDesign->ui_manager); + + if (dialog) + gtk_widget_destroy (dialog); + + if (ifsOptD) + gtk_widget_destroy (ifsOptD->dialog); + + gdk_flush (); + + g_free (ifsD); + + return ifscint.run; +} + +static void +design_area_create (GtkWidget *window, + gint design_width, + gint design_height) +{ + ifsDesign = g_new0 (IfsDesignArea, 1); + + ifsDesign->op = OP_TRANSLATE; + + ifsDesign->area = gtk_drawing_area_new (); + gtk_widget_set_size_request (ifsDesign->area, design_width, design_height); + + g_signal_connect (ifsDesign->area, "realize", + G_CALLBACK (design_area_realize), + NULL); + g_signal_connect (ifsDesign->area, "expose-event", + G_CALLBACK (design_area_expose), + NULL); + g_signal_connect (ifsDesign->area, "button-press-event", + G_CALLBACK (design_area_button_press), + NULL); + g_signal_connect (ifsDesign->area, "button-release-event", + G_CALLBACK (design_area_button_release), + NULL); + g_signal_connect (ifsDesign->area, "motion-notify-event", + G_CALLBACK (design_area_motion), + NULL); + g_signal_connect (ifsDesign->area, "configure-event", + G_CALLBACK (design_area_configure), + NULL); + gtk_widget_set_events (ifsDesign->area, + GDK_EXPOSURE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK); + + ifsDesign->ui_manager = design_op_menu_create (window); + design_op_actions_update (); +} + +static GtkUIManager * +design_op_menu_create (GtkWidget *window) +{ + static GtkActionEntry actions[] = + { + { "ifs-compose-menu", NULL, "IFS Fractal Menu" }, + + { "new", GIMP_ICON_DOCUMENT_NEW, + N_("_New"), "<primary>N", NULL, + G_CALLBACK (ifs_compose_new_callback) }, + + { "delete", GIMP_ICON_EDIT_DELETE, + N_("_Delete"), "<primary>D", NULL, + G_CALLBACK (ifs_compose_delete_callback) }, + + { "undo", GIMP_ICON_EDIT_UNDO, + N_("_Undo"), "<primary>Z", NULL, + G_CALLBACK (undo) }, + + { "redo", GIMP_ICON_EDIT_REDO, + N_("_Redo"), "<primary>Y", NULL, + G_CALLBACK (redo) }, + + { "select-all", GIMP_ICON_SELECTION_ALL, + N_("Select _All"), "<primary>A", NULL, + G_CALLBACK (design_area_select_all_callback) }, + + { "center", GIMP_ICON_CENTER, + N_("Re_center"), "<primary>C", N_("Recompute Center"), + G_CALLBACK (recompute_center_cb) }, + + { "options", GIMP_ICON_PREFERENCES_SYSTEM, + N_("Render Options"), NULL, NULL, + G_CALLBACK (ifs_compose_options_callback) } + }; + static GtkRadioActionEntry radio_actions[] = + { + { "move", GIMP_ICON_TOOL_MOVE, + N_("Move"), "M", NULL, OP_TRANSLATE }, + + { "rotate", GIMP_ICON_TOOL_ROTATE, + N_("Rotate"), "R", N_("Rotate / Scale"), OP_ROTATE }, + + { "stretch", GIMP_ICON_TOOL_PERSPECTIVE, + N_("Stretch"), "S", NULL, OP_STRETCH } + }; + + GtkUIManager *ui_manager = gtk_ui_manager_new (); + GtkActionGroup *group = gtk_action_group_new ("Actions"); + + gtk_action_group_set_translation_domain (group, NULL); + + gtk_action_group_add_actions (group, + actions, + G_N_ELEMENTS (actions), + window); + gtk_action_group_add_radio_actions (group, + radio_actions, + G_N_ELEMENTS (radio_actions), + ifsDesign->op, + G_CALLBACK (design_op_update_callback), + window); + + gtk_window_add_accel_group (GTK_WINDOW (window), + gtk_ui_manager_get_accel_group (ui_manager)); + gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager)); + + gtk_ui_manager_insert_action_group (ui_manager, group, -1); + g_object_unref (group); + + gtk_ui_manager_add_ui_from_string (ui_manager, + "<ui>" + " <menubar name=\"dummy-menubar\">" + " <menu action=\"ifs-compose-menu\">" + " <menuitem action=\"move\" />" + " <menuitem action=\"rotate\" />" + " <menuitem action=\"stretch\" />" + " <separator />" + " <menuitem action=\"new\" />" + " <menuitem action=\"delete\" />" + " <menuitem action=\"undo\" />" + " <menuitem action=\"redo\" />" + " <menuitem action=\"select-all\" />" + " <menuitem action=\"center\" />" + " <separator />" + " <menuitem action=\"options\" />" + " </menu>" + " </menubar>" + "</ui>", + -1, NULL); + + gtk_ui_manager_add_ui_from_string (ui_manager, + "<ui>" + " <toolbar name=\"ifs-compose-toolbar\">" + " <toolitem action=\"move\" />" + " <toolitem action=\"rotate\" />" + " <toolitem action=\"stretch\" />" + " <separator />" + " <toolitem action=\"new\" />" + " <toolitem action=\"delete\" />" + " <toolitem action=\"undo\" />" + " <toolitem action=\"redo\" />" + " <toolitem action=\"select-all\" />" + " <toolitem action=\"center\" />" + " <separator />" + " <toolitem action=\"options\" />" + " </toolbar>" + "</ui>", + -1, NULL); + + return ui_manager; +} + +static void +design_op_actions_update (void) +{ + GtkAction *act; + + act = gtk_ui_manager_get_action (ifsDesign->ui_manager, + "/ui/dummy-menubar/ifs-compose-menu/undo"); + gtk_action_set_sensitive (act, undo_cur >= 0); + + act = gtk_ui_manager_get_action (ifsDesign->ui_manager, + "/ui/dummy-menubar/ifs-compose-menu/redo"); + gtk_action_set_sensitive (act, undo_cur != undo_num - 1); + + act = gtk_ui_manager_get_action (ifsDesign->ui_manager, + "/ui/dummy-menubar/ifs-compose-menu/delete"); + gtk_action_set_sensitive (act, ifsvals.num_elements > 2); +} + +static void +ifs_options_dialog (GtkWidget *parent) +{ + if (!ifsOptD) + { + GtkWidget *table; + GtkWidget *label; + + ifsOptD = g_new0 (IfsOptionsDialog, 1); + + ifsOptD->dialog = + gimp_dialog_new (_("IFS Fractal Render Options"), PLUG_IN_ROLE, + parent, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + g_signal_connect (ifsOptD->dialog, "response", + G_CALLBACK (gtk_widget_hide), + NULL); + + /* Table of options */ + + table = gtk_table_new (4, 3, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (ifsOptD->dialog))), + table, FALSE, FALSE, 0); + gtk_widget_show (table); + + label = gtk_label_new (_("Max. memory:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsOptD->memory_pair = value_pair_create (&ifsvals.max_memory, + 1, 1000000, FALSE, + VALUE_PAIR_INT); + gtk_table_attach (GTK_TABLE (table), ifsOptD->memory_pair->spin, + 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsOptD->memory_pair->spin); + + label = gtk_label_new (_("Iterations:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsOptD->iterations_pair = value_pair_create (&ifsvals.iterations, + 1, 10000000, FALSE, + VALUE_PAIR_INT); + gtk_table_attach (GTK_TABLE (table), ifsOptD->iterations_pair->spin, + 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsOptD->iterations_pair->spin); + gtk_widget_show (label); + + label = gtk_label_new (_("Subdivide:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsOptD->subdivide_pair = value_pair_create (&ifsvals.subdivide, + 1, 10, FALSE, + VALUE_PAIR_INT); + gtk_table_attach (GTK_TABLE (table), ifsOptD->subdivide_pair->spin, + 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsOptD->subdivide_pair->spin); + + label = gtk_label_new (_("Spot radius:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + ifsOptD->radius_pair = value_pair_create (&ifsvals.radius, + 0, 5, TRUE, + VALUE_PAIR_DOUBLE); + gtk_table_attach (GTK_TABLE (table), ifsOptD->radius_pair->scale, + 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsOptD->radius_pair->scale); + gtk_table_attach (GTK_TABLE (table), ifsOptD->radius_pair->spin, + 2, 3, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (ifsOptD->radius_pair->spin); + + value_pair_update (ifsOptD->iterations_pair); + value_pair_update (ifsOptD->subdivide_pair); + value_pair_update (ifsOptD->memory_pair); + value_pair_update (ifsOptD->radius_pair); + + gtk_widget_show (ifsOptD->dialog); + } + else + { + gtk_window_present (GTK_WINDOW (ifsOptD->dialog)); + } +} + +static void +ifs_compose (gint32 drawable_id) +{ + GeglBuffer *buffer = gimp_drawable_get_shadow_buffer (drawable_id); + gint width = gimp_drawable_width (drawable_id); + gint height = gimp_drawable_height (drawable_id); + gboolean alpha = gimp_drawable_has_alpha (drawable_id); + const Babl *format; + gint num_bands; + gint band_height; + gint band_y; + gint band_no; + gint i, j; + guchar *data; + guchar *mask = NULL; + guchar *nhits; + guchar rc, gc, bc; + GimpRGB color; + + if (alpha) + format = babl_format ("R'G'B'A u8"); + else + format = babl_format ("R'G'B' u8"); + + num_bands = ceil ((gdouble) (width * height * SQR (ifsvals.subdivide) * 5) + / (1024 * ifsvals.max_memory)); + band_height = (height + num_bands - 1) / num_bands; + + if (band_height > height) + band_height = height; + + mask = g_new (guchar, width * band_height * SQR (ifsvals.subdivide)); + data = g_new (guchar, width * band_height * SQR (ifsvals.subdivide) * 3); + nhits = g_new (guchar, width * band_height * SQR (ifsvals.subdivide)); + + gimp_context_get_background (&color); + gimp_rgb_get_uchar (&color, &rc, &gc, &bc); + + for (band_no = 0, band_y = 0; band_no < num_bands; band_no++) + { + GeglBufferIterator *iter; + GeglRectangle *roi; + + gimp_progress_init_printf (_("Rendering IFS (%d/%d)"), + band_no + 1, num_bands); + + /* render the band to a buffer */ + if (band_y + band_height > height) + band_height = height - band_y; + + /* we don't need to clear data since we store nhits */ + memset (mask, 0, width * band_height * SQR (ifsvals.subdivide)); + memset (nhits, 0, width * band_height * SQR (ifsvals.subdivide)); + + ifs_render (elements, + ifsvals.num_elements, width, height, ifsvals.iterations, + &ifsvals, band_y, band_height, data, mask, nhits, FALSE); + + /* transfer the image to the drawable */ + + iter = gegl_buffer_iterator_new (buffer, + GEGL_RECTANGLE (0, band_y, + width, band_height), 0, + format, + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); + roi = &iter->items[0].roi; + + while (gegl_buffer_iterator_next (iter)) + { + guchar *destrow = iter->items[0].data; + + for (j = roi->y; j < (roi->y + roi->height); j++) + { + guchar *dest = destrow; + + for (i = roi->x; i < (roi->x + roi->width); i++) + { + /* Accumulate a reduced pixel */ + + gint rtot = 0; + gint btot = 0; + gint gtot = 0; + gint mtot = 0; + gint ii, jj; + + for (jj = 0; jj < ifsvals.subdivide; jj++) + { + guchar *ptr; + guchar *maskptr; + + ptr = data + + 3 * (((j - band_y) * ifsvals.subdivide + jj) * + ifsvals.subdivide * width + + i * ifsvals.subdivide); + + maskptr = mask + + ((j - band_y) * ifsvals.subdivide + jj) * + ifsvals.subdivide * width + + i * ifsvals.subdivide; + + for (ii = 0; ii < ifsvals.subdivide; ii++) + { + guchar maskval = *maskptr++; + + mtot += maskval; + rtot += maskval* *ptr++; + gtot += maskval* *ptr++; + btot += maskval* *ptr++; + } + } + + if (mtot) + { + rtot /= mtot; + gtot /= mtot; + btot /= mtot; + mtot /= SQR (ifsvals.subdivide); + } + + if (alpha) + { + *dest++ = rtot; + *dest++ = gtot; + *dest++ = btot; + *dest++ = mtot; + } + else + { + *dest++ = (mtot * rtot + (255 - mtot) * rc) / 255; + *dest++ = (mtot * gtot + (255 - mtot) * gc) / 255; + *dest++ = (mtot * btot + (255 - mtot) * bc) / 255; + } + } + + if (alpha) + destrow += roi->width * 4; + else + destrow += roi->width * 3; + } + } + + band_y += band_height; + } + + g_free (mask); + g_free (data); + g_free (nhits); + + g_object_unref (buffer); + + gimp_drawable_merge_shadow (drawable_id, TRUE); + gimp_drawable_update (drawable_id, 0, 0, width, height); +} + +static void +update_values (void) +{ + ifsD->in_update = TRUE; + + ifsD->current_vals = elements[ifsD->current_element]->v; + ifsD->current_vals.theta *= 180/G_PI; + + value_pair_update (ifsD->prob_pair); + value_pair_update (ifsD->x_pair); + value_pair_update (ifsD->y_pair); + value_pair_update (ifsD->scale_pair); + value_pair_update (ifsD->angle_pair); + value_pair_update (ifsD->asym_pair); + value_pair_update (ifsD->shear_pair); + color_map_update (ifsD->red_cmap); + color_map_update (ifsD->green_cmap); + color_map_update (ifsD->blue_cmap); + color_map_update (ifsD->black_cmap); + color_map_update (ifsD->target_cmap); + value_pair_update (ifsD->hue_scale_pair); + value_pair_update (ifsD->value_scale_pair); + + if (elements[ifsD->current_element]->v.simple_color) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ifsD->simple_button), + TRUE); + else + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ifsD->full_button), + TRUE); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ifsD->flip_check_button), + elements[ifsD->current_element]->v.flip); + + ifsD->in_update = FALSE; + + simple_color_set_sensitive (); +} + +static void +set_current_element (gint index) +{ + gchar *frame_name = g_strdup_printf (_("Transformation %s"), + elements[index]->name); + + ifsD->current_element = index; + + gtk_frame_set_label (GTK_FRAME (ifsD->current_frame),frame_name); + g_free (frame_name); + + update_values (); +} + +static void +design_area_realize (GtkWidget *widget) +{ + const gint cursors[3] = + { + GDK_FLEUR, /* OP_TRANSLATE */ + GDK_EXCHANGE, /* OP_ROTATE */ + GDK_CROSSHAIR /* OP_SHEAR */ + }; + + GdkDisplay *display = gtk_widget_get_display (widget); + GdkCursor *cursor = gdk_cursor_new_for_display (display, + cursors[ifsDesign->op]); + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_cursor_unref (cursor); +} + +static gboolean +design_area_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state = gtk_widget_get_state (widget); + cairo_t *cr; + GtkAllocation allocation; + PangoLayout *layout; + gint i; + gint cx, cy; + + gtk_widget_get_allocation (widget, &allocation); + + cr = gdk_cairo_create (ifsDesign->pixmap); + + gdk_cairo_set_source_color (cr, &style->bg[state]); + cairo_paint (cr); + + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_translate (cr, 0.5, 0.5); + + /* draw an indicator for the center */ + + cx = ifsvals.center_x * allocation.width; + cy = ifsvals.center_y * allocation.width; + + cairo_move_to (cr, cx - 10, cy); + cairo_line_to (cr, cx + 10, cy); + + cairo_move_to (cr, cx, cy - 10); + cairo_line_to (cr, cx, cy + 10); + + gdk_cairo_set_source_color (cr, &style->fg[state]); + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + + layout = gtk_widget_create_pango_layout (widget, NULL); + + for (i = 0; i < ifsvals.num_elements; i++) + { + aff_element_draw (elements[i], element_selected[i], + allocation.width, + allocation.height, + cr, + &style->fg[state], + layout); + } + + g_object_unref (layout); + + cairo_destroy (cr); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gdk_cairo_set_source_pixmap (cr, ifsDesign->pixmap, 0.0, 0.0); + cairo_paint (cr); + + cairo_destroy (cr); + + return FALSE; +} + +static gboolean +design_area_configure (GtkWidget *widget, + GdkEventConfigure *event) +{ + GtkAllocation allocation; + gint i; + + gtk_widget_get_allocation (widget, &allocation); + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_boundary (elements[i], + allocation.width, allocation.height, + elements, ifsvals.num_elements); + + if (ifsDesign->pixmap) + { + g_object_unref (ifsDesign->pixmap); + } + ifsDesign->pixmap = gdk_pixmap_new (gtk_widget_get_window (widget), + allocation.width, + allocation.height, + -1); /* Is this correct? */ + + return FALSE; +} + +static gint +design_area_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkAllocation allocation; + gint i; + gint old_current; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + gtk_widget_grab_focus (widget); + + if (gdk_event_triggers_context_menu ((GdkEvent *) event)) + { + GtkWidget *menu = + gtk_ui_manager_get_widget (ifsDesign->ui_manager, + "/dummy-menubar/ifs-compose-menu"); + + if (GTK_IS_MENU_ITEM (menu)) + menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu)); + + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget)); + + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, + event->button, event->time); + + return FALSE; + } + + old_current = ifsD->current_element; + ifsD->current_element = -1; + + /* Find out where the button press was */ + for (i = 0; i < ifsvals.num_elements; i++) + { + if (ipolygon_contains (elements[i]->click_boundary, event->x, event->y)) + { + set_current_element (i); + break; + } + } + + /* if the user started manipulating an object, set up a new + position on the undo ring */ + if (ifsD->current_element >= 0) + undo_begin (); + + if (!(event->state & GDK_SHIFT_MASK) + && ( (ifsD->current_element<0) + || !element_selected[ifsD->current_element] )) + { + for (i = 0; i < ifsvals.num_elements; i++) + element_selected[i] = FALSE; + } + + if (ifsD->current_element >= 0) + { + ifsDesign->button_state |= GDK_BUTTON1_MASK; + + element_selected[ifsD->current_element] = TRUE; + + ifsDesign->num_selected = 0; + ifsDesign->op_xcenter = 0.0; + ifsDesign->op_ycenter = 0.0; + for (i = 0; i < ifsvals.num_elements; i++) + { + if (element_selected[i]) + { + ifsD->selected_orig[i] = *elements[i]; + ifsDesign->op_xcenter += elements[i]->v.x; + ifsDesign->op_ycenter += elements[i]->v.y; + ifsDesign->num_selected++; + undo_update (i); + } + } + ifsDesign->op_xcenter /= ifsDesign->num_selected; + ifsDesign->op_ycenter /= ifsDesign->num_selected; + ifsDesign->op_x = (gdouble)event->x / allocation.width; + ifsDesign->op_y = (gdouble)event->y / allocation.width; + ifsDesign->op_center_x = ifsvals.center_x; + ifsDesign->op_center_y = ifsvals.center_y; + } + else + { + ifsD->current_element = old_current; + element_selected[old_current] = TRUE; + } + + design_area_redraw (); + + return FALSE; +} + +static gint +design_area_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + if (event->button == 1 && + (ifsDesign->button_state & GDK_BUTTON1_MASK)) + { + ifsDesign->button_state &= ~GDK_BUTTON1_MASK; + ifs_compose_preview (); + } + return FALSE; +} + +static gint +design_area_motion (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkAllocation allocation; + gint i; + gdouble xo; + gdouble yo; + gdouble xn; + gdouble yn; + Aff2 trans, t1, t2, t3; + + if (! (ifsDesign->button_state & GDK_BUTTON1_MASK)) + return FALSE; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + xo = (ifsDesign->op_x - ifsDesign->op_xcenter); + yo = (ifsDesign->op_y - ifsDesign->op_ycenter); + xn = (gdouble) event->x / allocation.width - ifsDesign->op_xcenter; + yn = (gdouble) event->y / allocation.width - ifsDesign->op_ycenter; + + switch (ifsDesign->op) + { + case OP_ROTATE: + aff2_translate (&t1,-ifsDesign->op_xcenter * allocation.width, + -ifsDesign->op_ycenter * allocation.width); + aff2_scale (&t2, + sqrt((SQR(xn)+SQR(yn))/(SQR(xo)+SQR(yo))), + 0); + aff2_compose (&t3, &t2, &t1); + aff2_rotate (&t1, - atan2(yn, xn) + atan2(yo, xo)); + aff2_compose (&t2, &t1, &t3); + aff2_translate (&t3, ifsDesign->op_xcenter * allocation.width, + ifsDesign->op_ycenter * allocation.width); + aff2_compose (&trans, &t3, &t2); + break; + + case OP_STRETCH: + aff2_translate (&t1,-ifsDesign->op_xcenter * allocation.width, + -ifsDesign->op_ycenter * allocation.width); + aff2_compute_stretch (&t2, xo, yo, xn, yn); + aff2_compose (&t3, &t2, &t1); + aff2_translate (&t1, ifsDesign->op_xcenter * allocation.width, + ifsDesign->op_ycenter * allocation.width); + aff2_compose (&trans, &t1, &t3); + break; + + case OP_TRANSLATE: + aff2_translate (&trans, + (xn-xo) * allocation.width, + (yn-yo) * allocation.width); + break; + } + + for (i = 0; i < ifsvals.num_elements; i++) + if (element_selected[i]) + { + if (ifsDesign->num_selected == ifsvals.num_elements) + { + gdouble cx, cy; + aff2_invert (&t1, &trans); + aff2_compose (&t2, &trans, &ifsD->selected_orig[i].trans); + aff2_compose (&elements[i]->trans, &t2, &t1); + + cx = ifsDesign->op_center_x * allocation.width; + cy = ifsDesign->op_center_y * allocation.width; + aff2_apply (&trans, cx, cy, &cx, &cy); + ifsvals.center_x = cx / allocation.width; + ifsvals.center_y = cy / allocation.width; + } + else + { + aff2_compose (&elements[i]->trans, &trans, + &ifsD->selected_orig[i].trans); + } + + aff_element_decompose_trans (elements[i],&elements[i]->trans, + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + } + + update_values (); + design_area_redraw (); + + /* Ask for more motion events in case the event was a hint */ + gdk_event_request_motions (event); + + return FALSE; +} + +static void +design_area_redraw (void) +{ + GtkAllocation allocation; + gint i; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_boundary (elements[i], + allocation.width, allocation.height, + elements, ifsvals.num_elements); + + gtk_widget_queue_draw (ifsDesign->area); +} + +/* Undo ring functions */ +static void +undo_begin (void) +{ + gint i, j; + gint to_delete; + gint new_index; + + if (undo_cur == UNDO_LEVELS-1) + { + to_delete = 1; + undo_start = (undo_start + 1) % UNDO_LEVELS; + } + else + { + undo_cur++; + to_delete = undo_num - undo_cur; + } + + undo_num = undo_num - to_delete + 1; + new_index = (undo_start + undo_cur) % UNDO_LEVELS; + + /* remove any redo elements or the oldest element if necessary */ + for (j = new_index; to_delete > 0; j = (j+1) % UNDO_LEVELS, to_delete--) + { + for (i = 0; i < undo_ring[j].ifsvals.num_elements; i++) + if (undo_ring[j].elements[i]) + aff_element_free (undo_ring[j].elements[i]); + g_free (undo_ring[j].elements); + g_free (undo_ring[j].element_selected); + } + + undo_ring[new_index].ifsvals = ifsvals; + undo_ring[new_index].elements = g_new (AffElement *,ifsvals.num_elements); + undo_ring[new_index].element_selected = g_new (gboolean, + ifsvals.num_elements); + undo_ring[new_index].current_element = ifsD->current_element; + + for (i = 0; i < ifsvals.num_elements; i++) + { + undo_ring[new_index].elements[i] = NULL; + undo_ring[new_index].element_selected[i] = element_selected[i]; + } + + design_op_actions_update (); +} + +static void +undo_update (gint el) +{ + AffElement *elem; + /* initialize */ + + elem = NULL; + + if (!undo_ring[(undo_start + undo_cur) % UNDO_LEVELS].elements[el]) + undo_ring[(undo_start + undo_cur) % UNDO_LEVELS].elements[el] + = elem = g_new (AffElement, 1); + + *elem = *elements[el]; + elem->draw_boundary = NULL; + elem->click_boundary = NULL; +} + +static void +undo_exchange (gint el) +{ + GtkAllocation allocation; + gint i; + AffElement **telements; + gboolean *tselected; + IfsComposeVals tifsvals; + gint tcurrent; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + /* swap the arrays and values*/ + telements = elements; + elements = undo_ring[el].elements; + undo_ring[el].elements = telements; + + tifsvals = ifsvals; + ifsvals = undo_ring[el].ifsvals; + undo_ring[el].ifsvals = tifsvals; + + tselected = element_selected; + element_selected = undo_ring[el].element_selected; + undo_ring[el].element_selected = tselected; + + tcurrent = ifsD->current_element; + ifsD->current_element = undo_ring[el].current_element; + undo_ring[el].current_element = tcurrent; + + /* now swap back any unchanged elements */ + for (i = 0; i < ifsvals.num_elements; i++) + if (!elements[i]) + { + elements[i] = undo_ring[el].elements[i]; + undo_ring[el].elements[i] = NULL; + } + else + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + + set_current_element (ifsD->current_element); + + design_area_redraw (); + + ifs_compose_preview (); +} + +static void +undo (void) +{ + if (undo_cur >= 0) + { + undo_exchange ((undo_start + undo_cur) % UNDO_LEVELS); + undo_cur--; + } + + design_op_actions_update (); +} + +static void +redo (void) +{ + if (undo_cur != undo_num - 1) + { + undo_cur++; + undo_exchange ((undo_start + undo_cur) % UNDO_LEVELS); + } + + design_op_actions_update (); +} + +static void +design_area_select_all_callback (GtkWidget *widget, + gpointer data) +{ + gint i; + + for (i = 0; i < ifsvals.num_elements; i++) + element_selected[i] = TRUE; + + design_area_redraw (); +} + +/* Interface functions */ + +static void +val_changed_update (void) +{ + GtkAllocation allocation; + AffElement *cur; + + if (ifsD->in_update) + return; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + cur = elements[ifsD->current_element]; + + undo_begin (); + undo_update (ifsD->current_element); + + cur->v = ifsD->current_vals; + cur->v.theta *= G_PI/180.0; + aff_element_compute_trans (cur, + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + aff_element_compute_color_trans (cur); + + design_area_redraw (); + + ifs_compose_preview (); +} + +/* Pseudo-widget representing a color mapping */ + +#define COLOR_SAMPLE_SIZE 30 + +static ColorMap * +color_map_create (const gchar *name, + GimpRGB *orig_color, + GimpRGB *data, + gboolean fixed_point) +{ + GtkWidget *frame; + GtkWidget *arrow; + ColorMap *color_map = g_new (ColorMap, 1); + + gimp_rgb_set_alpha (data, 1.0); + color_map->color = data; + color_map->fixed_point = fixed_point; + color_map->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (color_map->hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + color_map->orig_preview = + gimp_color_area_new (fixed_point ? data : orig_color, + GIMP_COLOR_AREA_FLAT, 0); + gtk_drag_dest_unset (color_map->orig_preview); + gtk_widget_set_size_request (color_map->orig_preview, + COLOR_SAMPLE_SIZE, COLOR_SAMPLE_SIZE); + gtk_container_add (GTK_CONTAINER (frame), color_map->orig_preview); + gtk_widget_show (color_map->orig_preview); + + arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (color_map->hbox), arrow, FALSE, FALSE, 0); + gtk_widget_show (arrow); + + color_map->button = gimp_color_button_new (name, + COLOR_SAMPLE_SIZE, + COLOR_SAMPLE_SIZE, + data, + GIMP_COLOR_AREA_FLAT); + gtk_box_pack_start (GTK_BOX (color_map->hbox), color_map->button, + FALSE, FALSE, 0); + gtk_widget_show (color_map->button); + + g_signal_connect (color_map->button, "color-changed", + G_CALLBACK (gimp_color_button_get_color), + data); + + g_signal_connect (color_map->button, "color-changed", + G_CALLBACK (color_map_color_changed_cb), + color_map); + + return color_map; +} + +static void +color_map_color_changed_cb (GtkWidget *widget, + ColorMap *color_map) +{ + if (ifsD->in_update) + return; + + undo_begin (); + undo_update (ifsD->current_element); + + elements[ifsD->current_element]->v = ifsD->current_vals; + elements[ifsD->current_element]->v.theta *= G_PI/180.0; + aff_element_compute_color_trans (elements[ifsD->current_element]); + + update_values (); + + ifs_compose_preview (); +} + +static void +color_map_update (ColorMap *color_map) +{ + gimp_color_button_set_color (GIMP_COLOR_BUTTON (color_map->button), + color_map->color); + + if (color_map->fixed_point) + gimp_color_area_set_color (GIMP_COLOR_AREA (color_map->orig_preview), + color_map->color); +} + +static void +simple_color_set_sensitive (void) +{ + gint sc = elements[ifsD->current_element]->v.simple_color; + + gtk_widget_set_sensitive (ifsD->target_cmap->hbox, sc); + gtk_widget_set_sensitive (ifsD->hue_scale_pair->scale, sc); + gtk_widget_set_sensitive (ifsD->hue_scale_pair->spin, sc); + gtk_widget_set_sensitive (ifsD->value_scale_pair->scale, sc); + gtk_widget_set_sensitive (ifsD->value_scale_pair->spin, sc); + + gtk_widget_set_sensitive (ifsD->red_cmap->hbox, !sc); + gtk_widget_set_sensitive (ifsD->green_cmap->hbox, !sc); + gtk_widget_set_sensitive (ifsD->blue_cmap->hbox, !sc); + gtk_widget_set_sensitive (ifsD->black_cmap->hbox, !sc); +} + +static void +simple_color_toggled (GtkWidget *widget, + gpointer data) +{ + AffElement *cur = elements[ifsD->current_element]; + + cur->v.simple_color = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + ifsD->current_vals.simple_color = cur->v.simple_color; + + if (cur->v.simple_color) + aff_element_compute_color_trans (cur); + + val_changed_update (); + simple_color_set_sensitive (); +} + +/* Generic mechanism for scale/entry combination (possibly without + scale) */ + +static ValuePair * +value_pair_create (gpointer data, + gdouble lower, + gdouble upper, + gboolean create_scale, + ValuePairType type) +{ + + ValuePair *value_pair = g_new (ValuePair, 1); + + value_pair->data.d = data; + value_pair->type = type; + value_pair->timeout_id = 0; + + value_pair->adjustment = (GtkAdjustment *) + gtk_adjustment_new (1.0, lower, upper, + (upper - lower) / 100, + (upper - lower) / 10, + 0.0); + value_pair->spin = gimp_spin_button_new (value_pair->adjustment, 1.0, 3); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (value_pair->spin), TRUE); + gtk_widget_set_size_request (value_pair->spin, 72, -1); + + g_signal_connect (value_pair->adjustment, "value-changed", + G_CALLBACK (value_pair_scale_callback), + value_pair); + + if (create_scale) + { + value_pair->scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, + value_pair->adjustment); + + if (type == VALUE_PAIR_INT) + gtk_scale_set_digits (GTK_SCALE (value_pair->scale), 0); + else + gtk_scale_set_digits (GTK_SCALE (value_pair->scale), 3); + + gtk_scale_set_draw_value (GTK_SCALE (value_pair->scale), FALSE); + } + else + { + value_pair->scale = NULL; + } + + return value_pair; +} + +static void +value_pair_update (ValuePair *value_pair) +{ + if (value_pair->type == VALUE_PAIR_INT) + gtk_adjustment_set_value (value_pair->adjustment, *value_pair->data.i); + else + gtk_adjustment_set_value (value_pair->adjustment, *value_pair->data.d); + +} + +static gboolean +value_pair_scale_callback_real (gpointer data) +{ + ValuePair *value_pair = data; + gint changed = FALSE; + + if (value_pair->type == VALUE_PAIR_DOUBLE) + { + if ((gdouble) *value_pair->data.d != + gtk_adjustment_get_value (value_pair->adjustment)) + { + changed = TRUE; + *value_pair->data.d = gtk_adjustment_get_value (value_pair->adjustment); + } + } + else + { + if (*value_pair->data.i != + (gint) gtk_adjustment_get_value (value_pair->adjustment)) + { + changed = TRUE; + *value_pair->data.i = gtk_adjustment_get_value (value_pair->adjustment); + } + } + + if (changed) + val_changed_update (); + + value_pair->timeout_id = 0; + + return FALSE; +} + +static void +value_pair_scale_callback (GtkAdjustment *adjustment, + ValuePair *value_pair) +{ + if (value_pair->timeout_id != 0) + return; + + value_pair->timeout_id = g_timeout_add (500, /* update every half second */ + value_pair_scale_callback_real, + value_pair); +} + +static void +design_op_update_callback (GtkRadioAction *action, + GtkRadioAction *current, + gpointer data) +{ + ifsDesign->op = gtk_radio_action_get_current_value (action); + + /* cursor switch */ + if (gtk_widget_get_realized (ifsDesign->area)) + design_area_realize (ifsDesign->area); +} + +static void +recompute_center_cb (GtkWidget *widget, + gpointer data) +{ + recompute_center (TRUE); +} + +static void +recompute_center (gboolean save_undo) +{ + GtkAllocation allocation; + gint i; + gdouble x, y; + gdouble center_x = 0.0; + gdouble center_y = 0.0; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + if (save_undo) + undo_begin (); + + for (i = 0; i < ifsvals.num_elements; i++) + { + if (save_undo) + undo_update (i); + + aff_element_compute_trans (elements[i],1, ifsvals.aspect_ratio, + ifsvals.center_x, ifsvals.center_y); + aff2_fixed_point (&elements[i]->trans, &x, &y); + center_x += x; + center_y += y; + } + + ifsvals.center_x = center_x/ifsvals.num_elements; + ifsvals.center_y = center_y/ifsvals.num_elements; + + for (i = 0; i < ifsvals.num_elements; i++) + { + aff_element_decompose_trans (elements[i],&elements[i]->trans, + 1, ifsvals.aspect_ratio, + ifsvals.center_x, ifsvals.center_y); + } + + if (allocation.width > 1 && allocation.height > 1) + { + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + design_area_redraw (); + update_values (); + } +} + +static void +flip_check_button_callback (GtkWidget *widget, + gpointer data) +{ + GtkAllocation allocation; + guint i; + gboolean active; + + if (ifsD->in_update) + return; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + undo_begin (); + for (i = 0; i < ifsvals.num_elements; i++) + { + if (element_selected[i]) + { + undo_update (i); + elements[i]->v.flip = active; + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + } + } + + update_values (); + design_area_redraw (); + + ifs_compose_preview (); +} + +static void +ifs_compose_set_defaults (void) +{ + gint i; + GimpRGB color; + + gimp_context_get_foreground (&color); + + ifsvals.aspect_ratio = + (gdouble)ifsD->drawable_height / ifsD->drawable_width; + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_free (elements[i]); + count_for_naming = 0; + + ifsvals.num_elements = 3; + elements = g_realloc (elements, ifsvals.num_elements * sizeof(AffElement *)); + element_selected = g_realloc (element_selected, + ifsvals.num_elements * sizeof(gboolean)); + + elements[0] = aff_element_new (0.3, 0.37 * ifsvals.aspect_ratio, &color, + ++count_for_naming); + element_selected[0] = FALSE; + elements[1] = aff_element_new (0.7, 0.37 * ifsvals.aspect_ratio, &color, + ++count_for_naming); + element_selected[1] = FALSE; + elements[2] = aff_element_new (0.5, 0.7 * ifsvals.aspect_ratio, &color, + ++count_for_naming); + element_selected[2] = FALSE; + + ifsvals.center_x = 0.5; + ifsvals.center_y = 0.5 * ifsvals.aspect_ratio; + ifsvals.iterations = ifsD->drawable_height * ifsD->drawable_width; + ifsvals.subdivide = 3; + ifsvals.max_memory = 4096; + + if (ifsOptD) + { + value_pair_update (ifsOptD->iterations_pair); + value_pair_update (ifsOptD->subdivide_pair); + value_pair_update (ifsOptD->radius_pair); + value_pair_update (ifsOptD->memory_pair); + } + + ifsvals.radius = 0.7; + + set_current_element (0); + element_selected[0] = TRUE; + recompute_center (FALSE); + + if (ifsD->selected_orig) + g_free (ifsD->selected_orig); + + ifsD->selected_orig = g_new (AffElement, ifsvals.num_elements); +} + +/* show a transient message dialog */ +static void +ifscompose_message_dialog (GtkMessageType type, + GtkWindow *parent, + const gchar *title, + const gchar *message) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, 0, type, GTK_BUTTONS_OK, + "%s", message); + + if (title) + gtk_window_set_title (GTK_WINDOW (dialog), title); + + gtk_window_set_role (GTK_WINDOW (dialog), "ifscompose-message"); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +/* save an ifs file */ +static void +ifsfile_save_response (GtkWidget *dialog, + gint response_id, + gpointer data) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *filename; + gchar *str; + FILE *fh; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + str = ifsvals_stringify (&ifsvals, elements); + + fh = g_fopen (filename, "wb"); + if (! fh) + { + gchar *message = + g_strdup_printf (_("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), + g_strerror (errno)); + + ifscompose_message_dialog (GTK_MESSAGE_ERROR, GTK_WINDOW (dialog), + _("Save failed"), message); + + g_free (message); + g_free (filename); + + return; + } + + fputs (str, fh); + fclose (fh); + } + + gtk_widget_destroy (dialog); +} + +/* replace ifsvals and elements with specified new values + * recompute and update everything necessary */ +static void +ifsfile_replace_ifsvals (IfsComposeVals *new_ifsvals, + AffElement **new_elements) +{ + GtkAllocation allocation; + guint i; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_free (elements[i]); + g_free (elements); + + ifsvals = *new_ifsvals; + elements = new_elements; + for (i = 0; i < ifsvals.num_elements; i++) + { + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + aff_element_compute_color_trans (elements[i]); + } + + element_selected = g_realloc (element_selected, + ifsvals.num_elements * sizeof(gboolean)); + for (i = 0; i < ifsvals.num_elements; i++) + element_selected[i] = FALSE; + + if (ifsOptD) + { + value_pair_update (ifsOptD->iterations_pair); + value_pair_update (ifsOptD->subdivide_pair); + value_pair_update (ifsOptD->radius_pair); + value_pair_update (ifsOptD->memory_pair); + } + + set_current_element (0); + element_selected[0] = TRUE; + recompute_center (FALSE); + + if (ifsD->selected_orig) + g_free (ifsD->selected_orig); + + ifsD->selected_orig = g_new (AffElement, ifsvals.num_elements); +} + +/* load an ifs file */ +static void +ifsfile_load_response (GtkWidget *dialog, + gint response_id, + gpointer data) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *filename; + gchar *buffer; + AffElement **new_elements; + IfsComposeVals new_ifsvals; + GError *error = NULL; + guint i; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + if (! g_file_get_contents (filename, &buffer, NULL, &error)) + { + ifscompose_message_dialog (GTK_MESSAGE_ERROR, GTK_WINDOW (dialog), + _("Open failed"), error->message); + g_error_free (error); + g_free (filename); + return; + } + + if (! ifsvals_parse_string (buffer, &new_ifsvals, &new_elements)) + { + gchar *message = g_strdup_printf (_("File '%s' doesn't seem to be " + "an IFS Fractal file."), + gimp_filename_to_utf8 (filename)); + + ifscompose_message_dialog (GTK_MESSAGE_ERROR, GTK_WINDOW (dialog), + _("Open failed"), message); + g_free (filename); + g_free (message); + g_free (buffer); + + return; + } + + g_free (buffer); + g_free (filename); + + undo_begin (); + for (i = 0; i < ifsvals.num_elements; i++) + undo_update (i); + + ifsfile_replace_ifsvals (&new_ifsvals, new_elements); + + design_op_actions_update (); + + ifs_compose_preview (); + + design_area_redraw (); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +ifs_compose_save (GtkWidget *parent) +{ + static GtkWidget *dialog = NULL; + + if (! dialog) + { + dialog = + gtk_file_chooser_dialog_new (_("Save as IFS Fractal file"), + GTK_WINDOW (parent), + GTK_FILE_CHOOSER_ACTION_SAVE, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &dialog); + g_signal_connect (dialog, "response", + G_CALLBACK (ifsfile_save_response), + NULL); + } + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +ifs_compose_load (GtkWidget *parent) +{ + static GtkWidget *dialog = NULL; + + if (! dialog) + { + dialog = + gtk_file_chooser_dialog_new (_("Open IFS Fractal file"), + GTK_WINDOW (parent), + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &dialog); + g_signal_connect (dialog, "response", + G_CALLBACK (ifsfile_load_response), + NULL); + } + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +ifs_compose_new_callback (GtkAction *action, + gpointer data) +{ + GtkAllocation allocation; + GimpRGB color; + gint i; + AffElement *elem; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + undo_begin (); + + gimp_context_get_foreground (&color); + + elem = aff_element_new (0.5, 0.5 * allocation.height / allocation.width, + &color, + ++count_for_naming); + + ifsvals.num_elements++; + elements = g_realloc (elements, ifsvals.num_elements * sizeof (AffElement *)); + element_selected = g_realloc (element_selected, + ifsvals.num_elements * sizeof (gboolean)); + + for (i = 0; i < ifsvals.num_elements-1; i++) + element_selected[i] = FALSE; + element_selected[ifsvals.num_elements-1] = TRUE; + + elements[ifsvals.num_elements-1] = elem; + set_current_element (ifsvals.num_elements-1); + + ifsD->selected_orig = g_realloc (ifsD->selected_orig, + ifsvals.num_elements * sizeof(AffElement)); + aff_element_compute_trans (elem, + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + + design_area_redraw (); + + ifs_compose_preview (); + + design_op_actions_update (); +} + +static void +ifs_compose_delete_callback (GtkAction *action, + gpointer data) +{ + gint i; + gint new_current; + + undo_begin (); + undo_update (ifsD->current_element); + + aff_element_free (elements[ifsD->current_element]); + + if (ifsD->current_element < ifsvals.num_elements-1) + { + undo_update (ifsvals.num_elements-1); + elements[ifsD->current_element] = elements[ifsvals.num_elements-1]; + new_current = ifsD->current_element; + } + else + new_current = ifsvals.num_elements-2; + + ifsvals.num_elements--; + + for (i = 0; i < ifsvals.num_elements; i++) + if (element_selected[i]) + { + new_current = i; + break; + } + + element_selected[new_current] = TRUE; + set_current_element (new_current); + + design_area_redraw (); + + ifs_compose_preview (); + + design_op_actions_update (); +} + +static void +ifs_compose_options_callback (GtkAction *action, + gpointer data) +{ + ifs_options_dialog (GTK_WIDGET (data)); +} + +static gint +preview_idle_render (gpointer data) +{ + GtkAllocation allocation; + gint iterations = PREVIEW_RENDER_CHUNK; + gint i; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + if (iterations > ifsD->preview_iterations) + iterations = ifsD->preview_iterations; + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + + ifs_render (elements, ifsvals.num_elements, + allocation.width, allocation.height, + iterations,&ifsvals, 0, allocation.height, + ifsD->preview_data, NULL, NULL, TRUE); + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + + ifsD->preview_iterations -= iterations; + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (ifsD->preview), + 0, 0, allocation.width, allocation.height, + GIMP_RGB_IMAGE, + ifsD->preview_data, + allocation.width * 3); + + return (ifsD->preview_iterations != 0); +} + +static void +ifs_compose_preview (void) +{ + /* Expansion isn't really supported for previews */ + gint i; + gint width = ifsD->preview_width; + gint height = ifsD->preview_height; + guchar rc, gc, bc; + guchar *ptr; + GimpRGB color; + + if (!ifsD->preview_data) + ifsD->preview_data = g_new (guchar, 3 * width * height); + + gimp_context_get_background (&color); + gimp_rgb_get_uchar (&color, &rc, &gc, &bc); + + ptr = ifsD->preview_data; + for (i = 0; i < width * height; i++) + { + *ptr++ = rc; + *ptr++ = gc; + *ptr++ = bc; + } + + if (ifsD->preview_iterations == 0) + g_idle_add (preview_idle_render, NULL); + + ifsD->preview_iterations = + ifsvals.iterations * ((gdouble) width * height / + (ifsD->drawable_width * ifsD->drawable_height)); +} + +static void +ifs_compose_response (GtkWidget *widget, + gint response_id, + gpointer data) +{ + switch (response_id) + { + case RESPONSE_OPEN: + ifs_compose_load (widget); + break; + + case RESPONSE_SAVE: + ifs_compose_save (widget); + break; + + case RESPONSE_RESET: + { + GtkAllocation allocation; + gint i; + + gtk_widget_get_allocation (ifsDesign->area, &allocation); + + undo_begin (); + for (i = 0; i < ifsvals.num_elements; i++) + undo_update (i); + + ifs_compose_set_defaults (); + + ifs_compose_preview (); + + for (i = 0; i < ifsvals.num_elements; i++) + aff_element_compute_trans (elements[i], + allocation.width, allocation.height, + ifsvals.center_x, ifsvals.center_y); + + design_area_redraw (); + design_op_actions_update (); + } + break; + + case GTK_RESPONSE_OK: + ifscint.run = TRUE; + + default: + gtk_widget_destroy (widget); + break; + } +} |