Commit f171d105 authored by Sven Neumann's avatar Sven Neumann Committed by Sven Neumann

Bug 471344 – Circular brush strokes are not smooth and have corners

2009-01-10  Sven Neumann  <sven@gimp.org>

	Bug 471344 – Circular brush strokes are not smooth and have 
corners

	Bug 127785 – stroking with size linked to pressure sensitivity
	should scale the spacing

	* app/core/gimpcoords-interpolate.[ch]
	* app/display/gimpdisplayshell.[ch]
	* app/display/gimpdisplayshell-callbacks.[ch]
	* app/display/gimpdisplayshell-coords.[ch]: applied patch from
	Alexia Death that introduces a Catmul-Rom splines based event
	interpolation and also adapts the brush spacing to brush size.


svn path=/trunk/; revision=27898
parent eeae281b
2009-01-10 Sven Neumann <sven@gimp.org>
Bug 471344 Circular brush strokes are not smooth and have corners
Bug 127785 stroking with size linked to pressure sensitivity
should scale the spacing
* app/core/gimpcoords-interpolate.[ch]
* app/display/gimpdisplayshell.[ch]
* app/display/gimpdisplayshell-callbacks.[ch]
* app/display/gimpdisplayshell-coords.[ch]: applied patch from
Alexia Death that introduces a Catmul-Rom splines based event
interpolation and also adapts the brush spacing to brush size.
2009-01-06 Sven Neumann <sven@gimp.org>
Bug 565046 Point snapping to guides does not work outside the
canvas
* app/core/gimpimage-snap.c: applied patch from Daniel Hornung
that introduces the utility function gimp_image_snap_grid() to
clean up and fix guide snapping.
2009-01-06 Sven Neumann <sven@gimp.org>
Bug 566498 Noise distribution error in RGB Noise and HSV Noise
......
......@@ -29,6 +29,7 @@
#include "gimpcoords.h"
#include "gimpcoords-interpolate.h"
/* Local helper functions declarations*/
static void gimp_coords_interpolate_bezier_internal (const GimpCoords bezier_pt1,
const GimpCoords bezier_pt2,
......@@ -40,6 +41,11 @@ static void gimp_coords_interpolate_bezier_internal (const GimpCoords bezie
GArray **ret_coords,
GArray **ret_params,
gint depth);
static gdouble gimp_coords_get_catmull_spline_point (gdouble t,
gdouble p0,
gdouble p1,
gdouble p2,
gdouble p3);
/* Functions for bezier subdivision */
......@@ -190,7 +196,7 @@ gimp_coords_bezier_is_straight (const GimpCoords bezier_pt1,
const GimpCoords bezier_pt2,
const GimpCoords bezier_pt3,
const GimpCoords bezier_pt4,
gdouble precision)
gdouble precision)
{
GimpCoords pt1, pt2;
......@@ -208,3 +214,116 @@ gimp_coords_bezier_is_straight (const GimpCoords bezier_pt1,
return (gimp_coords_manhattan_dist (&bezier_pt2, &pt1) < precision &&
gimp_coords_manhattan_dist (&bezier_pt3, &pt2) < precision);
}
/* Functions for camull-rom interpolation */
void
gimp_coords_interpolate_catmull (const GimpCoords catmul_pt1,
const GimpCoords catmul_pt2,
const GimpCoords catmul_pt3,
const GimpCoords catmul_pt4,
gdouble precision,
GArray **ret_coords,
GArray **ret_params)
{
gdouble delta_x, delta_y;
gdouble distance;
gint num_points;
gint n;
GimpCoords past_coords;
GimpCoords start_coords;
GimpCoords end_coords;
GimpCoords future_coords;
delta_x = catmul_pt3.x - catmul_pt2.x;
delta_y = catmul_pt3.y - catmul_pt2.y;
/* Catmull-Rom interpolation requires 4 points.
* Two endpoints plus one more at each end.
*/
past_coords = catmul_pt1;
start_coords = catmul_pt2;
end_coords = catmul_pt3;
future_coords = catmul_pt4;
distance = sqrt (SQR (delta_x) + SQR (delta_y));
num_points = distance / precision;
for (n = 1; n <=num_points; n++)
{
GimpCoords res_coords;
gdouble velocity;
gdouble p = (gdouble) n / num_points;
res_coords.x =
gimp_coords_get_catmull_spline_point (p,
past_coords.x,
start_coords.x,
end_coords.x,
future_coords.x);
res_coords.y =
gimp_coords_get_catmull_spline_point (p,
past_coords.y,
start_coords.y,
end_coords.y,
future_coords.y);
res_coords.pressure =
gimp_coords_get_catmull_spline_point (p,
past_coords.pressure,
start_coords.pressure,
end_coords.pressure,
future_coords.pressure);
res_coords.xtilt =
gimp_coords_get_catmull_spline_point (p,
past_coords.xtilt,
start_coords.xtilt,
end_coords.xtilt,
future_coords.xtilt);
res_coords.ytilt =
gimp_coords_get_catmull_spline_point (p,
past_coords.ytilt,
start_coords.ytilt,
end_coords.ytilt,
future_coords.ytilt);
res_coords.wheel =
gimp_coords_get_catmull_spline_point (p,
past_coords.wheel,
start_coords.wheel,
end_coords.wheel,
future_coords.wheel);
velocity = gimp_coords_get_catmull_spline_point (p,
past_coords.velocity,
start_coords.velocity,
end_coords.velocity,
future_coords.velocity);
res_coords.velocity = CLAMP (velocity, 0.0, 1.0);
g_array_append_val (*ret_coords, res_coords);
if (ret_params)
g_array_append_val (*ret_params, p);
}
}
static gdouble
gimp_coords_get_catmull_spline_point (gdouble t,
gdouble p0,
gdouble p1,
gdouble p2,
gdouble p3)
{
return ((((-t + 2.0) * t - 1.0) * t / 2.0) * p0 +
((((3.0 * t - 5.0) * t) * t + 2.0) / 2.0) * p1 +
(((-3.0 * t + 4.0) * t + 1.0) * t / 2.0) * p2 +
(((t - 1) * t * t) / 2.0) * p3);
}
......@@ -21,18 +21,26 @@
#ifndef __GIMP_COORDS_INTERPOLATE_H__
#define __GIMP_COORDS_INTERPOLATE_H__
void gimp_coords_interpolate_bezier (const GimpCoords bezier_pt1,
const GimpCoords bezier_pt2,
const GimpCoords bezier_pt3,
const GimpCoords bezier_pt4,
gdouble precision,
GArray **ret_coords,
GArray **ret_params);
void gimp_coords_interpolate_bezier (const GimpCoords bezier_pt1,
const GimpCoords bezier_pt2,
const GimpCoords bezier_pt3,
const GimpCoords bezier_pt4,
gdouble precision,
GArray **ret_coords,
GArray **ret_params);
gboolean gimp_coords_bezier_is_straight (const GimpCoords bezier_pt1,
const GimpCoords bezier_pt2,
const GimpCoords bezier_pt3,
const GimpCoords bezier_pt4,
gdouble precision);
gboolean gimp_coords_bezier_is_straight (const GimpCoords bezier_pt1,
const GimpCoords bezier_pt2,
const GimpCoords bezier_pt3,
const GimpCoords bezier_pt4,
gdouble precision);
void gimp_coords_interpolate_catmull (const GimpCoords catmul_pt1,
const GimpCoords catmul_pt2,
const GimpCoords catmul_pt3,
const GimpCoords catmul_pt4,
gdouble precision,
GArray **ret_coords,
GArray **ret_params);
#endif /* __GIMP_COORDS_INTERPOLATE_H__ */
......@@ -47,6 +47,7 @@
#include "tools/gimppainttool.h"
#include "tools/gimptoolcontrol.h"
#include "tools/tool_manager.h"
#include "tools/tools-enums.h"
#include "widgets/gimpcontrollers.h"
#include "widgets/gimpcontrollerkeyboard.h"
......@@ -80,28 +81,31 @@
/* local function prototypes */
static void gimp_display_shell_vscrollbar_update (GtkAdjustment *adjustment,
GimpDisplayShell *shell);
static void gimp_display_shell_hscrollbar_update (GtkAdjustment *adjustment,
GimpDisplayShell *shell);
static gboolean gimp_display_shell_vscrollbar_update_range (GtkRange *range,
GtkScrollType scroll,
gdouble value,
GimpDisplayShell *shell);
static gboolean gimp_display_shell_hscrollbar_update_range (GtkRange *range,
GtkScrollType scroll,
gdouble value,
GimpDisplayShell *shell);
static void gimp_display_shell_vscrollbar_update (GtkAdjustment *adjustment,
GimpDisplayShell *shell);
static void gimp_display_shell_hscrollbar_update (GtkAdjustment *adjustment,
GimpDisplayShell *shell);
static gboolean gimp_display_shell_vscrollbar_update_range (GtkRange *range,
GtkScrollType scroll,
gdouble value,
GimpDisplayShell *shell);
static gboolean gimp_display_shell_hscrollbar_update_range (GtkRange *range,
GtkScrollType scroll,
gdouble value,
GimpDisplayShell *shell);
static GdkModifierType
gimp_display_shell_key_to_state (gint key);
gimp_display_shell_key_to_state (gint key);
static GdkEvent * gimp_display_shell_compress_motion (GimpDisplayShell *shell);
static GdkEvent * gimp_display_shell_compress_motion (GimpDisplayShell *shell);
static void gimp_display_shell_canvas_expose_image (GimpDisplayShell *shell,
GdkEventExpose *eevent);
static void gimp_display_shell_canvas_expose_drop_zone (GimpDisplayShell *shell,
GdkEventExpose *eevent);
static void gimp_display_shell_canvas_expose_image (GimpDisplayShell *shell,
GdkEventExpose *eevent);
static void gimp_display_shell_canvas_expose_drop_zone (GimpDisplayShell *shell,
GdkEventExpose *eevent);
static void gimp_display_shell_process_tool_event_queue (GimpDisplayShell *shell,
GdkModifierType state,
guint32 time);
/* public functions */
......@@ -1002,6 +1006,10 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
{
if (gimp_tool_control_is_active (active_tool->control))
{
if (shell->event_queue->len > 0)
gimp_display_shell_flush_event_queue (shell);
tool_manager_button_release_active (gimp,
&image_coords,
time, state,
......@@ -1253,8 +1261,11 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
{
gint i;
tool_manager_control_active (gimp, GIMP_TOOL_ACTION_PAUSE, display);
for (i = 0; i < n_history_events; i++)
{
gimp_display_shell_get_time_coords (shell,
mevent->device,
history_events[i],
......@@ -1286,20 +1297,21 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
active_tool->max_coord_smooth,
history_events[i]->time))
{
tool_manager_motion_active (gimp,
&image_coords,
history_events[i]->time,
state,
display);
gimp_display_shell_process_tool_event_queue (shell,
state,
history_events[i]->time);
}
shell->last_read_motion_time = history_events[i]->time;
}
tool_manager_control_active (gimp, GIMP_TOOL_ACTION_RESUME, display);
gdk_device_free_history (history_events, n_history_events);
}
else
{
/* Early removal of useless events saves CPU time.
*/
if (gimp_display_shell_eval_event (shell,
......@@ -1307,11 +1319,9 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
active_tool->max_coord_smooth,
time))
{
tool_manager_motion_active (gimp,
&image_coords,
time,
state,
display);
gimp_display_shell_process_tool_event_queue (shell,
state,
time);
}
shell->last_read_motion_time = time;
......@@ -1325,17 +1335,25 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
/* Early removal of useless events saves CPU time.
* Smoothing is 0.0 here for coasting.
*/
if (gimp_display_shell_eval_event (shell,
&image_coords,
0.0,
time))
{
tool_manager_oper_update_active (gimp,
&image_coords, state,
shell->proximity,
display);
/* then update the tool. */
GimpCoords *buf_coords = &g_array_index (shell->event_queue,
GimpCoords, 0);
tool_manager_oper_update_active (gimp,
buf_coords, state,
shell->proximity,
display);
/* remove used event */
g_array_remove_index (shell->event_queue, 0);
}
gimp_display_shell_push_event_history (shell, &image_coords);
shell->last_read_motion_time = time;
}
......@@ -1778,7 +1796,97 @@ gimp_display_shell_nav_button_press (GtkWidget *widget,
}
/* Event delay timeout handler & generic event flusher */
gboolean
gimp_display_shell_flush_event_queue (GimpDisplayShell *shell)
{
GimpTool *active_tool = tool_manager_get_active (shell->display->gimp);
shell->event_delay = FALSE;
/* Set the timeout id to 0 */
shell->event_delay_timeout = 0;
if (active_tool &&
gimp_tool_control_is_active (active_tool->control) &&
shell->event_queue->len > 0)
{
GimpCoords last_coords = g_array_index (shell->event_queue,
GimpCoords, shell->event_queue->len - 1 );
gimp_display_shell_push_event_history (shell, &last_coords);
gimp_display_shell_process_tool_event_queue (shell,
shell->last_active_state,
shell->last_read_motion_time);
}
/* Return false so a potential timeout calling it gets removed */
return FALSE;
}
/* private functions */
static void
gimp_display_shell_process_tool_event_queue (GimpDisplayShell *shell,
GdkModifierType state,
guint32 time)
{
gint i;
gint keep = 0;
GdkModifierType event_state;
GimpCoords keep_event;
GimpCoords *buf_coords = NULL;
if (shell->event_delay)
{
keep = 1; /* Holding one event in buf */
/* If we are in delay we use LAST state, not current */
event_state = shell->last_active_state;
keep_event = g_array_index (shell->event_queue,
GimpCoords, shell->event_queue->len - 1 );
}
else
{
event_state = state; /* Save the state */
}
if (shell->event_delay_timeout != 0)
g_source_remove (shell->event_delay_timeout);
shell->last_active_state = state;
tool_manager_control_active (shell->display->gimp,
GIMP_TOOL_ACTION_PAUSE, shell->display);
for (i = 0; i < (shell->event_queue->len - keep); i++)
{
buf_coords = &g_array_index (shell->event_queue, GimpCoords, i);
tool_manager_motion_active (shell->display->gimp,
buf_coords,
time,
event_state,
shell->display);
}
tool_manager_control_active (shell->display->gimp,
GIMP_TOOL_ACTION_RESUME, shell->display);
g_array_set_size (shell->event_queue, 0);
if (shell->event_delay)
{
g_array_append_val (shell->event_queue, keep_event);
shell->event_delay_timeout =
g_timeout_add (50,
(GSourceFunc) gimp_display_shell_flush_event_queue,
shell);
}
}
static void
gimp_display_shell_vscrollbar_update (GtkAdjustment *adjustment,
......
......@@ -56,6 +56,7 @@ void gimp_display_shell_quick_mask_toggled (GtkWidget *widget,
gboolean gimp_display_shell_nav_button_press (GtkWidget *widget,
GdkEventButton *bevent,
GimpDisplayShell *shell);
gboolean gimp_display_shell_flush_event_queue (GimpDisplayShell *shell);
#endif /* __GIMP_DISPLAY_SHELL_CALLBACKS_H__ */
......@@ -27,9 +27,16 @@
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-coords.h"
#include "core/gimpcoords-interpolate.h"
/* Velocity unit is screen pixels per millisecond we pass to tools as 1. */
#define VELOCITY_UNIT 3.0
#define EVENT_FILL_PRECISION 6.0
static void gimp_display_shell_interpolate_stroke (GimpDisplayShell *shell,
GimpCoords *coords);
/* public functions */
gboolean
......@@ -210,13 +217,14 @@ gimp_display_shell_get_device_state (GimpDisplayShell *shell,
gboolean
gimp_display_shell_eval_event (GimpDisplayShell *shell,
GimpCoords *coords,
gdouble inertia_factor,
guint32 time)
gdouble inertia_factor,
guint32 time)
{
gdouble delta_time = 0.001;
gdouble delta_x = 0.0;
gdouble delta_y = 0.0;
gdouble distance = 1.0;
gdouble delta_time = 0.001;
gdouble delta_x = 0.0;
gdouble delta_y = 0.0;
gdouble distance = 1.0;
gboolean event_fill = (inertia_factor > 0);
/* Smoothing causes problems with cursor tracking
* when zoomed above screen resolution so we need to supress it.
......@@ -340,18 +348,48 @@ gimp_display_shell_eval_event (GimpDisplayShell *shell,
distance = sqrt (SQR (delta_x) + SQR (delta_y));
}
/* do event fill for devices that do not provide enough events*/
if (distance >= EVENT_FILL_PRECISION &&
event_fill &&
shell->event_history->len >= 2)
{
if (shell->event_delay)
{
gimp_display_shell_interpolate_stroke (shell,
coords);
}
else
{
shell->event_delay = TRUE;
}
}
else
{
if (shell->event_delay)
{
shell->event_delay = FALSE;
}
gimp_display_shell_push_event_history (shell, coords);
}
#ifdef VERBOSE
g_printerr ("DIST: %f, DT:%f, Vel:%f, Press:%f,smooth_dd:%f, sf %f\n",
distance,
delta_time,
shell->last_coords.velocity,
coords->pressure,
coords->distance - dist,
distance - dist,
inertia_factor);
#endif
}
shell->last_coords = *coords;
g_array_append_val (shell->event_queue, *coords);
shell->last_motion_time = time;
shell->last_motion_delta_time = delta_time;
......@@ -361,3 +399,51 @@ gimp_display_shell_eval_event (GimpDisplayShell *shell,
return TRUE;
}
/* Helper function fo managing event history */
void
gimp_display_shell_push_event_history (GimpDisplayShell *shell,
GimpCoords *coords)
{
if (shell->event_history->len == 4)
g_array_remove_index (shell->event_history, 0);
g_array_append_val (shell->event_history, *coords);
}
static void
gimp_display_shell_interpolate_stroke (GimpDisplayShell *shell,
GimpCoords *coords)
{
GArray *ret_coords;
gint i = shell->event_history->len - 1;
/* Note that there must be exactly one event in buffer or bad things
* can happen. This should never get called under other circumstances.
*/
ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
gimp_coords_interpolate_catmull (g_array_index (shell->event_history,
GimpCoords, i - 1),
g_array_index (shell->event_history,
GimpCoords, i),
g_array_index (shell->event_queue,
GimpCoords, 0),
*coords,
EVENT_FILL_PRECISION / 2,
&ret_coords,
NULL);
/* Push the last actual event in history */
gimp_display_shell_push_event_history (shell,
&g_array_index (shell->event_queue,
GimpCoords, 0));
g_array_set_size(shell->event_queue, 0);
g_array_append_vals (shell->event_queue,
&g_array_index (ret_coords, GimpCoords, 0),
ret_coords->len);
g_array_free(ret_coords, TRUE);
}
......@@ -20,28 +20,30 @@
#define __GIMP_DISPLAY_SHELL_COORDS_H__
gboolean gimp_display_shell_get_event_coords (GimpDisplayShell *shell,
GdkEvent *event,
GdkDevice *device,
GimpCoords *coords);
void gimp_display_shell_get_device_coords (GimpDisplayShell *shell,
GdkDevice *device,
GimpCoords *coords);
void gimp_display_shell_get_time_coords (GimpDisplayShell *shell,
GdkDevice *device,
GdkTimeCoord *event,
GimpCoords *coords);
gboolean gimp_display_shell_get_event_state (GimpDisplayShell *shell,
GdkEvent *event,
GdkDevice *device,
GdkModifierType *state);
void gimp_display_shell_get_device_state (GimpDisplayShell *shell,
GdkDevice *device,
GdkModifierType *state);
gboolean gimp_display_shell_eval_event (GimpDisplayShell *shell,
GimpCoords *coords,
gdouble inertia_factor,
guint32 time);
gboolean gimp_display_shell_get_event_coords (GimpDisplayShell *shell,
GdkEvent *event,
GdkDevice *device,
GimpCoords *coords);
void gimp_display_shell_get_device_coords (GimpDisplayShell *shell,
GdkDevice *device,
GimpCoords *coords);
void gimp_display_shell_get_time_coords (GimpDisplayShell *shell,
GdkDevice *device,
GdkTimeCoord *event,
GimpCoords *coords);
gboolean gimp_display_shell_get_event_state (GimpDisplayShell *shell,
GdkEvent *event,
GdkDevice *device,
GdkModifierType *state);
void gimp_display_shell_get_device_state (GimpDisplayShell *shell,
GdkDevice *device,
GdkModifierType *state);
gboolean gimp_display_shell_eval_event (GimpDisplayShell *shell,
GimpCoords *coords,
gdouble inertia_factor,
guint32 time);
void gimp_display_shell_push_event_history (GimpDisplayShell *shell,
GimpCoords *coords);
#endif /* __GIMP_DISPLAY_SHELL_COORDS_H__ */
......@@ -82,7 +82,6 @@
#include "gimp-intl.h"
enum
{
PROP_0,
......@@ -356,6 +355,17 @@ gimp_display_shell_init (GimpDisplayShell *shell)
shell->highlight = NULL;
shell->mask = NULL;
shell->event_history = g_array_new (FALSE, FALSE,
sizeof (GimpCoords));
shell->event_queue = g_array_new (FALSE, FALSE,
sizeof (GimpCoords));
shell->event_delay = FALSE;
shell->event_delay_timeout = 0;
shell->last_active_state = 0;
gtk_window_set_role (GTK_WINDOW (shell), "gimp-image-window");
gtk_window_set_resizable (GTK_WINDOW (shell), TRUE);
......@@ -498,6 +508,18 @@ gimp_display_shell_destroy (GtkObject *object)
shell->mask = NULL;
}
if (shell->event_history)
{
g_array_free (shell->event_history, TRUE);
shell->event_history = NULL;
}
if (shell->event_queue)
{
g_array_free (shell->event_queue, TRUE);
shell->event_queue = NULL;
}
if (shell->title_idle_id)
{
g_source_remove (shell->title_idle_id);
......
......@@ -206,6 +206,14 @@ struct _GimpDisplayShell
GdkRectangle *highlight; /* in image coordinates, can be NULL */
GimpDrawable *mask;
GimpChannelType mask_color;
GArray *event_history;
GArray *event_queue;
gboolean event_delay; /* TRUE if theres an unsent event in
the history buffer */
gint event_delay_timeout;
GdkModifierType last_active_state;
};
struct _GimpDisplayShellClass
......