app/core/core-types.h app/display/gimpdisplayshell-callbacks.c

2008-01-14  Sven Neumann  <sven@gimp.org>

	* app/core/core-types.h
	* app/display/gimpdisplayshell-callbacks.c
	* app/display/gimpdisplayshell-coords.[ch]
	* app/display/gimpdisplayshell.h
	* app/paint/gimpink.[ch]
	* app/paint/gimpinkundo.[ch]: applied patch from Alexia Death 
that
	adds an event evaluation function that decides if an event is
	needed or can be discarded. As a side-product some useful 
dynamics
	parameters like velocity are added to the GimpCoords struct. The
	Ink tool is changed to use this information. See bug #508639.


svn path=/trunk/; revision=24607
This commit is contained in:
Sven Neumann 2008-01-14 20:34:37 +00:00 committed by Sven Neumann
parent 68c4cb9cc4
commit 524870327c
10 changed files with 223 additions and 229 deletions

View File

@ -1,3 +1,16 @@
2008-01-14 Sven Neumann <sven@gimp.org>
* app/core/core-types.h
* app/display/gimpdisplayshell-callbacks.c
* app/display/gimpdisplayshell-coords.[ch]
* app/display/gimpdisplayshell.h
* app/paint/gimpink.[ch]
* app/paint/gimpinkundo.[ch]: applied patch from Alexia Death that
adds an event evaluation function that decides if an event is
needed or can be discarded. As a side-product some useful dynamics
parameters like velocity are added to the GimpCoords struct. The
Ink tool is changed to use this information. See bug #508639.
2008-01-13 Michael Natterer <mitch@gimp.org>
* modules/colorsel_cmyk_lcms.c (colorsel_cmyk_config_changed): set

View File

@ -190,6 +190,11 @@ struct _GimpCoords
gdouble xtilt;
gdouble ytilt;
gdouble wheel;
gdouble delta_time;
gdouble delta_x;
gdouble delta_y;
gdouble distance;
gdouble velocity;
};

View File

@ -902,7 +902,7 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
&image_coords,
time, state, display);
shell->last_motion_time = bevent->time;
shell->last_read_motion_time = bevent->time;
}
}
break;
@ -1182,7 +1182,7 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
GdkTimeCoord **history_events;
gint n_history_events;
/* if the first mouse button is down, check for automatic
/* if the first mouse button is down, check for automatic
* scrolling...
*/
if ((mevent->x < 0 ||
@ -1194,11 +1194,19 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
gimp_display_shell_autoscroll_start (shell, state, mevent);
}
if (gimp_tool_control_get_motion_mode (active_tool->control) ==
GIMP_MOTION_MODE_EXACT &&
/* gdk_device_get_history() has several quirks. First is
* that events with borderline timestamps at both ends
* are included. Because of that we need to add 1 to
* lower border. The second is due to poor X event
* resolution. We need to do -1 to ensure that the
* amount of events between timestamps is final or
* risk loosing some.
*/
if ((gimp_tool_control_get_motion_mode (active_tool->control) ==
GIMP_MOTION_MODE_EXACT) &&
gdk_device_get_history (mevent->device, mevent->window,
shell->last_motion_time,
mevent->time,
shell->last_read_motion_time+1,
mevent->time - 1,
&history_events,
&n_history_events))
{
@ -1206,6 +1214,7 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
for (i = 0; i < n_history_events; i++)
{
gimp_display_shell_get_time_coords (shell,
mevent->device,
history_events[i],
@ -1231,33 +1240,71 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
x, y, width, height);
}
tool_manager_motion_active (gimp,
&image_coords,
history_events[i]->time,
state,
display);
/* Early removal of useless events saves CPU time.
* Defaulting smoothing to 0.4.
*/
if (gimp_display_shell_eval_event (shell,
&image_coords, 0.4,
history_events[i]->time))
{
tool_manager_motion_active (gimp,
&image_coords,
history_events[i]->time,
state,
display);
shell->last_coords = image_coords;
shell->last_disp_motion_time = history_events[i]->time;
}
shell->last_read_motion_time=history_events[i]->time;
}
gdk_device_free_history (history_events, n_history_events);
}
else
{
tool_manager_motion_active (gimp,
&image_coords, time, state,
display);
}
/* Early removal of useless events saves CPU time.
* Defaulting smoothing to 0.4.
*/
if (gimp_display_shell_eval_event (shell,
&image_coords, 0.4,
time))
{
tool_manager_motion_active (gimp,
&image_coords,
time,
state,
display);
shell->last_motion_time = mevent->time;
shell->last_coords = image_coords;
shell->last_disp_motion_time = time;
}
shell->last_read_motion_time=time;
}
}
}
if (! (state &
(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)))
{
tool_manager_oper_update_active (gimp,
&image_coords, state,
shell->proximity,
display);
/* Early removal of useless events saves CPU time.
* Smoothing coasting to avoid unpredicted jumps when making contact.
* This may need a different solution but cant properly test it without
* adjustment.
*/
if (gimp_display_shell_eval_event (shell, &image_coords, 0.4, time))
{
tool_manager_oper_update_active (gimp,
&image_coords, state,
shell->proximity,
display);
shell->last_coords = image_coords;
shell->last_disp_motion_time = time;
}
shell->last_read_motion_time = time;
}
}
break;

