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 (®ion_sel->region_mask);
122
123 g_clear_pointer (®ion_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 (®ion_sel->region_mask);
229
230 g_clear_pointer (®ion_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 ®ion_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 (®ion_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