/* 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 . */ /* 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 #include #include #include #include #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, "/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"), "N", NULL, G_CALLBACK (ifs_compose_new_callback) }, { "delete", GIMP_ICON_EDIT_DELETE, N_("_Delete"), "D", NULL, G_CALLBACK (ifs_compose_delete_callback) }, { "undo", GIMP_ICON_EDIT_UNDO, N_("_Undo"), "Z", NULL, G_CALLBACK (undo) }, { "redo", GIMP_ICON_EDIT_REDO, N_("_Redo"), "Y", NULL, G_CALLBACK (redo) }, { "select-all", GIMP_ICON_SELECTION_ALL, N_("Select _All"), "A", NULL, G_CALLBACK (design_area_select_all_callback) }, { "center", GIMP_ICON_CENTER, N_("Re_center"), "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, "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "", -1, NULL); gtk_ui_manager_add_ui_from_string (ui_manager, "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " "", -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; } }