2933 lines
93 KiB
C
2933 lines
93 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* 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 <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#include "ifs-compose.h"
|
|
|
|
#include "libpika/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 "pika-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
|
|
{
|
|
PikaRGB *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;
|
|
cairo_surface_t *surface;
|
|
|
|
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;
|
|
|
|
|
|
struct _PikaIfs
|
|
{
|
|
PikaPlugIn parent_instance;
|
|
|
|
GtkApplication *app;
|
|
GtkWidget *dialog;
|
|
PikaDrawable *drawable;
|
|
gint response_id;
|
|
|
|
GtkWidget *delete_button;
|
|
GtkWidget *undo_button;
|
|
GtkWidget *redo_button;
|
|
|
|
GtkBuilder *builder;
|
|
};
|
|
|
|
|
|
#define PIKA_TYPE_IFS (pika_ifs_get_type ())
|
|
G_DECLARE_FINAL_TYPE (PikaIfs, pika_ifs, PIKA, IFS, PikaPlugIn)
|
|
|
|
GType ifs_get_type (void) G_GNUC_CONST;
|
|
|
|
static void pika_ifs_finalize (GObject *object);
|
|
|
|
static GList * ifs_query_procedures (PikaPlugIn *plug_in);
|
|
static PikaProcedure * ifs_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name);
|
|
|
|
static PikaValueArray * ifs_run (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
|
|
/* user interface functions */
|
|
static void on_app_activate (GApplication *gapp,
|
|
gpointer user_data);
|
|
static gint ifs_compose_dialog (PikaIfs *ifs,
|
|
PikaDrawable *drawable);
|
|
static void ifs_options_dialog (void);
|
|
static GtkWidget * ifs_compose_trans_page (void);
|
|
static GtkWidget * ifs_compose_color_page (void);
|
|
static void design_op_actions_update (void);
|
|
static void design_area_create (PikaIfs *ifs,
|
|
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_draw (GtkWidget *widget,
|
|
cairo_t *cr);
|
|
static gint design_area_button_press (GtkWidget *widget,
|
|
GdkEventButton *event,
|
|
PikaIfs *ifs);
|
|
static gint design_area_button_release (GtkWidget *widget,
|
|
GdkEventButton *event);
|
|
static void design_area_select_all_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
static void design_area_size_allocate (GtkWidget *widget,
|
|
GtkAllocation *allocation);
|
|
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_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
static void redo_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
|
|
static void recompute_center (gboolean save_undo);
|
|
static void recompute_center_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
|
|
static void ifs_compose (PikaDrawable *drawable);
|
|
|
|
static ColorMap *color_map_create (const gchar *name,
|
|
PikaRGB *orig_color,
|
|
PikaRGB *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 flip_check_button_callback (GtkWidget *widget, gpointer data);
|
|
static gint preview_idle_render (gpointer data);
|
|
|
|
static void transform_change_state (GSimpleAction *action,
|
|
GVariant *new_state,
|
|
gpointer user_data);
|
|
static void ifs_compose_preview (void);
|
|
static void ifs_compose_set_defaults (void);
|
|
static void ifs_compose_new_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
static void ifs_compose_delete_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
static void ifs_compose_options_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data);
|
|
static void ifs_compose_load (void);
|
|
static void ifs_compose_save (void);
|
|
static void ifs_compose_response (GtkWidget *widget,
|
|
gint response_id);
|
|
|
|
/* GAction helper methods */
|
|
static void window_destroy (GtkWidget *widget,
|
|
PikaIfs *ifs);
|
|
|
|
static GtkWidget * add_tool_button (GtkWidget *toolbar,
|
|
const char *action,
|
|
const char *icon,
|
|
const char *label,
|
|
const char *tooltip);
|
|
static GtkWidget * add_toggle_button (GtkWidget *toolbar,
|
|
const char *action,
|
|
const char *icon,
|
|
const char *label,
|
|
const char *tooltip);
|
|
static void add_tool_separator (GtkWidget *toolbar,
|
|
gboolean expand);
|
|
|
|
|
|
G_DEFINE_TYPE (PikaIfs, pika_ifs, PIKA_TYPE_PLUG_IN)
|
|
|
|
PIKA_MAIN (PIKA_TYPE_IFS)
|
|
DEFINE_STD_SET_I18N
|
|
|
|
|
|
static IfsDialog *ifsD = NULL;
|
|
static IfsOptionsDialog *ifsOptD = NULL;
|
|
static IfsDesignArea *ifsDesign = NULL;
|
|
static PikaIfs *ifs = 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 */
|
|
};
|
|
|
|
static const GActionEntry ACTIONS[] =
|
|
{
|
|
{ "new", ifs_compose_new_action },
|
|
{ "delete", ifs_compose_delete_action },
|
|
{ "undo", undo_action },
|
|
{ "redo", redo_action },
|
|
{ "select-all", design_area_select_all_action },
|
|
{ "center", recompute_center_action },
|
|
{ "options", ifs_compose_options_action },
|
|
|
|
/* RadioButtons - only the default state is shown here. */
|
|
{ "transform", transform_change_state, "s", "'move'", NULL },
|
|
};
|
|
|
|
|
|
static void
|
|
pika_ifs_class_init (PikaIfsClass *klass)
|
|
{
|
|
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = pika_ifs_finalize;
|
|
|
|
plug_in_class->query_procedures = ifs_query_procedures;
|
|
plug_in_class->create_procedure = ifs_create_procedure;
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
|
}
|
|
|
|
static void
|
|
pika_ifs_init (PikaIfs *ifs)
|
|
{
|
|
ifs->builder = NULL;
|
|
}
|
|
|
|
static void
|
|
pika_ifs_finalize (GObject *object)
|
|
{
|
|
PikaIfs *ifs = PIKA_IFS (object);
|
|
|
|
G_OBJECT_CLASS (pika_ifs_parent_class)->finalize (object);
|
|
|
|
g_clear_object (&ifs->builder);
|
|
}
|
|
|
|
static GList *
|
|
ifs_query_procedures (PikaPlugIn *plug_in)
|
|
{
|
|
return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
|
|
}
|
|
|
|
static PikaProcedure *
|
|
ifs_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name)
|
|
{
|
|
PikaProcedure *procedure = NULL;
|
|
|
|
if (! strcmp (name, PLUG_IN_PROC))
|
|
{
|
|
procedure = pika_image_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
ifs_run, NULL, NULL);
|
|
|
|
pika_procedure_set_image_types (procedure, "*");
|
|
pika_procedure_set_sensitivity_mask (procedure,
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLE);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("_IFS Fractal..."));
|
|
pika_procedure_add_menu_path (procedure,
|
|
"<Image>/Filters/Render/Fractals");
|
|
|
|
pika_procedure_set_documentation
|
|
(procedure,
|
|
_("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.",
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Owen Taylor",
|
|
"Owen Taylor",
|
|
"1997");
|
|
|
|
PIKA_PROC_AUX_ARG_STRING (procedure, "fractal-str",
|
|
"The fractal description serialized as string",
|
|
NULL, NULL,
|
|
PIKA_PARAM_READWRITE);
|
|
}
|
|
|
|
return procedure;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
ifs_run (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaDrawable *drawable;
|
|
PikaParasite *parasite = NULL;
|
|
gboolean found_parasite = FALSE;
|
|
|
|
ifs = PIKA_IFS (pika_procedure_get_plug_in (procedure));
|
|
#if GLIB_CHECK_VERSION(2,74,0)
|
|
ifs->app = gtk_application_new (NULL, G_APPLICATION_DEFAULT_FLAGS);
|
|
#else
|
|
ifs->app = gtk_application_new (NULL, G_APPLICATION_FLAGS_NONE);
|
|
#endif
|
|
|
|
ifs->builder = gtk_builder_new_from_resource ("/technology.heckin/ifs/ifs-menus.ui");
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
if (n_drawables != 1)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
g_set_error (&error, PIKA_PLUG_IN_ERROR, 0,
|
|
_("Procedure '%s' only works with one drawable."),
|
|
pika_procedure_get_name (procedure));
|
|
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CALLING_ERROR,
|
|
error);
|
|
}
|
|
else
|
|
{
|
|
drawable = drawables[0];
|
|
}
|
|
|
|
switch (run_mode)
|
|
{
|
|
case PIKA_RUN_INTERACTIVE:
|
|
/* Possibly retrieve data; first look for a parasite -
|
|
* if not found, fall back to global values
|
|
*/
|
|
parasite = pika_item_get_parasite (PIKA_ITEM (drawable),
|
|
PLUG_IN_PARASITE);
|
|
if (parasite)
|
|
{
|
|
gchar *parasite_data;
|
|
guint32 parasite_size;
|
|
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
parasite_data = g_strndup (parasite_data, parasite_size);
|
|
found_parasite = ifsvals_parse_string (parasite_data, &ifsvals, &elements);
|
|
pika_parasite_free (parasite);
|
|
g_free (parasite_data);
|
|
}
|
|
|
|
if (! found_parasite)
|
|
{
|
|
gchar *data = NULL;
|
|
|
|
g_object_get (config, "fractal-str", &data, NULL);
|
|
if (data != NULL && strlen (data) > 0)
|
|
ifsvals_parse_string (data, &ifsvals, &elements);
|
|
g_free (data);
|
|
}
|
|
|
|
/* after ifsvals_parse_string, need to set up naming */
|
|
count_for_naming = ifsvals.num_elements;
|
|
|
|
ifs->drawable = drawable;
|
|
|
|
/* First acquire information with a dialog */
|
|
g_signal_connect (ifs->app, "activate", G_CALLBACK (on_app_activate), ifs);
|
|
g_application_run (G_APPLICATION (ifs->app), 0, NULL);
|
|
g_clear_object (&ifs->app);
|
|
|
|
if (! ifscint.run)
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CANCEL,
|
|
NULL);
|
|
break;
|
|
|
|
case PIKA_RUN_NONINTERACTIVE:
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CALLING_ERROR,
|
|
NULL);
|
|
break;
|
|
|
|
case PIKA_RUN_WITH_LAST_VALS:
|
|
{
|
|
gchar *data = NULL;
|
|
|
|
g_object_get (config, "fractal-str", &data, NULL);
|
|
if (data != NULL && strlen (data) > 0)
|
|
{
|
|
ifsvals_parse_string (data, &ifsvals, &elements);
|
|
}
|
|
else
|
|
{
|
|
/* FIXME: there is a known crash in this code path, because some
|
|
* base structures (which are visibly created as part of the
|
|
* dialog or with ifsvals_parse_string() on a valid serialized
|
|
* fractal) don't exist. This should be fixed so that we can run
|
|
* with last vals even when no last vals exist (which should use
|
|
* defaults).
|
|
*/
|
|
ifs_compose_set_defaults ();
|
|
}
|
|
|
|
g_free (data);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (run_mode == PIKA_RUN_INTERACTIVE)
|
|
{
|
|
gchar *str;
|
|
PikaParasite *parasite;
|
|
|
|
pika_image_undo_group_start (image);
|
|
|
|
/* run the effect */
|
|
ifs_compose (drawable);
|
|
|
|
/* Store data for next invocation - both globally and
|
|
* as a parasite on this layer
|
|
*/
|
|
str = ifsvals_stringify (&ifsvals, elements);
|
|
g_object_set (config, "fractal-str", str, NULL);
|
|
|
|
parasite = pika_parasite_new (PLUG_IN_PARASITE,
|
|
PIKA_PARASITE_PERSISTENT |
|
|
PIKA_PARASITE_UNDOABLE,
|
|
strlen (str) + 1, str);
|
|
pika_item_attach_parasite (PIKA_ITEM (drawable), parasite);
|
|
pika_parasite_free (parasite);
|
|
|
|
g_free (str);
|
|
|
|
pika_image_undo_group_end (image);
|
|
|
|
pika_displays_flush ();
|
|
}
|
|
else
|
|
{
|
|
/* run the effect */
|
|
ifs_compose (drawable);
|
|
}
|
|
|
|
return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL);
|
|
}
|
|
|
|
static GtkWidget *
|
|
ifs_compose_trans_page (void)
|
|
{
|
|
GtkWidget *vbox;
|
|
GtkWidget *grid;
|
|
GtkWidget *label;
|
|
|
|
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
|
|
gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
|
|
|
|
grid = gtk_grid_new ();
|
|
gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
|
|
gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
|
|
gtk_widget_show (grid);
|
|
|
|
/* X */
|
|
|
|
label = gtk_label_new (_("X:"));
|
|
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
|
|
gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 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_grid_attach (GTK_GRID (grid), ifsD->x_pair->spin, 1, 0, 1, 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_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsD->y_pair->spin, 1, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), label, 2, 0, 1, 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_grid_attach (GTK_GRID (grid), ifsD->scale_pair->spin, 3, 0, 1, 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_grid_attach (GTK_GRID (grid), label, 2, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsD->angle_pair->spin, 3, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), label, 4, 0, 1, 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_grid_attach (GTK_GRID (grid), ifsD->asym_pair->spin, 5, 0, 1, 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_grid_attach (GTK_GRID (grid), label, 4, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsD->shear_pair->spin, 5, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsD->flip_check_button, 0, 2, 6, 1);
|
|
// 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 *grid;
|
|
GtkWidget *label;
|
|
GSList *group = NULL;
|
|
PikaRGB color;
|
|
|
|
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
|
|
gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
|
|
|
|
grid = gtk_grid_new ();
|
|
gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
|
|
gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
|
|
gtk_widget_show (grid);
|
|
|
|
/* Simple color control section */
|
|
|
|
ifsD->simple_button = gtk_radio_button_new_with_label (group, _("Simple"));
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->simple_button, 0, 0, 1, 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_grid_attach (GTK_GRID (grid), ifsD->target_cmap->hbox, 1, 0, 1, 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_grid_attach (GTK_GRID (grid), label, 2, 0, 1, 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_grid_attach (GTK_GRID (grid), ifsD->hue_scale_pair->scale, 3, 0, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsD->hue_scale_pair->scale);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->hue_scale_pair->spin, 4, 0, 1, 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_grid_attach (GTK_GRID (grid), label, 2, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsD->value_scale_pair->scale, 3, 1, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsD->value_scale_pair->scale);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->value_scale_pair->spin, 4, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsD->full_button, 0, 2, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (ifsD->full_button));
|
|
gtk_widget_show (ifsD->full_button);
|
|
|
|
pika_rgb_parse_name (&color, "red", -1);
|
|
pika_rgb_set_alpha (&color, 1.0);
|
|
ifsD->red_cmap = color_map_create (_("IFS Fractal: Red"), &color,
|
|
&ifsD->current_vals.red_color, FALSE);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->red_cmap->hbox, 1, 2, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsD->red_cmap->hbox);
|
|
|
|
pika_rgb_parse_name (&color, "green", -1);
|
|
pika_rgb_set_alpha (&color, 1.0);
|
|
ifsD->green_cmap = color_map_create (_("IFS Fractal: Green"), &color,
|
|
&ifsD->current_vals.green_color, FALSE);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->green_cmap->hbox, 2, 2, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsD->green_cmap->hbox);
|
|
|
|
pika_rgb_parse_name (&color, "blue", -1);
|
|
pika_rgb_set_alpha (&color, 1.0);
|
|
ifsD->blue_cmap = color_map_create (_("IFS Fractal: Blue"), &color,
|
|
&ifsD->current_vals.blue_color, FALSE);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->blue_cmap->hbox, 3, 2, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsD->blue_cmap->hbox);
|
|
|
|
pika_rgb_parse_name (&color, "black", -1);
|
|
pika_rgb_set_alpha (&color, 1.0);
|
|
ifsD->black_cmap = color_map_create (_("IFS Fractal: Black"), &color,
|
|
&ifsD->current_vals.black_color, FALSE);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsD->black_cmap->hbox, 4, 2, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsD->black_cmap->hbox);
|
|
|
|
return vbox;
|
|
}
|
|
|
|
static void
|
|
on_app_activate (GApplication *gapp,
|
|
gpointer user_data)
|
|
{
|
|
PikaIfs *ifs = PIKA_IFS (user_data);
|
|
|
|
ifs_compose_dialog (ifs, ifs->drawable);
|
|
|
|
gtk_application_set_accels_for_action (ifs->app, "app.transform::move", (const char*[]) { "M", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.transform::rotate", (const char*[]) { "R", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.transform::stretch", (const char*[]) { "S", NULL });
|
|
|
|
gtk_application_set_accels_for_action (ifs->app, "app.new", (const char*[]) { "<control>N", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.delete", (const char*[]) { "<control>D", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.undo", (const char*[]) { "<control>Z", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.redo", (const char*[]) { "<control>Y", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.select-all", (const char*[]) { "<control>A", NULL });
|
|
gtk_application_set_accels_for_action (ifs->app, "app.center", (const char*[]) {"<control>C", NULL });
|
|
|
|
gtk_application_set_accels_for_action (ifs->app, "app.options", (const char*[]) { NULL });
|
|
}
|
|
|
|
static gint
|
|
ifs_compose_dialog (PikaIfs *ifs,
|
|
PikaDrawable *drawable)
|
|
{
|
|
GtkWidget *label;
|
|
GtkWidget *vbox;
|
|
GtkWidget *hbox;
|
|
GtkWidget *main_vbox;
|
|
GtkWidget *toolbar;
|
|
GtkWidget *aspect_frame;
|
|
GtkWidget *notebook;
|
|
GtkWidget *page;
|
|
gint design_width = pika_drawable_get_width (drawable);
|
|
gint design_height = pika_drawable_get_height (drawable);
|
|
|
|
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 = pika_drawable_get_width (drawable);
|
|
ifsD->drawable_height = pika_drawable_get_height (drawable);
|
|
ifsD->preview_width = design_width;
|
|
ifsD->preview_height = design_height;
|
|
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
ifs->dialog = pika_dialog_new (_("IFS Fractal"), PLUG_IN_ROLE,
|
|
NULL, 0,
|
|
pika_standard_help_func, PLUG_IN_PROC,
|
|
|
|
_("_Open"), RESPONSE_OPEN,
|
|
_("_Save"), RESPONSE_SAVE,
|
|
_("_Reset"), RESPONSE_RESET,
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_OK"), GTK_RESPONSE_OK,
|
|
|
|
NULL);
|
|
|
|
pika_dialog_set_alternative_button_order (GTK_DIALOG (ifs->dialog),
|
|
RESPONSE_OPEN,
|
|
RESPONSE_SAVE,
|
|
RESPONSE_RESET,
|
|
GTK_RESPONSE_OK,
|
|
GTK_RESPONSE_CANCEL,
|
|
-1);
|
|
|
|
gtk_window_set_application (GTK_WINDOW (ifs->dialog), ifs->app);
|
|
pika_window_set_transient (GTK_WINDOW (ifs->dialog));
|
|
|
|
g_object_add_weak_pointer (G_OBJECT (ifs->dialog), (gpointer) &ifs->dialog);
|
|
|
|
g_signal_connect (ifs->dialog, "response",
|
|
G_CALLBACK (ifs_compose_response),
|
|
NULL);
|
|
g_signal_connect (GTK_WINDOW (ifs->dialog), "destroy",
|
|
G_CALLBACK (window_destroy),
|
|
NULL);
|
|
|
|
g_action_map_add_action_entries (G_ACTION_MAP (ifs->app),
|
|
ACTIONS, G_N_ELEMENTS (ACTIONS),
|
|
ifs);
|
|
|
|
/* 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 (ifs->dialog))),
|
|
main_vbox, TRUE, TRUE, 0);
|
|
|
|
toolbar = gtk_toolbar_new ();
|
|
add_toggle_button (toolbar, "app.transform::move", PIKA_ICON_TOOL_MOVE,
|
|
NULL, _("Move"));
|
|
add_toggle_button (toolbar, "app.transform::rotate", PIKA_ICON_TOOL_ROTATE,
|
|
NULL, _("Rotate"));
|
|
add_toggle_button (toolbar, "app.transform::stretch", PIKA_ICON_TOOL_PERSPECTIVE,
|
|
NULL, _("Stretch"));
|
|
add_tool_separator (toolbar, FALSE);
|
|
add_tool_button (toolbar, "app.new", PIKA_ICON_DOCUMENT_NEW,
|
|
NULL, _("New"));
|
|
ifs->delete_button = add_tool_button (toolbar, "app.delete", PIKA_ICON_EDIT_DELETE,
|
|
NULL, _("Delete"));
|
|
ifs->undo_button = add_tool_button (toolbar, "app.undo", PIKA_ICON_EDIT_UNDO,
|
|
NULL, _("Undo"));
|
|
ifs->redo_button = add_tool_button (toolbar, "app.redo", PIKA_ICON_EDIT_REDO,
|
|
NULL, _("Redo"));
|
|
add_tool_button (toolbar, "app.select-all", PIKA_ICON_SELECTION_ALL,
|
|
NULL, _("Select All"));
|
|
add_tool_button (toolbar, "app.center", PIKA_ICON_CENTER,
|
|
NULL, _("Recompute Center"));
|
|
add_tool_separator (toolbar, FALSE);
|
|
add_tool_button (toolbar, "app.options", PIKA_ICON_PREFERENCES_SYSTEM,
|
|
NULL, _("Render Options"));
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), toolbar, FALSE, FALSE, 0);
|
|
gtk_widget_show (toolbar);
|
|
|
|
design_area_create (ifs, ifs->dialog, design_width, design_height);
|
|
|
|
/* 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 = pika_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 = pika_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 (GTK_WIDGET (ifs->dialog));
|
|
|
|
ifs_compose_preview ();
|
|
|
|
return ifscint.run;
|
|
}
|
|
|
|
static void
|
|
design_area_create (PikaIfs *ifs,
|
|
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, "draw",
|
|
G_CALLBACK (design_area_draw),
|
|
NULL);
|
|
g_signal_connect (ifsDesign->area, "button-press-event",
|
|
G_CALLBACK (design_area_button_press),
|
|
ifs);
|
|
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, "size-allocate",
|
|
G_CALLBACK (design_area_size_allocate),
|
|
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);
|
|
|
|
design_op_actions_update ();
|
|
}
|
|
|
|
static void
|
|
design_op_actions_update (void)
|
|
{
|
|
GAction *action;
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (ifs->app), "undo");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
|
undo_cur >= 0);
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (ifs->app), "redo");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
|
undo_cur != undo_num - 1);
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (ifs->app), "delete");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
|
|
ifsvals.num_elements > 2);
|
|
}
|
|
|
|
static void
|
|
ifs_options_dialog (void)
|
|
{
|
|
if (!ifsOptD)
|
|
{
|
|
GtkWidget *grid;
|
|
GtkWidget *label;
|
|
|
|
ifsOptD = g_new0 (IfsOptionsDialog, 1);
|
|
|
|
ifsOptD->dialog =
|
|
pika_dialog_new (_("IFS Fractal Render Options"), PLUG_IN_ROLE,
|
|
NULL, 0,
|
|
pika_standard_help_func, PLUG_IN_PROC,
|
|
|
|
_("_Close"), GTK_RESPONSE_CLOSE,
|
|
|
|
NULL);
|
|
|
|
g_signal_connect (ifsOptD->dialog, "response",
|
|
G_CALLBACK (gtk_widget_hide),
|
|
NULL);
|
|
|
|
/* Grid of options */
|
|
|
|
grid = gtk_grid_new ();
|
|
gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
|
|
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
|
|
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (ifsOptD->dialog))),
|
|
grid, FALSE, FALSE, 0);
|
|
gtk_widget_show (grid);
|
|
|
|
label = gtk_label_new (_("Max. memory:"));
|
|
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
|
|
gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 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_grid_attach (GTK_GRID (grid), ifsOptD->memory_pair->spin, 1, 0, 1, 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_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsOptD->iterations_pair->spin,
|
|
1, 1, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsOptD->subdivide_pair->spin,
|
|
1, 2, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), label, 0, 3, 1, 1);
|
|
// 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_grid_attach (GTK_GRID (grid), ifsOptD->radius_pair->scale,
|
|
1, 3, 1, 1);
|
|
// GTK_FILL, GTK_FILL, 0, 0);
|
|
gtk_widget_show (ifsOptD->radius_pair->scale);
|
|
gtk_grid_attach (GTK_GRID (grid), ifsOptD->radius_pair->spin, 2, 3, 1, 1);
|
|
// 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 (PikaDrawable *drawable)
|
|
{
|
|
GeglBuffer *buffer = pika_drawable_get_shadow_buffer (drawable);
|
|
gint width = pika_drawable_get_width (drawable);
|
|
gint height = pika_drawable_get_height (drawable);
|
|
gboolean alpha = pika_drawable_has_alpha (drawable);
|
|
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;
|
|
PikaRGB 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));
|
|
|
|
pika_context_get_background (&color);
|
|
pika_rgb_get_uchar (&color, &rc, &gc, &bc);
|
|
|
|
for (band_no = 0, band_y = 0; band_no < num_bands; band_no++)
|
|
{
|
|
GeglBufferIterator *iter;
|
|
GeglRectangle *roi;
|
|
|
|
pika_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);
|
|
|
|
pika_drawable_merge_shadow (drawable, TRUE);
|
|
pika_drawable_update (drawable, 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);
|
|
g_object_unref (cursor);
|
|
}
|
|
|
|
static gboolean
|
|
design_area_draw (GtkWidget *widget,
|
|
cairo_t *cr)
|
|
{
|
|
cairo_t *design_cr;
|
|
GtkAllocation allocation;
|
|
PangoLayout *layout;
|
|
GdkRGBA black = { 0.0, 0.0, 0.0, 1.0 };
|
|
GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
|
|
gint i;
|
|
gint cx, cy;
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
design_cr = cairo_create (ifsDesign->surface);
|
|
|
|
gdk_cairo_set_source_rgba (design_cr, &white);
|
|
cairo_paint (design_cr);
|
|
|
|
cairo_set_line_join (design_cr, CAIRO_LINE_JOIN_ROUND);
|
|
cairo_set_line_cap (design_cr, CAIRO_LINE_CAP_ROUND);
|
|
cairo_translate (design_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 (design_cr, cx - 10, cy);
|
|
cairo_line_to (design_cr, cx + 10, cy);
|
|
|
|
cairo_move_to (design_cr, cx, cy - 10);
|
|
cairo_line_to (design_cr, cx, cy + 10);
|
|
|
|
gdk_cairo_set_source_rgba (design_cr, &black);
|
|
cairo_set_line_width (design_cr, 1.0);
|
|
cairo_stroke (design_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,
|
|
design_cr,
|
|
&black,
|
|
layout);
|
|
}
|
|
|
|
g_object_unref (layout);
|
|
|
|
cairo_destroy (design_cr);
|
|
|
|
cairo_set_source_surface (cr, ifsDesign->surface, 0.0, 0.0);
|
|
cairo_paint (cr);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
design_area_size_allocate (GtkWidget *widget,
|
|
GtkAllocation *allocation)
|
|
{
|
|
gint i;
|
|
|
|
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->surface)
|
|
cairo_surface_destroy (ifsDesign->surface);
|
|
|
|
ifsDesign->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
|
|
allocation->width,
|
|
allocation->height);
|
|
}
|
|
|
|
static gint
|
|
design_area_button_press (GtkWidget *widget,
|
|
GdkEventButton *event,
|
|
PikaIfs *ifs)
|
|
{
|
|
GtkAllocation allocation;
|
|
gint i;
|
|
gint old_current;
|
|
|
|
gtk_widget_get_allocation (ifsDesign->area, &allocation);
|
|
|
|
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
|
|
{
|
|
GtkWidget *menu;
|
|
GMenuModel *model;
|
|
|
|
model = G_MENU_MODEL (gtk_builder_get_object (ifs->builder, "ifs-compose-menu"));
|
|
menu = gtk_menu_new_from_model (model);
|
|
|
|
if (GTK_IS_MENU_ITEM (menu))
|
|
menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
|
|
|
|
gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (ifsDesign->area), NULL);
|
|
gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *) event);
|
|
|
|
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_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
if (undo_cur >= 0)
|
|
{
|
|
undo_exchange ((undo_start + undo_cur) % UNDO_LEVELS);
|
|
undo_cur--;
|
|
}
|
|
|
|
design_op_actions_update ();
|
|
}
|
|
|
|
static void
|
|
redo_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
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_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_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,
|
|
PikaRGB *orig_color,
|
|
PikaRGB *data,
|
|
gboolean fixed_point)
|
|
{
|
|
GtkWidget *frame;
|
|
GtkWidget *arrow;
|
|
ColorMap *color_map = g_new (ColorMap, 1);
|
|
|
|
pika_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 =
|
|
pika_color_area_new (fixed_point ? data : orig_color,
|
|
PIKA_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_image_new_from_icon_name ("pan-end-symbolic",
|
|
GTK_ICON_SIZE_SMALL_TOOLBAR);
|
|
gtk_box_pack_start (GTK_BOX (color_map->hbox), arrow, FALSE, FALSE, 0);
|
|
gtk_widget_show (arrow);
|
|
|
|
color_map->button = pika_color_button_new (name,
|
|
COLOR_SAMPLE_SIZE,
|
|
COLOR_SAMPLE_SIZE,
|
|
data,
|
|
PIKA_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 (pika_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)
|
|
{
|
|
pika_color_button_set_color (PIKA_COLOR_BUTTON (color_map->button),
|
|
color_map->color);
|
|
|
|
if (color_map->fixed_point)
|
|
pika_color_area_set_color (PIKA_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 = gtk_adjustment_new (1.0, lower, upper,
|
|
(upper - lower) / 100,
|
|
(upper - lower) / 10,
|
|
0.0);
|
|
value_pair->spin = pika_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
|
|
recompute_center_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_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;
|
|
PikaRGB color;
|
|
|
|
pika_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"),
|
|
pika_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."),
|
|
pika_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 (void)
|
|
{
|
|
static GtkWidget *dialog = NULL;
|
|
|
|
if (! dialog)
|
|
{
|
|
dialog =
|
|
gtk_file_chooser_dialog_new (_("Save as IFS Fractal file"),
|
|
NULL,
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_Save"), GTK_RESPONSE_OK,
|
|
|
|
NULL);
|
|
|
|
pika_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 (void)
|
|
{
|
|
static GtkWidget *dialog = NULL;
|
|
|
|
if (! dialog)
|
|
{
|
|
dialog =
|
|
gtk_file_chooser_dialog_new (_("Open IFS Fractal file"),
|
|
NULL,
|
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_Open"), GTK_RESPONSE_OK,
|
|
|
|
NULL);
|
|
|
|
pika_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_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GtkAllocation allocation;
|
|
PikaRGB color;
|
|
gint i;
|
|
AffElement *elem;
|
|
|
|
gtk_widget_get_allocation (ifsDesign->area, &allocation);
|
|
|
|
undo_begin ();
|
|
|
|
pika_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_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_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_action (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
ifs_options_dialog ();
|
|
}
|
|
|
|
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;
|
|
|
|
pika_preview_area_draw (PIKA_PREVIEW_AREA (ifsD->preview),
|
|
0, 0, allocation.width, allocation.height,
|
|
PIKA_RGB_IMAGE,
|
|
ifsD->preview_data,
|
|
allocation.width * 3);
|
|
|
|
return (ifsD->preview_iterations != 0);
|
|
}
|
|
|
|
static void transform_change_state (GSimpleAction *action,
|
|
GVariant *new_state,
|
|
gpointer user_data)
|
|
{
|
|
gchar *str;
|
|
|
|
str = g_strdup_printf ("%s", g_variant_get_string (new_state, NULL));
|
|
|
|
if (! strcmp (str, "move"))
|
|
ifsDesign->op = OP_TRANSLATE;
|
|
else if (! strcmp (str, "rotate"))
|
|
ifsDesign->op = OP_ROTATE;
|
|
else if (! strcmp (str, "stretch"))
|
|
ifsDesign->op = OP_STRETCH;
|
|
|
|
g_free (str);
|
|
|
|
g_simple_action_set_state (action, new_state);
|
|
|
|
/* cursor switch */
|
|
if (gtk_widget_get_realized (ifsDesign->area))
|
|
design_area_realize (ifsDesign->area);
|
|
}
|
|
|
|
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;
|
|
PikaRGB color;
|
|
|
|
if (!ifsD->preview_data)
|
|
ifsD->preview_data = g_new (guchar, 3 * width * height);
|
|
|
|
pika_context_get_background (&color);
|
|
pika_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)
|
|
{
|
|
switch (response_id)
|
|
{
|
|
case RESPONSE_OPEN:
|
|
ifs_compose_load ();
|
|
break;
|
|
|
|
case RESPONSE_SAVE:
|
|
ifs_compose_save ();
|
|
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_application_remove_window (ifs->app, GTK_WINDOW (ifs->dialog));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
window_destroy (GtkWidget *widget,
|
|
PikaIfs *ifs)
|
|
{
|
|
if (ifsOptD)
|
|
gtk_widget_destroy (ifsOptD->dialog);
|
|
|
|
g_free (ifsD);
|
|
|
|
gtk_application_remove_window (ifs->app, GTK_WINDOW (ifs->dialog));
|
|
}
|
|
|
|
static GtkWidget *
|
|
add_tool_button (GtkWidget *toolbar,
|
|
const char *action,
|
|
const char *icon,
|
|
const char *label,
|
|
const char *tooltip)
|
|
{
|
|
GtkWidget *tool_icon;
|
|
GtkToolItem *tool_button;
|
|
|
|
tool_icon = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_BUTTON);
|
|
gtk_widget_show (GTK_WIDGET (tool_icon));
|
|
tool_button = gtk_tool_button_new (tool_icon, label);
|
|
gtk_widget_show (GTK_WIDGET (tool_button));
|
|
gtk_tool_item_set_tooltip_text (tool_button, tooltip);
|
|
gtk_actionable_set_detailed_action_name (GTK_ACTIONABLE (tool_button), action);
|
|
|
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_button, -1);
|
|
|
|
return GTK_WIDGET (tool_button);
|
|
}
|
|
|
|
GtkWidget *
|
|
add_toggle_button (GtkWidget *toolbar,
|
|
const char *action,
|
|
const char *icon,
|
|
const char *label,
|
|
const char *tooltip)
|
|
{
|
|
GtkWidget *tool_icon;
|
|
GtkToolItem *toggle_tool_button;
|
|
|
|
tool_icon = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_BUTTON);
|
|
gtk_widget_show (GTK_WIDGET (tool_icon));
|
|
|
|
toggle_tool_button = gtk_toggle_tool_button_new ();
|
|
gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (toggle_tool_button),
|
|
tool_icon);
|
|
gtk_tool_button_set_label (GTK_TOOL_BUTTON (toggle_tool_button), label);
|
|
gtk_widget_show (GTK_WIDGET (toggle_tool_button));
|
|
gtk_tool_item_set_tooltip_text (toggle_tool_button, tooltip);
|
|
gtk_actionable_set_detailed_action_name (GTK_ACTIONABLE (toggle_tool_button), action);
|
|
|
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toggle_tool_button, -1);
|
|
|
|
return GTK_WIDGET (toggle_tool_button);
|
|
}
|
|
|
|
static void
|
|
add_tool_separator (GtkWidget *toolbar,
|
|
gboolean expand)
|
|
{
|
|
GtkToolItem *item;
|
|
|
|
item = gtk_separator_tool_item_new ();
|
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
|
|
gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
|
|
gtk_tool_item_set_expand (item, expand);
|
|
gtk_widget_show (GTK_WIDGET (item));
|
|
}
|