View File

@ -20,6 +20,8 @@
#include <gtk/gtk.h>
#include "libgimpmath/gimpmath.h"
#include "display-types.h"
#include "gimpdisplayshell.h"
@ -131,7 +133,8 @@ gimp_display_shell_get_time_coords (GimpDisplayShell *shell,
* requested axis does not exist.
*/
if (gdk_device_get_axis (device, event->axes, GDK_AXIS_PRESSURE, &coords->pressure))
if (gdk_device_get_axis (device,
event->axes, GDK_AXIS_PRESSURE, &coords->pressure))
coords->pressure = CLAMP (coords->pressure, GIMP_COORDS_MIN_PRESSURE,
GIMP_COORDS_MAX_PRESSURE);
else
@ -177,3 +180,127 @@ gimp_display_shell_get_device_state (GimpDisplayShell *shell,
{
gdk_device_get_state (device, shell->canvas->window, NULL, state);
}
/**
* gimp_display_shell_eval_event:
* @shell:
* @coords:
* @inertia_factor:
* @time:
*
* This function evaluates the event to decide if the change is
* big enough to need handling and returns FALSE, if change is less
* than one image pixel or when smoothed event distance covers less
* than one pixel taking a whole lot of load off any draw tools that
* have no use for these sub-pixel events anyway. If the event is
* seen fit at first look, it is evaluated for speed and smoothed.
* Due to lousy time resolution of events pretty strong smoothing is
* applied to timestamps for sensible speed result. This function is
* also ideal for other event adjustment like pressure curve or
* calculating other derived dynamics factors like angular velocity
* calculation from tilt values, to allow for even more dynamic
* brushes. Calculated distance to last event is stored in GimpCoords
* because its a sideproduct of velocity calculation and is currently
* calculated in each tool. If they were to use this distance, more
* resouces on recalculating the same value would be saved.
*
* Return value:
**/
gboolean
gimp_display_shell_eval_event (GimpDisplayShell *shell,
GimpCoords *coords,
gdouble inertia_factor,
guint32 time)
{
guint32 thistime = time;
gdouble dist;
gdouble xy_sfactor = 0;
const gdouble smooth_factor = 0.3;
if (shell->last_disp_motion_time == 0)
{
/* First pair is invalid to do any velocity calculation,
* so we apply constant values.
*/
coords->velocity = 100;
coords->delta_time = 0.001;
coords->distance = 0;
shell->last_coords = *coords;
}
else
{
gdouble dx = coords->delta_x = shell->last_coords.x - coords->x;
gdouble dy = coords->delta_y = shell->last_coords.y - coords->y;
/* Events with distances less than 1 in either motion direction
* are not worth handling.
*/
if (fabs (dx) < 1.0 && fabs (dy) < 1.0)
return FALSE;
coords->delta_time = thistime - shell->last_disp_motion_time;
coords->delta_time = (shell->last_coords.delta_time * (1 - smooth_factor)
+ coords->delta_time * smooth_factor);
coords->distance = dist = sqrt (SQR (dx) + SQR (dy));
/* If even smoothed time resolution does not allow to guess for speed,
* use last velocity.
*/
if ((coords->delta_time == 0))
{
coords->velocity = shell->last_coords.velocity;
}
else
{
coords->velocity = (coords->distance / (gdouble) coords->delta_time) / 10;
/* A little smooth on this too, feels better in tools this way. */
coords->velocity = (shell->last_coords.velocity * (1 - smooth_factor)
+ coords->velocity * smooth_factor);
/* Speed needs upper limit */
coords->velocity=MIN(coords->velocity,1.0);
}
if (inertia_factor > 0)
{
/* Apply smoothing to X and Y.
*
* The very high velocity values (yes, thats around 25
* according to tests) happen at great zoom outs and scream
* for heavyhanded smooth so I made it automatic. This
* should be bound to zoom level once theres a GUI foob to
* adjust smooth or disabled completely.
*/
/*Apply smoothing to X and Y.
Smooth is dampened for high speeds to minimize the whip effect.*/
xy_sfactor = inertia_factor - inertia_factor * fabs (coords->velocity);
coords->x = shell->last_coords.x - (shell->last_coords.delta_x * xy_sfactor
+ coords->delta_x * (1 - xy_sfactor));
coords->y = shell->last_coords.y - (shell->last_coords.delta_y * xy_sfactor
+ coords->delta_y * (1 - xy_sfactor));
/* Recalculate distance */
coords->distance = sqrt ((shell->last_coords.x - coords->x) *
(shell->last_coords.x - coords->x) +
(shell->last_coords.y - coords->y) *
(shell->last_coords.y - coords->y));
}
#ifdef VERBOSE
g_printerr ("DIST: %f, DT:%f, Vel:%f, Press:%f,smooth_dd:%f, sf %f\n",
coords->distance,
coords->delta_time,
shell->last_coords.velocity,
coords->pressure,
coords->distance - dist,
xy_sfactor);
#endif
}
return TRUE;
}

View File

@ -38,6 +38,10 @@ gboolean gimp_display_shell_get_event_state (GimpDisplayShell *shell,
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);
#endif /* __GIMP_DISPLAY_SHELL_COORDS_H__ */

View File

@ -178,13 +178,16 @@ struct _GimpDisplayShell
gint scroll_start_x;
gint scroll_start_y;
gboolean button_press_before_focus;
guint32 last_motion_time;
guint32 last_disp_motion_time; /* previous time of a forwarded motion event */
guint32 last_read_motion_time;
GdkRectangle *highlight; /* in image coordinates, can be NULL */
GimpDrawable *mask;
GimpChannelType mask_color;
gpointer scroll_info;
GimpCoords last_coords; /* last motion event */
};
struct _GimpDisplayShellClass

View File

@ -77,18 +77,6 @@ static Blob * ink_pen_ellipse (GimpInkOptions *options,
gdouble ytilt,
gdouble velocity);
static void time_smoother_add (GimpInk *ink,
guint32 value);
static guint32 time_smoother_result (GimpInk *ink);
static void time_smoother_init (GimpInk *ink,
guint32 initval);
static void dist_smoother_add (GimpInk *ink,
gdouble value);
static gdouble dist_smoother_result (GimpInk *ink);
static void dist_smoother_init (GimpInk *ink,
gdouble initval);
static void render_blob (Blob *blob,
PixelRegion *dest);
@ -270,67 +258,25 @@ gimp_ink_motion (GimpPaintCore *paint_core,
paint_core->cur_coords.pressure,
paint_core->cur_coords.xtilt,
paint_core->cur_coords.ytilt,
10.0);
100);
if (ink->start_blob)
g_free (ink->start_blob);
ink->start_blob = blob_duplicate (ink->last_blob);
time_smoother_init (ink, time);
ink->last_time = time;
dist_smoother_init (ink, 0.0);
ink->init_velocity = TRUE;
blob_to_render = ink->last_blob;
}
else
{
Blob *blob;
gdouble dist;
gdouble velocity;
guint32 lasttime = ink->last_time;
guint32 thistime;
time_smoother_add (ink, time);
thistime = ink->last_time = time_smoother_result (ink);
/* The time resolution on X-based GDK motion events is bloody
* awful, hence the use of the smoothing function. Sadly this
* also means that there is always the chance of having an
* indeterminite velocity since this event and the previous
* several may still appear to issue at the same
* instant. -ADM
*/
if (thistime == lasttime)
thistime = lasttime + 1;
dist = sqrt ((paint_core->last_coords.x - paint_core->cur_coords.x) *
(paint_core->last_coords.x - paint_core->cur_coords.x) +
(paint_core->last_coords.y - paint_core->cur_coords.y) *
(paint_core->last_coords.y - paint_core->cur_coords.y));
if (ink->init_velocity)
{
dist_smoother_init (ink, dist);
ink->init_velocity = FALSE;
}
else
{
dist_smoother_add (ink, dist);
dist = dist_smoother_result (ink);
}
velocity = 10.0 * sqrt ((dist) / (gdouble) (thistime - lasttime));
blob = ink_pen_ellipse (options,
paint_core->cur_coords.x,
paint_core->cur_coords.y,
paint_core->cur_coords.pressure,
paint_core->cur_coords.xtilt,
paint_core->cur_coords.ytilt,
velocity);
paint_core->cur_coords.velocity * 100);
blob_union = blob_convex_union (ink->last_blob, blob);
g_free (ink->last_blob);
@ -510,84 +456,6 @@ ink_pen_ellipse (GimpInkOptions *options,
}
static void
time_smoother_init (GimpInk *ink,
guint32 initval)
{
gint i;
ink->ts_index = 0;
for (i = 0; i < TIME_SMOOTHER_BUFFER; i++)
ink->ts_buffer[i] = initval;
}
static guint32
time_smoother_result (GimpInk *ink)
{
guint64 result = 0;
gint i;
for (i = 0; i < TIME_SMOOTHER_BUFFER; i++)
result += ink->ts_buffer[i];
return (result / (guint64) TIME_SMOOTHER_BUFFER);
}
static void
time_smoother_add (GimpInk *ink,
guint32 value)
{
guint64 long_value = (guint64) value;
/* handle wrap-around of time values */
if (long_value < ink->ts_buffer[ink->ts_index])
long_value += (guint64) + G_MAXUINT32;
ink->ts_buffer[ink->ts_index++] = long_value;
ink->ts_buffer[ink->ts_index++] = value;
if (ink->ts_index == TIME_SMOOTHER_BUFFER)
ink->ts_index = 0;
}
static void
dist_smoother_init (GimpInk *ink,
gdouble initval)
{
gint i;
ink->dt_index = 0;
for (i = 0; i < DIST_SMOOTHER_BUFFER; i++)
ink->dt_buffer[i] = initval;
}
static gdouble
dist_smoother_result (GimpInk *ink)
{
gint i;
gdouble result = 0.0;
for (i = 0; i < DIST_SMOOTHER_BUFFER; i++)
result += ink->dt_buffer[i];
return (result / (gdouble) DIST_SMOOTHER_BUFFER);
}
static void
dist_smoother_add (GimpInk *ink,
gdouble value)
{
ink->dt_buffer[ink->dt_index++] = value;
if (ink->dt_index == DIST_SMOOTHER_BUFFER)
ink->dt_index = 0;
}
/*********************************/
/* Rendering functions */
/*********************************/

