1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimptooltransformgrid.c
5 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
6 *
7 * Based on GimpUnifiedTransformTool
8 * Copyright (C) 2011 Mikael Magnusson <mikachu@src.gnome.org>
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
24 #include "config.h"
25
26 #include <gegl.h>
27 #include <gtk/gtk.h>
28
29 #include "libgimpbase/gimpbase.h"
30 #include "libgimpmath/gimpmath.h"
31
32 #include "display-types.h"
33
34 #include "core/gimp-transform-utils.h"
35 #include "core/gimp-utils.h"
36
37 #include "widgets/gimpwidgets-utils.h"
38
39 #include "gimpcanvashandle.h"
40 #include "gimpcanvastransformguides.h"
41 #include "gimpdisplayshell.h"
42 #include "gimptooltransformgrid.h"
43
44 #include "gimp-intl.h"
45
46
47 #define MIN_HANDLE_SIZE 6
48
49
50 enum
51 {
52 PROP_0,
53 PROP_TRANSFORM,
54 PROP_X1,
55 PROP_Y1,
56 PROP_X2,
57 PROP_Y2,
58 PROP_PIVOT_X,
59 PROP_PIVOT_Y,
60 PROP_GUIDE_TYPE,
61 PROP_N_GUIDES,
62 PROP_CLIP_GUIDES,
63 PROP_SHOW_GUIDES,
64 PROP_INSIDE_FUNCTION,
65 PROP_OUTSIDE_FUNCTION,
66 PROP_USE_CORNER_HANDLES,
67 PROP_USE_PERSPECTIVE_HANDLES,
68 PROP_USE_SIDE_HANDLES,
69 PROP_USE_SHEAR_HANDLES,
70 PROP_USE_CENTER_HANDLE,
71 PROP_USE_PIVOT_HANDLE,
72 PROP_DYNAMIC_HANDLE_SIZE,
73 PROP_CONSTRAIN_MOVE,
74 PROP_CONSTRAIN_SCALE,
75 PROP_CONSTRAIN_ROTATE,
76 PROP_CONSTRAIN_SHEAR,
77 PROP_CONSTRAIN_PERSPECTIVE,
78 PROP_FROMPIVOT_SCALE,
79 PROP_FROMPIVOT_SHEAR,
80 PROP_FROMPIVOT_PERSPECTIVE,
81 PROP_CORNERSNAP,
82 PROP_FIXEDPIVOT
83 };
84
85
86 struct _GimpToolTransformGridPrivate
87 {
88 GimpMatrix3 transform;
89 gdouble x1, y1;
90 gdouble x2, y2;
91 gdouble pivot_x;
92 gdouble pivot_y;
93 GimpGuidesType guide_type;
94 gint n_guides;
95 gboolean clip_guides;
96 gboolean show_guides;
97 GimpTransformFunction inside_function;
98 GimpTransformFunction outside_function;
99 gboolean use_corner_handles;
100 gboolean use_perspective_handles;
101 gboolean use_side_handles;
102 gboolean use_shear_handles;
103 gboolean use_center_handle;
104 gboolean use_pivot_handle;
105 gboolean dynamic_handle_size;
106 gboolean constrain_move;
107 gboolean constrain_scale;
108 gboolean constrain_rotate;
109 gboolean constrain_shear;
110 gboolean constrain_perspective;
111 gboolean frompivot_scale;
112 gboolean frompivot_shear;
113 gboolean frompivot_perspective;
114 gboolean cornersnap;
115 gboolean fixedpivot;
116
117 gdouble curx; /* current x coord */
118 gdouble cury; /* current y coord */
119
120 gdouble button_down; /* is the mouse button pressed */
121 gdouble mousex; /* x coord where mouse was clicked */
122 gdouble mousey; /* y coord where mouse was clicked */
123
124 gdouble cx, cy; /* center point (for moving) */
125
126 /* transformed handle coords */
127 gdouble tx1, ty1;
128 gdouble tx2, ty2;
129 gdouble tx3, ty3;
130 gdouble tx4, ty4;
131 gdouble tcx, tcy;
132 gdouble tpx, tpy;
133
134 /* previous transformed handle coords */
135 gdouble prev_tx1, prev_ty1;
136 gdouble prev_tx2, prev_ty2;
137 gdouble prev_tx3, prev_ty3;
138 gdouble prev_tx4, prev_ty4;
139 gdouble prev_tcx, prev_tcy;
140 gdouble prev_tpx, prev_tpy;
141
142 GimpTransformHandle handle; /* current tool activity */
143
144 GimpCanvasItem *guides;
145 GimpCanvasItem *handles[GIMP_N_TRANSFORM_HANDLES];
146 GimpCanvasItem *center_items[2];
147 GimpCanvasItem *pivot_items[2];
148 };
149
150
151 /* local function prototypes */
152
153 static void gimp_tool_transform_grid_constructed (GObject *object);
154 static void gimp_tool_transform_grid_set_property (GObject *object,
155 guint property_id,
156 const GValue *value,
157 GParamSpec *pspec);
158 static void gimp_tool_transform_grid_get_property (GObject *object,
159 guint property_id,
160 GValue *value,
161 GParamSpec *pspec);
162
163 static void gimp_tool_transform_grid_changed (GimpToolWidget *widget);
164 static gint gimp_tool_transform_grid_button_press (GimpToolWidget *widget,
165 const GimpCoords *coords,
166 guint32 time,
167 GdkModifierType state,
168 GimpButtonPressType press_type);
169 static void gimp_tool_transform_grid_button_release (GimpToolWidget *widget,
170 const GimpCoords *coords,
171 guint32 time,
172 GdkModifierType state,
173 GimpButtonReleaseType release_type);
174 static void gimp_tool_transform_grid_motion (GimpToolWidget *widget,
175 const GimpCoords *coords,
176 guint32 time,
177 GdkModifierType state);
178 static GimpHit gimp_tool_transform_grid_hit (GimpToolWidget *widget,
179 const GimpCoords *coords,
180 GdkModifierType state,
181 gboolean proximity);
182 static void gimp_tool_transform_grid_hover (GimpToolWidget *widget,
183 const GimpCoords *coords,
184 GdkModifierType state,
185 gboolean proximity);
186 static void gimp_tool_transform_grid_leave_notify (GimpToolWidget *widget);
187 static void gimp_tool_transform_grid_hover_modifier (GimpToolWidget *widget,
188 GdkModifierType key,
189 gboolean press,
190 GdkModifierType state);
191 static gboolean gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget,
192 const GimpCoords *coords,
193 GdkModifierType state,
194 GimpCursorType *cursor,
195 GimpToolCursorType *tool_cursor,
196 GimpCursorModifier *modifier);
197
198 static GimpTransformHandle
199 gimp_tool_transform_grid_get_handle_for_coords
200 (GimpToolTransformGrid *grid,
201 const GimpCoords *coords);
202 static void gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid);
203 static void gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid);
204 static void gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid);
205 static void gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid,
206 gint *handle_w,
207 gint *handle_h);
208
209
G_DEFINE_TYPE_WITH_PRIVATE(GimpToolTransformGrid,gimp_tool_transform_grid,GIMP_TYPE_TOOL_WIDGET)210 G_DEFINE_TYPE_WITH_PRIVATE (GimpToolTransformGrid, gimp_tool_transform_grid,
211 GIMP_TYPE_TOOL_WIDGET)
212
213 #define parent_class gimp_tool_transform_grid_parent_class
214
215
216 static void
217 gimp_tool_transform_grid_class_init (GimpToolTransformGridClass *klass)
218 {
219 GObjectClass *object_class = G_OBJECT_CLASS (klass);
220 GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
221
222 object_class->constructed = gimp_tool_transform_grid_constructed;
223 object_class->set_property = gimp_tool_transform_grid_set_property;
224 object_class->get_property = gimp_tool_transform_grid_get_property;
225
226 widget_class->changed = gimp_tool_transform_grid_changed;
227 widget_class->button_press = gimp_tool_transform_grid_button_press;
228 widget_class->button_release = gimp_tool_transform_grid_button_release;
229 widget_class->motion = gimp_tool_transform_grid_motion;
230 widget_class->hit = gimp_tool_transform_grid_hit;
231 widget_class->hover = gimp_tool_transform_grid_hover;
232 widget_class->leave_notify = gimp_tool_transform_grid_leave_notify;
233 widget_class->hover_modifier = gimp_tool_transform_grid_hover_modifier;
234 widget_class->get_cursor = gimp_tool_transform_grid_get_cursor;
235 widget_class->update_on_scale = TRUE;
236
237 g_object_class_install_property (object_class, PROP_TRANSFORM,
238 gimp_param_spec_matrix3 ("transform",
239 NULL, NULL,
240 NULL,
241 GIMP_PARAM_READWRITE |
242 G_PARAM_CONSTRUCT));
243
244 g_object_class_install_property (object_class, PROP_X1,
245 g_param_spec_double ("x1",
246 NULL, NULL,
247 -GIMP_MAX_IMAGE_SIZE,
248 GIMP_MAX_IMAGE_SIZE,
249 0.0,
250 GIMP_PARAM_READWRITE |
251 G_PARAM_CONSTRUCT));
252
253 g_object_class_install_property (object_class, PROP_Y1,
254 g_param_spec_double ("y1",
255 NULL, NULL,
256 -GIMP_MAX_IMAGE_SIZE,
257 GIMP_MAX_IMAGE_SIZE,
258 0.0,
259 GIMP_PARAM_READWRITE |
260 G_PARAM_CONSTRUCT));
261
262 g_object_class_install_property (object_class, PROP_X2,
263 g_param_spec_double ("x2",
264 NULL, NULL,
265 -GIMP_MAX_IMAGE_SIZE,
266 GIMP_MAX_IMAGE_SIZE,
267 0.0,
268 GIMP_PARAM_READWRITE |
269 G_PARAM_CONSTRUCT));
270
271 g_object_class_install_property (object_class, PROP_Y2,
272 g_param_spec_double ("y2",
273 NULL, NULL,
274 -GIMP_MAX_IMAGE_SIZE,
275 GIMP_MAX_IMAGE_SIZE,
276 0.0,
277 GIMP_PARAM_READWRITE |
278 G_PARAM_CONSTRUCT));
279
280 g_object_class_install_property (object_class, PROP_PIVOT_X,
281 g_param_spec_double ("pivot-x",
282 NULL, NULL,
283 -GIMP_MAX_IMAGE_SIZE,
284 GIMP_MAX_IMAGE_SIZE,
285 0.0,
286 GIMP_PARAM_READWRITE |
287 G_PARAM_CONSTRUCT));
288
289 g_object_class_install_property (object_class, PROP_PIVOT_Y,
290 g_param_spec_double ("pivot-y",
291 NULL, NULL,
292 -GIMP_MAX_IMAGE_SIZE,
293 GIMP_MAX_IMAGE_SIZE,
294 0.0,
295 GIMP_PARAM_READWRITE |
296 G_PARAM_CONSTRUCT));
297
298 g_object_class_install_property (object_class, PROP_GUIDE_TYPE,
299 g_param_spec_enum ("guide-type", NULL, NULL,
300 GIMP_TYPE_GUIDES_TYPE,
301 GIMP_GUIDES_NONE,
302 GIMP_PARAM_READWRITE |
303 G_PARAM_CONSTRUCT));
304
305 g_object_class_install_property (object_class, PROP_N_GUIDES,
306 g_param_spec_int ("n-guides", NULL, NULL,
307 1, 128, 4,
308 GIMP_PARAM_READWRITE |
309 G_PARAM_CONSTRUCT));
310
311 g_object_class_install_property (object_class, PROP_CLIP_GUIDES,
312 g_param_spec_boolean ("clip-guides", NULL, NULL,
313 FALSE,
314 GIMP_PARAM_READWRITE |
315 G_PARAM_CONSTRUCT));
316
317 g_object_class_install_property (object_class, PROP_SHOW_GUIDES,
318 g_param_spec_boolean ("show-guides", NULL, NULL,
319 TRUE,
320 GIMP_PARAM_READWRITE |
321 G_PARAM_CONSTRUCT));
322
323 g_object_class_install_property (object_class, PROP_INSIDE_FUNCTION,
324 g_param_spec_enum ("inside-function",
325 NULL, NULL,
326 GIMP_TYPE_TRANSFORM_FUNCTION,
327 GIMP_TRANSFORM_FUNCTION_MOVE,
328 GIMP_PARAM_READWRITE |
329 G_PARAM_CONSTRUCT));
330
331 g_object_class_install_property (object_class, PROP_OUTSIDE_FUNCTION,
332 g_param_spec_enum ("outside-function",
333 NULL, NULL,
334 GIMP_TYPE_TRANSFORM_FUNCTION,
335 GIMP_TRANSFORM_FUNCTION_ROTATE,
336 GIMP_PARAM_READWRITE |
337 G_PARAM_CONSTRUCT));
338
339 g_object_class_install_property (object_class, PROP_USE_CORNER_HANDLES,
340 g_param_spec_boolean ("use-corner-handles",
341 NULL, NULL,
342 FALSE,
343 GIMP_PARAM_READWRITE |
344 G_PARAM_CONSTRUCT));
345
346 g_object_class_install_property (object_class, PROP_USE_PERSPECTIVE_HANDLES,
347 g_param_spec_boolean ("use-perspective-handles",
348 NULL, NULL,
349 FALSE,
350 GIMP_PARAM_READWRITE |
351 G_PARAM_CONSTRUCT));
352
353 g_object_class_install_property (object_class, PROP_USE_SIDE_HANDLES,
354 g_param_spec_boolean ("use-side-handles",
355 NULL, NULL,
356 FALSE,
357 GIMP_PARAM_READWRITE |
358 G_PARAM_CONSTRUCT));
359
360 g_object_class_install_property (object_class, PROP_USE_SHEAR_HANDLES,
361 g_param_spec_boolean ("use-shear-handles",
362 NULL, NULL,
363 FALSE,
364 GIMP_PARAM_READWRITE |
365 G_PARAM_CONSTRUCT));
366
367 g_object_class_install_property (object_class, PROP_USE_CENTER_HANDLE,
368 g_param_spec_boolean ("use-center-handle",
369 NULL, NULL,
370 FALSE,
371 GIMP_PARAM_READWRITE |
372 G_PARAM_CONSTRUCT));
373
374 g_object_class_install_property (object_class, PROP_USE_PIVOT_HANDLE,
375 g_param_spec_boolean ("use-pivot-handle",
376 NULL, NULL,
377 FALSE,
378 GIMP_PARAM_READWRITE |
379 G_PARAM_CONSTRUCT));
380
381 g_object_class_install_property (object_class, PROP_DYNAMIC_HANDLE_SIZE,
382 g_param_spec_boolean ("dynamic-handle-size",
383 NULL, NULL,
384 TRUE,
385 GIMP_PARAM_READWRITE |
386 G_PARAM_CONSTRUCT));
387
388 g_object_class_install_property (object_class, PROP_CONSTRAIN_MOVE,
389 g_param_spec_boolean ("constrain-move",
390 NULL, NULL,
391 FALSE,
392 GIMP_PARAM_READWRITE |
393 G_PARAM_CONSTRUCT));
394
395 g_object_class_install_property (object_class, PROP_CONSTRAIN_SCALE,
396 g_param_spec_boolean ("constrain-scale",
397 NULL, NULL,
398 FALSE,
399 GIMP_PARAM_READWRITE |
400 G_PARAM_CONSTRUCT));
401
402 g_object_class_install_property (object_class, PROP_CONSTRAIN_ROTATE,
403 g_param_spec_boolean ("constrain-rotate",
404 NULL, NULL,
405 FALSE,
406 GIMP_PARAM_READWRITE |
407 G_PARAM_CONSTRUCT));
408
409 g_object_class_install_property (object_class, PROP_CONSTRAIN_SHEAR,
410 g_param_spec_boolean ("constrain-shear",
411 NULL, NULL,
412 FALSE,
413 GIMP_PARAM_READWRITE |
414 G_PARAM_CONSTRUCT));
415
416 g_object_class_install_property (object_class, PROP_CONSTRAIN_PERSPECTIVE,
417 g_param_spec_boolean ("constrain-perspective",
418 NULL, NULL,
419 FALSE,
420 GIMP_PARAM_READWRITE |
421 G_PARAM_CONSTRUCT));
422
423 g_object_class_install_property (object_class, PROP_FROMPIVOT_SCALE,
424 g_param_spec_boolean ("frompivot-scale",
425 NULL, NULL,
426 FALSE,
427 GIMP_PARAM_READWRITE |
428 G_PARAM_CONSTRUCT));
429
430 g_object_class_install_property (object_class, PROP_FROMPIVOT_SHEAR,
431 g_param_spec_boolean ("frompivot-shear",
432 NULL, NULL,
433 FALSE,
434 GIMP_PARAM_READWRITE |
435 G_PARAM_CONSTRUCT));
436
437 g_object_class_install_property (object_class, PROP_FROMPIVOT_PERSPECTIVE,
438 g_param_spec_boolean ("frompivot-perspective",
439 NULL, NULL,
440 FALSE,
441 GIMP_PARAM_READWRITE |
442 G_PARAM_CONSTRUCT));
443
444 g_object_class_install_property (object_class, PROP_CORNERSNAP,
445 g_param_spec_boolean ("cornersnap",
446 NULL, NULL,
447 FALSE,
448 GIMP_PARAM_READWRITE |
449 G_PARAM_CONSTRUCT));
450
451 g_object_class_install_property (object_class, PROP_FIXEDPIVOT,
452 g_param_spec_boolean ("fixedpivot",
453 NULL, NULL,
454 FALSE,
455 GIMP_PARAM_READWRITE |
456 G_PARAM_CONSTRUCT));
457 }
458
459 static void
gimp_tool_transform_grid_init(GimpToolTransformGrid * grid)460 gimp_tool_transform_grid_init (GimpToolTransformGrid *grid)
461 {
462 grid->private = gimp_tool_transform_grid_get_instance_private (grid);
463 }
464
465 static void
gimp_tool_transform_grid_constructed(GObject * object)466 gimp_tool_transform_grid_constructed (GObject *object)
467 {
468 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object);
469 GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
470 GimpToolTransformGridPrivate *private = grid->private;
471 GimpCanvasGroup *stroke_group;
472 gint i;
473
474 G_OBJECT_CLASS (parent_class)->constructed (object);
475
476 private->guides = gimp_tool_widget_add_transform_guides (widget,
477 &private->transform,
478 private->x1,
479 private->y1,
480 private->x2,
481 private->y2,
482 private->guide_type,
483 private->n_guides,
484 private->clip_guides);
485
486 for (i = 0; i < 4; i++)
487 {
488 /* draw the scale handles */
489 private->handles[GIMP_TRANSFORM_HANDLE_NW + i] =
490 gimp_tool_widget_add_handle (widget,
491 GIMP_HANDLE_SQUARE,
492 0, 0, 10, 10,
493 GIMP_HANDLE_ANCHOR_CENTER);
494
495 /* draw the perspective handles */
496 private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i] =
497 gimp_tool_widget_add_handle (widget,
498 GIMP_HANDLE_DIAMOND,
499 0, 0, 10, 10,
500 GIMP_HANDLE_ANCHOR_CENTER);
501
502 /* draw the side handles */
503 private->handles[GIMP_TRANSFORM_HANDLE_N + i] =
504 gimp_tool_widget_add_handle (widget,
505 GIMP_HANDLE_SQUARE,
506 0, 0, 10, 10,
507 GIMP_HANDLE_ANCHOR_CENTER);
508
509 /* draw the shear handles */
510 private->handles[GIMP_TRANSFORM_HANDLE_N_S + i] =
511 gimp_tool_widget_add_handle (widget,
512 GIMP_HANDLE_FILLED_DIAMOND,
513 0, 0, 10, 10,
514 GIMP_HANDLE_ANCHOR_CENTER);
515 }
516
517 /* draw the rotation center axis handle */
518 stroke_group = gimp_tool_widget_add_stroke_group (widget);
519
520 private->handles[GIMP_TRANSFORM_HANDLE_PIVOT] =
521 GIMP_CANVAS_ITEM (stroke_group);
522
523 gimp_tool_widget_push_group (widget, stroke_group);
524
525 private->pivot_items[0] =
526 gimp_tool_widget_add_handle (widget,
527 GIMP_HANDLE_CIRCLE,
528 0, 0, 10, 10,
529 GIMP_HANDLE_ANCHOR_CENTER);
530 private->pivot_items[1] =
531 gimp_tool_widget_add_handle (widget,
532 GIMP_HANDLE_CROSS,
533 0, 0, 10, 10,
534 GIMP_HANDLE_ANCHOR_CENTER);
535
536 gimp_tool_widget_pop_group (widget);
537
538 /* draw the center handle */
539 stroke_group = gimp_tool_widget_add_stroke_group (widget);
540
541 private->handles[GIMP_TRANSFORM_HANDLE_CENTER] =
542 GIMP_CANVAS_ITEM (stroke_group);
543
544 gimp_tool_widget_push_group (widget, stroke_group);
545
546 private->center_items[0] =
547 gimp_tool_widget_add_handle (widget,
548 GIMP_HANDLE_SQUARE,
549 0, 0, 10, 10,
550 GIMP_HANDLE_ANCHOR_CENTER);
551 private->center_items[1] =
552 gimp_tool_widget_add_handle (widget,
553 GIMP_HANDLE_CROSS,
554 0, 0, 10, 10,
555 GIMP_HANDLE_ANCHOR_CENTER);
556
557 gimp_tool_widget_pop_group (widget);
558
559 gimp_tool_transform_grid_changed (widget);
560 }
561
562 static void
gimp_tool_transform_grid_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)563 gimp_tool_transform_grid_set_property (GObject *object,
564 guint property_id,
565 const GValue *value,
566 GParamSpec *pspec)
567 {
568 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object);
569 GimpToolTransformGridPrivate *private = grid->private;
570 gboolean box = FALSE;
571
572 switch (property_id)
573 {
574 case PROP_TRANSFORM:
575 {
576 GimpMatrix3 *transform = g_value_get_boxed (value);
577
578 if (transform)
579 private->transform = *transform;
580 else
581 gimp_matrix3_identity (&private->transform);
582 }
583 break;
584
585 case PROP_X1:
586 private->x1 = g_value_get_double (value);
587 box = TRUE;
588 break;
589 case PROP_Y1:
590 private->y1 = g_value_get_double (value);
591 box = TRUE;
592 break;
593 case PROP_X2:
594 private->x2 = g_value_get_double (value);
595 box = TRUE;
596 break;
597 case PROP_Y2:
598 private->y2 = g_value_get_double (value);
599 box = TRUE;
600 break;
601
602 case PROP_PIVOT_X:
603 private->pivot_x = g_value_get_double (value);
604 break;
605 case PROP_PIVOT_Y:
606 private->pivot_y = g_value_get_double (value);
607 break;
608
609 case PROP_GUIDE_TYPE:
610 private->guide_type = g_value_get_enum (value);
611 break;
612 case PROP_N_GUIDES:
613 private->n_guides = g_value_get_int (value);
614 break;
615 case PROP_CLIP_GUIDES:
616 private->clip_guides = g_value_get_boolean (value);
617 break;
618 case PROP_SHOW_GUIDES:
619 private->show_guides = g_value_get_boolean (value);
620 break;
621
622 case PROP_INSIDE_FUNCTION:
623 private->inside_function = g_value_get_enum (value);
624 break;
625 case PROP_OUTSIDE_FUNCTION:
626 private->outside_function = g_value_get_enum (value);
627 break;
628
629 case PROP_USE_CORNER_HANDLES:
630 private->use_corner_handles = g_value_get_boolean (value);
631 break;
632 case PROP_USE_PERSPECTIVE_HANDLES:
633 private->use_perspective_handles = g_value_get_boolean (value);
634 break;
635 case PROP_USE_SIDE_HANDLES:
636 private->use_side_handles = g_value_get_boolean (value);
637 break;
638 case PROP_USE_SHEAR_HANDLES:
639 private->use_shear_handles = g_value_get_boolean (value);
640 break;
641 case PROP_USE_CENTER_HANDLE:
642 private->use_center_handle = g_value_get_boolean (value);
643 break;
644 case PROP_USE_PIVOT_HANDLE:
645 private->use_pivot_handle = g_value_get_boolean (value);
646 break;
647
648 case PROP_DYNAMIC_HANDLE_SIZE:
649 private->dynamic_handle_size = g_value_get_boolean (value);
650 break;
651
652 case PROP_CONSTRAIN_MOVE:
653 private->constrain_move = g_value_get_boolean (value);
654 break;
655 case PROP_CONSTRAIN_SCALE:
656 private->constrain_scale = g_value_get_boolean (value);
657 break;
658 case PROP_CONSTRAIN_ROTATE:
659 private->constrain_rotate = g_value_get_boolean (value);
660 break;
661 case PROP_CONSTRAIN_SHEAR:
662 private->constrain_shear = g_value_get_boolean (value);
663 break;
664 case PROP_CONSTRAIN_PERSPECTIVE:
665 private->constrain_perspective = g_value_get_boolean (value);
666 break;
667
668 case PROP_FROMPIVOT_SCALE:
669 private->frompivot_scale = g_value_get_boolean (value);
670 break;
671 case PROP_FROMPIVOT_SHEAR:
672 private->frompivot_shear = g_value_get_boolean (value);
673 break;
674 case PROP_FROMPIVOT_PERSPECTIVE:
675 private->frompivot_perspective = g_value_get_boolean (value);
676 break;
677
678 case PROP_CORNERSNAP:
679 private->cornersnap = g_value_get_boolean (value);
680 break;
681 case PROP_FIXEDPIVOT:
682 private->fixedpivot = g_value_get_boolean (value);
683 break;
684
685 default:
686 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
687 break;
688 }
689
690 if (box)
691 {
692 private->cx = (private->x1 + private->x2) / 2.0;
693 private->cy = (private->y1 + private->y2) / 2.0;
694 }
695 }
696
697 static void
gimp_tool_transform_grid_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)698 gimp_tool_transform_grid_get_property (GObject *object,
699 guint property_id,
700 GValue *value,
701 GParamSpec *pspec)
702 {
703 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object);
704 GimpToolTransformGridPrivate *private = grid->private;
705
706 switch (property_id)
707 {
708 case PROP_TRANSFORM:
709 g_value_set_boxed (value, &private->transform);
710 break;
711
712 case PROP_X1:
713 g_value_set_double (value, private->x1);
714 break;
715 case PROP_Y1:
716 g_value_set_double (value, private->y1);
717 break;
718 case PROP_X2:
719 g_value_set_double (value, private->x2);
720 break;
721 case PROP_Y2:
722 g_value_set_double (value, private->y2);
723 break;
724
725 case PROP_PIVOT_X:
726 g_value_set_double (value, private->pivot_x);
727 break;
728 case PROP_PIVOT_Y:
729 g_value_set_double (value, private->pivot_y);
730 break;
731
732 case PROP_GUIDE_TYPE:
733 g_value_set_enum (value, private->guide_type);
734 break;
735 case PROP_N_GUIDES:
736 g_value_set_int (value, private->n_guides);
737 break;
738 case PROP_CLIP_GUIDES:
739 g_value_set_boolean (value, private->clip_guides);
740 break;
741 case PROP_SHOW_GUIDES:
742 g_value_set_boolean (value, private->show_guides);
743 break;
744
745 case PROP_INSIDE_FUNCTION:
746 g_value_set_enum (value, private->inside_function);
747 break;
748 case PROP_OUTSIDE_FUNCTION:
749 g_value_set_enum (value, private->outside_function);
750 break;
751
752 case PROP_USE_CORNER_HANDLES:
753 g_value_set_boolean (value, private->use_corner_handles);
754 break;
755 case PROP_USE_PERSPECTIVE_HANDLES:
756 g_value_set_boolean (value, private->use_perspective_handles);
757 break;
758 case PROP_USE_SIDE_HANDLES:
759 g_value_set_boolean (value, private->use_side_handles);
760 break;
761 case PROP_USE_SHEAR_HANDLES:
762 g_value_set_boolean (value, private->use_shear_handles);
763 break;
764 case PROP_USE_CENTER_HANDLE:
765 g_value_set_boolean (value, private->use_center_handle);
766 break;
767 case PROP_USE_PIVOT_HANDLE:
768 g_value_set_boolean (value, private->use_pivot_handle);
769 break;
770
771 case PROP_DYNAMIC_HANDLE_SIZE:
772 g_value_set_boolean (value, private->dynamic_handle_size);
773 break;
774
775 case PROP_CONSTRAIN_MOVE:
776 g_value_set_boolean (value, private->constrain_move);
777 break;
778 case PROP_CONSTRAIN_SCALE:
779 g_value_set_boolean (value, private->constrain_scale);
780 break;
781 case PROP_CONSTRAIN_ROTATE:
782 g_value_set_boolean (value, private->constrain_rotate);
783 break;
784 case PROP_CONSTRAIN_SHEAR:
785 g_value_set_boolean (value, private->constrain_shear);
786 break;
787 case PROP_CONSTRAIN_PERSPECTIVE:
788 g_value_set_boolean (value, private->constrain_perspective);
789 break;
790
791 case PROP_FROMPIVOT_SCALE:
792 g_value_set_boolean (value, private->frompivot_scale);
793 break;
794 case PROP_FROMPIVOT_SHEAR:
795 g_value_set_boolean (value, private->frompivot_shear);
796 break;
797 case PROP_FROMPIVOT_PERSPECTIVE:
798 g_value_set_boolean (value, private->frompivot_perspective);
799 break;
800
801 case PROP_CORNERSNAP:
802 g_value_set_boolean (value, private->cornersnap);
803 break;
804 case PROP_FIXEDPIVOT:
805 g_value_set_boolean (value, private->fixedpivot);
806 break;
807
808 default:
809 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
810 break;
811 }
812 }
813
814 static gboolean
transform_is_convex(GimpVector2 * pos)815 transform_is_convex (GimpVector2 *pos)
816 {
817 return gimp_transform_polygon_is_convex (pos[0].x, pos[0].y,
818 pos[1].x, pos[1].y,
819 pos[2].x, pos[2].y,
820 pos[3].x, pos[3].y);
821 }
822
823 static gboolean
transform_grid_is_convex(GimpToolTransformGrid * grid)824 transform_grid_is_convex (GimpToolTransformGrid *grid)
825 {
826 GimpToolTransformGridPrivate *private = grid->private;
827
828 return gimp_transform_polygon_is_convex (private->tx1, private->ty1,
829 private->tx2, private->ty2,
830 private->tx3, private->ty3,
831 private->tx4, private->ty4);
832 }
833
834 static inline gboolean
vectorisnull(GimpVector2 v)835 vectorisnull (GimpVector2 v)
836 {
837 return ((v.x == 0.0) && (v.y == 0.0));
838 }
839
840 static inline gdouble
dotprod(GimpVector2 a,GimpVector2 b)841 dotprod (GimpVector2 a,
842 GimpVector2 b)
843 {
844 return a.x * b.x + a.y * b.y;
845 }
846
847 static inline gdouble
norm(GimpVector2 a)848 norm (GimpVector2 a)
849 {
850 return sqrt (dotprod (a, a));
851 }
852
853 static inline GimpVector2
vectorsubtract(GimpVector2 a,GimpVector2 b)854 vectorsubtract (GimpVector2 a,
855 GimpVector2 b)
856 {
857 GimpVector2 c;
858
859 c.x = a.x - b.x;
860 c.y = a.y - b.y;
861
862 return c;
863 }
864
865 static inline GimpVector2
vectoradd(GimpVector2 a,GimpVector2 b)866 vectoradd (GimpVector2 a,
867 GimpVector2 b)
868 {
869 GimpVector2 c;
870
871 c.x = a.x + b.x;
872 c.y = a.y + b.y;
873
874 return c;
875 }
876
877 static inline GimpVector2
scalemult(GimpVector2 a,gdouble b)878 scalemult (GimpVector2 a,
879 gdouble b)
880 {
881 GimpVector2 c;
882
883 c.x = a.x * b;
884 c.y = a.y * b;
885
886 return c;
887 }
888
889 static inline GimpVector2
vectorproject(GimpVector2 a,GimpVector2 b)890 vectorproject (GimpVector2 a,
891 GimpVector2 b)
892 {
893 return scalemult (b, dotprod (a, b) / dotprod (b, b));
894 }
895
896 /* finds the clockwise angle between the vectors given, 0-2π */
897 static inline gdouble
calcangle(GimpVector2 a,GimpVector2 b)898 calcangle (GimpVector2 a,
899 GimpVector2 b)
900 {
901 gdouble angle, angle2;
902 gdouble length;
903
904 if (vectorisnull (a) || vectorisnull (b))
905 return 0.0;
906
907 length = norm (a) * norm (b);
908
909 angle = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0));
910 angle2 = b.y;
911 b.y = -b.x;
912 b.x = angle2;
913 angle2 = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0));
914
915 return ((angle2 > G_PI / 2.0) ? angle : 2.0 * G_PI - angle);
916 }
917
918 static inline GimpVector2
rotate2d(GimpVector2 p,gdouble angle)919 rotate2d (GimpVector2 p,
920 gdouble angle)
921 {
922 GimpVector2 ret;
923
924 ret.x = cos (angle) * p.x-sin (angle) * p.y;
925 ret.y = sin (angle) * p.x+cos (angle) * p.y;
926
927 return ret;
928 }
929
930 static inline GimpVector2
lineintersect(GimpVector2 p1,GimpVector2 p2,GimpVector2 q1,GimpVector2 q2)931 lineintersect (GimpVector2 p1, GimpVector2 p2,
932 GimpVector2 q1, GimpVector2 q2)
933 {
934 gdouble denom, u;
935 GimpVector2 p;
936
937 denom = (q2.y - q1.y) * (p2.x - p1.x) - (q2.x - q1.x) * (p2.y - p1.y);
938 if (denom == 0.0)
939 {
940 p.x = (p1.x + p2.x + q1.x + q2.x) / 4;
941 p.y = (p1.y + p2.y + q1.y + q2.y) / 4;
942 }
943 else
944 {
945 u = (q2.x - q1.x) * (p1.y - q1.y) - (q2.y - q1.y) * (p1.x - q1.x);
946 u /= denom;
947
948 p.x = p1.x + u * (p2.x - p1.x);
949 p.y = p1.y + u * (p2.y - p1.y);
950 }
951
952 return p;
953 }
954
955 static inline GimpVector2
get_pivot_delta(GimpToolTransformGrid * grid,GimpVector2 * oldpos,GimpVector2 * newpos,GimpVector2 pivot)956 get_pivot_delta (GimpToolTransformGrid *grid,
957 GimpVector2 *oldpos,
958 GimpVector2 *newpos,
959 GimpVector2 pivot)
960 {
961 GimpToolTransformGridPrivate *private = grid->private;
962 GimpMatrix3 transform_before;
963 GimpMatrix3 transform_after;
964 GimpVector2 delta;
965
966 gimp_matrix3_identity (&transform_before);
967 gimp_matrix3_identity (&transform_after);
968
969 gimp_transform_matrix_perspective (&transform_before,
970 private->x1,
971 private->y1,
972 private->x2 - private->x1,
973 private->y2 - private->y1,
974 oldpos[0].x, oldpos[0].y,
975 oldpos[1].x, oldpos[1].y,
976 oldpos[2].x, oldpos[2].y,
977 oldpos[3].x, oldpos[3].y);
978 gimp_transform_matrix_perspective (&transform_after,
979 private->x1,
980 private->y1,
981 private->x2 - private->x1,
982 private->y2 - private->y1,
983 newpos[0].x, newpos[0].y,
984 newpos[1].x, newpos[1].y,
985 newpos[2].x, newpos[2].y,
986 newpos[3].x, newpos[3].y);
987 gimp_matrix3_invert (&transform_before);
988 gimp_matrix3_mult (&transform_after, &transform_before);
989 gimp_matrix3_transform_point (&transform_before,
990 pivot.x, pivot.y, &delta.x, &delta.y);
991
992 delta = vectorsubtract (delta, pivot);
993
994 return delta;
995 }
996
997 static gboolean
point_is_inside_polygon(gint n,gdouble * x,gdouble * y,gdouble px,gdouble py)998 point_is_inside_polygon (gint n,
999 gdouble *x,
1000 gdouble *y,
1001 gdouble px,
1002 gdouble py)
1003 {
1004 gint i, j;
1005 gboolean odd = FALSE;
1006
1007 for (i = 0, j = n - 1; i < n; j = i++)
1008 {
1009 if ((y[i] < py && y[j] >= py) ||
1010 (y[j] < py && y[i] >= py))
1011 {
1012 if (x[i] + (py - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < px)
1013 odd = !odd;
1014 }
1015 }
1016
1017 return odd;
1018 }
1019
1020 static gboolean
point_is_inside_polygon_pos(GimpVector2 * pos,GimpVector2 point)1021 point_is_inside_polygon_pos (GimpVector2 *pos,
1022 GimpVector2 point)
1023 {
1024 return point_is_inside_polygon (4,
1025 (gdouble[4]){ pos[0].x, pos[1].x,
1026 pos[3].x, pos[2].x },
1027 (gdouble[4]){ pos[0].y, pos[1].y,
1028 pos[3].y, pos[2].y },
1029 point.x, point.y);
1030 }
1031
1032 static void
get_handle_geometry(GimpToolTransformGrid * grid,GimpVector2 * position,gdouble * angle)1033 get_handle_geometry (GimpToolTransformGrid *grid,
1034 GimpVector2 *position,
1035 gdouble *angle)
1036 {
1037 GimpToolTransformGridPrivate *private = grid->private;
1038
1039 GimpVector2 o[] = { { .x = private->tx1, .y = private->ty1 },
1040 { .x = private->tx2, .y = private->ty2 },
1041 { .x = private->tx3, .y = private->ty3 },
1042 { .x = private->tx4, .y = private->ty4 } };
1043 GimpVector2 right = { .x = 1.0, .y = 0.0 };
1044 GimpVector2 up = { .x = 0.0, .y = 1.0 };
1045
1046 if (position)
1047 {
1048 position[0] = o[0];
1049 position[1] = o[1];
1050 position[2] = o[2];
1051 position[3] = o[3];
1052 }
1053
1054 angle[0] = calcangle (vectorsubtract (o[1], o[0]), right);
1055 angle[1] = calcangle (vectorsubtract (o[3], o[2]), right);
1056 angle[2] = calcangle (vectorsubtract (o[3], o[1]), up);
1057 angle[3] = calcangle (vectorsubtract (o[2], o[0]), up);
1058
1059 angle[4] = (angle[0] + angle[3]) / 2.0;
1060 angle[5] = (angle[0] + angle[2]) / 2.0;
1061 angle[6] = (angle[1] + angle[3]) / 2.0;
1062 angle[7] = (angle[1] + angle[2]) / 2.0;
1063
1064 angle[8] = (angle[0] + angle[1] + angle[2] + angle[3]) / 4.0;
1065 }
1066
1067 static void
gimp_tool_transform_grid_changed(GimpToolWidget * widget)1068 gimp_tool_transform_grid_changed (GimpToolWidget *widget)
1069 {
1070 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
1071 GimpToolTransformGridPrivate *private = grid->private;
1072 gdouble angle[9];
1073 GimpVector2 o[4], t[4];
1074 gint handle_w;
1075 gint handle_h;
1076 gint d, i;
1077
1078 gimp_tool_transform_grid_update_box (grid);
1079
1080 gimp_canvas_transform_guides_set (private->guides,
1081 &private->transform,
1082 private->x1,
1083 private->y1,
1084 private->x2,
1085 private->y2,
1086 private->guide_type,
1087 private->n_guides,
1088 private->clip_guides);
1089 gimp_canvas_item_set_visible (private->guides, private->show_guides);
1090
1091 get_handle_geometry (grid, o, angle);
1092 gimp_tool_transform_grid_calc_handles (grid, &handle_w, &handle_h);
1093
1094 for (i = 0; i < 4; i++)
1095 {
1096 GimpCanvasItem *h;
1097 gdouble factor;
1098
1099 /* the scale handles */
1100 factor = 1.0;
1101 if (private->use_perspective_handles)
1102 factor = 1.5;
1103
1104 h = private->handles[GIMP_TRANSFORM_HANDLE_NW + i];
1105 gimp_canvas_item_set_visible (h, private->use_corner_handles);
1106
1107 if (private->use_corner_handles)
1108 {
1109 gimp_canvas_handle_set_position (h, o[i].x, o[i].y);
1110 gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor);
1111 gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0);
1112 }
1113
1114 /* the perspective handles */
1115 factor = 1.0;
1116 if (private->use_corner_handles)
1117 factor = 0.8;
1118
1119 h = private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i];
1120 gimp_canvas_item_set_visible (h, private->use_perspective_handles);
1121
1122 if (private->use_perspective_handles)
1123 {
1124 gimp_canvas_handle_set_position (h, o[i].x, o[i].y);
1125 gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor);
1126 gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0);
1127 }
1128 }
1129
1130 /* draw the side handles */
1131 t[0] = scalemult (vectoradd (o[0], o[1]), 0.5);
1132 t[1] = scalemult (vectoradd (o[2], o[3]), 0.5);
1133 t[2] = scalemult (vectoradd (o[1], o[3]), 0.5);
1134 t[3] = scalemult (vectoradd (o[2], o[0]), 0.5);
1135
1136 for (i = 0; i < 4; i++)
1137 {
1138 GimpCanvasItem *h;
1139
1140 h = private->handles[GIMP_TRANSFORM_HANDLE_N + i];
1141 gimp_canvas_item_set_visible (h, private->use_side_handles);
1142
1143 if (private->use_side_handles)
1144 {
1145 gimp_canvas_handle_set_position (h, t[i].x, t[i].y);
1146 gimp_canvas_handle_set_size (h, handle_w, handle_h);
1147 gimp_canvas_handle_set_angles (h, angle[i], 0.0);
1148 }
1149 }
1150
1151 /* draw the shear handles */
1152 t[0] = scalemult (vectoradd ( o[0] , scalemult (o[1], 3.0)),
1153 0.25);
1154 t[1] = scalemult (vectoradd (scalemult (o[2], 3.0), o[3] ),
1155 0.25);
1156 t[2] = scalemult (vectoradd ( o[1] , scalemult (o[3], 3.0)),
1157 0.25);
1158 t[3] = scalemult (vectoradd (scalemult (o[0], 3.0), o[2] ),
1159 0.25);
1160
1161 for (i = 0; i < 4; i++)
1162 {
1163 GimpCanvasItem *h;
1164
1165 h = private->handles[GIMP_TRANSFORM_HANDLE_N_S + i];
1166 gimp_canvas_item_set_visible (h, private->use_shear_handles);
1167
1168 if (private->use_shear_handles)
1169 {
1170 gimp_canvas_handle_set_position (h, t[i].x, t[i].y);
1171 gimp_canvas_handle_set_size (h, handle_w, handle_h);
1172 gimp_canvas_handle_set_angles (h, angle[i], 0.0);
1173 }
1174 }
1175
1176 d = MIN (handle_w, handle_h);
1177 if (private->use_center_handle)
1178 d *= 2; /* so you can grab it from under the center handle */
1179
1180 gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_PIVOT],
1181 private->use_pivot_handle);
1182
1183 if (private->use_pivot_handle)
1184 {
1185 gimp_canvas_handle_set_position (private->pivot_items[0],
1186 private->tpx, private->tpy);
1187 gimp_canvas_handle_set_size (private->pivot_items[0], d, d);
1188
1189 gimp_canvas_handle_set_position (private->pivot_items[1],
1190 private->tpx, private->tpy);
1191 gimp_canvas_handle_set_size (private->pivot_items[1], d, d);
1192 }
1193
1194 d = MIN (handle_w, handle_h);
1195
1196 gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_CENTER],
1197 private->use_center_handle);
1198
1199 if (private->use_center_handle)
1200 {
1201 gimp_canvas_handle_set_position (private->center_items[0],
1202 private->tcx, private->tcy);
1203 gimp_canvas_handle_set_size (private->center_items[0], d, d);
1204 gimp_canvas_handle_set_angles (private->center_items[0], angle[8], 0.0);
1205
1206 gimp_canvas_handle_set_position (private->center_items[1],
1207 private->tcx, private->tcy);
1208 gimp_canvas_handle_set_size (private->center_items[1], d, d);
1209 gimp_canvas_handle_set_angles (private->center_items[1], angle[8], 0.0);
1210 }
1211
1212 gimp_tool_transform_grid_update_hilight (grid);
1213 }
1214
1215 gint
gimp_tool_transform_grid_button_press(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type)1216 gimp_tool_transform_grid_button_press (GimpToolWidget *widget,
1217 const GimpCoords *coords,
1218 guint32 time,
1219 GdkModifierType state,
1220 GimpButtonPressType press_type)
1221 {
1222 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
1223 GimpToolTransformGridPrivate *private = grid->private;
1224
1225 private->button_down = TRUE;
1226 private->mousex = coords->x;
1227 private->mousey = coords->y;
1228
1229 if (private->handle != GIMP_TRANSFORM_HANDLE_NONE)
1230 {
1231 if (private->handles[private->handle])
1232 {
1233 GimpCanvasItem *handle;
1234 gdouble x, y;
1235
1236 switch (private->handle)
1237 {
1238 case GIMP_TRANSFORM_HANDLE_CENTER:
1239 handle = private->center_items[0];
1240 break;
1241
1242 case GIMP_TRANSFORM_HANDLE_PIVOT:
1243 handle = private->pivot_items[0];
1244 break;
1245
1246 default:
1247 handle = private->handles[private->handle];
1248 break;
1249 }
1250
1251 gimp_canvas_handle_get_position (handle, &x, &y);
1252
1253 gimp_tool_widget_set_snap_offsets (widget,
1254 SIGNED_ROUND (x - coords->x),
1255 SIGNED_ROUND (y - coords->y),
1256 0, 0);
1257 }
1258 else
1259 {
1260 gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
1261 }
1262
1263 private->prev_tx1 = private->tx1;
1264 private->prev_ty1 = private->ty1;
1265 private->prev_tx2 = private->tx2;
1266 private->prev_ty2 = private->ty2;
1267 private->prev_tx3 = private->tx3;
1268 private->prev_ty3 = private->ty3;
1269 private->prev_tx4 = private->tx4;
1270 private->prev_ty4 = private->ty4;
1271 private->prev_tpx = private->tpx;
1272 private->prev_tpy = private->tpy;
1273 private->prev_tcx = private->tcx;
1274 private->prev_tcy = private->tcy;
1275
1276 return private->handle;
1277 }
1278
1279 gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
1280
1281 return 0;
1282 }
1283
1284 void
gimp_tool_transform_grid_button_release(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type)1285 gimp_tool_transform_grid_button_release (GimpToolWidget *widget,
1286 const GimpCoords *coords,
1287 guint32 time,
1288 GdkModifierType state,
1289 GimpButtonReleaseType release_type)
1290 {
1291 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
1292 GimpToolTransformGridPrivate *private = grid->private;
1293
1294 private->button_down = FALSE;
1295 }
1296
1297 void
gimp_tool_transform_grid_motion(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state)1298 gimp_tool_transform_grid_motion (GimpToolWidget *widget,
1299 const GimpCoords *coords,
1300 guint32 time,
1301 GdkModifierType state)
1302 {
1303 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
1304 GimpToolTransformGridPrivate *private = grid->private;
1305 gdouble *x[4], *y[4];
1306 gdouble *newpivot_x, *newpivot_y;
1307
1308 GimpVector2 oldpos[5], newpos[4];
1309 GimpVector2 cur = { .x = coords->x,
1310 .y = coords->y };
1311 GimpVector2 mouse = { .x = private->mousex,
1312 .y = private->mousey };
1313 GimpVector2 d;
1314 GimpVector2 pivot;
1315
1316 gboolean fixedpivot = private->fixedpivot;
1317 GimpTransformHandle handle = private->handle;
1318 gint i;
1319
1320 private->curx = coords->x;
1321 private->cury = coords->y;
1322
1323 x[0] = &private->tx1;
1324 y[0] = &private->ty1;
1325 x[1] = &private->tx2;
1326 y[1] = &private->ty2;
1327 x[2] = &private->tx3;
1328 y[2] = &private->ty3;
1329 x[3] = &private->tx4;
1330 y[3] = &private->ty4;
1331
1332 newpos[0].x = oldpos[0].x = private->prev_tx1;
1333 newpos[0].y = oldpos[0].y = private->prev_ty1;
1334 newpos[1].x = oldpos[1].x = private->prev_tx2;
1335 newpos[1].y = oldpos[1].y = private->prev_ty2;
1336 newpos[2].x = oldpos[2].x = private->prev_tx3;
1337 newpos[2].y = oldpos[2].y = private->prev_ty3;
1338 newpos[3].x = oldpos[3].x = private->prev_tx4;
1339 newpos[3].y = oldpos[3].y = private->prev_ty4;
1340
1341 /* put center point in this array too */
1342 oldpos[4].x = private->prev_tcx;
1343 oldpos[4].y = private->prev_tcy;
1344
1345 d = vectorsubtract (cur, mouse);
1346
1347 newpivot_x = &private->tpx;
1348 newpivot_y = &private->tpy;
1349
1350 if (private->use_pivot_handle)
1351 {
1352 pivot.x = private->prev_tpx;
1353 pivot.y = private->prev_tpy;
1354 }
1355 else
1356 {
1357 /* when the transform grid doesn't use a pivot handle, use the center
1358 * point as the pivot instead.
1359 */
1360 pivot.x = private->prev_tcx;
1361 pivot.y = private->prev_tcy;
1362
1363 fixedpivot = TRUE;
1364 }
1365
1366 /* move */
1367 if (handle == GIMP_TRANSFORM_HANDLE_CENTER)
1368 {
1369 if (private->constrain_move)
1370 {
1371 /* snap to 45 degree vectors from starting point */
1372 gdouble angle = 16.0 * calcangle ((GimpVector2) { 1.0, 0.0 },
1373 d) / (2.0 * G_PI);
1374 gdouble dist = norm (d) / sqrt (2);
1375
1376 if (angle < 1.0 || angle >= 15.0)
1377 d.y = 0;
1378 else if (angle < 3.0)
1379 d.y = -(d.x = dist);
1380 else if (angle < 5.0)
1381 d.x = 0;
1382 else if (angle < 7.0)
1383 d.x = d.y = -dist;
1384 else if (angle < 9.0)
1385 d.y = 0;
1386 else if (angle < 11.0)
1387 d.x = -(d.y = dist);
1388 else if (angle < 13.0)
1389 d.x = 0;
1390 else if (angle < 15.0)
1391 d.x = d.y = dist;
1392 }
1393
1394 for (i = 0; i < 4; i++)
1395 newpos[i] = vectoradd (oldpos[i], d);
1396 }
1397
1398 /* rotate */
1399 if (handle == GIMP_TRANSFORM_HANDLE_ROTATION)
1400 {
1401 gdouble angle = calcangle (vectorsubtract (cur, pivot),
1402 vectorsubtract (mouse, pivot));
1403
1404 if (private->constrain_rotate)
1405 {
1406 /* round to 15 degree multiple */
1407 angle /= 2 * G_PI / 24.0;
1408 angle = round (angle);
1409 angle *= 2 * G_PI / 24.0;
1410 }
1411
1412 for (i = 0; i < 4; i++)
1413 newpos[i] = vectoradd (pivot,
1414 rotate2d (vectorsubtract (oldpos[i], pivot),
1415 angle));
1416
1417 fixedpivot = TRUE;
1418 }
1419
1420 /* move rotation axis */
1421 if (handle == GIMP_TRANSFORM_HANDLE_PIVOT)
1422 {
1423 pivot = vectoradd (pivot, d);
1424
1425 if (private->cornersnap)
1426 {
1427 /* snap to corner points and center */
1428 gint closest = 0;
1429 gdouble closest_dist = G_MAXDOUBLE, dist;
1430
1431 for (i = 0; i < 5; i++)
1432 {
1433 dist = norm (vectorsubtract (pivot, oldpos[i]));
1434 if (dist < closest_dist)
1435 {
1436 closest_dist = dist;
1437 closest = i;
1438 }
1439 }
1440
1441 if (closest_dist *
1442 gimp_tool_widget_get_shell (widget)->scale_x < 50)
1443 {
1444 pivot = oldpos[closest];
1445 }
1446 }
1447
1448 fixedpivot = TRUE;
1449 }
1450
1451 /* scaling via corner */
1452 if (handle == GIMP_TRANSFORM_HANDLE_NW ||
1453 handle == GIMP_TRANSFORM_HANDLE_NE ||
1454 handle == GIMP_TRANSFORM_HANDLE_SE ||
1455 handle == GIMP_TRANSFORM_HANDLE_SW)
1456 {
1457 /* Scaling through scale handles means translating one corner point,
1458 * with all sides at constant angles.
1459 */
1460
1461 gint this, left, right, opposite;
1462
1463 /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
1464 if (handle == GIMP_TRANSFORM_HANDLE_NW)
1465 {
1466 this = 0; left = 1; right = 2; opposite = 3;
1467 }
1468 else if (handle == GIMP_TRANSFORM_HANDLE_NE)
1469 {
1470 this = 1; left = 3; right = 0; opposite = 2;
1471 }
1472 else if (handle == GIMP_TRANSFORM_HANDLE_SW)
1473 {
1474 this = 2; left = 0; right = 3; opposite = 1;
1475 }
1476 else if (handle == GIMP_TRANSFORM_HANDLE_SE)
1477 {
1478 this = 3; left = 2; right = 1; opposite = 0;
1479 }
1480 else
1481 gimp_assert_not_reached ();
1482
1483 /* when the keep aspect transformation constraint is enabled,
1484 * the translation shall only be along the diagonal that runs
1485 * trough this corner point.
1486 */
1487 if (private->constrain_scale)
1488 {
1489 /* restrict to movement along the diagonal */
1490 GimpVector2 diag = vectorsubtract (oldpos[this], oldpos[opposite]);
1491
1492 d = vectorproject (d, diag);
1493 }
1494
1495 /* Move the corner being interacted with */
1496 /* rp---------tp
1497 * / /\ <- d, the interaction vector
1498 * / / tp
1499 * op----------/
1500 *
1501 */
1502 newpos[this] = vectoradd (oldpos[this], d);
1503
1504 /* Where the corner to the right and left would go, need these to form
1505 * lines to intersect with the sides */
1506 /* rp----------/
1507 * /\ /\
1508 * / nr / nt
1509 * op----------lp
1510 * \
1511 * nl
1512 */
1513
1514 newpos[right] = vectoradd (oldpos[right], d);
1515 newpos[left] = vectoradd (oldpos[left], d);
1516
1517 /* Now we just need to find the intersection of op-rp and nr-nt.
1518 * rp----------/
1519 * / /
1520 * / nr==========nt
1521 * op----------/
1522 *
1523 */
1524 newpos[right] = lineintersect (newpos[right], newpos[this],
1525 oldpos[opposite], oldpos[right]);
1526 newpos[left] = lineintersect (newpos[left], newpos[this],
1527 oldpos[opposite], oldpos[left]);
1528 /* /-----------/
1529 * / /
1530 * rp============nt
1531 * op----------/
1532 *
1533 */
1534
1535 /*
1536 *
1537 * /--------------/
1538 * /--------------/
1539 *
1540 */
1541
1542 if (private->frompivot_scale &&
1543 transform_is_convex (newpos) &&
1544 transform_is_convex (oldpos))
1545 {
1546 /* transform the pivot point before the interaction and
1547 * after, and move everything by this difference
1548 */
1549 //TODO the handle doesn't actually end up where the mouse cursor is
1550 GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
1551 for (i = 0; i < 4; i++)
1552 newpos[i] = vectorsubtract (newpos[i], delta);
1553
1554 fixedpivot = TRUE;
1555 }
1556 }
1557
1558 /* scaling via sides */
1559 if (handle == GIMP_TRANSFORM_HANDLE_N ||
1560 handle == GIMP_TRANSFORM_HANDLE_E ||
1561 handle == GIMP_TRANSFORM_HANDLE_S ||
1562 handle == GIMP_TRANSFORM_HANDLE_W)
1563 {
1564 gint this_l, this_r, opp_l, opp_r;
1565 GimpVector2 side_l, side_r, midline;
1566
1567 /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
1568 if (handle == GIMP_TRANSFORM_HANDLE_N)
1569 {
1570 this_l = 1; this_r = 0;
1571 }
1572 else if (handle == GIMP_TRANSFORM_HANDLE_E)
1573 {
1574 this_l = 3; this_r = 1;
1575 }
1576 else if (handle == GIMP_TRANSFORM_HANDLE_S)
1577 {
1578 this_l = 2; this_r = 3;
1579 }
1580 else if (handle == GIMP_TRANSFORM_HANDLE_W)
1581 {
1582 this_l = 0; this_r = 2;
1583 }
1584 else
1585 gimp_assert_not_reached ();
1586
1587 opp_l = 3 - this_r; opp_r = 3 - this_l;
1588
1589 side_l = vectorsubtract (oldpos[opp_l], oldpos[this_l]);
1590 side_r = vectorsubtract (oldpos[opp_r], oldpos[this_r]);
1591 midline = vectoradd (side_l, side_r);
1592
1593 /* restrict to movement along the midline */
1594 d = vectorproject (d, midline);
1595
1596 if (private->constrain_scale)
1597 {
1598 GimpVector2 before, after, effective_pivot = pivot;
1599 gdouble distance;
1600
1601 if (! private->frompivot_scale)
1602 {
1603 /* center of the opposite side is pivot */
1604 effective_pivot = scalemult (vectoradd (oldpos[opp_l],
1605 oldpos[opp_r]), 0.5);
1606 }
1607
1608 /* get the difference between the distance from the pivot to
1609 * where interaction started and the distance from the pivot
1610 * to where cursor is now, and scale all corners distance
1611 * from the pivot with this factor
1612 */
1613 before = vectorsubtract (effective_pivot, mouse);
1614 after = vectorsubtract (effective_pivot, cur);
1615 after = vectorproject (after, before);
1616
1617 distance = 0.5 * (after.x / before.x + after.y / before.y);
1618
1619 for (i = 0; i < 4; i++)
1620 newpos[i] = vectoradd (effective_pivot,
1621 scalemult (vectorsubtract (oldpos[i],
1622 effective_pivot),
1623 distance));
1624 }
1625 else
1626 {
1627 /* just move the side */
1628 newpos[this_l] = vectoradd (oldpos[this_l], d);
1629 newpos[this_r] = vectoradd (oldpos[this_r], d);
1630 }
1631
1632 if (! private->constrain_scale &&
1633 private->frompivot_scale &&
1634 transform_is_convex (newpos) &&
1635 transform_is_convex (oldpos))
1636 {
1637 GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
1638 for (i = 0; i < 4; i++)
1639 newpos[i] = vectorsubtract (newpos[i], delta);
1640
1641 fixedpivot = TRUE;
1642 }
1643 }
1644
1645 /* shear */
1646 if (handle == GIMP_TRANSFORM_HANDLE_N_S ||
1647 handle == GIMP_TRANSFORM_HANDLE_E_S ||
1648 handle == GIMP_TRANSFORM_HANDLE_S_S ||
1649 handle == GIMP_TRANSFORM_HANDLE_W_S)
1650 {
1651 gint this_l, this_r;
1652
1653 /* set up indices for this edge and the opposite edge */
1654 if (handle == GIMP_TRANSFORM_HANDLE_N_S)
1655 {
1656 this_l = 1; this_r = 0;
1657 }
1658 else if (handle == GIMP_TRANSFORM_HANDLE_W_S)
1659 {
1660 this_l = 0; this_r = 2;
1661 }
1662 else if (handle == GIMP_TRANSFORM_HANDLE_S_S)
1663 {
1664 this_l = 2; this_r = 3;
1665 }
1666 else if (handle == GIMP_TRANSFORM_HANDLE_E_S)
1667 {
1668 this_l = 3; this_r = 1;
1669 }
1670 else
1671 gimp_assert_not_reached ();
1672
1673 if (private->constrain_shear)
1674 {
1675 /* restrict to movement along the side */
1676 GimpVector2 side = vectorsubtract (oldpos[this_r], oldpos[this_l]);
1677
1678 d = vectorproject (d, side);
1679 }
1680
1681 newpos[this_l] = vectoradd (oldpos[this_l], d);
1682 newpos[this_r] = vectoradd (oldpos[this_r], d);
1683
1684 if (private->frompivot_shear &&
1685 transform_is_convex (newpos) &&
1686 transform_is_convex (oldpos))
1687 {
1688 GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
1689 for (i = 0; i < 4; i++)
1690 newpos[i] = vectorsubtract (newpos[i], delta);
1691
1692 fixedpivot = TRUE;
1693 }
1694 }
1695
1696 /* perspective transform */
1697 if (handle == GIMP_TRANSFORM_HANDLE_NW_P ||
1698 handle == GIMP_TRANSFORM_HANDLE_NE_P ||
1699 handle == GIMP_TRANSFORM_HANDLE_SE_P ||
1700 handle == GIMP_TRANSFORM_HANDLE_SW_P)
1701 {
1702 gint this, left, right, opposite;
1703
1704 /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
1705 if (handle == GIMP_TRANSFORM_HANDLE_NW_P)
1706 {
1707 this = 0; left = 1; right = 2; opposite = 3;
1708 }
1709 else if (handle == GIMP_TRANSFORM_HANDLE_NE_P)
1710 {
1711 this = 1; left = 3; right = 0; opposite = 2;
1712 }
1713 else if (handle == GIMP_TRANSFORM_HANDLE_SW_P)
1714 {
1715 this = 2; left = 0; right = 3; opposite = 1;
1716 }
1717 else if (handle == GIMP_TRANSFORM_HANDLE_SE_P)
1718 {
1719 this = 3; left = 2; right = 1; opposite = 0;
1720 }
1721 else
1722 gimp_assert_not_reached ();
1723
1724 if (private->constrain_perspective)
1725 {
1726 /* when the constrain transformation constraint is enabled,
1727 * the translation shall only be either along the side
1728 * angles of the two sides that run to this corner point, or
1729 * along the diagonal that runs trough this corner point.
1730 */
1731 GimpVector2 proj[4];
1732 gdouble rej[4];
1733
1734 for (i = 0; i < 4; i++)
1735 {
1736 if (i == this)
1737 continue;
1738
1739 /* get the vectors along the sides and the diagonal */
1740 proj[i] = vectorsubtract (oldpos[this], oldpos[i]);
1741
1742 /* project d on each candidate vector and see which has
1743 * the shortest rejection
1744 */
1745 proj[i] = vectorproject (d, proj[i]);
1746 rej[i] = norm (vectorsubtract (d, proj[i]));
1747 }
1748
1749 if (rej[left] < rej[right] && rej[left] < rej[opposite])
1750 d = proj[left];
1751 else if (rej[right] < rej[opposite])
1752 d = proj[right];
1753 else
1754 d = proj[opposite];
1755 }
1756
1757 newpos[this] = vectoradd (oldpos[this], d);
1758
1759 if (private->frompivot_perspective &&
1760 transform_is_convex (newpos) &&
1761 transform_is_convex (oldpos))
1762 {
1763 GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
1764
1765 for (i = 0; i < 4; i++)
1766 newpos[i] = vectorsubtract (newpos[i], delta);
1767
1768 fixedpivot = TRUE;
1769 }
1770 }
1771
1772 /* this will have been set to TRUE if an operation used the pivot in
1773 * addition to being a user option
1774 */
1775 if (! fixedpivot &&
1776 transform_is_convex (newpos) &&
1777 transform_is_convex (oldpos) &&
1778 point_is_inside_polygon_pos (oldpos, pivot))
1779 {
1780 GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
1781 pivot = vectoradd (pivot, delta);
1782 }
1783
1784 /* make sure the new coordinates are valid */
1785 for (i = 0; i < 4; i++)
1786 {
1787 if (! isfinite (newpos[i].x) || ! isfinite (newpos[i].y))
1788 return;
1789 }
1790
1791 if (! isfinite (pivot.x) || ! isfinite (pivot.y))
1792 return;
1793
1794 for (i = 0; i < 4; i++)
1795 {
1796 *x[i] = newpos[i].x;
1797 *y[i] = newpos[i].y;
1798 }
1799
1800 /* set unconditionally: if options get toggled during operation, we
1801 * have to move pivot back
1802 */
1803 *newpivot_x = pivot.x;
1804 *newpivot_y = pivot.y;
1805
1806 gimp_tool_transform_grid_update_matrix (grid);
1807 }
1808
1809 static const gchar *
get_friendly_operation_name(GimpTransformHandle handle)1810 get_friendly_operation_name (GimpTransformHandle handle)
1811 {
1812 switch (handle)
1813 {
1814 case GIMP_TRANSFORM_HANDLE_NONE:
1815 return "";
1816 case GIMP_TRANSFORM_HANDLE_NW_P:
1817 case GIMP_TRANSFORM_HANDLE_NE_P:
1818 case GIMP_TRANSFORM_HANDLE_SW_P:
1819 case GIMP_TRANSFORM_HANDLE_SE_P:
1820 return _("Click-Drag to change perspective");
1821 case GIMP_TRANSFORM_HANDLE_NW:
1822 case GIMP_TRANSFORM_HANDLE_NE:
1823 case GIMP_TRANSFORM_HANDLE_SW:
1824 case GIMP_TRANSFORM_HANDLE_SE:
1825 return _("Click-Drag to scale");
1826 case GIMP_TRANSFORM_HANDLE_N:
1827 case GIMP_TRANSFORM_HANDLE_S:
1828 case GIMP_TRANSFORM_HANDLE_E:
1829 case GIMP_TRANSFORM_HANDLE_W:
1830 return _("Click-Drag to scale");
1831 case GIMP_TRANSFORM_HANDLE_CENTER:
1832 return _("Click-Drag to move");
1833 case GIMP_TRANSFORM_HANDLE_PIVOT:
1834 return _("Click-Drag to move the pivot point");
1835 case GIMP_TRANSFORM_HANDLE_N_S:
1836 case GIMP_TRANSFORM_HANDLE_S_S:
1837 case GIMP_TRANSFORM_HANDLE_E_S:
1838 case GIMP_TRANSFORM_HANDLE_W_S:
1839 return _("Click-Drag to shear");
1840 case GIMP_TRANSFORM_HANDLE_ROTATION:
1841 return _("Click-Drag to rotate");
1842 default:
1843 gimp_assert_not_reached ();
1844 }
1845 }
1846
1847 static GimpTransformHandle
gimp_tool_transform_get_area_handle(GimpToolTransformGrid * grid,const GimpCoords * coords,GimpTransformFunction function)1848 gimp_tool_transform_get_area_handle (GimpToolTransformGrid *grid,
1849 const GimpCoords *coords,
1850 GimpTransformFunction function)
1851 {
1852 GimpToolTransformGridPrivate *private = grid->private;
1853 GimpTransformHandle handle = GIMP_TRANSFORM_HANDLE_NONE;
1854
1855 switch (function)
1856 {
1857 case GIMP_TRANSFORM_FUNCTION_NONE:
1858 break;
1859
1860 case GIMP_TRANSFORM_FUNCTION_MOVE:
1861 handle = GIMP_TRANSFORM_HANDLE_CENTER;
1862 break;
1863
1864 case GIMP_TRANSFORM_FUNCTION_ROTATE:
1865 handle = GIMP_TRANSFORM_HANDLE_ROTATION;
1866 break;
1867
1868 case GIMP_TRANSFORM_FUNCTION_SCALE:
1869 case GIMP_TRANSFORM_FUNCTION_PERSPECTIVE:
1870 {
1871 gdouble closest_dist;
1872 gdouble dist;
1873
1874 dist = gimp_canvas_item_transform_distance_square (private->guides,
1875 coords->x, coords->y,
1876 private->tx1,
1877 private->ty1);
1878 closest_dist = dist;
1879 if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
1880 handle = GIMP_TRANSFORM_HANDLE_NW_P;
1881 else
1882 handle = GIMP_TRANSFORM_HANDLE_NW;
1883
1884 dist = gimp_canvas_item_transform_distance_square (private->guides,
1885 coords->x, coords->y,
1886 private->tx2,
1887 private->ty2);
1888 if (dist < closest_dist)
1889 {
1890 closest_dist = dist;
1891 if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
1892 handle = GIMP_TRANSFORM_HANDLE_NE_P;
1893 else
1894 handle = GIMP_TRANSFORM_HANDLE_NE;
1895 }
1896
1897 dist = gimp_canvas_item_transform_distance_square (private->guides,
1898 coords->x, coords->y,
1899 private->tx3,
1900 private->ty3);
1901 if (dist < closest_dist)
1902 {
1903 closest_dist = dist;
1904 if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
1905 handle = GIMP_TRANSFORM_HANDLE_SW_P;
1906 else
1907 handle = GIMP_TRANSFORM_HANDLE_SW;
1908 }
1909
1910 dist = gimp_canvas_item_transform_distance_square (private->guides,
1911 coords->x, coords->y,
1912 private->tx4,
1913 private->ty4);
1914 if (dist < closest_dist)
1915 {
1916 closest_dist = dist;
1917 if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
1918 handle = GIMP_TRANSFORM_HANDLE_SE_P;
1919 else
1920 handle = GIMP_TRANSFORM_HANDLE_SE;
1921 }
1922 }
1923 break;
1924
1925 case GIMP_TRANSFORM_FUNCTION_SHEAR:
1926 {
1927 gdouble handle_x;
1928 gdouble handle_y;
1929 gdouble closest_dist;
1930 gdouble dist;
1931
1932 gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_N],
1933 &handle_x, &handle_y);
1934 dist = gimp_canvas_item_transform_distance_square (private->guides,
1935 coords->x, coords->y,
1936 handle_x, handle_y);
1937 closest_dist = dist;
1938 handle = GIMP_TRANSFORM_HANDLE_N_S;
1939
1940 gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_W],
1941 &handle_x, &handle_y);
1942 dist = gimp_canvas_item_transform_distance_square (private->guides,
1943 coords->x, coords->y,
1944 handle_x, handle_y);
1945 if (dist < closest_dist)
1946 {
1947 closest_dist = dist;
1948 handle = GIMP_TRANSFORM_HANDLE_W_S;
1949 }
1950
1951 gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_E],
1952 &handle_x, &handle_y);
1953 dist = gimp_canvas_item_transform_distance_square (private->guides,
1954 coords->x, coords->y,
1955 handle_x, handle_y);
1956 if (dist < closest_dist)
1957 {
1958 closest_dist = dist;
1959 handle = GIMP_TRANSFORM_HANDLE_E_S;
1960 }
1961
1962 gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_S],
1963 &handle_x, &handle_y);
1964 dist = gimp_canvas_item_transform_distance_square (private->guides,
1965 coords->x, coords->y,
1966 handle_x, handle_y);
1967 if (dist < closest_dist)
1968 {
1969 closest_dist = dist;
1970 handle = GIMP_TRANSFORM_HANDLE_S_S;
1971 }
1972 }
1973 break;
1974 }
1975
1976 return handle;
1977 }
1978
1979 GimpHit
gimp_tool_transform_grid_hit(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)1980 gimp_tool_transform_grid_hit (GimpToolWidget *widget,
1981 const GimpCoords *coords,
1982 GdkModifierType state,
1983 gboolean proximity)
1984 {
1985 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
1986 GimpTransformHandle handle;
1987
1988 handle = gimp_tool_transform_grid_get_handle_for_coords (grid, coords);
1989
1990 if (handle != GIMP_TRANSFORM_HANDLE_NONE)
1991 return GIMP_HIT_DIRECT;
1992
1993 return GIMP_HIT_INDIRECT;
1994 }
1995
1996 void
gimp_tool_transform_grid_hover(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)1997 gimp_tool_transform_grid_hover (GimpToolWidget *widget,
1998 const GimpCoords *coords,
1999 GdkModifierType state,
2000 gboolean proximity)
2001 {
2002 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
2003 GimpToolTransformGridPrivate *private = grid->private;
2004 GimpTransformHandle handle;
2005
2006 handle = gimp_tool_transform_grid_get_handle_for_coords (grid, coords);
2007
2008 if (handle == GIMP_TRANSFORM_HANDLE_NONE)
2009 {
2010 /* points passed in clockwise order */
2011 if (point_is_inside_polygon (4,
2012 (gdouble[4]){ private->tx1, private->tx2,
2013 private->tx4, private->tx3 },
2014 (gdouble[4]){ private->ty1, private->ty2,
2015 private->ty4, private->ty3 },
2016 coords->x, coords->y))
2017 {
2018 handle = gimp_tool_transform_get_area_handle (grid, coords,
2019 private->inside_function);
2020 }
2021 else
2022 {
2023 handle = gimp_tool_transform_get_area_handle (grid, coords,
2024 private->outside_function);
2025 }
2026 }
2027
2028 if (handle != GIMP_TRANSFORM_HANDLE_NONE && proximity)
2029 {
2030 gimp_tool_widget_set_status (widget,
2031 get_friendly_operation_name (handle));
2032 }
2033 else
2034 {
2035 gimp_tool_widget_set_status (widget, NULL);
2036 }
2037
2038 private->handle = handle;
2039
2040 gimp_tool_transform_grid_update_hilight (grid);
2041 }
2042
2043 void
gimp_tool_transform_grid_leave_notify(GimpToolWidget * widget)2044 gimp_tool_transform_grid_leave_notify (GimpToolWidget *widget)
2045 {
2046 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
2047 GimpToolTransformGridPrivate *private = grid->private;
2048
2049 private->handle = GIMP_TRANSFORM_HANDLE_NONE;
2050
2051 gimp_tool_transform_grid_update_hilight (grid);
2052
2053 GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
2054 }
2055
2056 static void
gimp_tool_transform_grid_modifier(GimpToolWidget * widget,GdkModifierType key)2057 gimp_tool_transform_grid_modifier (GimpToolWidget *widget,
2058 GdkModifierType key)
2059 {
2060 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
2061 GimpToolTransformGridPrivate *private = grid->private;
2062
2063 if (key == gimp_get_constrain_behavior_mask ())
2064 {
2065 g_object_set (widget,
2066 "frompivot-scale", ! private->frompivot_scale,
2067 "frompivot-shear", ! private->frompivot_shear,
2068 "frompivot-perspective", ! private->frompivot_perspective,
2069 NULL);
2070 }
2071 else if (key == gimp_get_extend_selection_mask ())
2072 {
2073 g_object_set (widget,
2074 "cornersnap", ! private->cornersnap,
2075 "constrain-move", ! private->constrain_move,
2076 "constrain-scale", ! private->constrain_scale,
2077 "constrain-rotate", ! private->constrain_rotate,
2078 "constrain-shear", ! private->constrain_shear,
2079 "constrain-perspective", ! private->constrain_perspective,
2080 NULL);
2081 }
2082 }
2083
2084 static void
gimp_tool_transform_grid_hover_modifier(GimpToolWidget * widget,GdkModifierType key,gboolean press,GdkModifierType state)2085 gimp_tool_transform_grid_hover_modifier (GimpToolWidget *widget,
2086 GdkModifierType key,
2087 gboolean press,
2088 GdkModifierType state)
2089 {
2090 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
2091 GimpToolTransformGridPrivate *private = grid->private;
2092 GimpCoords coords = { 0.0, };
2093
2094 gimp_tool_transform_grid_modifier (widget, key);
2095
2096 if (private->button_down)
2097 {
2098 /* send a non-motion to update the grid with the new constraints */
2099 coords.x = private->curx;
2100 coords.y = private->cury;
2101 gimp_tool_transform_grid_motion (widget, &coords, 0, state);
2102 }
2103 }
2104
2105 static gboolean
gimp_tool_transform_grid_get_cursor(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,GimpCursorType * cursor,GimpToolCursorType * tool_cursor,GimpCursorModifier * modifier)2106 gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget,
2107 const GimpCoords *coords,
2108 GdkModifierType state,
2109 GimpCursorType *cursor,
2110 GimpToolCursorType *tool_cursor,
2111 GimpCursorModifier *modifier)
2112 {
2113 GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
2114 GimpToolTransformGridPrivate *private = grid->private;
2115 gdouble angle[9];
2116 gint i;
2117 GimpCursorType map[8];
2118 GimpVector2 pos[4], this, that;
2119 gboolean flip = FALSE;
2120 gboolean side = FALSE;
2121 gboolean set_cursor = TRUE;
2122
2123 map[0] = GIMP_CURSOR_CORNER_TOP_LEFT;
2124 map[1] = GIMP_CURSOR_CORNER_TOP;
2125 map[2] = GIMP_CURSOR_CORNER_TOP_RIGHT;
2126 map[3] = GIMP_CURSOR_CORNER_RIGHT;
2127 map[4] = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
2128 map[5] = GIMP_CURSOR_CORNER_BOTTOM;
2129 map[6] = GIMP_CURSOR_CORNER_BOTTOM_LEFT;
2130 map[7] = GIMP_CURSOR_CORNER_LEFT;
2131
2132 get_handle_geometry (grid, pos, angle);
2133
2134 for (i = 0; i < 8; i++)
2135 angle[i] = round (angle[i] * 180.0 / G_PI / 45.0);
2136
2137 switch (private->handle)
2138 {
2139 case GIMP_TRANSFORM_HANDLE_NW_P:
2140 case GIMP_TRANSFORM_HANDLE_NW:
2141 i = (gint) angle[4] + 0;
2142 this = pos[0];
2143 that = pos[3];
2144 break;
2145
2146 case GIMP_TRANSFORM_HANDLE_NE_P:
2147 case GIMP_TRANSFORM_HANDLE_NE:
2148 i = (gint) angle[5] + 2;
2149 this = pos[1];
2150 that = pos[2];
2151 break;
2152
2153 case GIMP_TRANSFORM_HANDLE_SW_P:
2154 case GIMP_TRANSFORM_HANDLE_SW:
2155 i = (gint) angle[6] + 6;
2156 this = pos[2];
2157 that = pos[1];
2158 break;
2159
2160 case GIMP_TRANSFORM_HANDLE_SE_P:
2161 case GIMP_TRANSFORM_HANDLE_SE:
2162 i = (gint) angle[7] + 4;
2163 this = pos[3];
2164 that = pos[0];
2165 break;
2166
2167 case GIMP_TRANSFORM_HANDLE_N:
2168 case GIMP_TRANSFORM_HANDLE_N_S:
2169 i = (gint) angle[0] + 1;
2170 this = vectoradd (pos[0], pos[1]);
2171 that = vectoradd (pos[2], pos[3]);
2172 side = TRUE;
2173 break;
2174
2175 case GIMP_TRANSFORM_HANDLE_S:
2176 case GIMP_TRANSFORM_HANDLE_S_S:
2177 i = (gint) angle[1] + 5;
2178 this = vectoradd (pos[2], pos[3]);
2179 that = vectoradd (pos[0], pos[1]);
2180 side = TRUE;
2181 break;
2182
2183 case GIMP_TRANSFORM_HANDLE_E:
2184 case GIMP_TRANSFORM_HANDLE_E_S:
2185 i = (gint) angle[2] + 3;
2186 this = vectoradd (pos[1], pos[3]);
2187 that = vectoradd (pos[0], pos[2]);
2188 side = TRUE;
2189 break;
2190
2191 case GIMP_TRANSFORM_HANDLE_W:
2192 case GIMP_TRANSFORM_HANDLE_W_S:
2193 i = (gint) angle[3] + 7;
2194 this = vectoradd (pos[0], pos[2]);
2195 that = vectoradd (pos[1], pos[3]);
2196 side = TRUE;
2197 break;
2198
2199 default:
2200 set_cursor = FALSE;
2201 break;
2202 }
2203
2204 if (set_cursor)
2205 {
2206 i %= 8;
2207
2208 switch (map[i])
2209 {
2210 case GIMP_CURSOR_CORNER_TOP_LEFT:
2211 if (this.x + this.y > that.x + that.y)
2212 flip = TRUE;
2213 break;
2214 case GIMP_CURSOR_CORNER_TOP:
2215 if (this.y > that.y)
2216 flip = TRUE;
2217 break;
2218 case GIMP_CURSOR_CORNER_TOP_RIGHT:
2219 if (this.x - this.y < that.x - that.y)
2220 flip = TRUE;
2221 break;
2222 case GIMP_CURSOR_CORNER_RIGHT:
2223 if (this.x < that.x)
2224 flip = TRUE;
2225 break;
2226 case GIMP_CURSOR_CORNER_BOTTOM_RIGHT:
2227 if (this.x + this.y < that.x + that.y)
2228 flip = TRUE;
2229 break;
2230 case GIMP_CURSOR_CORNER_BOTTOM:
2231 if (this.y < that.y)
2232 flip = TRUE;
2233 break;
2234 case GIMP_CURSOR_CORNER_BOTTOM_LEFT:
2235 if (this.x - this.y > that.x - that.y)
2236 flip = TRUE;
2237 break;
2238 case GIMP_CURSOR_CORNER_LEFT:
2239 if (this.x > that.x)
2240 flip = TRUE;
2241 break;
2242 default:
2243 gimp_assert_not_reached ();
2244 }
2245
2246 if (flip)
2247 *cursor = map[(i + 4) % 8];
2248 else
2249 *cursor = map[i];
2250
2251 if (side)
2252 *cursor += 8;
2253 }
2254
2255 /* parent class handles *cursor and *modifier for most handles */
2256 switch (private->handle)
2257 {
2258 case GIMP_TRANSFORM_HANDLE_NONE:
2259 *tool_cursor = GIMP_TOOL_CURSOR_NONE;
2260 break;
2261
2262 case GIMP_TRANSFORM_HANDLE_NW_P:
2263 case GIMP_TRANSFORM_HANDLE_NE_P:
2264 case GIMP_TRANSFORM_HANDLE_SW_P:
2265 case GIMP_TRANSFORM_HANDLE_SE_P:
2266 *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
2267 break;
2268
2269 case GIMP_TRANSFORM_HANDLE_NW:
2270 case GIMP_TRANSFORM_HANDLE_NE:
2271 case GIMP_TRANSFORM_HANDLE_SW:
2272 case GIMP_TRANSFORM_HANDLE_SE:
2273 case GIMP_TRANSFORM_HANDLE_N:
2274 case GIMP_TRANSFORM_HANDLE_S:
2275 case GIMP_TRANSFORM_HANDLE_E:
2276 case GIMP_TRANSFORM_HANDLE_W:
2277 *tool_cursor = GIMP_TOOL_CURSOR_RESIZE;
2278 break;
2279
2280 case GIMP_TRANSFORM_HANDLE_CENTER:
2281 *tool_cursor = GIMP_TOOL_CURSOR_MOVE;
2282 break;
2283
2284 case GIMP_TRANSFORM_HANDLE_PIVOT:
2285 *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
2286 *modifier = GIMP_CURSOR_MODIFIER_MOVE;
2287 break;
2288
2289 case GIMP_TRANSFORM_HANDLE_N_S:
2290 case GIMP_TRANSFORM_HANDLE_S_S:
2291 case GIMP_TRANSFORM_HANDLE_E_S:
2292 case GIMP_TRANSFORM_HANDLE_W_S:
2293 *tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
2294 break;
2295
2296 case GIMP_TRANSFORM_HANDLE_ROTATION:
2297 *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
2298 break;
2299
2300 default:
2301 g_return_val_if_reached (FALSE);
2302 }
2303
2304 return TRUE;
2305 }
2306
2307 static GimpTransformHandle
gimp_tool_transform_grid_get_handle_for_coords(GimpToolTransformGrid * grid,const GimpCoords * coords)2308 gimp_tool_transform_grid_get_handle_for_coords (GimpToolTransformGrid *grid,
2309 const GimpCoords *coords)
2310 {
2311 GimpToolTransformGridPrivate *private = grid->private;
2312 GimpTransformHandle i;
2313
2314 for (i = GIMP_TRANSFORM_HANDLE_NONE + 1; i < GIMP_N_TRANSFORM_HANDLES; i++)
2315 {
2316 if (private->handles[i] &&
2317 gimp_canvas_item_hit (private->handles[i], coords->x, coords->y))
2318 {
2319 return i;
2320 }
2321 }
2322
2323 return GIMP_TRANSFORM_HANDLE_NONE;
2324 }
2325
2326 static void
gimp_tool_transform_grid_update_hilight(GimpToolTransformGrid * grid)2327 gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid)
2328 {
2329 GimpToolTransformGridPrivate *private = grid->private;
2330 GimpTransformHandle handle;
2331
2332 for (handle = GIMP_TRANSFORM_HANDLE_NONE;
2333 handle < GIMP_N_TRANSFORM_HANDLES;
2334 handle++)
2335 {
2336 if (private->handles[handle])
2337 {
2338 gimp_canvas_item_set_highlight (private->handles[handle],
2339 handle == private->handle);
2340 }
2341 }
2342 }
2343
2344 static void
gimp_tool_transform_grid_update_box(GimpToolTransformGrid * grid)2345 gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid)
2346 {
2347 GimpToolTransformGridPrivate *private = grid->private;
2348
2349 gimp_matrix3_transform_point (&private->transform,
2350 private->x1, private->y1,
2351 &private->tx1, &private->ty1);
2352 gimp_matrix3_transform_point (&private->transform,
2353 private->x2, private->y1,
2354 &private->tx2, &private->ty2);
2355 gimp_matrix3_transform_point (&private->transform,
2356 private->x1, private->y2,
2357 &private->tx3, &private->ty3);
2358 gimp_matrix3_transform_point (&private->transform,
2359 private->x2, private->y2,
2360 &private->tx4, &private->ty4);
2361
2362 /* don't transform pivot */
2363 private->tpx = private->pivot_x;
2364 private->tpy = private->pivot_y;
2365
2366 if (transform_grid_is_convex (grid))
2367 {
2368 gimp_matrix3_transform_point (&private->transform,
2369 (private->x1 + private->x2) / 2.0,
2370 (private->y1 + private->y2) / 2.0,
2371 &private->tcx, &private->tcy);
2372 }
2373 else
2374 {
2375 private->tcx = (private->tx1 +
2376 private->tx2 +
2377 private->tx3 +
2378 private->tx4) / 4.0;
2379 private->tcy = (private->ty1 +
2380 private->ty2 +
2381 private->ty3 +
2382 private->ty4) / 4.0;
2383 }
2384 }
2385
2386 static void
gimp_tool_transform_grid_update_matrix(GimpToolTransformGrid * grid)2387 gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid)
2388 {
2389 GimpToolTransformGridPrivate *private = grid->private;
2390
2391 gimp_matrix3_identity (&private->transform);
2392 gimp_transform_matrix_perspective (&private->transform,
2393 private->x1,
2394 private->y1,
2395 private->x2 - private->x1,
2396 private->y2 - private->y1,
2397 private->tx1,
2398 private->ty1,
2399 private->tx2,
2400 private->ty2,
2401 private->tx3,
2402 private->ty3,
2403 private->tx4,
2404 private->ty4);
2405
2406 private->pivot_x = private->tpx;
2407 private->pivot_y = private->tpy;
2408
2409 g_object_freeze_notify (G_OBJECT (grid));
2410 g_object_notify (G_OBJECT (grid), "transform");
2411 g_object_notify (G_OBJECT (grid), "pivot-x");
2412 g_object_notify (G_OBJECT (grid), "pivot-x");
2413 g_object_thaw_notify (G_OBJECT (grid));
2414 }
2415
2416 static void
gimp_tool_transform_grid_calc_handles(GimpToolTransformGrid * grid,gint * handle_w,gint * handle_h)2417 gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid,
2418 gint *handle_w,
2419 gint *handle_h)
2420 {
2421 GimpToolTransformGridPrivate *private = grid->private;
2422 gint dx1, dy1;
2423 gint dx2, dy2;
2424 gint dx3, dy3;
2425 gint dx4, dy4;
2426 gint x1, y1;
2427 gint x2, y2;
2428
2429 if (! private->dynamic_handle_size)
2430 {
2431 *handle_w = GIMP_CANVAS_HANDLE_SIZE_LARGE;
2432 *handle_h = GIMP_CANVAS_HANDLE_SIZE_LARGE;
2433
2434 return;
2435 }
2436
2437 gimp_canvas_item_transform_xy (private->guides,
2438 private->tx1, private->ty1,
2439 &dx1, &dy1);
2440 gimp_canvas_item_transform_xy (private->guides,
2441 private->tx2, private->ty2,
2442 &dx2, &dy2);
2443 gimp_canvas_item_transform_xy (private->guides,
2444 private->tx3, private->ty3,
2445 &dx3, &dy3);
2446 gimp_canvas_item_transform_xy (private->guides,
2447 private->tx4, private->ty4,
2448 &dx4, &dy4);
2449
2450 x1 = MIN4 (dx1, dx2, dx3, dx4);
2451 y1 = MIN4 (dy1, dy2, dy3, dy4);
2452 x2 = MAX4 (dx1, dx2, dx3, dx4);
2453 y2 = MAX4 (dy1, dy2, dy3, dy4);
2454
2455 *handle_w = CLAMP ((x2 - x1) / 3,
2456 MIN_HANDLE_SIZE, GIMP_CANVAS_HANDLE_SIZE_LARGE);
2457 *handle_h = CLAMP ((y2 - y1) / 3,
2458 MIN_HANDLE_SIZE, GIMP_CANVAS_HANDLE_SIZE_LARGE);
2459 }
2460
2461
2462 /* public functions */
2463
2464 GimpToolWidget *
gimp_tool_transform_grid_new(GimpDisplayShell * shell,const GimpMatrix3 * transform,gdouble x1,gdouble y1,gdouble x2,gdouble y2)2465 gimp_tool_transform_grid_new (GimpDisplayShell *shell,
2466 const GimpMatrix3 *transform,
2467 gdouble x1,
2468 gdouble y1,
2469 gdouble x2,
2470 gdouble y2)
2471 {
2472 g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
2473
2474 return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_GRID,
2475 "shell", shell,
2476 "transform", transform,
2477 "x1", x1,
2478 "y1", y1,
2479 "x2", x2,
2480 "y2", y2,
2481 NULL);
2482 }
2483
2484
2485 /* protected functions */
2486
2487 GimpTransformHandle
gimp_tool_transform_grid_get_handle(GimpToolTransformGrid * grid)2488 gimp_tool_transform_grid_get_handle (GimpToolTransformGrid *grid)
2489 {
2490 g_return_val_if_fail (GIMP_IS_TOOL_TRANSFORM_GRID (grid),
2491 GIMP_TRANSFORM_HANDLE_NONE);
2492
2493 return grid->private->handle;
2494 }
2495