From 524870327cab62238b1e9538d774b85692a832e7 Mon Sep 17 00:00:00 2001 From: Sven Neumann Date: Mon, 14 Jan 2008 20:34:37 +0000 Subject: [PATCH] app/core/core-types.h app/display/gimpdisplayshell-callbacks.c 2008-01-14 Sven Neumann * 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 --- ChangeLog | 13 +++ app/core/core-types.h | 5 + app/display/gimpdisplayshell-callbacks.c | 87 +++++++++++---- app/display/gimpdisplayshell-coords.c | 129 ++++++++++++++++++++- app/display/gimpdisplayshell-coords.h | 4 + app/display/gimpdisplayshell.h | 5 +- app/paint/gimpink.c | 136 +---------------------- app/paint/gimpink.h | 16 --- app/paint/gimpinkundo.c | 47 -------- app/paint/gimpinkundo.h | 10 -- 10 files changed, 223 insertions(+), 229 deletions(-) diff --git a/ChangeLog b/ChangeLog index 005f29976b..789a8748cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2008-01-14 Sven Neumann + + * 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 * modules/colorsel_cmyk_lcms.c (colorsel_cmyk_config_changed): set diff --git a/app/core/core-types.h b/app/core/core-types.h index a7f02ebbfa..bac87cc9c0 100644 --- a/app/core/core-types.h +++ b/app/core/core-types.h @@ -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; }; diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c index f2951489e1..2b387029c9 100644 --- a/app/display/gimpdisplayshell-callbacks.c +++ b/app/display/gimpdisplayshell-callbacks.c @@ -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; diff --git a/app/display/gimpdisplayshell-coords.c b/app/display/gimpdisplayshell-coords.c index 6460a6746d..2fdfcd0e93 100644 --- a/app/display/gimpdisplayshell-coords.c +++ b/app/display/gimpdisplayshell-coords.c @@ -20,6 +20,8 @@ #include +#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; +} diff --git a/app/display/gimpdisplayshell-coords.h b/app/display/gimpdisplayshell-coords.h index 0803fb6289..6c2903047a 100644 --- a/app/display/gimpdisplayshell-coords.h +++ b/app/display/gimpdisplayshell-coords.h @@ -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__ */ diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h index 1eef059925..ea5337a122 100644 --- a/app/display/gimpdisplayshell.h +++ b/app/display/gimpdisplayshell.h @@ -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 diff --git a/app/paint/gimpink.c b/app/paint/gimpink.c index d6a453b631..3b99dfe0f2 100644 --- a/app/paint/gimpink.c +++ b/app/paint/gimpink.c @@ -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 */ /*********************************/ diff --git a/app/paint/gimpink.h b/app/paint/gimpink.h index 38a61b379b..3fe0e41e7d 100644 --- a/app/paint/gimpink.h +++ b/app/paint/gimpink.h @@ -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 diff --git a/app/paint/gimpinkundo.c b/app/paint/gimpinkundo.c index 29e66f2a4e..f643024de5 100644 --- a/app/paint/gimpinkundo.c +++ b/app/paint/gimpinkundo.c @@ -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; } } diff --git a/app/paint/gimpinkundo.h b/app/paint/gimpinkundo.h index acb1548917..533ca07d4a 100644 --- a/app/paint/gimpinkundo.h +++ b/app/paint/gimpinkundo.h @@ -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