3090 lines
98 KiB
C
3090 lines
98 KiB
C
/* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#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, "<Image>/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);
|
|
}
|