/* curve_bend plugin for PIKA */ /* 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 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* Revision history * (2004/02/08) v1.3.18 hof: #133244 exit with execution error if there is * an empty selection * (2003/08/24) v1.3.18 hof: #119937 show busy cursor when recalculating * preview * (2002/09/xx) v1.1.18 mitch and gsr: clean interface * (2000/02/16) v1.1.17b hof: added spinbuttons for rotate entry * (2000/02/16) v1.1.17 hof: undo bugfix (#6012) * don't call pika_undo_push_group_end * after pika_displays_flush * (1999/09/13) v1.01 hof: PDB-calls updated for pika 1.1.9 * (1999/05/10) v1.0 hof: first public release * (1999/04/23) v0.0 hof: coding started, * splines and dialog parts are similar to curves.c */ #include "config.h" #include #include #include #include #include #include #include "libpika/stdplugins-intl.h" /* Defines */ #define PLUG_IN_PROC "plug-in-curve-bend" #define PLUG_IN_BINARY "curve-bend" #define PLUG_IN_ROLE "pika-curve-bend" #define PLUG_IN_VERSION "v1.3.18 (2003/08/26)" #define PLUG_IN_IMAGE_TYPES "RGB*, GRAY*" #define PLUG_IN_AUTHOR "Wolfgang Hofer (hof@hotbot.com)" #define PLUG_IN_COPYRIGHT "Wolfgang Hofer" #define KEY_POINTFILE "POINTFILE_CURVE_BEND" #define KEY_POINTS "POINTS" #define KEY_VAL_Y "VAL_Y" #define MIDDLE 127 #define MIX_CHANNEL(a, b, m) (((a * m) + (b * (255 - m))) / 255) #define UP_GRAPH 0x1 #define UP_PREVIEW_EXPOSE 0x4 #define UP_PREVIEW 0x8 #define UP_DRAW 0x10 #define UP_ALL 0xFF #define GRAPH_WIDTH 256 #define GRAPH_HEIGHT 256 #define PREVIEW_SIZE_X 256 #define PREVIEW_SIZE_Y 256 #define PV_IMG_WIDTH 128 #define PV_IMG_HEIGHT 128 #define RADIUS 3 #define MIN_DISTANCE 8 #define PREVIEW_BPP 4 #define SMOOTH 0 #define GFREE 1 #define GRAPH_MASK GDK_EXPOSURE_MASK | \ GDK_POINTER_MOTION_MASK | \ GDK_POINTER_MOTION_HINT_MASK | \ GDK_ENTER_NOTIFY_MASK | \ GDK_BUTTON_PRESS_MASK | \ GDK_BUTTON_RELEASE_MASK | \ GDK_BUTTON1_MOTION_MASK #define OUTLINE_UPPER 0 #define OUTLINE_LOWER 1 typedef struct _BenderValues BenderValues; struct _BenderValues { guchar curve[2][256]; /* for curve_type freehand mode 0 <= curve <= 255 */ gdouble points[2][17][2]; /* for curve_type smooth mode 0.0 <= points <= 1.0 */ gint curve_type; gboolean work_on_copy; }; typedef struct _Curves Curves; struct _Curves { int x, y; /* coords for last mouse click */ }; typedef struct _BenderDialog BenderDialog; struct _BenderDialog { GtkWidget *shell; GtkWidget *outline_menu; GtkWidget *pv_widget; GtkWidget *graph; GtkWidget *filechooser; GdkCursor *cursor_busy; PikaProcedureConfig *config; PikaDrawable *drawable; int color; int outline; gint preview; int grab_point; int last; int leftmost; int rightmost; int curve_type; gdouble points[2][17][2]; /* 0.0 <= points <= 1.0 */ guchar curve[2][256]; /* 0 <= curve <= 255 */ gint32 *curve_ptr[2]; /* 0 <= curve_ptr <= src_drawable_width * both arrays are allocated dynamic, * depending on drawable width */ gint32 min2[2]; gint32 max2[2]; gint32 zero2[2]; gboolean show_progress; PikaImage *preview_image; PikaLayer *preview_layer1; PikaLayer *preview_layer2; gboolean run; }; /* points Coords: * * 1.0 +----+----+----+----+ * | . | | | * +--.-+--.-+----+----+ * . | . | | * 0.5 +----+----+-.--+----+ * | | | . . * +----+----+----+-.-.+ * | | | | | * 0.0 +----+----+----+----+ * 0.0 0.5 1.0 * * curve Coords: * * OUTLINE_UPPER OUTLINE_LOWER * * 255 +----+----+----+----+ 255 +----+----+----+----+ * | . | | | --- max | . | | | --- max * +--.-+--.-+----+----+ +--.-+--.-+----+----+ * . | . | | zero ___ . | . | | * +----+----+-.--+----+ +----+----+-.--+----+ * | | | . . --- zero | | | . . * +----+----+----+-.-.+ ___ min +----+----+----+-.-.+ ___ min * | | | | | | | | | | * 0 +----+----+----+----+ 0 +----+----+----+----+ * 0 255 0 255 */ typedef double CRMatrix[4][4]; typedef struct { PikaDrawable *drawable; gint width; gint height; GeglBuffer *buffer; const Babl *format; gint x1; gint y1; gint x2; gint y2; gint index_alpha; /* 0 == no alpha, 1 == GREYA, 3 == RGBA */ gint bpp; gint tile_width; gint tile_height; } t_GDRW; typedef struct { gint32 y; guchar color[4]; } t_Last; typedef struct _Bender Bender; typedef struct _BenderClass BenderClass; struct _Bender { PikaPlugIn parent_instance; }; struct _BenderClass { PikaPlugInClass parent_class; }; #define BENDER_TYPE (bender_get_type ()) #define BENDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BENDER_TYPE, Bender)) GType bender_get_type (void) G_GNUC_CONST; static GList * bender_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * bender_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * bender_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, PikaProcedureConfig *config, gpointer run_data); static BenderDialog * bender_new_dialog (PikaProcedure *procedure, PikaProcedureConfig *config, PikaDrawable *drawable); static void bender_update (BenderDialog *, int); static void bender_plot_curve (BenderDialog *, int, int, int, int, gint32, gint32, gint); static void bender_calculate_curve (BenderDialog *, gint32, gint32, gint); static void bender_global_notify (GtkWidget *, gpointer); static void bender_type_callback (GtkWidget *, gpointer); static void bender_reset_callback (GtkWidget *, gpointer); static void bender_copy_callback (GtkWidget *, gpointer); static void bender_copy_inv_callback (GtkWidget *, gpointer); static void bender_swap_callback (GtkWidget *, gpointer); static void bender_preview_update (GtkWidget *, gpointer); static void bender_preview_update_once (GtkWidget *, gpointer); static void bender_load_callback (GtkWidget *, BenderDialog *); static void bender_save_callback (GtkWidget *, BenderDialog *); static gint bender_graph_events (GtkWidget *, GdkEvent *, BenderDialog *); static gint bender_graph_draw (GtkWidget *, cairo_t *, BenderDialog *); static void bender_CR_compose (CRMatrix, CRMatrix, CRMatrix); static void bender_init_min_max (BenderDialog *, gint32); static BenderDialog * do_dialog (PikaProcedure *procedure, PikaProcedureConfig *config, PikaDrawable *drawable); static void p_init_gdrw (t_GDRW *gdrw, PikaDrawable *drawable, int shadow); static void p_end_gdrw (t_GDRW *gdrw); static PikaLayer * p_main_bend (BenderDialog *cd, PikaDrawable *original_drawable, PikaProcedureConfig *config, gint work_on_copy); static PikaImage * p_create_pv_image (PikaDrawable *src_drawable, PikaLayer **layer); static void p_render_preview (BenderDialog *cd, PikaLayer *layer); static void p_get_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *pixel); static void p_put_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *pixel); static void p_put_mix_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *color, gint32 nb_curvy, gint32 nb2_curvy, gint32 curvy); static void p_stretch_curves (BenderDialog *cd, gint32 xmax, gint32 ymax); static void p_cd_to_bval (BenderDialog *cd, BenderValues *bval); static void p_cd_from_bval (BenderDialog *cd, BenderValues *bval); static void p_store_values (PikaProcedureConfig *config, BenderDialog *cd); static void p_retrieve_values (PikaProcedureConfig *config, BenderDialog *cd); static void p_bender_calculate_iter_curve (BenderDialog *cd, PikaProcedureConfig *config, gint32 xmax, gint32 ymax); static int p_save_pointfile (BenderDialog *cd, const gchar *filename); G_DEFINE_TYPE (Bender, bender, PIKA_TYPE_PLUG_IN) PIKA_MAIN (BENDER_TYPE) DEFINE_STD_SET_I18N static CRMatrix CR_basis = { { -0.5, 1.5, -1.5, 0.5 }, { 1.0, -2.5, 2.0, -0.5 }, { -0.5, 0.0, 0.5, 0.0 }, { 0.0, 1.0, 0.0, 0.0 }, }; static int gb_debug = FALSE; static void bender_class_init (BenderClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = bender_query_procedures; plug_in_class->create_procedure = bender_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void bender_init (Bender *bender) { } static GList * bender_query_procedures (PikaPlugIn *plug_in) { return g_list_append (NULL, g_strdup (PLUG_IN_PROC)); } static PikaProcedure * bender_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, bender_run, NULL, NULL); pika_procedure_set_image_types (procedure, PLUG_IN_IMAGE_TYPES); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE); pika_procedure_set_menu_label (procedure, _("_Curve Bend...")); pika_procedure_add_menu_path (procedure, "/Filters/Distorts"); pika_procedure_set_documentation (procedure, _("Bend the image using two control curves"), _("This plug-in bends the active layer. " "If there is a current selection it is copied to " "floating selection and the curve_bend distortion " "is done on the floating selection. If " "work_on_copy parameter is TRUE, the curve_bend " "distortion is done on a copy of the active layer " "(or floating selection). The upper and lower edges " "are bent in shape of 2 spline curves. Both (upper " "and lower) curves are determined by up to 17 points " "or by 256 Y-Values if curve_type == 1 (freehand " "mode). If rotation is not 0, the layer is rotated " "before and rotated back after the bend operation. " "This enables bending in other directions than " "vertical. Bending usually changes the size of " "the handled layer. This plug-in sets the offsets " "of the handled layer to keep its center at the " "same position."), name); pika_procedure_set_attribution (procedure, PLUG_IN_AUTHOR, PLUG_IN_COPYRIGHT, PLUG_IN_VERSION); PIKA_PROC_ARG_DOUBLE (procedure, "rotation", _("Rotat_e"), _("Direction {angle 0 to 360 degree } " "of the bend effect"), 0, 360, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "smoothing", _("Smoo_thing"), _("Smoothing"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "antialias", _("_Antialiasing"), _("Antialias"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "work-on-copy", _("Work on cop_y"), _("Copy the drawable and bend the copy"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "curve-type", _("Curve Type"), _("{ 0 == smooth (use 17 points), " "1 == freehand (use 256 val_y) }"), 0, 1, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "curve-border", _("Curve for Border"), _("Choose the active border line to edit"), 0, 1, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "argc-upper-point-x", _("Argc upper point X"), _("Argc upper point X"), 2, 17, 2, G_PARAM_READWRITE); PIKA_PROC_ARG_FLOAT_ARRAY (procedure, "upper-point-x", _("Upper point X"), _("Array of 17 x point coords " "{ 0.0 <= x <= 1.0 or -1 for unused point }"), G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "argc-upper-point-y", _("Argc upper point Y"), _("Argc upper point Y"), 2, 17, 2, G_PARAM_READWRITE); PIKA_PROC_ARG_FLOAT_ARRAY (procedure, "upper-point-y", _("Upper point Y"), _("Array of 17 y point coords " "{ 0.0 <= y <= 1.0 or -1 for unused point }"), G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "argc-lower-point-x", _("Argc lower point X"), _("Argc lower point X"), 2, 17, 2, G_PARAM_READWRITE); PIKA_PROC_ARG_FLOAT_ARRAY (procedure, "lower-point-x", _("Lower point X"), _("Array of 17 x point coords " "{ 0.0 <= x <= 1.0 or -1 for unused point }"), G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "argc-lower-point-y", _("Argc lower point Y"), _("Argc lower point Y"), 2, 17, 2, G_PARAM_READWRITE); PIKA_PROC_ARG_FLOAT_ARRAY (procedure, "lower-point-y", _("Lower point Y"), _("Array of 17 y point coords " "{ 0.0 <= y <= 1.0 or -1 for unused point }"), G_PARAM_READWRITE); PIKA_PROC_ARG_BYTES (procedure, "upper-val-y", _("Upper val Y"), _("Array of 256 y freehand coords " "{ 0 <= y <= 255 }"), G_PARAM_READWRITE); PIKA_PROC_ARG_BYTES (procedure, "lower-val-y", _("Lower val Y"), _("Array of 256 y freehand coords " "{ 0 <= y <= 255 }"), G_PARAM_READWRITE); PIKA_PROC_VAL_LAYER (procedure, "bent-layer", _("Bent layer"), _("The transformed layer"), FALSE, G_PARAM_READWRITE); PIKA_PROC_AUX_ARG_BYTES (procedure, "settings-data", "Settings data", "TODO: eventually we must implement proper args for every settings", PIKA_PARAM_READWRITE); } return procedure; } /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX PDB_STUFF XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ static PikaItem * p_pika_rotate (PikaImage *image, PikaDrawable *drawable, gint32 interpolation, gdouble angle_deg) { gdouble angle_rad; PikaItem *rc; angle_rad = (angle_deg * G_PI) / 180.0; pika_context_push (); if (! interpolation) pika_context_set_interpolation (PIKA_INTERPOLATION_NONE); pika_context_set_transform_resize (PIKA_TRANSFORM_RESIZE_ADJUST); rc = pika_item_transform_rotate (PIKA_ITEM (drawable), angle_rad, TRUE /*auto_center*/, -1.0 /*center_x*/, -1.0 /*center_y*/); pika_context_pop (); if (! rc) g_printerr ("Error: pika_drawable_transform_rotate_default call failed\n"); return rc; } /* ============================================================================ * p_if_selection_float_it * ============================================================================ */ static PikaLayer * p_if_selection_float_it (PikaImage *image, PikaLayer *layer) { if (! pika_layer_is_floating_sel (layer)) { PikaSelection *sel_channel; gint32 x1, x2, y1, y2; gint32 non_empty; /* check and see if we have a selection mask */ sel_channel = pika_image_get_selection (image); pika_selection_bounds (image, &non_empty, &x1, &y1, &x2, &y2); if (non_empty && sel_channel) { /* selection is TRUE, make a layer (floating selection) from the selection */ if (pika_edit_copy (1, (const PikaItem **) &layer)) { PikaLayer **layers; gint num_layers; layers = pika_edit_paste (PIKA_DRAWABLE (layer), FALSE, &num_layers); /* Since we explicitly copied a single layer, there should * be no more than a single layer in the paste as well. */ layer = num_layers ? layers[0] : NULL; g_free (layers); } else { return NULL; } } } return layer; } /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX END_PDB_STUFF XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ static PikaValueArray * bender_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, PikaProcedureConfig *config, gpointer run_data) { const gchar *env; PikaValueArray *return_vals; BenderDialog *cd = NULL; PikaDrawable *active_drawable = NULL; PikaDrawable *drawable; PikaLayer *layer = NULL; PikaLayer *bent_layer = NULL; GError *error = NULL; 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."), PLUG_IN_PROC); return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); } else { drawable = drawables[0]; } env = g_getenv ("BEND_DEBUG"); if (env != NULL) if ((*env != 'n') && (*env != 'N')) gb_debug = 1; if (! PIKA_IS_LAYER (drawable)) { g_set_error (&error, 0, 0, "%s", _("Can operate on layers only " "(but was called on channel or mask).")); return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); } layer = PIKA_LAYER (drawable); /* check for layermask */ if (pika_layer_get_mask (layer)) { g_set_error (&error, 0, 0, "%s", _("Cannot operate on layers with masks.")); return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); } /* if there is a selection, make it the floating selection layer */ active_drawable = PIKA_DRAWABLE (p_if_selection_float_it (image, layer)); if (! active_drawable) { /* could not float the selection because selection rectangle * is completely empty return PIKA_PDB_EXECUTION_ERROR */ g_set_error (&error, 0, 0, "%s", _("Cannot operate on empty selections.")); return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); } switch (run_mode) { case PIKA_RUN_INTERACTIVE: /* Get information from the dialog */ cd = do_dialog (procedure, config, active_drawable); cd->show_progress = TRUE; break; case PIKA_RUN_NONINTERACTIVE: cd = g_new (BenderDialog, 1); cd->run = TRUE; cd->show_progress = TRUE; cd->drawable = active_drawable; cd->config = config; break; case PIKA_RUN_WITH_LAST_VALS: cd = g_new (BenderDialog, 1); cd->run = TRUE; cd->show_progress = TRUE; cd->drawable = active_drawable; cd->config = config; p_retrieve_values (config, cd); /* Possibly retrieve data from a previous run */ break; } if (! cd) { return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); } if (cd->run) { gboolean work_on_copy; g_object_get (config, "work-on-copy", &work_on_copy, NULL); pika_image_undo_group_start (image); bent_layer = p_main_bend (cd, cd->drawable, config, work_on_copy); pika_image_undo_group_end (image); /* Store variable states for next run */ if (run_mode == PIKA_RUN_INTERACTIVE) p_store_values (config, cd); } else { return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); } if (run_mode != PIKA_RUN_NONINTERACTIVE) pika_displays_flush (); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_LAYER (return_vals, 1, bent_layer); return return_vals; } static int p_save_pointfile (BenderDialog *cd, const gchar *filename) { FILE *fp; gint j; fp = g_fopen(filename, "w+b"); if (! fp) { g_message (_("Could not open '%s' for writing: %s"), pika_filename_to_utf8 (filename), g_strerror (errno)); return -1; } fprintf (fp, "%s\n", KEY_POINTFILE); fprintf (fp, "VERSION 1.0\n\n"); fprintf (fp, "# points for upper and lower smooth curve (0.0 <= pt <= 1.0)\n"); fprintf (fp, "# there are upto 17 points where unused points are set to -1\n"); fprintf (fp, "# UPPERX UPPERY LOWERX LOWERY\n"); fprintf (fp, "\n"); for(j = 0; j < 17; j++) { fprintf (fp, "%s %+.6f %+.6f %+.6f %+.6f\n", KEY_POINTS, (float)cd->points[OUTLINE_UPPER][j][0], (float)cd->points[OUTLINE_UPPER][j][1], (float)cd->points[OUTLINE_LOWER][j][0], (float)cd->points[OUTLINE_LOWER][j][1] ); } fprintf (fp, "\n"); fprintf (fp, "# y values for upper/lower freehand curve (0 <= y <= 255) \n"); fprintf (fp, "# there must be exactly 256 y values \n"); fprintf (fp, "# UPPER_Y LOWER_Y\n"); fprintf (fp, "\n"); for (j = 0; j < 256; j++) { fprintf (fp, "%s %3d %3d\n", KEY_VAL_Y, (int)cd->curve[OUTLINE_UPPER][j], (int)cd->curve[OUTLINE_LOWER][j]); } fclose (fp); return 0; } static int p_load_pointfile (BenderDialog *cd, const gchar *filename) { gint pi, ci, n, len; FILE *fp; char buff[2000]; float fux, fuy, flx, fly; gint iuy, ily ; fp = g_fopen(filename, "rb"); if (! fp) { g_message (_("Could not open '%s' for reading: %s"), pika_filename_to_utf8 (filename), g_strerror (errno)); return -1; } pi = 0; ci = 0; if (! fgets (buff, 2000 - 1, fp)) { g_message (_("Error while reading '%s': %s"), pika_filename_to_utf8 (filename), g_strerror (errno)); fclose (fp); return -1; } if (strncmp (buff, KEY_POINTFILE, strlen(KEY_POINTFILE)) == 0) { while (NULL != fgets (buff, 2000-1, fp)) { len = strlen(KEY_POINTS); if (strncmp (buff, KEY_POINTS, len) == 0) { n = sscanf (&buff[len], "%f %f %f %f", &fux, &fuy, &flx, &fly); if ((n == 4) && (pi < 17)) { cd->points[OUTLINE_UPPER][pi][0] = fux; cd->points[OUTLINE_UPPER][pi][1] = fuy; cd->points[OUTLINE_LOWER][pi][0] = flx; cd->points[OUTLINE_LOWER][pi][1] = fly; pi++; } else { g_printf("warning: BAD points[%d] in file %s are ignored\n", pi, filename); } } len = strlen (KEY_VAL_Y); if (strncmp (buff, KEY_VAL_Y, len) == 0) { n = sscanf (&buff[len], "%d %d", &iuy, &ily); if ((n == 2) && (ci < 256)) { cd->curve[OUTLINE_UPPER][ci] = iuy; cd->curve[OUTLINE_LOWER][ci] = ily; ci++; } else { g_printf("warning: BAD y_vals[%d] in file %s are ignored\n", ci, filename); } } } } fclose (fp); return 0; } static void p_cd_to_bval (BenderDialog *cd, BenderValues *bval) { gint i, j; for (i = 0; i < 2; i++) { for (j = 0; j < 256; j++) { bval->curve[i][j] = cd->curve[i][j]; } for (j = 0; j < 17; j++) { bval->points[i][j][0] = cd->points[i][j][0]; /* x */ bval->points[i][j][1] = cd->points[i][j][1]; /* y */ } } } static void p_cd_from_bval (BenderDialog *cd, BenderValues *bval) { gint i, j; for (i = 0; i < 2; i++) { for (j = 0; j < 256; j++) { cd->curve[i][j] = bval->curve[i][j]; } for (j = 0; j < 17; j++) { cd->points[i][j][0] = bval->points[i][j][0]; /* x */ cd->points[i][j][1] = bval->points[i][j][1]; /* y */ } } } static void p_store_values (PikaProcedureConfig *config, BenderDialog *cd) { BenderValues bval; GBytes *settings_bytes; p_cd_to_bval (cd, &bval); settings_bytes = g_bytes_new (&bval, sizeof (BenderValues)); g_object_set (config, "settings-data", settings_bytes, NULL); g_bytes_unref (settings_bytes); } static void p_retrieve_values (PikaProcedureConfig *config, BenderDialog *cd) { GBytes *settings_bytes; g_object_get (config, "settings-data", &settings_bytes, NULL); if (settings_bytes != NULL && g_bytes_get_size (settings_bytes) == sizeof (BenderValues)) { BenderValues bval; bval = *((BenderValues *) g_bytes_get_data (settings_bytes, NULL)); p_cd_from_bval(cd, &bval); } g_bytes_unref (settings_bytes); } /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ /* curves machinery */ /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ static BenderDialog * do_dialog (PikaProcedure *procedure, PikaProcedureConfig *config, PikaDrawable *drawable) { BenderDialog *cd; /* Init GTK */ pika_ui_init (PLUG_IN_BINARY); /* The curve_bend dialog */ cd = bender_new_dialog (procedure, config, drawable); /* create temporary image (with a small copy of drawable) for the preview */ cd->preview_image = p_create_pv_image (drawable, &cd->preview_layer1); cd->preview_layer2 = NULL; if (! gtk_widget_get_visible (cd->shell)) gtk_widget_show (cd->shell); bender_update (cd, UP_GRAPH | UP_DRAW | UP_PREVIEW_EXPOSE); cd->run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (cd->shell)); gtk_widget_destroy (cd->shell); pika_image_delete (cd->preview_image); cd->preview_image = NULL; cd->preview_layer1 = NULL; cd->preview_layer2 = NULL; return cd; } /**************************/ /* Select Curves dialog */ /**************************/ static BenderDialog * bender_new_dialog (PikaProcedure *procedure, PikaProcedureConfig *config, PikaDrawable *drawable) { BenderDialog *cd; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *vbox2; GtkWidget *frame; GtkWidget *toggle; GtkWidget *button; GdkDisplay *display; GtkListStore *store; gint i, j; cd = g_new (BenderDialog, 1); cd->config = config; cd->preview = FALSE; cd->filechooser = NULL; cd->show_progress = FALSE; cd->drawable = drawable; cd->color = pika_drawable_is_rgb (drawable); cd->run = FALSE; for (i = 0; i < 2; i++) for (j = 0; j < 256; j++) cd->curve[i][j] = MIDDLE; cd->grab_point = -1; for (i = 0; i < 2; i++) { for (j = 0; j < 17; j++) { cd->points[i][j][0] = -1; cd->points[i][j][1] = -1; } cd->points[i][0][0] = 0.0; /* x */ cd->points[i][0][1] = 0.5; /* y */ cd->points[i][16][0] = 1.0; /* x */ cd->points[i][16][1] = 0.5; /* y */ } p_retrieve_values (config, cd); /* Possibly retrieve data from a previous run */ /* The shell and main vbox */ cd->shell = pika_procedure_dialog_new (procedure, PIKA_PROCEDURE_CONFIG (config), _("Curve Bend")); pika_dialog_set_alternative_button_order (GTK_DIALOG (cd->shell), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); pika_window_set_transient (GTK_WINDOW (cd->shell)); /* busy cursor */ display = gtk_widget_get_display (cd->shell); cd->cursor_busy = gdk_cursor_new_for_display (display, GDK_WATCH); /* Left side column */ /* Config-connected widgets first */ pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (cd->shell), "option-label", _("Options"), FALSE, FALSE); pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (cd->shell), "options-hbox", "rotation", "smoothing", "antialias", "work-on-copy", NULL); g_signal_connect (config, "notify::rotation", G_CALLBACK (bender_global_notify), cd); g_signal_connect (config, "notify::smoothing", G_CALLBACK (bender_global_notify), cd); g_signal_connect (config, "notify::antialias", G_CALLBACK (bender_global_notify), cd); pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (cd->shell), "option-frame", "option-label", FALSE, "options-hbox"); vbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-left-col", "option-frame", NULL); /* GUI-only widgets */ /* Preview area, top of column */ frame = pika_frame_new (_("Preview")); gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); gtk_widget_set_visible (frame, TRUE); gtk_box_reorder_child (GTK_BOX (vbox), frame, 0); vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_container_add (GTK_CONTAINER (frame), vbox2); gtk_widget_set_visible (vbox2, TRUE); /* The range drawing area */ frame = gtk_frame_new (NULL); gtk_widget_set_halign (frame, GTK_ALIGN_CENTER); gtk_widget_set_valign (frame, GTK_ALIGN_CENTER); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0); gtk_widget_set_visible (frame, TRUE); cd->pv_widget = pika_preview_area_new (); gtk_widget_set_size_request (cd->pv_widget, PREVIEW_SIZE_X, PREVIEW_SIZE_Y); gtk_container_add (GTK_CONTAINER (frame), cd->pv_widget); gtk_widget_set_visible (cd->pv_widget, TRUE); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_end (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); gtk_widget_set_visible (hbox, TRUE); /* The preview button */ button = gtk_button_new_with_mnemonic (_("_Preview Once")); gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); gtk_widget_set_visible (button, TRUE); g_signal_connect (button, "clicked", G_CALLBACK (bender_preview_update_once), cd); /* The preview toggle */ toggle = gtk_check_button_new_with_mnemonic (_("Automatic pre_view")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cd->preview); gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0); gtk_widget_set_visible (toggle, TRUE); g_signal_connect (toggle, "toggled", G_CALLBACK (bender_preview_update), cd); /* The curves graph */ /* Config-connected widgets first */ store = pika_int_store_new (_("Smooth"), 0, _("Free"), 1, NULL); pika_procedure_dialog_get_int_radio (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-type", PIKA_INT_STORE (store)); g_signal_connect (config, "notify::curve-type", G_CALLBACK (bender_type_callback), cd); store = pika_int_store_new (_("Upper"), OUTLINE_UPPER, C_("adjective", "Lower"), OUTLINE_LOWER, NULL); pika_procedure_dialog_get_int_radio (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-border", PIKA_INT_STORE (store)); g_signal_connect (config, "notify::curve-border", G_CALLBACK (bender_global_notify), cd); hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-hbox", "curve-border", "curve-type", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); vbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-right-col", "curve-hbox", NULL); pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (cd->shell), "modify-label", _("Modify Curves"), FALSE, FALSE); pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-right-frame", "modify-label", FALSE, "curve-right-col"); /* GUI-only widgets */ cd->graph = gtk_drawing_area_new (); gtk_widget_set_halign (cd->graph, GTK_ALIGN_CENTER); gtk_widget_set_valign (cd->graph, GTK_ALIGN_CENTER); gtk_widget_set_size_request (cd->graph, GRAPH_WIDTH + RADIUS * 2, GRAPH_HEIGHT + RADIUS * 2); gtk_widget_set_events (cd->graph, GRAPH_MASK); gtk_box_pack_start (GTK_BOX (vbox), cd->graph, FALSE, FALSE, 0); gtk_widget_set_visible (cd->graph, TRUE); g_signal_connect (cd->graph, "event", G_CALLBACK (bender_graph_events), cd); g_signal_connect (cd->graph, "draw", G_CALLBACK (bender_graph_draw), cd); gtk_box_reorder_child (GTK_BOX (vbox), cd->graph, 0); /* hbox for curve options */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_set_visible (hbox, TRUE); /* The Copy button */ button = gtk_button_new_with_mnemonic (_("_Copy")); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_set_visible (button, TRUE); pika_help_set_help_data (button, _("Copy the active curve to the other border"), NULL); g_signal_connect (button, "clicked", G_CALLBACK (bender_copy_callback), cd); /* The CopyInv button */ button = gtk_button_new_with_mnemonic (_("_Mirror")); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_set_visible (button, TRUE); pika_help_set_help_data (button, _("Mirror the active curve to the other border"), NULL); g_signal_connect (button, "clicked", G_CALLBACK (bender_copy_inv_callback), cd); /* The Swap button */ button = gtk_button_new_with_mnemonic (_("S_wap")); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_set_visible (button, TRUE); pika_help_set_help_data (button, _("Swap the two curves"), NULL); g_signal_connect (button, "clicked", G_CALLBACK (bender_swap_callback), cd); /* The Reset button */ button = gtk_button_new_with_mnemonic (_("_Reset")); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_set_visible (button, TRUE); pika_help_set_help_data (button, _("Reset the active curve"), NULL); g_signal_connect (button, "clicked", G_CALLBACK (bender_reset_callback), cd); /* hbox for curve load and save */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_set_visible (hbox, TRUE); /* The Load button */ button = gtk_button_new_with_mnemonic (_("_Open")); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_set_visible (button, TRUE); pika_help_set_help_data (button, _("Load the curves from a file"), NULL); g_signal_connect (button, "clicked", G_CALLBACK (bender_load_callback), cd); /* The Save button */ button = gtk_button_new_with_mnemonic (_("_Save")); gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); gtk_widget_set_visible (button, TRUE); pika_help_set_help_data (button, _("Save the curves to a file"), NULL); g_signal_connect (button, "clicked", G_CALLBACK (bender_save_callback), cd); /* Combine the two columns */ hbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-columns", "curve-left-col", "curve-right-frame", NULL); gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox), GTK_ORIENTATION_HORIZONTAL); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (cd->shell), "curve-columns", NULL); return cd; } static void bender_update (BenderDialog *cd, int update) { if (update & UP_PREVIEW) { gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (cd->shell)), cd->cursor_busy); gdk_display_flush (gtk_widget_get_display (cd->shell)); if (cd->preview_layer2) pika_image_remove_layer (cd->preview_image, cd->preview_layer2); cd->preview_layer2 = p_main_bend (cd, PIKA_DRAWABLE (cd->preview_layer1), cd->config, TRUE /* work_on_copy*/ ); p_render_preview (cd, cd->preview_layer2); if (update & UP_DRAW) gtk_widget_queue_draw (cd->pv_widget); gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (cd->shell)), NULL); } if (update & UP_PREVIEW_EXPOSE) { /* on expose just redraw cd->preview_layer2 * that holds the bent version of the preview (if there is one) */ if (! cd->preview_layer2) cd->preview_layer2 = p_main_bend (cd, PIKA_DRAWABLE (cd->preview_layer1), cd->config, TRUE /* work_on_copy*/ ); p_render_preview (cd, cd->preview_layer2); if (update & UP_DRAW) gtk_widget_queue_draw (cd->pv_widget); } if ((update & UP_GRAPH) && (update & UP_DRAW)) gtk_widget_queue_draw (cd->graph); } static void bender_plot_curve (BenderDialog *cd, int p1, int p2, int p3, int p4, gint32 xmax, gint32 ymax, gint fix255) { CRMatrix geometry; CRMatrix tmp1, tmp2; CRMatrix deltas; double x, dx, dx2, dx3; double y, dy, dy2, dy3; double d, d2, d3; int lastx, lasty; gint32 newx, newy; gint32 ntimes; gint32 i; gint outline; g_object_get (cd->config, "curve-border", &outline, NULL); /* construct the geometry matrix from the segment */ for (i = 0; i < 4; i++) { geometry[i][2] = 0; geometry[i][3] = 0; } geometry[0][0] = (cd->points[outline][p1][0] * xmax); geometry[1][0] = (cd->points[outline][p2][0] * xmax); geometry[2][0] = (cd->points[outline][p3][0] * xmax); geometry[3][0] = (cd->points[outline][p4][0] * xmax); geometry[0][1] = (cd->points[outline][p1][1] * ymax); geometry[1][1] = (cd->points[outline][p2][1] * ymax); geometry[2][1] = (cd->points[outline][p3][1] * ymax); geometry[3][1] = (cd->points[outline][p4][1] * ymax); /* subdivide the curve ntimes (1000) times */ ntimes = 4 * xmax; /* ntimes can be adjusted to give a finer or coarser curve */ d = 1.0 / ntimes; d2 = d * d; d3 = d * d * d; /* construct a temporary matrix for determining the forward differencing deltas */ tmp2[0][0] = 0; tmp2[0][1] = 0; tmp2[0][2] = 0; tmp2[0][3] = 1; tmp2[1][0] = d3; tmp2[1][1] = d2; tmp2[1][2] = d; tmp2[1][3] = 0; tmp2[2][0] = 6*d3; tmp2[2][1] = 2*d2; tmp2[2][2] = 0; tmp2[2][3] = 0; tmp2[3][0] = 6*d3; tmp2[3][1] = 0; tmp2[3][2] = 0; tmp2[3][3] = 0; /* compose the basis and geometry matrices */ bender_CR_compose (CR_basis, geometry, tmp1); /* compose the above results to get the deltas matrix */ bender_CR_compose (tmp2, tmp1, deltas); /* extract the x deltas */ x = deltas[0][0]; dx = deltas[1][0]; dx2 = deltas[2][0]; dx3 = deltas[3][0]; /* extract the y deltas */ y = deltas[0][1]; dy = deltas[1][1]; dy2 = deltas[2][1]; dy3 = deltas[3][1]; lastx = CLAMP (x, 0, xmax); lasty = CLAMP (y, 0, ymax); if (fix255) { cd->curve[outline][lastx] = lasty; } else { cd->curve_ptr[outline][lastx] = lasty; if(gb_debug) g_printf("bender_plot_curve xmax:%d ymax:%d\n", (gint) xmax, (gint) ymax); } /* loop over the curve */ for (i = 0; i < ntimes; i++) { /* increment the x values */ x += dx; dx += dx2; dx2 += dx3; /* increment the y values */ y += dy; dy += dy2; dy2 += dy3; newx = CLAMP ((ROUND (x)), 0, xmax); newy = CLAMP ((ROUND (y)), 0, ymax); /* if this point is different than the last one...then draw it */ if ((lastx != newx) || (lasty != newy)) { if (fix255) { /* use fixed array size (for the curve graph) */ cd->curve[outline][newx] = newy; } else { /* use dynamic allocated curve_ptr (for the real curve) */ cd->curve_ptr[outline][newx] = newy; if (gb_debug) g_printf("outline: %d cX: %d cY: %d\n", (gint) outline, (int) newx, (int) newy); } } lastx = newx; lasty = newy; } } static void bender_calculate_curve (BenderDialog *cd, gint32 xmax, gint32 ymax, gint fix255) { gint i; gint points[17]; gint num_pts; gint p1, p2, p3, p4; gint xmid; gint yfirst, ylast; gint curve_type; gint outline; g_object_get (cd->config, "curve-type", &curve_type, "curve-border", &outline, NULL); switch (curve_type) { case GFREE: break; case SMOOTH: /* cycle through the curves */ num_pts = 0; for (i = 0; i < 17; i++) if (cd->points[outline][i][0] != -1) points[num_pts++] = i; xmid = xmax / 2; /* Initialize boundary curve points */ if (num_pts != 0) { if (fix255) { for (i = 0; i < (cd->points[outline][points[0]][0] * 255); i++) cd->curve[outline][i] = (cd->points[outline][points[0]][1] * 255); for (i = (cd->points[outline][points[num_pts - 1]][0] * 255); i < 256; i++) cd->curve[outline][i] = (cd->points[outline][points[num_pts - 1]][1] * 255); } else { yfirst = cd->points[outline][points[0]][1] * ymax; ylast = cd->points[outline][points[num_pts - 1]][1] * ymax; for (i = 0; i < xmid; i++) { cd->curve_ptr[outline][i] = yfirst; } for (i = xmid; i <= xmax; i++) { cd->curve_ptr[outline][i] = ylast; } } } for (i = 0; i < num_pts - 1; i++) { p1 = (i == 0) ? points[i] : points[(i - 1)]; p2 = points[i]; p3 = points[(i + 1)]; p4 = (i == (num_pts - 2)) ? points[(num_pts - 1)] : points[(i + 2)]; bender_plot_curve (cd, p1, p2, p3, p4, xmax, ymax, fix255); } break; } } static void bender_global_notify (GtkWidget *widget, gpointer data) { BenderDialog *cd; cd = g_object_get_data (G_OBJECT (widget), "cd"); if (! cd) return; if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } static void bender_type_callback (GtkWidget *widget, gpointer data) { BenderDialog *cd; gint curve_type; gint outline; cd = g_object_get_data (G_OBJECT (widget), "cd"); if (! cd) return; g_object_get (cd->config, "curve-type", &curve_type, "curve-border", &outline, NULL); if (curve_type == SMOOTH) { gint i; /* pick representative points from the curve and make them control points */ for (i = 0; i <= 8; i++) { gint index = CLAMP ((i * 32), 0, 255); cd->points[outline][i * 2][0] = (gdouble)index / 255.0; cd->points[outline][i * 2][1] = (gdouble)cd->curve[outline][index] / 255.0; } bender_calculate_curve (cd, 255, 255, TRUE); bender_update (cd, UP_GRAPH | UP_DRAW); if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } else { bender_update (cd, UP_GRAPH | UP_DRAW); } } static void bender_reset_callback (GtkWidget *widget, gpointer client_data) { BenderDialog *cd = (BenderDialog *) client_data; gint i; gint outline; g_object_get (cd->config, "curve-border", &outline, NULL); /* Initialize the values */ for (i = 0; i < 256; i++) cd->curve[outline][i] = MIDDLE; cd->grab_point = -1; for (i = 0; i < 17; i++) { cd->points[outline][i][0] = -1; cd->points[outline][i][1] = -1; } cd->points[outline][0][0] = 0.0; /* x */ cd->points[outline][0][1] = 0.5; /* y */ cd->points[outline][16][0] = 1.0; /* x */ cd->points[outline][16][1] = 0.5; /* y */ bender_update (cd, UP_GRAPH | UP_DRAW); if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } static void bender_copy_callback (GtkWidget *widget, gpointer client_data) { BenderDialog *cd = (BenderDialog *) client_data; gint i; gint other; gint outline; g_object_get (cd->config, "curve-border", &outline, NULL); other = (outline) ? 0 : 1; for (i = 0; i < 17; i++) { cd->points[other][i][0] = cd->points[outline][i][0]; cd->points[other][i][1] = cd->points[outline][i][1]; } for (i= 0; i < 256; i++) { cd->curve[other][i] = cd->curve[outline][i]; } bender_update (cd, UP_GRAPH | UP_DRAW); if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } static void bender_copy_inv_callback (GtkWidget *widget, gpointer client_data) { BenderDialog *cd = (BenderDialog *) client_data; gint i; gint other; gint outline; g_object_get (cd->config, "curve-border", &outline, NULL); other = (outline) ? 0 : 1; for (i = 0; i < 17; i++) { cd->points[other][i][0] = cd->points[outline][i][0]; /* x */ cd->points[other][i][1] = 1.0 - cd->points[outline][i][1]; /* y */ } for (i= 0; i < 256; i++) { cd->curve[other][i] = 255 - cd->curve[outline][i]; } bender_update (cd, UP_GRAPH | UP_DRAW); if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } static void bender_swap_callback (GtkWidget *widget, gpointer client_data) { #define SWAP_VALUE(a, b, h) { h=a; a=b; b=h; } BenderDialog *cd = (BenderDialog *) client_data; gint i; gint other; gint outline; gdouble hd; guchar hu; g_object_get (cd->config, "curve-border", &outline, NULL); other = (outline) ? 0 : 1; for (i = 0; i < 17; i++) { SWAP_VALUE(cd->points[other][i][0], cd->points[outline][i][0], hd); /* x */ SWAP_VALUE(cd->points[other][i][1], cd->points[outline][i][1], hd); /* y */ } for (i= 0; i < 256; i++) { SWAP_VALUE(cd->curve[other][i], cd->curve[outline][i], hu); } bender_update (cd, UP_GRAPH | UP_DRAW); if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } static void bender_preview_update (GtkWidget *widget, gpointer data) { BenderDialog *cd = (BenderDialog*) data; if (! cd) return; cd->preview = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); if(cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); } static void bender_preview_update_once (GtkWidget *widget, gpointer data) { BenderDialog *cd = (BenderDialog*) data; if (! cd) return; bender_update (cd, UP_PREVIEW | UP_DRAW); } static void p_points_save_to_file_response (GtkWidget *dialog, gint response_id, BenderDialog *cd) { if (response_id == GTK_RESPONSE_OK) { gchar *filename; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); p_save_pointfile (cd, filename); g_free (filename); } gtk_widget_destroy (dialog); } static void p_points_load_from_file_response (GtkWidget *dialog, gint response_id, BenderDialog *cd) { if (response_id == GTK_RESPONSE_OK) { gchar *filename; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); p_load_pointfile (cd, filename); bender_update (cd, UP_ALL); g_free (filename); } gtk_widget_destroy (dialog); } static void bender_load_callback (GtkWidget *w, BenderDialog *cd) { if (! cd->filechooser) { cd->filechooser = gtk_file_chooser_dialog_new (_("Load Curve Points from File"), GTK_WINDOW (gtk_widget_get_toplevel (w)), GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_OK, NULL); pika_dialog_set_alternative_button_order (GTK_DIALOG (cd->filechooser), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); gtk_dialog_set_default_response (GTK_DIALOG (cd->filechooser), GTK_RESPONSE_OK); g_signal_connect (cd->filechooser, "response", G_CALLBACK (p_points_load_from_file_response), cd); g_signal_connect (cd->filechooser, "destroy", G_CALLBACK (gtk_widget_destroyed), &cd->filechooser); } gtk_window_present (GTK_WINDOW (cd->filechooser)); } static void bender_save_callback (GtkWidget *w, BenderDialog *cd) { if (! cd->filechooser) { cd->filechooser = gtk_file_chooser_dialog_new (_("Save Curve Points to File"), GTK_WINDOW (gtk_widget_get_toplevel (w)), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, NULL); g_signal_connect (cd->filechooser, "response", G_CALLBACK (p_points_save_to_file_response), cd); g_signal_connect (cd->filechooser, "destroy", G_CALLBACK (gtk_widget_destroyed), &cd->filechooser); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (cd->filechooser), "curve_bend.points"); } gtk_window_present (GTK_WINDOW (cd->filechooser)); } static gboolean bender_graph_events (GtkWidget *widget, GdkEvent *event, BenderDialog *cd) { static GdkCursorType cursor_type = GDK_TOP_LEFT_ARROW; GdkCursorType new_type; GdkEventMotion *mevent; gint i; gint tx, ty; gint x, y; gint closest_point; gint distance; gint x1, x2, y1, y2; gint curve_type; gint outline; g_object_get (cd->config, "curve-type", &curve_type, "curve-border", &outline, NULL); new_type = GDK_X_CURSOR; closest_point = 0; if (! gdk_event_get_device (event)) return FALSE; /* get the pointer position */ gdk_window_get_device_position (gtk_widget_get_window (cd->graph), gdk_event_get_device (event), &tx, &ty, NULL); x = CLAMP ((tx - RADIUS), 0, 255); y = CLAMP ((ty - RADIUS), 0, 255); distance = G_MAXINT; for (i = 0; i < 17; i++) { if (cd->points[outline][i][0] != -1) if (abs ((int) (x - (cd->points[outline][i][0] * 255.0))) < distance) { distance = abs ((int) (x - (cd->points[outline][i][0] * 255.0))); closest_point = i; } } if (distance > MIN_DISTANCE) closest_point = (x + 8) / 16; switch (event->type) { case GDK_BUTTON_PRESS: new_type = GDK_TCROSS; switch (curve_type) { case SMOOTH: /* determine the leftmost and rightmost points */ cd->leftmost = -1; for (i = closest_point - 1; i >= 0; i--) if (cd->points[outline][i][0] != -1) { cd->leftmost = (cd->points[outline][i][0] * 255.0); break; } cd->rightmost = 256; for (i = closest_point + 1; i < 17; i++) if (cd->points[outline][i][0] != -1) { cd->rightmost = (cd->points[outline][i][0] * 255.0); break; } cd->grab_point = closest_point; cd->points[outline][cd->grab_point][0] = (gdouble)x / 255.0; cd->points[outline][cd->grab_point][1] = (gdouble)(255 - y) / 255.0; bender_calculate_curve (cd, 255, 255, TRUE); break; case GFREE: cd->curve[outline][x] = 255 - y; cd->grab_point = x; cd->last = y; break; } bender_update (cd, UP_GRAPH | UP_DRAW); break; case GDK_BUTTON_RELEASE: new_type = GDK_FLEUR; cd->grab_point = -1; if (cd->preview) bender_update (cd, UP_PREVIEW | UP_DRAW); break; case GDK_MOTION_NOTIFY: mevent = (GdkEventMotion *) event; if (mevent->is_hint) { mevent->x = tx; mevent->y = ty; } switch (curve_type) { case SMOOTH: /* If no point is grabbed... */ if (cd->grab_point == -1) { if (cd->points[outline][closest_point][0] != -1) new_type = GDK_FLEUR; else new_type = GDK_TCROSS; } /* Else, drag the grabbed point */ else { new_type = GDK_TCROSS; cd->points[outline][cd->grab_point][0] = -1; if (x > cd->leftmost && x < cd->rightmost) { closest_point = (x + 8) / 16; if (cd->points[outline][closest_point][0] == -1) cd->grab_point = closest_point; cd->points[outline][cd->grab_point][0] = (gdouble)x / 255.0; cd->points[outline][cd->grab_point][1] = (gdouble)(255 - y) / 255.0; } bender_calculate_curve (cd, 255, 255, TRUE); bender_update (cd, UP_GRAPH | UP_DRAW); } break; case GFREE: if (cd->grab_point != -1) { if (cd->grab_point > x) { x1 = x; x2 = cd->grab_point; y1 = y; y2 = cd->last; } else { x1 = cd->grab_point; x2 = x; y1 = cd->last; y2 = y; } if (x2 != x1) for (i = x1; i <= x2; i++) cd->curve[outline][i] = 255 - (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1)); else cd->curve[outline][x] = 255 - y; cd->grab_point = x; cd->last = y; bender_update (cd, UP_GRAPH | UP_DRAW); } if (mevent->state & GDK_BUTTON1_MASK) new_type = GDK_TCROSS; else new_type = GDK_PENCIL; break; } if (new_type != cursor_type) { cursor_type = new_type; /* change_win_cursor (gtk_widget_get_window (cd->graph), cursor_type); */ } break; default: break; } return FALSE; } static gboolean bender_graph_draw (GtkWidget *widget, cairo_t *cr, BenderDialog *cd) { GdkRGBA black = { 0.0, 0.0, 0.0, 1.0 }; GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 }; GdkRGBA gray = { 0.5, 0.5, 0.5, 1.0 }; gint i; gint other; gint curve_type; gint outline; g_object_get (cd->config, "curve-type", &curve_type, "curve-border", &outline, NULL); cairo_set_line_width (cr, 1.0); cairo_translate (cr, 0.5, 0.5); /* Clear the background */ gdk_cairo_set_source_rgba (cr, &white); cairo_paint (cr); /* Draw the grid lines */ for (i = 0; i < 5; i++) { cairo_move_to (cr, RADIUS, i * (GRAPH_HEIGHT / 4) + RADIUS); cairo_line_to (cr, GRAPH_WIDTH + RADIUS, i * (GRAPH_HEIGHT / 4) + RADIUS); cairo_move_to (cr, i * (GRAPH_WIDTH / 4) + RADIUS, RADIUS); cairo_line_to (cr, i * (GRAPH_WIDTH / 4) + RADIUS, GRAPH_HEIGHT + RADIUS); } gdk_cairo_set_source_rgba (cr, &gray); cairo_stroke (cr); /* Draw the other curve */ other = (outline == 0) ? 1 : 0; cairo_move_to (cr, RADIUS, 255 - cd->curve[other][0] + RADIUS); for (i = 1; i < 256; i++) { cairo_line_to (cr, i + RADIUS, 255 - cd->curve[other][i] + RADIUS); } gdk_cairo_set_source_rgba (cr, &gray); cairo_stroke (cr); /* Draw the active curve */ cairo_move_to (cr, RADIUS, 255 - cd->curve[outline][0] + RADIUS); for (i = 1; i < 256; i++) { cairo_line_to (cr, i + RADIUS, 255 - cd->curve[outline][i] + RADIUS); } /* Draw the points */ if (curve_type == SMOOTH) { for (i = 0; i < 17; i++) { if (cd->points[outline][i][0] != -1) { cairo_new_sub_path (cr); cairo_arc (cr, (cd->points[outline][i][0] * 255.0) + RADIUS, 255 - (cd->points[outline][i][1] * 255.0) + RADIUS, RADIUS, 0, 2 * G_PI); } } } gdk_cairo_set_source_rgba (cr, &black); cairo_stroke (cr); return FALSE; } static void bender_CR_compose (CRMatrix a, CRMatrix b, CRMatrix ab) { gint i, j; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { ab[i][j] = (a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j] + a[i][3] * b[3][j]); } } } static void p_render_preview (BenderDialog *cd, PikaLayer *layer) { guchar pixel[4]; guchar *buf, *ptr; gint x, y; gint ofx, ofy; gint width, height; t_GDRW l_gdrw; t_GDRW *gdrw; width = pika_drawable_get_width (PIKA_DRAWABLE (layer)); height = pika_drawable_get_height (PIKA_DRAWABLE (layer)); ptr = buf = g_new (guchar, PREVIEW_BPP * PREVIEW_SIZE_X * PREVIEW_SIZE_Y); gdrw = &l_gdrw; p_init_gdrw (gdrw, PIKA_DRAWABLE (layer), FALSE); /* offsets to set bend layer to preview center */ ofx = (width / 2) - (PREVIEW_SIZE_X / 2); ofy = (height / 2) - (PREVIEW_SIZE_Y / 2); /* render preview */ for (y = 0; y < PREVIEW_SIZE_Y; y++) { for (x = 0; x < PREVIEW_SIZE_X; x++) { p_get_pixel (gdrw, x + ofx, y + ofy, &pixel[0]); if (cd->color) { ptr[0] = pixel[0]; ptr[1] = pixel[1]; ptr[2] = pixel[2]; } else { ptr[0] = pixel[0]; ptr[1] = pixel[0]; ptr[2] = pixel[0]; } ptr[3] = pixel[gdrw->index_alpha]; ptr += PREVIEW_BPP; } } pika_preview_area_draw (PIKA_PREVIEW_AREA (cd->pv_widget), 0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y, PIKA_RGBA_IMAGE, buf, PREVIEW_BPP * PREVIEW_SIZE_X); g_free (buf); p_end_gdrw(gdrw); } /* ===================================================== */ /* curve_bend worker procedures */ /* ===================================================== */ static void p_stretch_curves (BenderDialog *cd, gint32 xmax, gint32 ymax) { gint32 x1, x2; gdouble ya, yb; gdouble rest; int outline; for (outline = 0; outline < 2; outline++) { for(x1 = 0; x1 <= xmax; x1++) { x2 = (x1 * 255) / xmax; if ((xmax <= 255) && (x2 < 255)) { cd->curve_ptr[outline][x1] = ROUND ((cd->curve[outline][x2] * ymax) / 255); } else { /* interpolate */ rest = (((gdouble)x1 * 255.0) / (gdouble)xmax) - x2; ya = cd->curve[outline][x2]; /* y of this point */ yb = cd->curve[outline][x2 +1]; /* y of next point */ cd->curve_ptr[outline][x1] = ROUND (((ya + ((yb -ya) * rest)) * ymax) / 255); } } } } static void bender_init_min_max (BenderDialog *cd, gint32 xmax) { int i, j; for (i = 0; i < 2; i++) { cd->min2[i] = 65000; cd->max2[i] = 0; for (j = 0; j <= xmax; j++) { if(cd->curve_ptr[i][j] > cd->max2[i]) { cd->max2[i] = cd->curve_ptr[i][j]; } if(cd->curve_ptr[i][j] < cd->min2[i]) { cd->min2[i] = cd->curve_ptr[i][j]; } } } /* for UPPER outline : y-zero line is assumed at the min leftmost or * rightmost point */ cd->zero2[OUTLINE_UPPER] = MIN (cd->curve_ptr[OUTLINE_UPPER][0], cd->curve_ptr[OUTLINE_UPPER][xmax]); /* for LOWER outline : y-zero line is assumed at the min leftmost or * rightmost point */ cd->zero2[OUTLINE_LOWER] = MAX (cd->curve_ptr[OUTLINE_LOWER][0], cd->curve_ptr[OUTLINE_LOWER][xmax]); } static gint32 p_curve_get_dy (BenderDialog *cd, gint32 x, gint32 drawable_width, gint32 total_steps, gdouble current_step) { /* get y values of both upper and lower curve, * and return the iterated value in between */ gdouble y1, y2; gdouble delta; y1 = cd->zero2[OUTLINE_UPPER] - cd->curve_ptr[OUTLINE_UPPER][x]; y2 = cd->zero2[OUTLINE_LOWER] - cd->curve_ptr[OUTLINE_LOWER][x]; delta = ((double)(y2 - y1) / (double)(total_steps - 1)) * current_step; return SIGNED_ROUND (y1 + delta); } static gint32 p_upper_curve_extend (BenderDialog *cd, gint32 drawable_width, gint32 drawable_height) { gint32 y1, y2; y1 = cd->max2[OUTLINE_UPPER] - cd->zero2[OUTLINE_UPPER]; y2 = (cd->max2[OUTLINE_LOWER] - cd->zero2[OUTLINE_LOWER]) - drawable_height; return MAX (y1, y2); } static gint32 p_lower_curve_extend (BenderDialog *cd, gint32 drawable_width, gint32 drawable_height) { gint32 y1, y2; y1 = cd->zero2[OUTLINE_LOWER] - cd->min2[OUTLINE_LOWER]; y2 = (cd->zero2[OUTLINE_UPPER] - cd->min2[OUTLINE_UPPER]) - drawable_height; return MAX (y1, y2); } static void p_end_gdrw (t_GDRW *gdrw) { g_object_unref (gdrw->buffer); } static void p_init_gdrw (t_GDRW *gdrw, PikaDrawable *drawable, int shadow) { gint w, h; gdrw->drawable = drawable; if (shadow) gdrw->buffer = pika_drawable_get_shadow_buffer (drawable); else gdrw->buffer = pika_drawable_get_buffer (drawable); gdrw->width = pika_drawable_get_width (gdrw->drawable); gdrw->height = pika_drawable_get_height (gdrw->drawable); gdrw->tile_width = pika_tile_width (); gdrw->tile_height = pika_tile_height (); if (! pika_drawable_mask_intersect (gdrw->drawable, &gdrw->x1, &gdrw->y1, &w, &h)) { w = 0; h = 0; } gdrw->x2 = gdrw->x1 + w; gdrw->y2 = gdrw->y1 + h; if (pika_drawable_has_alpha (drawable)) gdrw->format = babl_format ("R'G'B'A u8"); else gdrw->format = babl_format ("R'G'B' u8"); gdrw->bpp = babl_format_get_bytes_per_pixel (gdrw->format); if (pika_drawable_has_alpha (drawable)) { /* index of the alpha channelbyte {1|3} */ gdrw->index_alpha = gdrw->bpp - 1; } else { gdrw->index_alpha = 0; /* there is no alpha channel */ } } /* get pixel value * return light transparent black gray pixel if out of bounds * (should occur in the previews only) */ static void p_get_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *pixel) { pixel[1] = 255; pixel[3] = 255; /* simulate full visible alpha channel */ gegl_buffer_sample (gdrw->buffer, x, y, NULL, pixel, gdrw->format, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); } static void p_put_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *pixel) { gegl_buffer_set (gdrw->buffer, GEGL_RECTANGLE (x, y, 1, 1), 0, gdrw->format, pixel, GEGL_AUTO_ROWSTRIDE); } static void p_put_mix_pixel (t_GDRW *gdrw, gint32 x, gint32 y, guchar *color, gint32 nb_curvy, gint32 nb2_curvy, gint32 curvy) { guchar pixel[4]; guchar mixmask; gint idx; gint diff; mixmask = 255 - 96; diff = abs(nb_curvy - curvy); if (diff == 0) { mixmask = 255 - 48; diff = abs(nb2_curvy - curvy); if (diff == 0) { /* last 2 neighbours were not shifted against current pixel, do not mix */ p_put_pixel(gdrw, x, y, color); return; } } /* get left neighbour pixel */ p_get_pixel(gdrw, x-1, y, &pixel[0]); if (pixel[gdrw->index_alpha] < 10) { /* neighbour is (nearly or full) transparent, do not mix */ p_put_pixel(gdrw, x, y, color); return; } for (idx = 0; idx < gdrw->index_alpha ; idx++) { /* mix in left neighbour color */ pixel[idx] = MIX_CHANNEL(color[idx], pixel[idx], mixmask); } pixel[gdrw->index_alpha] = color[gdrw->index_alpha]; p_put_pixel(gdrw, x, y, &pixel[0]); } /* ============================================================================ * p_create_pv_image * ============================================================================ */ static PikaImage * p_create_pv_image (PikaDrawable *src_drawable, PikaLayer **layer) { PikaImage *new_image; guint new_width; guint new_height; PikaImageType type; guint x, y; double scale; guchar pixel[4]; t_GDRW src_gdrw; t_GDRW dst_gdrw; gint src_width; gint src_height; src_width = pika_drawable_get_width (src_drawable); src_height = pika_drawable_get_height (src_drawable); new_image = pika_image_new (PREVIEW_SIZE_X, PREVIEW_SIZE_Y, pika_image_get_base_type (pika_item_get_image (PIKA_ITEM (src_drawable)))); pika_image_undo_disable (new_image); type = pika_drawable_type (src_drawable); if (src_height > src_width) { new_height = PV_IMG_HEIGHT; new_width = (src_width * new_height) / src_height; scale = (float) src_height / PV_IMG_HEIGHT; } else { new_width = PV_IMG_WIDTH; new_height = (src_height * new_width) / src_width; scale = (float) src_width / PV_IMG_WIDTH; } *layer = pika_layer_new (new_image, "preview_original", new_width, new_height, type, 100.0, /* opacity */ 0); /* mode NORMAL */ if (! pika_drawable_has_alpha (PIKA_DRAWABLE (*layer))) { /* always add alpha channel */ pika_layer_add_alpha (*layer); } pika_image_insert_layer (new_image, *layer, NULL, 0); p_init_gdrw (&src_gdrw, src_drawable, FALSE); p_init_gdrw (&dst_gdrw, PIKA_DRAWABLE (*layer), FALSE); for (y = 0; y < new_height; y++) { for (x = 0; x < new_width; x++) { p_get_pixel(&src_gdrw, x * scale, y * scale, &pixel[0]); p_put_pixel(&dst_gdrw, x, y, &pixel[0]); } } p_end_gdrw (&src_gdrw); p_end_gdrw (&dst_gdrw); return new_image; } /* ============================================================================ * p_add_layer * ============================================================================ */ static PikaLayer * p_add_layer (gint width, gint height, PikaDrawable *src_drawable) { PikaImageType type; PikaLayer *new_layer; char *name; char *name2; gdouble opacity; PikaLayerMode mode; gint visible; PikaImage *image; gint stack_position; image = pika_item_get_image (PIKA_ITEM (src_drawable)); stack_position = 0; /* TODO: should be same as src_layer */ /* copy type, name, opacity and mode from src_drawable */ type = pika_drawable_type (src_drawable); visible = pika_item_get_visible (PIKA_ITEM (src_drawable)); name2 = pika_item_get_name (PIKA_ITEM (src_drawable)); name = g_strdup_printf ("%s_b", name2); g_free (name2); mode = pika_layer_get_mode (PIKA_LAYER (src_drawable)); opacity = pika_layer_get_opacity (PIKA_LAYER (src_drawable)); /* full opacity */ new_layer = pika_layer_new (image, name, width, height, type, opacity, mode); g_free (name); if (! pika_drawable_has_alpha (PIKA_DRAWABLE (new_layer))) { /* always add alpha channel */ pika_layer_add_alpha (new_layer); } /* add the copied layer to the temp. working image */ pika_image_insert_layer (image, new_layer, NULL, stack_position); /* copy visibility state */ pika_item_set_visible (PIKA_ITEM (new_layer), visible); return new_layer; } /* ============================================================================ * p_bender_calculate_iter_curve * ============================================================================ */ static void p_bender_calculate_iter_curve (BenderDialog *cd, PikaProcedureConfig *config, gint32 xmax, gint32 ymax) { gint outline; gint curve_type; g_object_get (config, "curve-type", &curve_type, "curve-border", &outline, NULL); if (gb_debug) g_printf ("p_bender_calculate_iter_curve NORMAL1\n"); if (curve_type == SMOOTH) { g_object_set (cd->config, "curve-border", OUTLINE_UPPER, NULL); bender_calculate_curve (cd, xmax, ymax, FALSE); g_object_set (cd->config, "curve-border", OUTLINE_LOWER, NULL); bender_calculate_curve (cd, xmax, ymax, FALSE); } else { p_stretch_curves (cd, xmax, ymax); } g_object_set (config, "curve-border", outline, NULL); } /* ============================================================================ * p_vertical_bend * ============================================================================ */ static void p_vertical_bend (BenderDialog *cd, t_GDRW *src_gdrw, t_GDRW *dst_gdrw) { gint32 row, col; gint32 first_row, first_col, last_row, last_col; gint32 x, y; gint32 x2, y2; gint32 curvy, nb_curvy, nb2_curvy; gint32 desty, othery; gint32 miny, maxy; gint32 sign, dy, diff; gint32 topshift; float progress_step; float progress_max; float progress; t_Last *last_arr; t_Last *first_arr; guchar color[4]; guchar mixcolor[4]; gint alias_dir; guchar mixmask; gboolean antialias; gboolean smoothing; g_object_get (cd->config, "antialias", &antialias, "smoothing", &smoothing, NULL); topshift = p_upper_curve_extend (cd, src_gdrw->width, src_gdrw->height); diff = curvy = nb_curvy = nb2_curvy= miny = maxy = 0; /* allocate array of last values (one element foreach x coordinate) */ last_arr = g_new (t_Last, src_gdrw->x2); first_arr = g_new (t_Last, src_gdrw->x2); /* ------------------------------------------------ * foreach pixel in the SAMPLE_drawable: * ------------------------------------------------ * the inner loops (x/y) are designed to process * all pixels of one tile in the sample drawable, the outer loops (row/col) do step * to the next tiles. (this was done to reduce tile swapping) */ first_row = src_gdrw->y1 / src_gdrw->tile_height; last_row = (src_gdrw->y2 / src_gdrw->tile_height); first_col = src_gdrw->x1 / src_gdrw->tile_width; last_col = (src_gdrw->x2 / src_gdrw->tile_width); /* init progress */ progress_max = (1 + last_row - first_row) * (1 + last_col - first_col); progress_step = 1.0 / progress_max; progress = 0.0; if (cd->show_progress) pika_progress_init ( _("Curve Bend")); for (row = first_row; row <= last_row; row++) { for (col = first_col; col <= last_col; col++) { if (col == first_col) x = src_gdrw->x1; else x = col * src_gdrw->tile_width; if (col == last_col) x2 = src_gdrw->x2; else x2 = (col +1) * src_gdrw->tile_width; if (cd->show_progress) pika_progress_update (progress += progress_step); for( ; x < x2; x++) { if (row == first_row) y = src_gdrw->y1; else y = row * src_gdrw->tile_height; if (row == last_row) y2 = src_gdrw->y2; else y2 = (row +1) * src_gdrw->tile_height ; for( ; y < y2; y++) { /* ---------- copy SRC_PIXEL to curve position ------ */ p_get_pixel (src_gdrw, x, y, color); curvy = p_curve_get_dy (cd, x, (gint32)src_gdrw->width, (gint32)src_gdrw->height, (gdouble)y); desty = y + topshift + curvy; /* ----------- SMOOTHING ------------------ */ if (smoothing && (x > 0)) { nb_curvy = p_curve_get_dy (cd, x -1, (gint32) src_gdrw->width, (gint32) src_gdrw->height, (gdouble) y); if ((nb_curvy == curvy) && (x > 1)) { nb2_curvy = p_curve_get_dy (cd, x -2, (gint32) src_gdrw->width, (gint32) src_gdrw->height, (gdouble) y); } else { nb2_curvy = nb_curvy; } p_put_mix_pixel (dst_gdrw, x, desty, color, nb_curvy, nb2_curvy, curvy); } else { p_put_pixel (dst_gdrw, x, desty, color); } /* ----------- render ANTIALIAS ------------------ */ if (antialias) { othery = desty; if (y == src_gdrw->y1) /* Upper outline */ { first_arr[x].y = curvy; memcpy (first_arr[x].color, color, dst_gdrw->bpp); if (x > 0) { memcpy (mixcolor, first_arr[x-1].color, dst_gdrw->bpp); diff = abs(first_arr[x - 1].y - curvy) +1; miny = MIN(first_arr[x - 1].y, curvy) -1; maxy = MAX(first_arr[x - 1].y, curvy) +1; othery = (src_gdrw->y2 -1) + topshift + p_curve_get_dy(cd, x, (gint32)src_gdrw->width, (gint32)src_gdrw->height, (gdouble)(src_gdrw->y2 -1)); } } if (y == src_gdrw->y2 - 1) /* Lower outline */ { if (x > 0) { memcpy (mixcolor, last_arr[x-1].color, dst_gdrw->bpp); diff = abs (last_arr[x - 1].y - curvy) +1; maxy = MAX (last_arr[x - 1].y, curvy) +1; miny = MIN (last_arr[x - 1].y, curvy) -1; } othery = (src_gdrw->y1) + topshift + p_curve_get_dy(cd, x, (gint32)src_gdrw->width, (gint32)src_gdrw->height, (gdouble)(src_gdrw->y1)); } if (desty < othery) alias_dir = 1; /* fade to transp. with descending dy */ else if (desty > othery) alias_dir = -1; /* fade to transp. with ascending dy */ else alias_dir = 0; /* no antialias at curve crossing point(s) */ if (alias_dir != 0) { guchar alpha_lo = 20; if (pika_drawable_has_alpha (src_gdrw->drawable)) { alpha_lo = MIN (20, mixcolor[src_gdrw->index_alpha]); } for (dy = 0; dy < diff; dy++) { /* iterate for fading alpha channel */ mixmask = 255 * ((gdouble)(dy + 1) / (gdouble) (diff+1)); mixcolor[dst_gdrw->index_alpha] = MIX_CHANNEL(color[dst_gdrw->index_alpha], alpha_lo, mixmask); if(alias_dir > 0) { p_put_pixel (dst_gdrw, x -1, y + topshift + miny + dy, mixcolor); } else { p_put_pixel (dst_gdrw, x -1, y + topshift + (maxy - dy), mixcolor); } } } } /* ------------------ FILL HOLES ------------------ */ if (y == src_gdrw->y1) { diff = 0; sign = 1; } else { diff = last_arr[x].y - curvy; if (diff < 0) { diff = 0 - diff; sign = -1; } else { sign = 1; } memcpy (mixcolor, color, dst_gdrw->bpp); } for (dy = 1; dy <= diff; dy++) { /* y differs more than 1 pixel from last y in the * destination drawable. So we have to fill the empty * space between using a mixed color */ if (smoothing) { /* smoothing is on, so we are using a mixed color */ gulong alpha1 = last_arr[x].color[3]; gulong alpha2 = color[3]; gulong alpha; mixmask = 255 * ((gdouble)(dy) / (gdouble)(diff+1)); alpha = alpha1 * mixmask + alpha2 * (255 - mixmask); mixcolor[3] = alpha/255; if (mixcolor[3]) { mixcolor[0] = (alpha1 * mixmask * last_arr[x].color[0] + alpha2 * (255 - mixmask) * color[0])/alpha; mixcolor[1] = (alpha1 * mixmask * last_arr[x].color[1] + alpha2 * (255 - mixmask) * color[1])/alpha; mixcolor[2] = (alpha1 * mixmask * last_arr[x].color[2] + alpha2 * (255 - mixmask) * color[2])/alpha; /*mixcolor[2] = MIX_CHANNEL(last_arr[x].color[2], color[2], mixmask);*/ } } else { /* smoothing is off, so we are using this color or the last color */ if (dy < diff / 2) { memcpy (mixcolor, color, dst_gdrw->bpp); } else { memcpy (mixcolor, last_arr[x].color, dst_gdrw->bpp); } } if (smoothing) { p_put_mix_pixel (dst_gdrw, x, desty + (dy * sign), mixcolor, nb_curvy, nb2_curvy, curvy ); } else { p_put_pixel (dst_gdrw, x, desty + (dy * sign), mixcolor); } } /* store y and color */ last_arr[x].y = curvy; memcpy (last_arr[x].color, color, dst_gdrw->bpp); } } } } pika_progress_update (1.0); g_free (last_arr); g_free (first_arr); } /* ============================================================================ * p_main_bend * ============================================================================ */ static PikaLayer * p_main_bend (BenderDialog *cd, PikaDrawable *original_drawable, PikaProcedureConfig *config, gint work_on_copy) { t_GDRW src_gdrw; t_GDRW dst_gdrw; PikaDrawable *src_drawable; PikaDrawable *dst_drawable; gint src_width; gint src_height; gint32 dst_height; PikaImage *image; PikaLayer *tmp_layer; gint32 interpolation; gint offset_x, offset_y; gint center_x, center_y; gint32 xmax, ymax; gdouble rotation; g_object_get (config, "smoothing", &interpolation, "rotation", &rotation, NULL); image = pika_item_get_image (PIKA_ITEM (original_drawable)); pika_drawable_get_offsets (original_drawable, &offset_x, &offset_y); center_x = offset_x + (pika_drawable_get_width (original_drawable) / 2 ); center_y = offset_y + (pika_drawable_get_height (original_drawable) / 2 ); /* always copy original_drawable to a tmp src_layer */ tmp_layer = pika_layer_copy (PIKA_LAYER (original_drawable)); /* set layer invisible and dummyname and * add at top of the image while working * (for the case of undo PIKA must know, * that the layer was part of the image) */ pika_image_insert_layer (image, tmp_layer, NULL, 0); pika_item_set_visible (PIKA_ITEM (tmp_layer), FALSE); pika_item_set_name (PIKA_ITEM (tmp_layer), "curve_bend_dummylayer"); if(gb_debug) g_printf("p_main_bend tmp_layer_id %d\n", (int) pika_item_get_id (PIKA_ITEM (tmp_layer))); if (rotation != 0.0) { if(gb_debug) g_printf("p_main_bend rotate: %f\n", (float) rotation); p_pika_rotate (image, PIKA_DRAWABLE (tmp_layer), interpolation, rotation); } src_drawable = PIKA_DRAWABLE (tmp_layer); src_width = pika_drawable_get_width (PIKA_DRAWABLE (tmp_layer)); src_height = pika_drawable_get_height (PIKA_DRAWABLE (tmp_layer)); xmax = ymax = src_width -1; cd->curve_ptr[OUTLINE_UPPER] = g_new (gint32, 1+xmax); cd->curve_ptr[OUTLINE_LOWER] = g_new (gint32, 1+xmax); p_bender_calculate_iter_curve (cd, config, xmax, ymax); bender_init_min_max (cd, xmax); dst_height = src_height + p_upper_curve_extend(cd, src_width, src_height) + p_lower_curve_extend(cd, src_width, src_height); if(gb_debug) g_printf("p_main_bend: dst_height:%d\n", dst_height); if (work_on_copy) { dst_drawable = PIKA_DRAWABLE (p_add_layer (src_width, dst_height, src_drawable)); if (gb_debug) g_printf("p_main_bend: DONE add layer\n"); } else { /* work on the original */ pika_layer_resize (PIKA_LAYER (original_drawable), src_width, dst_height, offset_x, offset_y); if (gb_debug) g_printf("p_main_bend: DONE layer resize\n"); if (! pika_drawable_has_alpha (original_drawable)) { /* always add alpha channel */ pika_layer_add_alpha (PIKA_LAYER (original_drawable)); } dst_drawable = original_drawable; } pika_drawable_fill (dst_drawable, PIKA_FILL_TRANSPARENT); p_init_gdrw (&src_gdrw, src_drawable, FALSE); p_init_gdrw (&dst_gdrw, dst_drawable, FALSE); p_vertical_bend (cd, &src_gdrw, &dst_gdrw); if(gb_debug) g_printf("p_main_bend: DONE vertical bend\n"); p_end_gdrw (&src_gdrw); p_end_gdrw (&dst_gdrw); if (rotation != 0.0) { p_pika_rotate (image, dst_drawable, interpolation, (gdouble)(360.0 - rotation)); /* TODO: here we should crop dst_drawable to cut off full transparent borderpixels */ } /* set offsets of the resulting new layer *(center == center of original_drawable) */ offset_x = center_x - (pika_drawable_get_width (dst_drawable) / 2 ); offset_y = center_y - (pika_drawable_get_height (dst_drawable) / 2 ); pika_layer_set_offsets (PIKA_LAYER (dst_drawable), offset_x, offset_y); /* delete the temp layer */ pika_image_remove_layer (image, tmp_layer); g_free (cd->curve_ptr[OUTLINE_UPPER]); g_free (cd->curve_ptr[OUTLINE_LOWER]); if (gb_debug) g_printf("p_main_bend: DONE bend main\n"); return PIKA_LAYER (dst_drawable); }