1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <string.h>
21
22 #include <gegl.h>
23 #include <gtk/gtk.h>
24
25 #include "libgimpmath/gimpmath.h"
26
27 #include "display-types.h"
28
29 #include "core/gimpcoords.h"
30 #include "core/gimpcoords-interpolate.h"
31 #include "core/gimpmarshal.h"
32
33 #include "gimpmotionbuffer.h"
34
35
36 /* Velocity unit is screen pixels per millisecond we pass to tools as 1. */
37 #define VELOCITY_UNIT 3.0
38 #define EVENT_FILL_PRECISION 6.0
39 #define DIRECTION_RADIUS (1.0 / MAX (scale_x, scale_y))
40 #define SMOOTH_FACTOR 0.3
41
42
43 enum
44 {
45 PROP_0
46 };
47
48 enum
49 {
50 STROKE,
51 HOVER,
52 LAST_SIGNAL
53 };
54
55
56 /* local function prototypes */
57
58 static void gimp_motion_buffer_dispose (GObject *object);
59 static void gimp_motion_buffer_finalize (GObject *object);
60 static void gimp_motion_buffer_set_property (GObject *object,
61 guint property_id,
62 const GValue *value,
63 GParamSpec *pspec);
64 static void gimp_motion_buffer_get_property (GObject *object,
65 guint property_id,
66 GValue *value,
67 GParamSpec *pspec);
68
69 static void gimp_motion_buffer_push_event_history (GimpMotionBuffer *buffer,
70 const GimpCoords *coords);
71 static void gimp_motion_buffer_pop_event_queue (GimpMotionBuffer *buffer,
72 GimpCoords *coords);
73
74 static void gimp_motion_buffer_interpolate_stroke (GimpMotionBuffer *buffer,
75 GimpCoords *coords);
76 static gboolean gimp_motion_buffer_event_queue_timeout (GimpMotionBuffer *buffer);
77
78
79 G_DEFINE_TYPE (GimpMotionBuffer, gimp_motion_buffer, GIMP_TYPE_OBJECT)
80
81 #define parent_class gimp_motion_buffer_parent_class
82
83 static guint motion_buffer_signals[LAST_SIGNAL] = { 0 };
84
85
86 static void
gimp_motion_buffer_class_init(GimpMotionBufferClass * klass)87 gimp_motion_buffer_class_init (GimpMotionBufferClass *klass)
88 {
89 GObjectClass *object_class = G_OBJECT_CLASS (klass);
90
91 motion_buffer_signals[STROKE] =
92 g_signal_new ("stroke",
93 G_TYPE_FROM_CLASS (klass),
94 G_SIGNAL_RUN_FIRST,
95 G_STRUCT_OFFSET (GimpMotionBufferClass, stroke),
96 NULL, NULL,
97 gimp_marshal_VOID__POINTER_UINT_FLAGS,
98 G_TYPE_NONE, 3,
99 G_TYPE_POINTER,
100 G_TYPE_UINT,
101 GDK_TYPE_MODIFIER_TYPE);
102
103 motion_buffer_signals[HOVER] =
104 g_signal_new ("hover",
105 G_TYPE_FROM_CLASS (klass),
106 G_SIGNAL_RUN_FIRST,
107 G_STRUCT_OFFSET (GimpMotionBufferClass, hover),
108 NULL, NULL,
109 gimp_marshal_VOID__POINTER_FLAGS_BOOLEAN,
110 G_TYPE_NONE, 3,
111 G_TYPE_POINTER,
112 GDK_TYPE_MODIFIER_TYPE,
113 G_TYPE_BOOLEAN);
114
115 object_class->dispose = gimp_motion_buffer_dispose;
116 object_class->finalize = gimp_motion_buffer_finalize;
117 object_class->set_property = gimp_motion_buffer_set_property;
118 object_class->get_property = gimp_motion_buffer_get_property;
119 }
120
121 static void
gimp_motion_buffer_init(GimpMotionBuffer * buffer)122 gimp_motion_buffer_init (GimpMotionBuffer *buffer)
123 {
124 buffer->event_history = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
125 buffer->event_queue = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
126 }
127
128 static void
gimp_motion_buffer_dispose(GObject * object)129 gimp_motion_buffer_dispose (GObject *object)
130 {
131 GimpMotionBuffer *buffer = GIMP_MOTION_BUFFER (object);
132
133 if (buffer->event_delay_timeout)
134 {
135 g_source_remove (buffer->event_delay_timeout);
136 buffer->event_delay_timeout = 0;
137 }
138
139 G_OBJECT_CLASS (parent_class)->dispose (object);
140 }
141
142 static void
gimp_motion_buffer_finalize(GObject * object)143 gimp_motion_buffer_finalize (GObject *object)
144 {
145 GimpMotionBuffer *buffer = GIMP_MOTION_BUFFER (object);
146
147 if (buffer->event_history)
148 {
149 g_array_free (buffer->event_history, TRUE);
150 buffer->event_history = NULL;
151 }
152
153 if (buffer->event_queue)
154 {
155 g_array_free (buffer->event_queue, TRUE);
156 buffer->event_queue = NULL;
157 }
158
159 G_OBJECT_CLASS (parent_class)->finalize (object);
160 }
161
162 static void
gimp_motion_buffer_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)163 gimp_motion_buffer_set_property (GObject *object,
164 guint property_id,
165 const GValue *value,
166 GParamSpec *pspec)
167 {
168 switch (property_id)
169 {
170 default:
171 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
172 break;
173 }
174 }
175
176 static void
gimp_motion_buffer_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)177 gimp_motion_buffer_get_property (GObject *object,
178 guint property_id,
179 GValue *value,
180 GParamSpec *pspec)
181 {
182 switch (property_id)
183 {
184 default:
185 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186 break;
187 }
188 }
189
190
191 /* public functions */
192
193 GimpMotionBuffer *
gimp_motion_buffer_new(void)194 gimp_motion_buffer_new (void)
195 {
196 return g_object_new (GIMP_TYPE_MOTION_BUFFER,
197 NULL);
198 }
199
200 void
gimp_motion_buffer_begin_stroke(GimpMotionBuffer * buffer,guint32 time,GimpCoords * last_motion)201 gimp_motion_buffer_begin_stroke (GimpMotionBuffer *buffer,
202 guint32 time,
203 GimpCoords *last_motion)
204 {
205 g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
206 g_return_if_fail (last_motion != NULL);
207
208 buffer->last_read_motion_time = time;
209
210 *last_motion = buffer->last_coords;
211 }
212
213 void
gimp_motion_buffer_end_stroke(GimpMotionBuffer * buffer)214 gimp_motion_buffer_end_stroke (GimpMotionBuffer *buffer)
215 {
216 g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
217
218 if (buffer->event_delay_timeout)
219 {
220 g_source_remove (buffer->event_delay_timeout);
221 buffer->event_delay_timeout = 0;
222 }
223
224 gimp_motion_buffer_event_queue_timeout (buffer);
225 }
226
227 /**
228 * gimp_motion_buffer_motion_event:
229 * @buffer:
230 * @coords:
231 * @time:
232 * @event_fill:
233 *
234 * This function evaluates the event to decide if the change is big
235 * enough to need handling and returns FALSE, if change is less than
236 * set filter level taking a whole lot of load off any draw tools that
237 * have no use for these events anyway. If the event is seen fit at
238 * first look, it is evaluated for speed and smoothed. Due to lousy
239 * time resolution of events pretty strong smoothing is applied to
240 * timestamps for sensible speed result. This function is also ideal
241 * for other event adjustment like pressure curve or calculating other
242 * derived dynamics factors like angular velocity calculation from
243 * tilt values, to allow for even more dynamic brushes. Calculated
244 * distance to last event is stored in GimpCoords because its a
245 * sideproduct of velocity calculation and is currently calculated in
246 * each tool. If they were to use this distance, more resources on
247 * recalculating the same value would be saved.
248 *
249 * Return value: %TRUE if the motion was significant enough to be
250 * processed, %FALSE otherwise.
251 **/
252 gboolean
gimp_motion_buffer_motion_event(GimpMotionBuffer * buffer,GimpCoords * coords,guint32 time,gboolean event_fill)253 gimp_motion_buffer_motion_event (GimpMotionBuffer *buffer,
254 GimpCoords *coords,
255 guint32 time,
256 gboolean event_fill)
257 {
258 gdouble delta_time = 0.001;
259 gdouble delta_x = 0.0;
260 gdouble delta_y = 0.0;
261 gdouble distance = 1.0;
262 gdouble scale_x = coords->xscale;
263 gdouble scale_y = coords->yscale;
264
265 g_return_val_if_fail (GIMP_IS_MOTION_BUFFER (buffer), FALSE);
266 g_return_val_if_fail (coords != NULL, FALSE);
267
268 /* the last_read_motion_time most be set unconditionally, so set
269 * it early
270 */
271 buffer->last_read_motion_time = time;
272
273 delta_time = (buffer->last_motion_delta_time * (1 - SMOOTH_FACTOR) +
274 (time - buffer->last_motion_time) * SMOOTH_FACTOR);
275
276 if (buffer->last_motion_time == 0)
277 {
278 /* First pair is invalid to do any velocity calculation, so we
279 * apply a constant value.
280 */
281 coords->velocity = 1.0;
282 }
283 else
284 {
285 GimpCoords last_dir_event = buffer->last_coords;
286 gdouble filter;
287 gdouble dist;
288 gdouble delta_dir;
289 gdouble dir_delta_x = 0.0;
290 gdouble dir_delta_y = 0.0;
291
292 delta_x = last_dir_event.x - coords->x;
293 delta_y = last_dir_event.y - coords->y;
294
295 /* Events with distances less than the screen resolution are
296 * not worth handling.
297 */
298 filter = MIN (1.0 / scale_x, 1.0 / scale_y) / 2.0;
299
300 if (fabs (delta_x) < filter &&
301 fabs (delta_y) < filter)
302 {
303 return FALSE;
304 }
305
306 distance = dist = sqrt (SQR (delta_x) + SQR (delta_y));
307
308 /* If even smoothed time resolution does not allow to guess for
309 * speed, use last velocity.
310 */
311 if (delta_time == 0)
312 {
313 coords->velocity = buffer->last_coords.velocity;
314 }
315 else
316 {
317 /* We need to calculate the velocity in screen coordinates
318 * for human interaction
319 */
320 gdouble screen_distance = (distance * MIN (scale_x, scale_y));
321
322 /* Calculate raw valocity */
323 coords->velocity = ((screen_distance / delta_time) / VELOCITY_UNIT);
324
325 /* Adding velocity dependent smoothing, feels better in tools. */
326 coords->velocity = (buffer->last_coords.velocity *
327 (1 - MIN (SMOOTH_FACTOR, coords->velocity)) +
328 coords->velocity *
329 MIN (SMOOTH_FACTOR, coords->velocity));
330
331 /* Speed needs upper limit */
332 coords->velocity = MIN (coords->velocity, 1.0);
333 }
334
335 if (((fabs (delta_x) > DIRECTION_RADIUS) &&
336 (fabs (delta_y) > DIRECTION_RADIUS)) ||
337 (buffer->event_history->len < 4))
338 {
339 dir_delta_x = delta_x;
340 dir_delta_y = delta_y;
341 }
342 else
343 {
344 gint x = CLAMP ((buffer->event_history->len - 1), 3, 15);
345
346 while (((fabs (dir_delta_x) < DIRECTION_RADIUS) ||
347 (fabs (dir_delta_y) < DIRECTION_RADIUS)) &&
348 (x >= 0))
349 {
350 last_dir_event = g_array_index (buffer->event_history,
351 GimpCoords, x);
352
353 dir_delta_x = last_dir_event.x - coords->x;
354 dir_delta_y = last_dir_event.y - coords->y;
355
356 x--;
357 }
358 }
359
360 if ((fabs (dir_delta_x) < DIRECTION_RADIUS) ||
361 (fabs (dir_delta_y) < DIRECTION_RADIUS))
362 {
363 coords->direction = buffer->last_coords.direction;
364 }
365 else
366 {
367 coords->direction = gimp_coords_direction (&last_dir_event, coords);
368 }
369
370 coords->direction = coords->direction - floor (coords->direction);
371
372 delta_dir = coords->direction - buffer->last_coords.direction;
373
374 if (delta_dir < -0.5)
375 {
376 coords->direction = (0.5 * coords->direction +
377 0.5 * (buffer->last_coords.direction - 1.0));
378 }
379 else if (delta_dir > 0.5)
380 {
381 coords->direction = (0.5 * coords->direction +
382 0.5 * (buffer->last_coords.direction + 1.0));
383 }
384 else
385 {
386 coords->direction = (0.5 * coords->direction +
387 0.5 * buffer->last_coords.direction);
388 }
389
390 coords->direction = coords->direction - floor (coords->direction);
391
392 /* do event fill for devices that do not provide enough events */
393 if (distance >= EVENT_FILL_PRECISION &&
394 event_fill &&
395 buffer->event_history->len >= 2)
396 {
397 if (buffer->event_delay)
398 {
399 gimp_motion_buffer_interpolate_stroke (buffer, coords);
400 }
401 else
402 {
403 buffer->event_delay = TRUE;
404 gimp_motion_buffer_push_event_history (buffer, coords);
405 }
406 }
407 else
408 {
409 if (buffer->event_delay)
410 buffer->event_delay = FALSE;
411
412 gimp_motion_buffer_push_event_history (buffer, coords);
413 }
414
415 #ifdef EVENT_VERBOSE
416 g_printerr ("DIST: %f, DT:%f, Vel:%f, Press:%f,smooth_dd:%f, POS: (%f, %f)\n",
417 distance,
418 delta_time,
419 buffer->last_coords.velocity,
420 coords->pressure,
421 distance - dist,
422 coords->x,
423 coords->y);
424 #endif
425 }
426
427 g_array_append_val (buffer->event_queue, *coords);
428
429 buffer->last_coords = *coords;
430 buffer->last_motion_time = time;
431 buffer->last_motion_delta_time = delta_time;
432 buffer->last_motion_delta_x = delta_x;
433 buffer->last_motion_delta_y = delta_y;
434 buffer->last_motion_distance = distance;
435
436 return TRUE;
437 }
438
439 guint32
gimp_motion_buffer_get_last_motion_time(GimpMotionBuffer * buffer)440 gimp_motion_buffer_get_last_motion_time (GimpMotionBuffer *buffer)
441 {
442 g_return_val_if_fail (GIMP_IS_MOTION_BUFFER (buffer), 0);
443
444 return buffer->last_read_motion_time;
445 }
446
447 void
gimp_motion_buffer_request_stroke(GimpMotionBuffer * buffer,GdkModifierType state,guint32 time)448 gimp_motion_buffer_request_stroke (GimpMotionBuffer *buffer,
449 GdkModifierType state,
450 guint32 time)
451 {
452 GdkModifierType event_state;
453 gint keep = 0;
454
455 g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
456
457 if (buffer->event_delay)
458 {
459 /* If we are in delay we use LAST state, not current */
460 event_state = buffer->last_active_state;
461
462 keep = 1; /* Holding one event in buf */
463 }
464 else
465 {
466 /* Save the state */
467 event_state = state;
468 }
469
470 if (buffer->event_delay_timeout)
471 {
472 g_source_remove (buffer->event_delay_timeout);
473 buffer->event_delay_timeout = 0;
474 }
475
476 buffer->last_active_state = state;
477
478 while (buffer->event_queue->len > keep)
479 {
480 GimpCoords buf_coords;
481
482 gimp_motion_buffer_pop_event_queue (buffer, &buf_coords);
483
484 g_signal_emit (buffer, motion_buffer_signals[STROKE], 0,
485 &buf_coords, time, event_state);
486 }
487
488 if (buffer->event_delay)
489 {
490 buffer->event_delay_timeout =
491 g_timeout_add (50,
492 (GSourceFunc) gimp_motion_buffer_event_queue_timeout,
493 buffer);
494 }
495 }
496
497 void
gimp_motion_buffer_request_hover(GimpMotionBuffer * buffer,GdkModifierType state,gboolean proximity)498 gimp_motion_buffer_request_hover (GimpMotionBuffer *buffer,
499 GdkModifierType state,
500 gboolean proximity)
501 {
502 g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
503
504 if (buffer->event_queue->len > 0)
505 {
506 GimpCoords buf_coords = g_array_index (buffer->event_queue,
507 GimpCoords,
508 buffer->event_queue->len - 1);
509
510 g_signal_emit (buffer, motion_buffer_signals[HOVER], 0,
511 &buf_coords, state, proximity);
512
513 g_array_set_size (buffer->event_queue, 0);
514 }
515 }
516
517
518 /* private functions */
519
520 static void
gimp_motion_buffer_push_event_history(GimpMotionBuffer * buffer,const GimpCoords * coords)521 gimp_motion_buffer_push_event_history (GimpMotionBuffer *buffer,
522 const GimpCoords *coords)
523 {
524 if (buffer->event_history->len == 4)
525 g_array_remove_index (buffer->event_history, 0);
526
527 g_array_append_val (buffer->event_history, *coords);
528 }
529
530 static void
gimp_motion_buffer_pop_event_queue(GimpMotionBuffer * buffer,GimpCoords * coords)531 gimp_motion_buffer_pop_event_queue (GimpMotionBuffer *buffer,
532 GimpCoords *coords)
533 {
534 *coords = g_array_index (buffer->event_queue, GimpCoords, 0);
535
536 g_array_remove_index (buffer->event_queue, 0);
537 }
538
539 static void
gimp_motion_buffer_interpolate_stroke(GimpMotionBuffer * buffer,GimpCoords * coords)540 gimp_motion_buffer_interpolate_stroke (GimpMotionBuffer *buffer,
541 GimpCoords *coords)
542 {
543 GimpCoords catmull[4];
544 GArray *ret_coords;
545 gint i = buffer->event_history->len - 1;
546
547 /* Note that there must be exactly one event in buffer or bad things
548 * can happen. This must never get called under other circumstances.
549 */
550 ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
551
552 catmull[0] = g_array_index (buffer->event_history, GimpCoords, i - 1);
553 catmull[1] = g_array_index (buffer->event_history, GimpCoords, i);
554 catmull[2] = g_array_index (buffer->event_queue, GimpCoords, 0);
555 catmull[3] = *coords;
556
557 gimp_coords_interpolate_catmull (catmull, EVENT_FILL_PRECISION / 2,
558 ret_coords, NULL);
559
560 /* Push the last actual event in history */
561 gimp_motion_buffer_push_event_history (buffer,
562 &g_array_index (buffer->event_queue,
563 GimpCoords, 0));
564
565 g_array_set_size (buffer->event_queue, 0);
566
567 g_array_append_vals (buffer->event_queue,
568 &g_array_index (ret_coords, GimpCoords, 0),
569 ret_coords->len);
570
571 g_array_free (ret_coords, TRUE);
572 }
573
574 static gboolean
gimp_motion_buffer_event_queue_timeout(GimpMotionBuffer * buffer)575 gimp_motion_buffer_event_queue_timeout (GimpMotionBuffer *buffer)
576 {
577 buffer->event_delay = FALSE;
578 buffer->event_delay_timeout = 0;
579
580 if (buffer->event_queue->len > 0)
581 {
582 GimpCoords last_coords = g_array_index (buffer->event_queue,
583 GimpCoords,
584 buffer->event_queue->len - 1);
585
586 gimp_motion_buffer_push_event_history (buffer, &last_coords);
587
588 gimp_motion_buffer_request_stroke (buffer,
589 buffer->last_active_state,
590 buffer->last_read_motion_time);
591 }
592
593 return FALSE;
594 }
595