View File

@ -24,10 +24,6 @@
#include "gimpink-blob.h"
#define DIST_SMOOTHER_BUFFER 10
#define TIME_SMOOTHER_BUFFER 10
#define GIMP_TYPE_INK (gimp_ink_get_type ())
#define GIMP_INK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INK, GimpInk))
#define GIMP_INK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INK, GimpInkClass))
@ -46,18 +42,6 @@ struct _GimpInk
Blob *cur_blob; /* current blob */
Blob *last_blob; /* blob for last cursor position */
/* circular distance history buffer */
gdouble dt_buffer[DIST_SMOOTHER_BUFFER];
gint dt_index;
/* circular timing history buffer */
guint32 ts_buffer[TIME_SMOOTHER_BUFFER];
gint ts_index;
guint32 last_time; /* previous time of a motion event */
gboolean init_velocity;
};
struct _GimpInkClass

View File

@ -82,20 +82,6 @@ gimp_ink_undo_constructor (GType type,
if (ink->start_blob)
ink_undo->last_blob = blob_duplicate (ink->start_blob);
memcpy (ink_undo->dt_buffer, ink->dt_buffer,
sizeof (ink_undo->dt_buffer));
ink_undo->dt_index = ink->dt_index;
memcpy (ink_undo->ts_buffer, ink->ts_buffer,
sizeof (ink_undo->ts_buffer));
ink_undo->ts_index = ink->ts_index;
ink_undo->last_time = ink->last_time;
ink_undo->init_velocity = ink->init_velocity;
return object;
}
@ -112,44 +98,11 @@ gimp_ink_undo_pop (GimpUndo *undo,
{
GimpInk *ink = GIMP_INK (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core);
Blob *tmp_blob;
gint tmp_int;
gdouble tmp_double;
guint32 tmp_int_buf[DIST_SMOOTHER_BUFFER];
gdouble tmp_double_buf[DIST_SMOOTHER_BUFFER];
tmp_blob = ink->last_blob;
ink->last_blob = ink_undo->last_blob;
ink_undo->last_blob = tmp_blob;
memcpy (tmp_double_buf, ink->dt_buffer,
sizeof (tmp_double_buf));
memcpy (ink->dt_buffer, ink_undo->dt_buffer,
sizeof (tmp_double_buf));
memcpy (ink_undo->dt_buffer, tmp_double_buf,
sizeof (tmp_double_buf));
tmp_int = ink->dt_index;
ink->dt_index = ink_undo->dt_index;
ink_undo->dt_index = tmp_int;
memcpy (tmp_int_buf, ink->ts_buffer,
sizeof (tmp_int_buf));
memcpy (ink->ts_buffer, ink_undo->ts_buffer,
sizeof (tmp_int_buf));
memcpy (ink_undo->ts_buffer, tmp_int_buf,
sizeof (tmp_int_buf));
tmp_int = ink->ts_index;
ink->ts_index = ink_undo->ts_index;
ink_undo->ts_index = tmp_int;
tmp_double = ink->last_time;
ink->last_time = ink_undo->last_time;
ink_undo->last_time = tmp_double;
tmp_int = ink->init_velocity;
ink->init_velocity = ink_undo->init_velocity;
ink_undo->init_velocity = tmp_int;
}
}

View File

@ -38,16 +38,6 @@ struct _GimpInkUndo
GimpPaintCoreUndo parent_instance;
Blob *last_blob;
gdouble dt_buffer[DIST_SMOOTHER_BUFFER];
gint dt_index;
guint32 ts_buffer[TIME_SMOOTHER_BUFFER];
gint ts_index;
gdouble last_time;
gboolean init_velocity;
};
struct _GimpInkUndoClass