mirror of https://github.com/GNOME/gimp.git
1784 lines
43 KiB
C
1784 lines
43 KiB
C
/* The GIMP -- an image manipulation program
|
|
* 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 2 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "appenv.h"
|
|
#include "cursorutil.h"
|
|
#include "drawable.h"
|
|
#include "gdisplay.h"
|
|
#include "gimphistogram.h"
|
|
#include "gimpui.h"
|
|
#include "curves.h"
|
|
#include "gimplut.h"
|
|
|
|
#include "libgimp/gimpenv.h"
|
|
#include "libgimp/gimpmath.h"
|
|
|
|
#include "libgimp/gimpintl.h"
|
|
|
|
#define GRAPH 0x1
|
|
#define XRANGE_TOP 0x2
|
|
#define XRANGE_BOTTOM 0x4
|
|
#define YRANGE 0x8
|
|
#define DRAW 0x10
|
|
#define ALL 0xFF
|
|
|
|
/* NB: take care when changing these values: make sure the curve[] array in
|
|
* curves.h is large enough.
|
|
*/
|
|
#define GRAPH_WIDTH 256
|
|
#define GRAPH_HEIGHT 256
|
|
#define XRANGE_WIDTH 256
|
|
#define XRANGE_HEIGHT 16
|
|
#define YRANGE_WIDTH 16
|
|
#define YRANGE_HEIGHT 256
|
|
#define RADIUS 3
|
|
#define MIN_DISTANCE 8
|
|
|
|
#define RANGE_MASK GDK_EXPOSURE_MASK | \
|
|
GDK_ENTER_NOTIFY_MASK
|
|
|
|
#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
|
|
|
|
/* the curves structures */
|
|
|
|
typedef struct _Curves Curves;
|
|
|
|
struct _Curves
|
|
{
|
|
gint x, y; /* coords for last mouse click */
|
|
};
|
|
|
|
typedef gdouble CRMatrix[4][4];
|
|
|
|
/* the curves tool options */
|
|
static ToolOptions * curves_options = NULL;
|
|
|
|
/* the curves dialog */
|
|
static CurvesDialog * curves_dialog = NULL;
|
|
|
|
/* the curves file dialog */
|
|
static GtkWidget *file_dlg = NULL;
|
|
static gboolean load_save;
|
|
|
|
static GtkWidget *channel_items[5];
|
|
|
|
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 },
|
|
};
|
|
|
|
|
|
/* curves action functions */
|
|
|
|
static void curves_button_press (Tool *, GdkEventButton *, gpointer);
|
|
static void curves_button_release (Tool *, GdkEventButton *, gpointer);
|
|
static void curves_motion (Tool *, GdkEventMotion *, gpointer);
|
|
static void curves_control (Tool *, ToolAction, gpointer);
|
|
|
|
static CurvesDialog * curves_dialog_new (void);
|
|
|
|
static void curves_update (CurvesDialog *, int);
|
|
static void curves_plot_curve (CurvesDialog *, int, int, int, int);
|
|
static void curves_preview (CurvesDialog *);
|
|
|
|
static void curves_channel_callback (GtkWidget *, gpointer);
|
|
|
|
static void curves_smooth_callback (GtkWidget *, gpointer);
|
|
static void curves_free_callback (GtkWidget *, gpointer);
|
|
|
|
static void curves_channel_reset (int);
|
|
static void curves_reset_callback (GtkWidget *, gpointer);
|
|
static void curves_ok_callback (GtkWidget *, gpointer);
|
|
static void curves_cancel_callback (GtkWidget *, gpointer);
|
|
static void curves_load_callback (GtkWidget *, gpointer);
|
|
static void curves_save_callback (GtkWidget *, gpointer);
|
|
static void curves_preview_update (GtkWidget *, gpointer);
|
|
static gint curves_xrange_events (GtkWidget *, GdkEvent *, CurvesDialog *);
|
|
static gint curves_yrange_events (GtkWidget *, GdkEvent *, CurvesDialog *);
|
|
static gint curves_graph_events (GtkWidget *, GdkEvent *, CurvesDialog *);
|
|
static void curves_CR_compose (CRMatrix, CRMatrix, CRMatrix);
|
|
|
|
static void file_dialog_create (GtkWidget *);
|
|
static void file_dialog_ok_callback (GtkWidget *, gpointer);
|
|
static void file_dialog_cancel_callback (GtkWidget *, gpointer);
|
|
|
|
static gboolean curves_read_from_file (FILE *f);
|
|
static void curves_write_to_file (FILE *f);
|
|
|
|
|
|
/* curves machinery */
|
|
|
|
gfloat
|
|
curves_lut_func (CurvesDialog *cd,
|
|
gint nchannels,
|
|
gint channel,
|
|
gfloat value)
|
|
{
|
|
gfloat f;
|
|
gint index;
|
|
gdouble inten;
|
|
gint j;
|
|
|
|
if (nchannels == 1)
|
|
j = 0;
|
|
else
|
|
j = channel + 1;
|
|
|
|
inten = value;
|
|
|
|
/* For color images this runs through the loop with j = channel +1
|
|
the first time and j = 0 the second time */
|
|
/* For bw images this runs through the loop with j = 0 the first and
|
|
only time */
|
|
for (; j >= 0; j -= (channel + 1))
|
|
{
|
|
/* don't apply the overall curve to the alpha channel */
|
|
if (j == 0 && (nchannels == 2 || nchannels == 4)
|
|
&& channel == nchannels -1)
|
|
return inten;
|
|
|
|
if (inten < 0.0)
|
|
inten = cd->curve[j][0]/255.0;
|
|
else if (inten >= 1.0)
|
|
inten = cd->curve[j][255]/255.0;
|
|
else /* interpolate the curve */
|
|
{
|
|
index = floor(inten * 255.0);
|
|
f = inten*255.0 - index;
|
|
inten = ((1.0 - f) * cd->curve[j][index ] +
|
|
( f) * cd->curve[j][index + 1] ) / 255.0;
|
|
}
|
|
}
|
|
return inten;
|
|
}
|
|
|
|
static void
|
|
curves_colour_update (Tool *tool,
|
|
GDisplay *gdisp,
|
|
GimpDrawable *drawable,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
guchar *color;
|
|
gint offx;
|
|
gint offy;
|
|
gint maxval;
|
|
gboolean has_alpha;
|
|
gboolean is_indexed;
|
|
GimpImageType sample_type;
|
|
|
|
if(!tool || tool->state != ACTIVE)
|
|
return;
|
|
|
|
drawable_offsets (drawable, &offx, &offy);
|
|
|
|
x -= offx;
|
|
y -= offy;
|
|
|
|
if (!(color = image_map_get_color_at(curves_dialog->image_map, x, y)))
|
|
return;
|
|
|
|
sample_type = gimp_drawable_type (drawable);
|
|
is_indexed = gimp_drawable_is_indexed (drawable);
|
|
has_alpha = GIMP_IMAGE_TYPE_HAS_ALPHA (sample_type);
|
|
|
|
curves_dialog->col_value[HISTOGRAM_RED] = color[RED_PIX];
|
|
curves_dialog->col_value[HISTOGRAM_GREEN] = color[GREEN_PIX];
|
|
curves_dialog->col_value[HISTOGRAM_BLUE] = color[BLUE_PIX];
|
|
|
|
if (has_alpha)
|
|
{
|
|
curves_dialog->col_value [HISTOGRAM_ALPHA] = color[3];
|
|
}
|
|
|
|
if (is_indexed)
|
|
curves_dialog->col_value [HISTOGRAM_ALPHA] = color[4];
|
|
|
|
maxval = MAX (color[RED_PIX], color[GREEN_PIX]);
|
|
curves_dialog->col_value[HISTOGRAM_VALUE] = MAX (maxval, color[BLUE_PIX]);
|
|
|
|
g_free (color);
|
|
}
|
|
|
|
static void
|
|
curves_add_point (GimpDrawable *drawable,
|
|
gint x,
|
|
gint y,
|
|
gint cchan)
|
|
{
|
|
/* Add point onto the curve */
|
|
gint closest_point = 0;
|
|
gint distance;
|
|
gint curvex;
|
|
gint i;
|
|
|
|
switch (curves_dialog->curve_type[cchan])
|
|
{
|
|
case SMOOTH:
|
|
curvex = curves_dialog->col_value[cchan];
|
|
distance = G_MAXINT;
|
|
for (i = 0; i < 17; i++)
|
|
{
|
|
if (curves_dialog->points[cchan][i][0] != -1)
|
|
if (abs (curvex - curves_dialog->points[cchan][i][0]) < distance)
|
|
{
|
|
distance = abs (curvex - curves_dialog->points[cchan][i][0]);
|
|
closest_point = i;
|
|
}
|
|
}
|
|
|
|
if (distance > MIN_DISTANCE)
|
|
closest_point = (curvex + 8) / 16;
|
|
|
|
curves_dialog->points[cchan][closest_point][0] = curvex;
|
|
curves_dialog->points[cchan][closest_point][1] = curves_dialog->curve[cchan][curvex];
|
|
break;
|
|
|
|
case GFREE:
|
|
curves_dialog->curve[cchan][x] = 255 - y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* curves action functions */
|
|
|
|
static void
|
|
curves_button_press (Tool *tool,
|
|
GdkEventButton *bevent,
|
|
gpointer gdisp_ptr)
|
|
{
|
|
gint x, y;
|
|
GDisplay *gdisp;
|
|
GimpDrawable *drawable;
|
|
|
|
gdisp = gdisp_ptr;
|
|
drawable = gimage_active_drawable (gdisp->gimage);
|
|
|
|
tool->gdisp_ptr = gdisp;
|
|
|
|
if (drawable != tool->drawable)
|
|
{
|
|
active_tool->preserve = TRUE;
|
|
image_map_abort (curves_dialog->image_map);
|
|
active_tool->preserve = FALSE;
|
|
|
|
tool->drawable = drawable;
|
|
|
|
curves_dialog->drawable = drawable;
|
|
curves_dialog->color = drawable_color (drawable);
|
|
curves_dialog->image_map = image_map_create (gdisp, drawable);
|
|
}
|
|
|
|
tool->state = ACTIVE;
|
|
|
|
gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &x, &y,
|
|
FALSE, FALSE);
|
|
curves_colour_update (tool, gdisp, drawable, x, y);
|
|
curves_update (curves_dialog, GRAPH | DRAW);
|
|
}
|
|
|
|
static void
|
|
curves_button_release (Tool *tool,
|
|
GdkEventButton *bevent,
|
|
gpointer gdisp_ptr)
|
|
{
|
|
gint x, y;
|
|
GimpDrawable *drawable;
|
|
GDisplay *gdisp;
|
|
|
|
gdisp = (GDisplay *) gdisp_ptr;
|
|
|
|
if(!curves_dialog ||
|
|
!gdisp ||
|
|
!(drawable = gimage_active_drawable (gdisp->gimage)))
|
|
return;
|
|
|
|
gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &x, &y, FALSE, FALSE);
|
|
curves_colour_update (tool, gdisp, drawable, x, y);
|
|
|
|
if (bevent->state & GDK_SHIFT_MASK)
|
|
{
|
|
curves_add_point (drawable, x, y, curves_dialog->channel);
|
|
curves_calculate_curve (curves_dialog);
|
|
}
|
|
else if (bevent->state & GDK_CONTROL_MASK)
|
|
{
|
|
curves_add_point (drawable, x, y, HISTOGRAM_VALUE);
|
|
curves_add_point (drawable, x, y, HISTOGRAM_RED);
|
|
curves_add_point (drawable, x, y, HISTOGRAM_GREEN);
|
|
curves_add_point (drawable, x, y, HISTOGRAM_BLUE);
|
|
curves_add_point (drawable, x, y, HISTOGRAM_ALPHA);
|
|
curves_calculate_curve (curves_dialog);
|
|
}
|
|
|
|
curves_update (curves_dialog, GRAPH | DRAW);
|
|
}
|
|
|
|
static void
|
|
curves_motion (Tool *tool,
|
|
GdkEventMotion *mevent,
|
|
gpointer gdisp_ptr)
|
|
{
|
|
gint x, y;
|
|
GDisplay *gdisp;
|
|
GimpDrawable *drawable;
|
|
|
|
gdisp = (GDisplay *) gdisp_ptr;
|
|
|
|
if(!curves_dialog ||
|
|
!gdisp ||
|
|
!(drawable = gimage_active_drawable (gdisp->gimage)))
|
|
return;
|
|
|
|
gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, FALSE, FALSE);
|
|
curves_colour_update (tool, gdisp, drawable, x, y);
|
|
curves_update (curves_dialog, GRAPH | DRAW);
|
|
}
|
|
|
|
static void
|
|
curves_control (Tool *tool,
|
|
ToolAction action,
|
|
gpointer gdisp_ptr)
|
|
{
|
|
switch (action)
|
|
{
|
|
case PAUSE:
|
|
break;
|
|
|
|
case RESUME:
|
|
break;
|
|
|
|
case HALT:
|
|
curves_dialog_hide ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Tool *
|
|
tools_new_curves (void)
|
|
{
|
|
Tool *tool;
|
|
Curves *private;
|
|
|
|
/* The tool options */
|
|
if (!curves_options)
|
|
{
|
|
curves_options = tool_options_new (_("Curves"));
|
|
tools_register (CURVES, curves_options);
|
|
}
|
|
|
|
tool = tools_new_tool (CURVES);
|
|
private = g_new0 (Curves, 1);
|
|
|
|
tool->scroll_lock = TRUE; /* Disallow scrolling */
|
|
tool->preserve = FALSE; /* Don't preserve on drawable change */
|
|
|
|
tool->private = (void *) private;
|
|
|
|
tool->button_press_func = curves_button_press;
|
|
tool->button_release_func = curves_button_release;
|
|
tool->motion_func = curves_motion;
|
|
tool->control_func = curves_control;
|
|
|
|
return tool;
|
|
}
|
|
|
|
void
|
|
curves_dialog_hide (void)
|
|
{
|
|
if (curves_dialog)
|
|
curves_cancel_callback (NULL, (gpointer) curves_dialog);
|
|
}
|
|
|
|
void
|
|
tools_free_curves (Tool *tool)
|
|
{
|
|
Curves *private;
|
|
|
|
private = (Curves *) tool->private;
|
|
|
|
/* Close the color select dialog */
|
|
curves_dialog_hide ();
|
|
|
|
g_free (private);
|
|
}
|
|
|
|
void
|
|
curves_initialize (GDisplay *gdisp)
|
|
{
|
|
gint i, j;
|
|
|
|
if (drawable_indexed (gimage_active_drawable (gdisp->gimage)))
|
|
{
|
|
g_message (_("Curves for indexed drawables cannot be adjusted."));
|
|
return;
|
|
}
|
|
|
|
/* The curves dialog */
|
|
if (!curves_dialog)
|
|
{
|
|
curves_dialog = curves_dialog_new ();
|
|
|
|
/* Initialize the values */
|
|
curves_dialog->channel = HISTOGRAM_VALUE;
|
|
for (i = 0; i < 5; i++)
|
|
for (j = 0; j < 256; j++)
|
|
curves_dialog->curve[i][j] = j;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
curves_channel_reset (i);
|
|
}
|
|
}
|
|
|
|
curves_dialog->drawable = gimage_active_drawable (gdisp->gimage);
|
|
curves_dialog->color = drawable_color (curves_dialog->drawable);
|
|
curves_dialog->image_map = image_map_create (gdisp, curves_dialog->drawable);
|
|
|
|
/* check for alpha channel */
|
|
if (drawable_has_alpha (curves_dialog->drawable))
|
|
gtk_widget_set_sensitive (channel_items[4], TRUE);
|
|
else
|
|
gtk_widget_set_sensitive (channel_items[4], FALSE);
|
|
|
|
/* hide or show the channel menu based on image type */
|
|
if (curves_dialog->color)
|
|
for (i = 0; i < 4; i++)
|
|
gtk_widget_set_sensitive (channel_items[i], TRUE);
|
|
else
|
|
for (i = 1; i < 4; i++)
|
|
gtk_widget_set_sensitive (channel_items[i], FALSE);
|
|
|
|
/* set the current selection */
|
|
gtk_option_menu_set_history (GTK_OPTION_MENU (curves_dialog->channel_menu),
|
|
curves_dialog->channel);
|
|
|
|
gimp_lut_setup (curves_dialog->lut, (GimpLutFunc) curves_lut_func,
|
|
(void *) curves_dialog, gimp_drawable_bytes (curves_dialog->drawable));
|
|
|
|
if (!GTK_WIDGET_VISIBLE (curves_dialog->shell))
|
|
gtk_widget_show (curves_dialog->shell);
|
|
|
|
curves_update (curves_dialog, GRAPH | DRAW);
|
|
}
|
|
|
|
void
|
|
curves_free (void)
|
|
{
|
|
if (curves_dialog)
|
|
{
|
|
if (curves_dialog->image_map)
|
|
{
|
|
active_tool->preserve = TRUE;
|
|
image_map_abort (curves_dialog->image_map);
|
|
active_tool->preserve = FALSE;
|
|
|
|
curves_dialog->image_map = NULL;
|
|
}
|
|
|
|
if (curves_dialog->pixmap)
|
|
gdk_pixmap_unref (curves_dialog->pixmap);
|
|
|
|
gtk_widget_destroy (curves_dialog->shell);
|
|
}
|
|
}
|
|
|
|
/*******************/
|
|
/* Curves dialog */
|
|
/*******************/
|
|
|
|
static CurvesDialog *
|
|
curves_dialog_new (void)
|
|
{
|
|
CurvesDialog *cd;
|
|
GtkWidget *vbox;
|
|
GtkWidget *hbox;
|
|
GtkWidget *hbbox;
|
|
GtkWidget *label;
|
|
GtkWidget *frame;
|
|
GtkWidget *toggle;
|
|
GtkWidget *channel_hbox;
|
|
GtkWidget *table;
|
|
GtkWidget *button;
|
|
gint i, j;
|
|
|
|
cd = g_new (CurvesDialog, 1);
|
|
cd->cursor_ind_height = -1;
|
|
cd->cursor_ind_width = -1;
|
|
cd->preview = TRUE;
|
|
cd->pixmap = NULL;
|
|
cd->channel = HISTOGRAM_VALUE;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
cd->curve_type[i] = SMOOTH;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
for (j = 0; j < 256; j++)
|
|
cd->curve[i][j] = j;
|
|
|
|
for (i = 0; i < (sizeof (cd->col_value) / sizeof (cd->col_value[0])); i++)
|
|
cd->col_value[i] = 0;
|
|
|
|
cd->lut = gimp_lut_new ();
|
|
|
|
/* The shell and main vbox */
|
|
cd->shell = gimp_dialog_new (_("Curves"), "curves",
|
|
tools_help_func, tool_info[CURVES].private_tip,
|
|
GTK_WIN_POS_NONE,
|
|
FALSE, TRUE, FALSE,
|
|
|
|
_("OK"), curves_ok_callback,
|
|
cd, NULL, NULL, TRUE, FALSE,
|
|
_("Reset"), curves_reset_callback,
|
|
cd, NULL, NULL, FALSE, FALSE,
|
|
_("Cancel"), curves_cancel_callback,
|
|
cd, NULL, NULL, FALSE, TRUE,
|
|
|
|
NULL);
|
|
|
|
vbox = gtk_vbox_new (FALSE, 4);
|
|
gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
|
|
gtk_container_add (GTK_CONTAINER (GTK_DIALOG (cd->shell)->vbox), vbox);
|
|
|
|
/* The option menu for selecting channels */
|
|
channel_hbox = gtk_hbox_new (FALSE, 4);
|
|
gtk_box_pack_start (GTK_BOX (vbox), channel_hbox, FALSE, FALSE, 0);
|
|
|
|
label = gtk_label_new (_("Modify Curves for Channel:"));
|
|
gtk_box_pack_start (GTK_BOX (channel_hbox), label, FALSE, FALSE, 0);
|
|
|
|
cd->channel_menu = gimp_option_menu_new2
|
|
(FALSE, curves_channel_callback,
|
|
cd, (gpointer) cd->channel,
|
|
|
|
_("Value"), (gpointer) HISTOGRAM_VALUE, &channel_items[0],
|
|
_("Red"), (gpointer) HISTOGRAM_RED, &channel_items[1],
|
|
_("Green"), (gpointer) HISTOGRAM_GREEN, &channel_items[2],
|
|
_("Blue"), (gpointer) HISTOGRAM_BLUE, &channel_items[3],
|
|
_("Alpha"), (gpointer) HISTOGRAM_ALPHA, &channel_items[4],
|
|
|
|
NULL);
|
|
|
|
gtk_box_pack_start (GTK_BOX (channel_hbox), cd->channel_menu, FALSE, FALSE, 2);
|
|
|
|
gtk_widget_show (label);
|
|
gtk_widget_show (cd->channel_menu);
|
|
gtk_widget_show (channel_hbox);
|
|
|
|
/* The table for the yrange and the graph */
|
|
table = gtk_table_new (2, 2, FALSE);
|
|
gtk_table_set_col_spacings (GTK_TABLE (table), 2);
|
|
gtk_table_set_row_spacings (GTK_TABLE (table), 2);
|
|
gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
|
|
|
|
/* The range drawing area */
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
|
|
gtk_table_attach (GTK_TABLE (table), frame, 0, 1, 0, 1,
|
|
GTK_EXPAND, GTK_EXPAND, 0, 0);
|
|
|
|
cd->yrange = gtk_preview_new (GTK_PREVIEW_COLOR);
|
|
gtk_preview_size (GTK_PREVIEW (cd->yrange), YRANGE_WIDTH, YRANGE_HEIGHT);
|
|
gtk_widget_set_events (cd->yrange, RANGE_MASK);
|
|
gtk_container_add (GTK_CONTAINER (frame), cd->yrange);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (cd->yrange), "event",
|
|
GTK_SIGNAL_FUNC (curves_yrange_events),
|
|
cd);
|
|
|
|
gtk_widget_show (cd->yrange);
|
|
gtk_widget_show (frame);
|
|
|
|
/* The curves graph */
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
|
|
gtk_table_attach (GTK_TABLE (table), frame, 1, 2, 0, 1,
|
|
GTK_SHRINK | GTK_FILL,
|
|
GTK_SHRINK | GTK_FILL, 0, 0);
|
|
|
|
cd->graph = gtk_drawing_area_new ();
|
|
gtk_drawing_area_size (GTK_DRAWING_AREA (cd->graph),
|
|
GRAPH_WIDTH + RADIUS * 2,
|
|
GRAPH_HEIGHT + RADIUS * 2);
|
|
gtk_widget_set_events (cd->graph, GRAPH_MASK);
|
|
gtk_container_add (GTK_CONTAINER (frame), cd->graph);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (cd->graph), "event",
|
|
GTK_SIGNAL_FUNC (curves_graph_events),
|
|
cd);
|
|
|
|
gtk_widget_show (cd->graph);
|
|
gtk_widget_show (frame);
|
|
|
|
/* The range drawing area */
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
|
|
gtk_table_attach (GTK_TABLE (table), frame, 1, 2, 1, 2,
|
|
GTK_EXPAND, GTK_EXPAND, 0, 0);
|
|
|
|
cd->xrange = gtk_preview_new (GTK_PREVIEW_COLOR);
|
|
gtk_preview_size (GTK_PREVIEW (cd->xrange), XRANGE_WIDTH, XRANGE_HEIGHT);
|
|
gtk_widget_set_events (cd->xrange, RANGE_MASK);
|
|
gtk_container_add (GTK_CONTAINER (frame), cd->xrange);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (cd->xrange), "event",
|
|
GTK_SIGNAL_FUNC (curves_xrange_events),
|
|
cd);
|
|
|
|
gtk_widget_show (cd->xrange);
|
|
gtk_widget_show (frame);
|
|
gtk_widget_show (table);
|
|
|
|
/* Horizontal box for preview */
|
|
hbox = gtk_hbox_new (FALSE, 4);
|
|
gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
|
|
|
|
/* The option menu for selecting the drawing method */
|
|
label = gtk_label_new (_("Curve Type:"));
|
|
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
|
|
gtk_widget_show (label);
|
|
|
|
cd->curve_type_menu = gimp_option_menu_new
|
|
(FALSE,
|
|
|
|
_("Smooth"), curves_smooth_callback, cd, NULL, NULL, TRUE,
|
|
_("Free"), curves_free_callback, cd, NULL, NULL, FALSE,
|
|
|
|
NULL);
|
|
gtk_box_pack_start (GTK_BOX (hbox), cd->curve_type_menu, FALSE, FALSE, 2);
|
|
gtk_widget_show (cd->curve_type_menu);
|
|
|
|
gtk_widget_show (hbox);
|
|
|
|
/* The preview toggle */
|
|
toggle = gtk_check_button_new_with_label (_("Preview"));
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), cd->preview);
|
|
gtk_box_pack_end (GTK_BOX (hbox), toggle, FALSE, FALSE, 0);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
|
|
GTK_SIGNAL_FUNC (curves_preview_update),
|
|
cd);
|
|
|
|
gtk_widget_show (toggle);
|
|
gtk_widget_show (hbox);
|
|
|
|
/* Horizontal button box for load / save */
|
|
hbbox = gtk_hbutton_box_new ();
|
|
gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbbox), 4);
|
|
gtk_button_box_set_layout (GTK_BUTTON_BOX (hbbox), GTK_BUTTONBOX_SPREAD);
|
|
gtk_box_pack_end (GTK_BOX (vbox), hbbox, FALSE, FALSE, 0);
|
|
|
|
button = gtk_button_new_with_label (_("Load"));
|
|
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
|
|
gtk_box_pack_start (GTK_BOX (hbbox), button, FALSE, FALSE, 0);
|
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|
GTK_SIGNAL_FUNC (curves_load_callback),
|
|
cd->shell);
|
|
gtk_widget_show (button);
|
|
|
|
button = gtk_button_new_with_label (_("Save"));
|
|
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
|
|
gtk_box_pack_start (GTK_BOX (hbbox), button, FALSE, FALSE, 0);
|
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|
GTK_SIGNAL_FUNC (curves_save_callback),
|
|
cd->shell);
|
|
gtk_widget_show (button);
|
|
|
|
gtk_widget_show (hbbox);
|
|
gtk_widget_show (vbox);
|
|
|
|
return cd;
|
|
}
|
|
|
|
static void
|
|
curve_print_loc (CurvesDialog *cd,
|
|
gint xpos,
|
|
gint ypos)
|
|
{
|
|
gchar buf[32];
|
|
gint width;
|
|
gint ascent;
|
|
gint descent;
|
|
|
|
if (cd->cursor_ind_width < 0)
|
|
{
|
|
/* Calc max extents */
|
|
gdk_string_extents (cd->graph->style->font,
|
|
"x:888 y:888",
|
|
NULL,
|
|
NULL,
|
|
&width,
|
|
&ascent,
|
|
&descent);
|
|
|
|
cd->cursor_ind_width = width;
|
|
cd->cursor_ind_height = ascent + descent;
|
|
cd->cursor_ind_ascent = ascent;
|
|
}
|
|
|
|
if (xpos >= 0 && xpos <= 255 && ypos >=0 && ypos <= 255)
|
|
{
|
|
g_snprintf (buf, sizeof (buf), "x:%d y:%d",xpos,ypos);
|
|
|
|
gdk_draw_rectangle (cd->graph->window,
|
|
cd->graph->style->bg_gc[GTK_STATE_ACTIVE],
|
|
TRUE, RADIUS*2 + 2 , RADIUS*2 + 2,
|
|
cd->cursor_ind_width+4,
|
|
cd->cursor_ind_height+5);
|
|
|
|
gdk_draw_rectangle (cd->graph->window,
|
|
cd->graph->style->black_gc,
|
|
FALSE, RADIUS*2 + 2 , RADIUS*2 + 2,
|
|
cd->cursor_ind_width+3,
|
|
cd->cursor_ind_height+4);
|
|
|
|
gdk_draw_string (cd->graph->window,
|
|
cd->graph->style->font,
|
|
cd->graph->style->black_gc,
|
|
RADIUS*2 + 4,
|
|
RADIUS*2 + 5 + cd->cursor_ind_ascent,
|
|
buf);
|
|
}
|
|
}
|
|
|
|
/* TODO: preview alpha channel stuff correctly. -- austin, 20/May/99 */
|
|
static void
|
|
curves_update (CurvesDialog *cd,
|
|
gint update)
|
|
{
|
|
GdkRectangle area;
|
|
gint i, j;
|
|
gchar buf[32];
|
|
gint offset;
|
|
gint sel_channel;
|
|
|
|
if(cd->color) {
|
|
sel_channel = cd->channel;
|
|
} else {
|
|
if(cd->channel == 2)
|
|
sel_channel = HISTOGRAM_ALPHA;
|
|
else
|
|
sel_channel = HISTOGRAM_VALUE;
|
|
}
|
|
|
|
|
|
if (update & XRANGE_TOP)
|
|
{
|
|
guchar buf[XRANGE_WIDTH * 3];
|
|
|
|
switch (sel_channel)
|
|
{
|
|
case HISTOGRAM_VALUE:
|
|
case HISTOGRAM_ALPHA:
|
|
for (i = 0; i < XRANGE_HEIGHT / 2; i++)
|
|
{
|
|
for (j = 0; j < XRANGE_WIDTH ; j++)
|
|
{
|
|
buf[j*3+0] = cd->curve[sel_channel][j];
|
|
buf[j*3+1] = cd->curve[sel_channel][j];
|
|
buf[j*3+2] = cd->curve[sel_channel][j];
|
|
}
|
|
gtk_preview_draw_row (GTK_PREVIEW (cd->xrange),
|
|
buf, 0, i, XRANGE_WIDTH);
|
|
}
|
|
break;
|
|
|
|
case HISTOGRAM_RED:
|
|
case HISTOGRAM_GREEN:
|
|
case HISTOGRAM_BLUE:
|
|
{
|
|
for (i = 0; i < XRANGE_HEIGHT / 2; i++)
|
|
{
|
|
for (j = 0; j < XRANGE_WIDTH; j++)
|
|
{
|
|
buf[j*3+0] = cd->curve[HISTOGRAM_RED][j];
|
|
buf[j*3+1] = cd->curve[HISTOGRAM_GREEN][j];
|
|
buf[j*3+2] = cd->curve[HISTOGRAM_BLUE][j];
|
|
}
|
|
gtk_preview_draw_row (GTK_PREVIEW (cd->xrange),
|
|
buf, 0, i, XRANGE_WIDTH);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
g_warning ("unknown channel type %d, can't happen!?!?",
|
|
cd->channel);
|
|
break;
|
|
}
|
|
|
|
if (update & DRAW)
|
|
{
|
|
area.x = 0;
|
|
area.y = 0;
|
|
area.width = XRANGE_WIDTH;
|
|
area.height = XRANGE_HEIGHT / 2;
|
|
gtk_widget_draw (cd->xrange, &area);
|
|
}
|
|
}
|
|
if (update & XRANGE_BOTTOM)
|
|
{
|
|
guchar buf[XRANGE_WIDTH * 3];
|
|
|
|
for (i = 0; i < XRANGE_WIDTH; i++)
|
|
{
|
|
buf[i*3+0] = i;
|
|
buf[i*3+1] = i;
|
|
buf[i*3+2] = i;
|
|
}
|
|
|
|
for (i = XRANGE_HEIGHT / 2; i < XRANGE_HEIGHT; i++)
|
|
gtk_preview_draw_row (GTK_PREVIEW (cd->xrange), buf, 0, i, XRANGE_WIDTH);
|
|
|
|
if (update & DRAW)
|
|
{
|
|
area.x = 0;
|
|
area.y = XRANGE_HEIGHT / 2;
|
|
area.width = XRANGE_WIDTH;
|
|
area.height = XRANGE_HEIGHT / 2;
|
|
gtk_widget_draw (cd->xrange, &area);
|
|
}
|
|
}
|
|
if (update & YRANGE)
|
|
{
|
|
guchar buf[YRANGE_WIDTH * 3];
|
|
guchar pix[3];
|
|
|
|
for (i = 0; i < YRANGE_HEIGHT; i++)
|
|
{
|
|
switch (sel_channel)
|
|
{
|
|
case HISTOGRAM_VALUE:
|
|
case HISTOGRAM_ALPHA:
|
|
pix[0] = pix[1] = pix[2] = (255 - i);
|
|
break;
|
|
|
|
case HISTOGRAM_RED:
|
|
case HISTOGRAM_GREEN:
|
|
case HISTOGRAM_BLUE:
|
|
pix[0] = pix[1] = pix[2] = 0;
|
|
pix[sel_channel - 1] = (255 - i);
|
|
break;
|
|
|
|
default:
|
|
g_warning ("unknown channel type %d, can't happen!?!?",
|
|
cd->channel);
|
|
break;
|
|
}
|
|
|
|
for (j = 0; j < YRANGE_WIDTH * 3; j++)
|
|
buf[j] = pix[j%3];
|
|
|
|
gtk_preview_draw_row (GTK_PREVIEW (cd->yrange),
|
|
buf, 0, i, YRANGE_WIDTH);
|
|
}
|
|
|
|
if (update & DRAW)
|
|
gtk_widget_draw (cd->yrange, NULL);
|
|
}
|
|
if ((update & GRAPH) && (update & DRAW) && cd->pixmap != NULL)
|
|
{
|
|
GdkPoint points[256];
|
|
|
|
/* Clear the pixmap */
|
|
gdk_draw_rectangle (cd->pixmap, cd->graph->style->bg_gc[GTK_STATE_NORMAL],
|
|
TRUE, 0, 0,
|
|
GRAPH_WIDTH + RADIUS * 2, GRAPH_HEIGHT + RADIUS * 2);
|
|
|
|
/* Draw the grid lines */
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
gdk_draw_line (cd->pixmap, cd->graph->style->dark_gc[GTK_STATE_NORMAL],
|
|
RADIUS, i * (GRAPH_HEIGHT / 4) + RADIUS,
|
|
GRAPH_WIDTH + RADIUS, i * (GRAPH_HEIGHT / 4) + RADIUS);
|
|
gdk_draw_line (cd->pixmap, cd->graph->style->dark_gc[GTK_STATE_NORMAL],
|
|
i * (GRAPH_WIDTH / 4) + RADIUS, RADIUS,
|
|
i * (GRAPH_WIDTH / 4) + RADIUS, GRAPH_HEIGHT + RADIUS);
|
|
}
|
|
|
|
/* Draw the curve */
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
points[i].x = i + RADIUS;
|
|
points[i].y = 255 - cd->curve[cd->channel][i] + RADIUS;
|
|
}
|
|
gdk_draw_points (cd->pixmap, cd->graph->style->black_gc, points, 256);
|
|
|
|
/* Draw the points */
|
|
if (cd->curve_type[cd->channel] == SMOOTH)
|
|
for (i = 0; i < 17; i++)
|
|
{
|
|
if (cd->points[cd->channel][i][0] != -1)
|
|
gdk_draw_arc (cd->pixmap, cd->graph->style->black_gc, TRUE,
|
|
cd->points[cd->channel][i][0],
|
|
255 - cd->points[cd->channel][i][1],
|
|
RADIUS * 2, RADIUS * 2, 0, 23040);
|
|
}
|
|
|
|
/* draw the colour line */
|
|
gdk_draw_line (cd->pixmap, cd->graph->style->black_gc,
|
|
cd->col_value[sel_channel]+RADIUS,RADIUS,
|
|
cd->col_value[sel_channel]+RADIUS,GRAPH_HEIGHT + RADIUS);
|
|
|
|
/* and xpos indicator */
|
|
g_snprintf (buf, sizeof (buf), "x:%d",cd->col_value[sel_channel]);
|
|
|
|
if ((cd->col_value[sel_channel]+RADIUS) < 127)
|
|
{
|
|
offset = RADIUS + 4;
|
|
}
|
|
else
|
|
{
|
|
offset = - gdk_string_width (cd->graph->style->font,buf) - 2;
|
|
}
|
|
|
|
gdk_draw_string (cd->pixmap,
|
|
cd->graph->style->font,
|
|
cd->graph->style->black_gc,
|
|
cd->col_value[sel_channel]+offset,
|
|
GRAPH_HEIGHT,
|
|
buf);
|
|
|
|
gdk_draw_pixmap (cd->graph->window, cd->graph->style->black_gc, cd->pixmap,
|
|
0, 0, 0, 0, GRAPH_WIDTH + RADIUS * 2, GRAPH_HEIGHT + RADIUS * 2);
|
|
|
|
}
|
|
}
|
|
|
|
static void
|
|
curves_plot_curve (CurvesDialog *cd,
|
|
gint p1,
|
|
gint p2,
|
|
gint p3,
|
|
gint p4)
|
|
{
|
|
CRMatrix geometry;
|
|
CRMatrix tmp1, tmp2;
|
|
CRMatrix deltas;
|
|
gdouble x, dx, dx2, dx3;
|
|
gdouble y, dy, dy2, dy3;
|
|
gdouble d, d2, d3;
|
|
gint lastx, lasty;
|
|
gint32 newx, newy;
|
|
gint i;
|
|
|
|
/* construct the geometry matrix from the segment */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
geometry[i][2] = 0;
|
|
geometry[i][3] = 0;
|
|
}
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
geometry[0][i] = cd->points[cd->channel][p1][i];
|
|
geometry[1][i] = cd->points[cd->channel][p2][i];
|
|
geometry[2][i] = cd->points[cd->channel][p3][i];
|
|
geometry[3][i] = cd->points[cd->channel][p4][i];
|
|
}
|
|
|
|
/* subdivide the curve 1000 times */
|
|
/* n can be adjusted to give a finer or coarser curve */
|
|
d = 1.0 / 1000;
|
|
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 */
|
|
curves_CR_compose (CR_basis, geometry, tmp1);
|
|
|
|
/* compose the above results to get the deltas matrix */
|
|
curves_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, 255);
|
|
lasty = CLAMP (y, 0, 255);
|
|
|
|
cd->curve[cd->channel][lastx] = lasty;
|
|
|
|
/* loop over the curve */
|
|
for (i = 0; i < 1000; i++)
|
|
{
|
|
/* increment the x values */
|
|
x += dx;
|
|
dx += dx2;
|
|
dx2 += dx3;
|
|
|
|
/* increment the y values */
|
|
y += dy;
|
|
dy += dy2;
|
|
dy2 += dy3;
|
|
|
|
newx = CLAMP0255 (ROUND (x));
|
|
newy = CLAMP0255 (ROUND (y));
|
|
|
|
/* if this point is different than the last one...then draw it */
|
|
if ((lastx != newx) || (lasty != newy))
|
|
cd->curve[cd->channel][newx] = newy;
|
|
|
|
lastx = newx;
|
|
lasty = newy;
|
|
}
|
|
}
|
|
|
|
void
|
|
curves_calculate_curve (CurvesDialog *cd)
|
|
{
|
|
gint i;
|
|
gint points[17];
|
|
gint num_pts;
|
|
gint p1, p2, p3, p4;
|
|
|
|
switch (cd->curve_type[cd->channel])
|
|
{
|
|
case GFREE:
|
|
break;
|
|
case SMOOTH:
|
|
/* cycle through the curves */
|
|
num_pts = 0;
|
|
for (i = 0; i < 17; i++)
|
|
if (cd->points[cd->channel][i][0] != -1)
|
|
points[num_pts++] = i;
|
|
|
|
/* Initialize boundary curve points */
|
|
if (num_pts != 0)
|
|
{
|
|
for (i = 0; i < cd->points[cd->channel][points[0]][0]; i++)
|
|
cd->curve[cd->channel][i] = cd->points[cd->channel][points[0]][1];
|
|
for (i = cd->points[cd->channel][points[num_pts - 1]][0]; i < 256; i++)
|
|
cd->curve[cd->channel][i] = cd->points[cd->channel][points[num_pts - 1]][1];
|
|
}
|
|
|
|
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)];
|
|
|
|
curves_plot_curve (cd, p1, p2, p3, p4);
|
|
}
|
|
break;
|
|
}
|
|
gimp_lut_setup (cd->lut, (GimpLutFunc) curves_lut_func,
|
|
(void *) cd, gimp_drawable_bytes (cd->drawable));
|
|
|
|
}
|
|
|
|
static void
|
|
curves_preview (CurvesDialog *cd)
|
|
{
|
|
if (!cd->image_map)
|
|
{
|
|
g_message ("curves_preview(): No image map");
|
|
return;
|
|
}
|
|
|
|
active_tool->preserve = TRUE;
|
|
image_map_apply (cd->image_map, (ImageMapApplyFunc)gimp_lut_process_2,
|
|
(void *) cd->lut);
|
|
active_tool->preserve = FALSE;
|
|
}
|
|
|
|
static void
|
|
curves_channel_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
gimp_menu_item_update (widget, &cd->channel);
|
|
|
|
if(!cd->color) {
|
|
if(cd->channel > 1)
|
|
{
|
|
cd->channel = 2;
|
|
}
|
|
else
|
|
{
|
|
cd->channel = 1;
|
|
}
|
|
}
|
|
|
|
gtk_option_menu_set_history (GTK_OPTION_MENU (cd->curve_type_menu),
|
|
cd->curve_type[cd->channel]);
|
|
|
|
curves_update (cd, XRANGE_TOP | YRANGE | GRAPH | DRAW);
|
|
}
|
|
|
|
static void
|
|
curves_smooth_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
gint i;
|
|
gint32 index;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
if (cd->curve_type[cd->channel] != SMOOTH)
|
|
{
|
|
cd->curve_type[cd->channel] = SMOOTH;
|
|
|
|
/* pick representative points from the curve and make them control points */
|
|
for (i = 0; i <= 8; i++)
|
|
{
|
|
index = CLAMP0255 (i * 32);
|
|
cd->points[cd->channel][i * 2][0] = index;
|
|
cd->points[cd->channel][i * 2][1] = cd->curve[cd->channel][index];
|
|
}
|
|
|
|
curves_calculate_curve (cd);
|
|
curves_update (cd, GRAPH | DRAW);
|
|
|
|
if (cd->preview)
|
|
curves_preview (cd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
curves_free_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
if (cd->curve_type[cd->channel] != GFREE)
|
|
{
|
|
cd->curve_type[cd->channel] = GFREE;
|
|
curves_calculate_curve (cd);
|
|
curves_update (cd, GRAPH | DRAW);
|
|
|
|
if (cd->preview)
|
|
curves_preview (cd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
curves_channel_reset (int i)
|
|
{
|
|
gint j;
|
|
|
|
curves_dialog->grab_point = -1;
|
|
|
|
for (j = 0; j < 17; j++)
|
|
{
|
|
curves_dialog->points[i][j][0] = -1;
|
|
curves_dialog->points[i][j][1] = -1;
|
|
}
|
|
curves_dialog->points[i][0][0] = 0;
|
|
curves_dialog->points[i][0][1] = 0;
|
|
curves_dialog->points[i][16][0] = 255;
|
|
curves_dialog->points[i][16][1] = 255;
|
|
}
|
|
|
|
|
|
static void
|
|
curves_reset_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
gint i;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
/* Initialize the values */
|
|
for (i = 0; i < 256; i++)
|
|
cd->curve[cd->channel][i] = i;
|
|
|
|
curves_channel_reset (cd->channel);
|
|
|
|
curves_calculate_curve (cd);
|
|
curves_update (cd, GRAPH | XRANGE_TOP | DRAW);
|
|
|
|
if (cd->preview)
|
|
curves_preview (cd);
|
|
}
|
|
|
|
static void
|
|
curves_ok_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
gimp_dialog_hide (cd->shell);
|
|
|
|
active_tool->preserve = TRUE; /* We're about to dirty... */
|
|
|
|
if (!cd->preview)
|
|
image_map_apply (cd->image_map, (ImageMapApplyFunc)gimp_lut_process_2,
|
|
(void *) cd->lut);
|
|
|
|
if (cd->image_map)
|
|
image_map_commit (cd->image_map);
|
|
|
|
active_tool->preserve = FALSE;
|
|
|
|
cd->image_map = NULL;
|
|
|
|
active_tool->gdisp_ptr = NULL;
|
|
active_tool->drawable = NULL;
|
|
}
|
|
|
|
static void
|
|
curves_cancel_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
gimp_dialog_hide (cd->shell);
|
|
|
|
if (cd->image_map)
|
|
{
|
|
active_tool->preserve = TRUE;
|
|
image_map_abort (cd->image_map);
|
|
active_tool->preserve = FALSE;
|
|
|
|
gdisplays_flush ();
|
|
cd->image_map = NULL;
|
|
}
|
|
|
|
active_tool->gdisp_ptr = NULL;
|
|
active_tool->drawable = NULL;
|
|
}
|
|
|
|
static void
|
|
curves_load_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
if (!file_dlg)
|
|
file_dialog_create (GTK_WIDGET (data));
|
|
else if (GTK_WIDGET_VISIBLE (file_dlg))
|
|
return;
|
|
|
|
load_save = TRUE;
|
|
|
|
gtk_window_set_title (GTK_WINDOW (file_dlg), _("Load Curves"));
|
|
gtk_widget_show (file_dlg);
|
|
}
|
|
|
|
static void
|
|
curves_save_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
if (!file_dlg)
|
|
file_dialog_create (GTK_WIDGET (data));
|
|
else if (GTK_WIDGET_VISIBLE (file_dlg))
|
|
return;
|
|
|
|
load_save = FALSE;
|
|
|
|
gtk_window_set_title (GTK_WINDOW (file_dlg), _("Save Curves"));
|
|
gtk_widget_show (file_dlg);
|
|
}
|
|
|
|
static void
|
|
curves_preview_update (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
CurvesDialog *cd;
|
|
|
|
cd = (CurvesDialog *) data;
|
|
|
|
if (GTK_TOGGLE_BUTTON (widget)->active)
|
|
{
|
|
cd->preview = TRUE;
|
|
curves_preview (cd);
|
|
}
|
|
else
|
|
{
|
|
cd->preview = FALSE;
|
|
if (cd->image_map)
|
|
{
|
|
active_tool->preserve = TRUE;
|
|
image_map_clear (cd->image_map);
|
|
active_tool->preserve = FALSE;
|
|
gdisplays_flush ();
|
|
}
|
|
}
|
|
}
|
|
|
|
static gint
|
|
curves_graph_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
CurvesDialog *cd)
|
|
{
|
|
static GdkCursorType cursor_type = GDK_TOP_LEFT_ARROW;
|
|
GdkCursorType new_type;
|
|
GdkEventButton *bevent;
|
|
GdkEventMotion *mevent;
|
|
gint i;
|
|
gint tx, ty;
|
|
gint x, y;
|
|
gint closest_point;
|
|
gint distance;
|
|
gint x1, x2, y1, y2;
|
|
|
|
new_type = GDK_X_CURSOR;
|
|
closest_point = 0;
|
|
|
|
/* get the pointer position */
|
|
gdk_window_get_pointer (cd->graph->window, &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[cd->channel][i][0] != -1)
|
|
if (abs (x - cd->points[cd->channel][i][0]) < distance)
|
|
{
|
|
distance = abs (x - cd->points[cd->channel][i][0]);
|
|
closest_point = i;
|
|
}
|
|
}
|
|
if (distance > MIN_DISTANCE)
|
|
closest_point = (x + 8) / 16;
|
|
|
|
switch (event->type)
|
|
{
|
|
case GDK_EXPOSE:
|
|
if (cd->pixmap == NULL)
|
|
cd->pixmap = gdk_pixmap_new (cd->graph->window,
|
|
GRAPH_WIDTH + RADIUS * 2,
|
|
GRAPH_HEIGHT + RADIUS * 2, -1);
|
|
|
|
curves_update (cd, GRAPH | DRAW);
|
|
break;
|
|
|
|
case GDK_BUTTON_PRESS:
|
|
bevent = (GdkEventButton *) event;
|
|
new_type = GDK_TCROSS;
|
|
|
|
switch (cd->curve_type[cd->channel])
|
|
{
|
|
case SMOOTH:
|
|
/* determine the leftmost and rightmost points */
|
|
cd->leftmost = -1;
|
|
for (i = closest_point - 1; i >= 0; i--)
|
|
if (cd->points[cd->channel][i][0] != -1)
|
|
{
|
|
cd->leftmost = cd->points[cd->channel][i][0];
|
|
break;
|
|
}
|
|
cd->rightmost = 256;
|
|
for (i = closest_point + 1; i < 17; i++)
|
|
if (cd->points[cd->channel][i][0] != -1)
|
|
{
|
|
cd->rightmost = cd->points[cd->channel][i][0];
|
|
break;
|
|
}
|
|
|
|
cd->grab_point = closest_point;
|
|
cd->points[cd->channel][cd->grab_point][0] = x;
|
|
cd->points[cd->channel][cd->grab_point][1] = 255 - y;
|
|
|
|
break;
|
|
|
|
case GFREE:
|
|
cd->curve[cd->channel][x] = 255 - y;
|
|
cd->grab_point = x;
|
|
cd->last = y;
|
|
break;
|
|
}
|
|
|
|
curves_calculate_curve (cd);
|
|
curves_update (cd, GRAPH | XRANGE_TOP | DRAW);
|
|
gtk_grab_add (widget);
|
|
|
|
break;
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
new_type = GDK_FLEUR;
|
|
cd->grab_point = -1;
|
|
|
|
if (cd->preview)
|
|
curves_preview (cd);
|
|
|
|
gtk_grab_remove (widget);
|
|
|
|
break;
|
|
|
|
case GDK_MOTION_NOTIFY:
|
|
mevent = (GdkEventMotion *) event;
|
|
|
|
if (mevent->is_hint)
|
|
{
|
|
mevent->x = tx;
|
|
mevent->y = ty;
|
|
}
|
|
|
|
switch (cd->curve_type[cd->channel])
|
|
{
|
|
case SMOOTH:
|
|
/* If no point is grabbed... */
|
|
if (cd->grab_point == -1)
|
|
{
|
|
if (cd->points[cd->channel][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[cd->channel][cd->grab_point][0] = -1;
|
|
|
|
if (x > cd->leftmost && x < cd->rightmost)
|
|
{
|
|
closest_point = (x + 8) / 16;
|
|
if (cd->points[cd->channel][closest_point][0] == -1)
|
|
cd->grab_point = closest_point;
|
|
cd->points[cd->channel][cd->grab_point][0] = x;
|
|
cd->points[cd->channel][cd->grab_point][1] = 255 - y;
|
|
}
|
|
|
|
curves_calculate_curve (cd);
|
|
curves_update (cd, GRAPH | XRANGE_TOP | 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[cd->channel][i] = 255 - (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1));
|
|
else
|
|
cd->curve[cd->channel][x] = 255 - y;
|
|
|
|
cd->grab_point = x;
|
|
cd->last = y;
|
|
|
|
curves_update (cd, GRAPH | XRANGE_TOP | 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 (cd->graph->window, cursor_type,
|
|
TOOL_TYPE_NONE, CURSOR_MODIFIER_NONE, FALSE);
|
|
}
|
|
|
|
curve_print_loc (cd, x, 255 - y);
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
curves_xrange_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
CurvesDialog *cd)
|
|
{
|
|
switch (event->type)
|
|
{
|
|
case GDK_EXPOSE:
|
|
curves_update (cd, XRANGE_TOP | XRANGE_BOTTOM);
|
|
break;
|
|
|
|
case GDK_DELETE:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
curves_yrange_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
CurvesDialog *cd)
|
|
{
|
|
switch (event->type)
|
|
{
|
|
case GDK_EXPOSE:
|
|
curves_update (cd, YRANGE);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
curves_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
|
|
file_dialog_create (GtkWidget *parent)
|
|
{
|
|
gchar *temp;
|
|
|
|
file_dlg = gtk_file_selection_new (_("Load/Save Curves"));
|
|
gtk_window_set_wmclass (GTK_WINDOW (file_dlg), "load_save_curves", "Gimp");
|
|
gtk_window_set_position (GTK_WINDOW (file_dlg), GTK_WIN_POS_MOUSE);
|
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (file_dlg), 2);
|
|
gtk_container_set_border_width (GTK_CONTAINER (GTK_FILE_SELECTION (file_dlg)->button_area), 2);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_dlg)->cancel_button),
|
|
"clicked", GTK_SIGNAL_FUNC (file_dialog_cancel_callback),
|
|
NULL);
|
|
gtk_signal_connect (GTK_OBJECT (file_dlg), "delete_event",
|
|
GTK_SIGNAL_FUNC (file_dialog_cancel_callback),
|
|
NULL);
|
|
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_dlg)->ok_button),
|
|
"clicked", GTK_SIGNAL_FUNC (file_dialog_ok_callback),
|
|
NULL);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (parent), "unmap",
|
|
GTK_SIGNAL_FUNC (file_dialog_cancel_callback),
|
|
NULL);
|
|
|
|
temp = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "curves" G_DIR_SEPARATOR_S,
|
|
gimp_directory ());
|
|
gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_dlg), temp);
|
|
g_free (temp);
|
|
|
|
gimp_help_connect_help_accel (file_dlg, tools_help_func,
|
|
tool_info[CURVES].private_tip);
|
|
}
|
|
|
|
static void
|
|
file_dialog_ok_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
FILE *f;
|
|
gchar *filename;
|
|
|
|
filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_dlg));
|
|
|
|
if (load_save)
|
|
{
|
|
f = fopen (filename, "rt");
|
|
|
|
if (!f)
|
|
{
|
|
g_message (_("Unable to open file %s"), filename);
|
|
return;
|
|
}
|
|
|
|
if (!curves_read_from_file (f))
|
|
{
|
|
g_message (("Error in reading file %s"), filename);
|
|
return;
|
|
}
|
|
|
|
fclose (f);
|
|
}
|
|
else
|
|
{
|
|
f = fopen (filename, "wt");
|
|
|
|
if (!f)
|
|
{
|
|
g_message (_("Unable to open file %s"), filename);
|
|
return;
|
|
}
|
|
|
|
curves_write_to_file (f);
|
|
|
|
fclose (f);
|
|
}
|
|
|
|
file_dialog_cancel_callback (file_dlg, NULL);
|
|
}
|
|
|
|
static void
|
|
file_dialog_cancel_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
gimp_dialog_hide (file_dlg);
|
|
}
|
|
|
|
static gboolean
|
|
curves_read_from_file (FILE *f)
|
|
{
|
|
gint i, j;
|
|
gint fields;
|
|
gchar buf[50];
|
|
gint index[5][17];
|
|
gint value[5][17];
|
|
gint current_channel;
|
|
|
|
if (!fgets (buf, 50, f))
|
|
return FALSE;
|
|
|
|
if (strcmp (buf, "# GIMP Curves File\n") != 0)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
for (j = 0; j < 17; j++)
|
|
{
|
|
fields = fscanf (f, "%d %d ", &index[i][j], &value[i][j]);
|
|
if (fields != 2)
|
|
{
|
|
g_print ("fields != 2");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
curves_dialog->curve_type[i] = SMOOTH;
|
|
for (j = 0; j < 17; j++)
|
|
{
|
|
curves_dialog->points[i][j][0] = index[i][j];
|
|
curves_dialog->points[i][j][1] = value[i][j];
|
|
}
|
|
}
|
|
|
|
/* this is ugly, but works ... */
|
|
current_channel = curves_dialog->channel;
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
curves_dialog->channel = i;
|
|
curves_calculate_curve (curves_dialog);
|
|
}
|
|
curves_dialog->channel = current_channel;
|
|
|
|
curves_update (curves_dialog, ALL);
|
|
gtk_option_menu_set_history (GTK_OPTION_MENU (curves_dialog->curve_type_menu),
|
|
SMOOTH);
|
|
|
|
if (curves_dialog->preview)
|
|
curves_preview (curves_dialog);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
curves_write_to_file (FILE *f)
|
|
{
|
|
gint i, j;
|
|
gint32 index;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
if (curves_dialog->curve_type[i] == GFREE)
|
|
{
|
|
/* pick representative points from the curve and make them control points */
|
|
for (j = 0; j <= 8; j++)
|
|
{
|
|
index = CLAMP0255 (j * 32);
|
|
curves_dialog->points[i][j * 2][0] = index;
|
|
curves_dialog->points[i][j * 2][1] = curves_dialog->curve[i][index];
|
|
}
|
|
}
|
|
|
|
fprintf (f, "# GIMP Curves File\n");
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
for (j = 0; j < 17; j++)
|
|
fprintf (f, "%d %d ", curves_dialog->points[i][j][0],
|
|
curves_dialog->points[i][j][1]);
|
|
|
|
fprintf (f, "\n");
|
|
}
|
|
}
|
|
|