1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpregionselecttool.c
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include <gegl.h>
23 #include <gtk/gtk.h>
24 
25 #include "libgimpwidgets/gimpwidgets.h"
26 
27 #include "tools-types.h"
28 
29 #include "core/gimp-utils.h"
30 #include "core/gimpboundary.h"
31 #include "core/gimpchannel.h"
32 #include "core/gimpchannel-select.h"
33 #include "core/gimpimage.h"
34 #include "core/gimplayer-floating-selection.h"
35 
36 #include "display/gimpdisplay.h"
37 #include "display/gimpdisplayshell.h"
38 #include "display/gimpdisplayshell-cursor.h"
39 
40 #include "gimpregionselectoptions.h"
41 #include "gimpregionselecttool.h"
42 #include "gimptoolcontrol.h"
43 
44 #include "gimp-intl.h"
45 
46 
47 static void   gimp_region_select_tool_finalize       (GObject               *object);
48 
49 static void   gimp_region_select_tool_button_press   (GimpTool              *tool,
50                                                       const GimpCoords      *coords,
51                                                       guint32                time,
52                                                       GdkModifierType        state,
53                                                       GimpButtonPressType    press_type,
54                                                       GimpDisplay           *display);
55 static void   gimp_region_select_tool_button_release (GimpTool              *tool,
56                                                       const GimpCoords      *coords,
57                                                       guint32                time,
58                                                       GdkModifierType        state,
59                                                       GimpButtonReleaseType  release_type,
60                                                       GimpDisplay           *display);
61 static void   gimp_region_select_tool_motion         (GimpTool              *tool,
62                                                       const GimpCoords      *coords,
63                                                       guint32                time,
64                                                       GdkModifierType        state,
65                                                       GimpDisplay           *display);
66 static void   gimp_region_select_tool_cursor_update  (GimpTool              *tool,
67                                                       const GimpCoords      *coords,
68                                                       GdkModifierType        state,
69                                                       GimpDisplay           *display);
70 
71 static void   gimp_region_select_tool_draw           (GimpDrawTool          *draw_tool);
72 
73 static void   gimp_region_select_tool_get_mask       (GimpRegionSelectTool  *region_sel,
74                                                       GimpDisplay           *display);
75 
76 
G_DEFINE_TYPE(GimpRegionSelectTool,gimp_region_select_tool,GIMP_TYPE_SELECTION_TOOL)77 G_DEFINE_TYPE (GimpRegionSelectTool, gimp_region_select_tool,
78                GIMP_TYPE_SELECTION_TOOL)
79 
80 #define parent_class gimp_region_select_tool_parent_class
81 
82 
83 static void
84 gimp_region_select_tool_class_init (GimpRegionSelectToolClass *klass)
85 {
86   GObjectClass      *object_class    = G_OBJECT_CLASS (klass);
87   GimpToolClass     *tool_class      = GIMP_TOOL_CLASS (klass);
88   GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
89 
90   object_class->finalize     = gimp_region_select_tool_finalize;
91 
92   tool_class->button_press   = gimp_region_select_tool_button_press;
93   tool_class->button_release = gimp_region_select_tool_button_release;
94   tool_class->motion         = gimp_region_select_tool_motion;
95   tool_class->cursor_update  = gimp_region_select_tool_cursor_update;
96 
97   draw_tool_class->draw      = gimp_region_select_tool_draw;
98 }
99 
100 static void
gimp_region_select_tool_init(GimpRegionSelectTool * region_select)101 gimp_region_select_tool_init (GimpRegionSelectTool *region_select)
102 {
103   GimpTool *tool = GIMP_TOOL (region_select);
104 
105   gimp_tool_control_set_scroll_lock (tool->control, TRUE);
106 
107   region_select->x               = 0;
108   region_select->y               = 0;
109   region_select->saved_threshold = 0.0;
110 
111   region_select->region_mask     = NULL;
112   region_select->segs            = NULL;
113   region_select->n_segs          = 0;
114 }
115 
116 static void
gimp_region_select_tool_finalize(GObject * object)117 gimp_region_select_tool_finalize (GObject *object)
118 {
119   GimpRegionSelectTool *region_sel = GIMP_REGION_SELECT_TOOL (object);
120 
121   g_clear_object (&region_sel->region_mask);
122 
123   g_clear_pointer (&region_sel->segs, g_free);
124   region_sel->n_segs = 0;
125 
126   G_OBJECT_CLASS (parent_class)->finalize (object);
127 }
128 
129 static void
gimp_region_select_tool_button_press(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type,GimpDisplay * display)130 gimp_region_select_tool_button_press (GimpTool            *tool,
131                                       const GimpCoords    *coords,
132                                       guint32              time,
133                                       GdkModifierType      state,
134                                       GimpButtonPressType  press_type,
135                                       GimpDisplay         *display)
136 {
137   GimpRegionSelectTool    *region_sel = GIMP_REGION_SELECT_TOOL (tool);
138   GimpRegionSelectOptions *options    = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
139 
140   region_sel->x               = coords->x;
141   region_sel->y               = coords->y;
142   region_sel->saved_threshold = options->threshold;
143 
144   if (gimp_selection_tool_start_edit (GIMP_SELECTION_TOOL (region_sel),
145                                       display, coords))
146     {
147       return;
148     }
149 
150   gimp_tool_control_activate (tool->control);
151   tool->display = display;
152 
153   gimp_tool_push_status (tool, display,
154                          _("Move the mouse to change threshold"));
155 
156   gimp_region_select_tool_get_mask (region_sel, display);
157 
158   gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
159 }
160 
161 static void
gimp_region_select_tool_button_release(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type,GimpDisplay * display)162 gimp_region_select_tool_button_release (GimpTool              *tool,
163                                         const GimpCoords      *coords,
164                                         guint32                time,
165                                         GdkModifierType        state,
166                                         GimpButtonReleaseType  release_type,
167                                         GimpDisplay           *display)
168 {
169   GimpRegionSelectTool    *region_sel  = GIMP_REGION_SELECT_TOOL (tool);
170   GimpSelectionOptions    *sel_options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
171   GimpRegionSelectOptions *options     = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
172   GimpImage               *image       = gimp_display_get_image (display);
173 
174   gimp_tool_pop_status (tool, display);
175 
176   gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
177 
178   gimp_tool_control_halt (tool->control);
179 
180   if (options->draw_mask)
181     gimp_display_shell_set_mask (gimp_display_get_shell (display),
182                                  NULL, 0, 0, NULL, FALSE);
183 
184   if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
185     {
186       if (GIMP_SELECTION_TOOL (tool)->function == SELECTION_ANCHOR)
187         {
188           if (gimp_image_get_floating_selection (image))
189             {
190               /*  If there is a floating selection, anchor it  */
191               floating_sel_anchor (gimp_image_get_floating_selection (image));
192             }
193           else
194             {
195               /*  Otherwise, clear the selection mask  */
196               gimp_channel_clear (gimp_image_get_mask (image), NULL, TRUE);
197             }
198 
199           gimp_image_flush (image);
200         }
201       else if (region_sel->region_mask)
202         {
203           gint off_x = 0;
204           gint off_y = 0;
205 
206           if (! options->sample_merged)
207             {
208               GimpDrawable *drawable = gimp_image_get_active_drawable (image);
209 
210               gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
211             }
212 
213           gimp_channel_select_buffer (gimp_image_get_mask (image),
214                                       GIMP_REGION_SELECT_TOOL_GET_CLASS (tool)->undo_desc,
215                                       region_sel->region_mask,
216                                       off_x,
217                                       off_y,
218                                       sel_options->operation,
219                                       sel_options->feather,
220                                       sel_options->feather_radius,
221                                       sel_options->feather_radius);
222 
223 
224           gimp_image_flush (image);
225         }
226     }
227 
228   g_clear_object (&region_sel->region_mask);
229 
230   g_clear_pointer (&region_sel->segs, g_free);
231   region_sel->n_segs = 0;
232 
233   /*  Restore the original threshold  */
234   g_object_set (options,
235                 "threshold", region_sel->saved_threshold,
236                 NULL);
237 }
238 
239 static void
gimp_region_select_tool_motion(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpDisplay * display)240 gimp_region_select_tool_motion (GimpTool         *tool,
241                                 const GimpCoords *coords,
242                                 guint32           time,
243                                 GdkModifierType   state,
244                                 GimpDisplay      *display)
245 {
246   GimpRegionSelectTool    *region_sel = GIMP_REGION_SELECT_TOOL (tool);
247   GimpRegionSelectOptions *options    = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
248   gint                     diff_x, diff_y;
249   gdouble                  diff;
250 
251   static guint32 last_time = 0;
252 
253   /* don't let the events come in too fast, ignore below a delay of 100 ms */
254   if (time - last_time < 100)
255     return;
256 
257   last_time = time;
258 
259   diff_x = coords->x - region_sel->x;
260   diff_y = coords->y - region_sel->y;
261 
262   diff = ((ABS (diff_x) > ABS (diff_y)) ? diff_x : diff_y) / 2.0;
263 
264   g_object_set (options,
265                 "threshold", CLAMP (region_sel->saved_threshold + diff, 0, 255),
266                 NULL);
267 
268   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
269 
270   gimp_region_select_tool_get_mask (region_sel, display);
271 
272   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
273 }
274 
275 static void
gimp_region_select_tool_cursor_update(GimpTool * tool,const GimpCoords * coords,GdkModifierType state,GimpDisplay * display)276 gimp_region_select_tool_cursor_update (GimpTool         *tool,
277                                        const GimpCoords *coords,
278                                        GdkModifierType   state,
279                                        GimpDisplay      *display)
280 {
281   GimpRegionSelectOptions *options  = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
282   GimpCursorModifier       modifier = GIMP_CURSOR_MODIFIER_NONE;
283   GimpImage               *image    = gimp_display_get_image (display);
284 
285   if (! gimp_image_coords_in_active_pickable (image, coords,
286                                               FALSE, options->sample_merged,
287                                               FALSE))
288     modifier = GIMP_CURSOR_MODIFIER_BAD;
289 
290   gimp_tool_control_set_cursor_modifier (tool->control, modifier);
291 
292   GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
293 }
294 
295 static void
gimp_region_select_tool_draw(GimpDrawTool * draw_tool)296 gimp_region_select_tool_draw (GimpDrawTool *draw_tool)
297 {
298   GimpRegionSelectTool    *region_sel = GIMP_REGION_SELECT_TOOL (draw_tool);
299   GimpRegionSelectOptions *options    = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (draw_tool);
300 
301   if (! options->draw_mask && region_sel->region_mask)
302     {
303       if (! region_sel->segs)
304         {
305           /*  calculate and allocate a new segment array which represents
306            *  the boundary of the contiguous region
307            */
308           region_sel->segs = gimp_boundary_find (region_sel->region_mask, NULL,
309                                                  babl_format ("Y float"),
310                                                  GIMP_BOUNDARY_WITHIN_BOUNDS,
311                                                  0, 0,
312                                                  gegl_buffer_get_width  (region_sel->region_mask),
313                                                  gegl_buffer_get_height (region_sel->region_mask),
314                                                  GIMP_BOUNDARY_HALF_WAY,
315                                                  &region_sel->n_segs);
316 
317         }
318 
319       if (region_sel->segs)
320         {
321           gint off_x = 0;
322           gint off_y = 0;
323 
324           if (! options->sample_merged)
325             {
326               GimpImage    *image    = gimp_display_get_image (draw_tool->display);
327               GimpDrawable *drawable = gimp_image_get_active_drawable (image);
328 
329               gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
330             }
331 
332           gimp_draw_tool_add_boundary (draw_tool,
333                                        region_sel->segs,
334                                        region_sel->n_segs,
335                                        NULL,
336                                        off_x, off_y);
337         }
338     }
339 }
340 
341 static void
gimp_region_select_tool_get_mask(GimpRegionSelectTool * region_sel,GimpDisplay * display)342 gimp_region_select_tool_get_mask (GimpRegionSelectTool *region_sel,
343                                   GimpDisplay          *display)
344 {
345   GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (region_sel);
346   GimpDisplayShell        *shell   = gimp_display_get_shell (display);
347 
348   gimp_display_shell_set_override_cursor (shell, (GimpCursorType) GDK_WATCH);
349 
350   g_clear_pointer (&region_sel->segs, g_free);
351   region_sel->n_segs = 0;
352 
353   if (region_sel->region_mask)
354     g_object_unref (region_sel->region_mask);
355 
356   region_sel->region_mask =
357     GIMP_REGION_SELECT_TOOL_GET_CLASS (region_sel)->get_mask (region_sel,
358                                                               display);
359 
360   if (options->draw_mask)
361     {
362       if (region_sel->region_mask)
363         {
364           GimpRGB color = { 1.0, 0.0, 1.0, 1.0 };
365           gint    off_x = 0;
366           gint    off_y = 0;
367 
368           if (! options->sample_merged)
369             {
370               GimpImage    *image    = gimp_display_get_image (display);
371               GimpDrawable *drawable = gimp_image_get_active_drawable (image);
372 
373               gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
374             }
375 
376           gimp_display_shell_set_mask (shell, region_sel->region_mask,
377                                        off_x, off_y, &color, FALSE);
378         }
379       else
380         {
381           gimp_display_shell_set_mask (shell, NULL, 0, 0, NULL, FALSE);
382         }
383     }
384 
385   gimp_display_shell_unset_override_cursor (shell);
386 }
387