Gimp/app/tools/gimppaintselecttool.c
Jehan 046c8f2745 app: use the same mode box in Paint Select as in other Selection tools.
To avoid duplicating code, I make a friend function to be reusable in
Paint Select options code: gimp_selection_options_get_mode_box().

I also get rid of GimpPaintSelectMode enum, which was clearly a
duplicate type to GimpChannelOps. Right now only ADD and SUBTRACT are
supported in Paint Select tool, yet we are perfectly able to generate
widgets showing only partial data. Which is what I do here by adding
appropriate min/max value args to the new function.

Note though that I don't see why we should not be able to replace or
intersect using Paint Select. But since these 2 modes are not
implemented right now, I haven't tried to add support in this commit.
For now, only making our GUI consistent so that Paint Select looks like
any other selection tools (simply only with 2 modes).
2025-11-27 22:24:54 +01:00

986 lines
36 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* GimpPaintSelectTool
* Copyright (C) 2020 Thomas Manni <thomas.manni@free.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <math.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "tools-types.h"
#include "config/gimpguiconfig.h"
#include "gegl/gimp-gegl-loops.h"
#include "gegl/gimp-gegl-mask.h"
#include "gegl/gimp-gegl-utils.h"
#include "core/gimp.h"
#include "core/gimpchannel-select.h"
#include "core/gimperror.h"
#include "core/gimpimage.h"
#include "core/gimplayer.h"
#include "core/gimplayermask.h"
#include "core/gimpprogress.h"
#include "core/gimpscanconvert.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpwidgets-utils.h"
#include "display/gimpdisplay.h"
#include "display/gimpdisplayshell.h"
#include "display/gimpdisplayshell-scroll.h"
#include "display/gimptoolgui.h"
#include "core/gimptooloptions.h"
#include "gimppaintselecttool.h"
#include "gimppaintselectoptions.h"
#include "gimptoolcontrol.h"
#include "gimp-intl.h"
#include "config/gimpguiconfig.h" /* playground */
static void gimp_paint_select_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display);
static void gimp_paint_select_tool_button_press (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonPressType press_type,
GimpDisplay *display);
static void gimp_paint_select_tool_button_release (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonReleaseType release_type,
GimpDisplay *display);
static void gimp_paint_select_tool_motion (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpDisplay *display);
static gboolean gimp_paint_select_tool_key_press (GimpTool *tool,
GdkEventKey *kevent,
GimpDisplay *display);
static void gimp_paint_select_tool_modifier_key (GimpTool *tool,
GdkModifierType key,
gboolean press,
GdkModifierType state,
GimpDisplay *display);
static void gimp_paint_select_tool_oper_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
gboolean proximity,
GimpDisplay *display);
static void gimp_paint_select_tool_options_notify (GimpTool *tool,
GimpToolOptions *options,
const GParamSpec *pspec);
static void gimp_paint_select_tool_cursor_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
GimpDisplay *display);
static void gimp_paint_select_tool_draw (GimpDrawTool *draw_tool);
static void gimp_paint_select_tool_progress (GeglNode *ps_node,
gdouble value,
GimpProgress *progress);
static void gimp_paint_select_tool_halt (GimpPaintSelectTool *ps_tool);
static void gimp_paint_select_tool_update_image_mask (GimpPaintSelectTool *ps_tool,
GeglBuffer *buffer,
gint offset_x,
gint offset_y);
static void gimp_paint_select_tool_init_buffers (GimpPaintSelectTool *ps_tool,
GimpImage *image,
GimpDrawable *drawable);
static gboolean gimp_paint_select_tool_can_paint (GimpPaintSelectTool *ps_tool,
GimpDisplay *display,
gboolean show_message);
static gboolean gimp_paint_select_tool_start (GimpPaintSelectTool *ps_tool,
GimpDisplay *display);
static void gimp_paint_select_tool_init_scribble (GimpPaintSelectTool *ps_tool);
static void gimp_paint_select_tool_create_graph (GimpPaintSelectTool *ps_tool);
static gboolean gimp_paint_select_tool_paint_scribble (GimpPaintSelectTool *ps_tool);
static void gimp_paint_select_tool_toggle_scribbles_visibility (GimpPaintSelectTool *ps_tool);
static GeglRectangle gimp_paint_select_tool_get_local_region (GimpPaintSelectTool *ps_tool,
GimpDisplay *display);
static gfloat euclidean_distance (gint x1,
gint y1,
gint x2,
gint y2);
G_DEFINE_TYPE (GimpPaintSelectTool, gimp_paint_select_tool, GIMP_TYPE_DRAW_TOOL)
#define parent_class gimp_paint_select_tool_parent_class
void
gimp_paint_select_tool_register (GimpToolRegisterCallback callback,
gpointer data)
{
if (gegl_has_operation ("gegl:paint-select") &&
GIMP_GUI_CONFIG (GIMP (data)->config)->playground_paint_select_tool)
(* callback) (GIMP_TYPE_PAINT_SELECT_TOOL,
GIMP_TYPE_PAINT_SELECT_OPTIONS,
gimp_paint_select_options_gui,
0,
"gimp-paint-select-tool",
_("Paint Select"),
_("Paint Select Tool: Select objects by painting roughly"),
N_("P_aint Select"), NULL,
NULL, GIMP_HELP_TOOL_FOREGROUND_SELECT,
GIMP_ICON_TOOL_PAINT_SELECT,
data);
}
static void
gimp_paint_select_tool_class_init (GimpPaintSelectToolClass *klass)
{
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
tool_class->button_press = gimp_paint_select_tool_button_press;
tool_class->button_release = gimp_paint_select_tool_button_release;
tool_class->control = gimp_paint_select_tool_control;
tool_class->cursor_update = gimp_paint_select_tool_cursor_update;
tool_class->key_press = gimp_paint_select_tool_key_press;
tool_class->modifier_key = gimp_paint_select_tool_modifier_key;
tool_class->motion = gimp_paint_select_tool_motion;
tool_class->oper_update = gimp_paint_select_tool_oper_update;
tool_class->options_notify = gimp_paint_select_tool_options_notify;
draw_tool_class->draw = gimp_paint_select_tool_draw;
}
static void
gimp_paint_select_tool_init (GimpPaintSelectTool *ps_tool)
{
GimpTool *tool = GIMP_TOOL (ps_tool);
gimp_tool_control_set_motion_mode (tool->control, GIMP_MOTION_MODE_EXACT);
gimp_tool_control_set_scroll_lock (tool->control, FALSE);
gimp_tool_control_set_preserve (tool->control, FALSE);
gimp_tool_control_set_dirty_mask (tool->control,
GIMP_DIRTY_IMAGE |
GIMP_DIRTY_ACTIVE_DRAWABLE);
gimp_tool_control_set_dirty_action (tool->control,
GIMP_TOOL_ACTION_HALT);
gimp_tool_control_set_precision (tool->control,
GIMP_CURSOR_PRECISION_SUBPIXEL);
gimp_tool_control_set_tool_cursor (tool->control,
GIMP_TOOL_CURSOR_PAINTBRUSH);
gimp_tool_control_set_cursor_modifier (tool->control,
GIMP_CURSOR_MODIFIER_PLUS);
gimp_tool_control_set_action_pixel_size (tool->control,
"tools-paint-select-pixel-size-set");
/* TODO: the size-set action is not implemented. */
gimp_tool_control_set_action_size (tool->control,
"tools-paint-select-size-set");
ps_tool->image_mask = NULL;
ps_tool->trimap = NULL;
ps_tool->drawable = NULL;
ps_tool->scribble = NULL;
ps_tool->graph = NULL;
ps_tool->ps_node = NULL;
ps_tool->render_node = NULL;
}
static void
gimp_paint_select_tool_button_press (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonPressType press_type,
GimpDisplay *display)
{
GimpPaintSelectTool *ps_tool = GIMP_PAINT_SELECT_TOOL (tool);
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (ps_tool);
GeglColor *grey = gegl_color_new ("#888");
if (tool->display && display != tool->display)
gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
if (! tool->display)
{
if (! gimp_paint_select_tool_start (ps_tool, display))
return;
}
g_return_if_fail (g_list_length (tool->drawables) == 1);
ps_tool->last_pos.x = coords->x;
ps_tool->last_pos.y = coords->y;
/* Always reset the "scribbles" to start with a blank slate. */
gegl_buffer_set_color (ps_tool->trimap, NULL, grey);
if (options->operation == GIMP_CHANNEL_OP_ADD)
{
gegl_node_set (ps_tool->ps_node, "mode", 0, NULL);
gegl_node_set (ps_tool->threshold_node, "value", 0.99, NULL);
}
else
{
gegl_node_set (ps_tool->ps_node, "mode", 1, NULL);
gegl_node_set (ps_tool->threshold_node, "value", 0.01, NULL);
}
ps_tool->painting_op = options->operation;
ps_tool->process = gimp_paint_select_tool_paint_scribble (ps_tool);
gimp_tool_control_activate (tool->control);
g_object_unref (grey);
}
static void
gimp_paint_select_tool_button_release (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonReleaseType release_type,
GimpDisplay *display)
{
GimpPaintSelectTool *ps_tool = GIMP_PAINT_SELECT_TOOL (tool);
GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
gimp_draw_tool_pause (draw_tool);
gimp_tool_control_halt (tool->control);
if (ps_tool->process)
{
GTimer *timer = g_timer_new ();
GimpProgress *progress;
GeglBuffer *result;
GeglRectangle local_region;
progress = gimp_progress_start (GIMP_PROGRESS (tool), FALSE,
"%s", "Selecting");
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
local_region = gimp_paint_select_tool_get_local_region (ps_tool, display);
if (local_region.width < ps_tool->drawable_width ||
local_region.height < ps_tool->drawable_height)
{
gegl_node_set (ps_tool->ps_node,
"use_local_region", TRUE,
"region_x", local_region.x,
"region_y", local_region.y,
"region_width", local_region.width,
"region_height", local_region.height,
NULL);
}
else
{
gegl_node_set (ps_tool->ps_node,
"use_local_region", FALSE, NULL);
}
gegl_node_set (ps_tool->render_node, "buffer", &result, NULL);
g_signal_connect (ps_tool->ps_node, "progress",
G_CALLBACK (gimp_paint_select_tool_progress),
ps_tool);
gegl_node_process (ps_tool->render_node);
g_signal_handlers_disconnect_by_func (ps_tool->ps_node,
G_CALLBACK (gimp_paint_select_tool_progress),
ps_tool);
g_timer_stop (timer);
g_printerr ("processing graph takes %.3f s\n\n", g_timer_elapsed (timer, NULL));
g_timer_destroy (timer);
gimp_paint_select_tool_update_image_mask (ps_tool,
result,
ps_tool->drawable_off_x,
ps_tool->drawable_off_y);
if (progress)
gimp_progress_end (progress);
g_object_unref (result);
}
gimp_draw_tool_resume (draw_tool);
}
static void
gimp_paint_select_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display)
{
GimpPaintSelectTool *paint_select = GIMP_PAINT_SELECT_TOOL (tool);
switch (action)
{
case GIMP_TOOL_ACTION_PAUSE:
break;
case GIMP_TOOL_ACTION_RESUME:
break;
case GIMP_TOOL_ACTION_HALT:
gimp_paint_select_tool_halt (paint_select);
break;
case GIMP_TOOL_ACTION_COMMIT:
break;
}
GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
}
static void
gimp_paint_select_tool_cursor_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
GimpDisplay *display)
{
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (tool);
GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
if (options->operation == GIMP_CHANNEL_OP_ADD)
{
modifier = GIMP_CURSOR_MODIFIER_PLUS;
}
else
{
modifier = GIMP_CURSOR_MODIFIER_MINUS;
}
gimp_tool_control_set_cursor_modifier (tool->control, modifier);
GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
}
static gboolean
gimp_paint_select_tool_can_paint (GimpPaintSelectTool *ps_tool,
GimpDisplay *display,
gboolean show_message)
{
GimpTool *tool = GIMP_TOOL (ps_tool);
GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
GimpImage *image = gimp_display_get_image (display);
GList *drawables = gimp_image_get_selected_drawables (image);
GimpDrawable *drawable;
if (g_list_length (drawables) != 1)
{
if (show_message)
{
if (g_list_length (drawables) > 1)
gimp_tool_message_literal (tool, display,
_("Cannot paint select on multiple layers. Select only one layer."));
else
gimp_tool_message_literal (tool, display,
_("No active drawables."));
}
g_list_free (drawables);
return FALSE;
}
drawable = drawables->data;
g_list_free (drawables);
if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
{
if (show_message)
{
gimp_tool_message_literal (tool, display,
_("Cannot paint select on layer groups."));
}
return FALSE;
}
if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
! config->edit_non_visible)
{
if (show_message)
{
gimp_tool_message_literal (tool, display,
_("The active layer is not visible."));
}
return FALSE;
}
return TRUE;
}
static gboolean
gimp_paint_select_tool_start (GimpPaintSelectTool *ps_tool,
GimpDisplay *display)
{
GimpTool *tool = GIMP_TOOL (ps_tool);
GimpImage *image = gimp_display_get_image (display);
GimpDrawable *drawable;
if (! gimp_paint_select_tool_can_paint (ps_tool, display, TRUE))
return FALSE;
tool->display = display;
g_list_free (tool->drawables);
tool->drawables = gimp_image_get_selected_drawables (image);
drawable = tool->drawables->data;
gimp_paint_select_tool_init_buffers (ps_tool, image, drawable);
gimp_paint_select_tool_create_graph (ps_tool);
if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (ps_tool)))
gimp_draw_tool_start (GIMP_DRAW_TOOL (ps_tool), display);
return TRUE;
}
static gboolean
gimp_paint_select_tool_key_press (GimpTool *tool,
GdkEventKey *kevent,
GimpDisplay *display)
{
return GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
}
/* Based on gimp_selection_tool_modifier_key () */
static void
gimp_paint_select_tool_modifier_key (GimpTool *tool,
GdkModifierType key,
gboolean press,
GdkModifierType state,
GimpDisplay *display)
{
GimpPaintSelectTool *ps_tool = GIMP_PAINT_SELECT_TOOL (tool);
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (ps_tool);
GdkModifierType extend_mask;
GdkModifierType modify_mask;
extend_mask = gimp_get_extend_selection_mask ();
modify_mask = gimp_get_modify_selection_mask ();
if (key == extend_mask ||
key == modify_mask ||
key == GDK_MOD1_MASK)
{
GimpChannelOps button_op = options->operation;
state &= extend_mask | modify_mask | GDK_MOD1_MASK;
if (press)
{
if (key == state || ! state)
ps_tool->saved_op = options->operation;
}
else
{
if (! state)
button_op = ps_tool->saved_op;
}
if (state & GDK_MOD1_MASK)
{
button_op = ps_tool->saved_op;
}
else if (state & (extend_mask | modify_mask))
{
GimpChannelOps op = gimp_modifiers_to_channel_op (state);
switch (op)
{
case GIMP_CHANNEL_OP_ADD:
button_op = GIMP_CHANNEL_OP_ADD;
break;
case GIMP_CHANNEL_OP_SUBTRACT:
button_op = GIMP_CHANNEL_OP_SUBTRACT;
break;
default:
break;
}
}
if (button_op != options->operation)
g_object_set (options, "operation", button_op, NULL);
}
}
static void
gimp_paint_select_tool_motion (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpDisplay *display)
{
GimpPaintSelectTool *ps_tool = GIMP_PAINT_SELECT_TOOL (tool);
GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display);
if (state & GDK_BUTTON1_MASK)
{
gfloat distance = euclidean_distance (coords->x,
coords->y,
ps_tool->last_pos.x,
ps_tool->last_pos.y);
if (distance >= 2.f)
{
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
ps_tool->last_pos.x = coords->x;
ps_tool->last_pos.y = coords->y;
ps_tool->process |= gimp_paint_select_tool_paint_scribble (ps_tool);
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
}
}
}
static void
gimp_paint_select_tool_oper_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
gboolean proximity,
GimpDisplay *display)
{
GimpPaintSelectTool *ps = GIMP_PAINT_SELECT_TOOL (tool);
GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
if (proximity)
{
gimp_draw_tool_pause (draw_tool);
if (! tool->display || display == tool->display)
{
ps->last_pos.x = coords->x;
ps->last_pos.y = coords->y;
}
if (! gimp_draw_tool_is_active (draw_tool))
gimp_draw_tool_start (draw_tool, display);
gimp_draw_tool_resume (draw_tool);
}
else if (gimp_draw_tool_is_active (draw_tool))
{
gimp_draw_tool_stop (draw_tool);
}
}
static void
gimp_paint_select_tool_draw (GimpDrawTool *draw_tool)
{
GimpPaintSelectTool *paint_select = GIMP_PAINT_SELECT_TOOL (draw_tool);
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (paint_select);
gint size = options->stroke_width;
gimp_draw_tool_add_arc (draw_tool,
FALSE,
paint_select->last_pos.x - (size / 2.0),
paint_select->last_pos.y - (size / 2.0),
size, size,
0.0, (2.0 * G_PI));
}
static void
gimp_paint_select_tool_options_notify (GimpTool *tool,
GimpToolOptions *options,
const GParamSpec *pspec)
{
GimpPaintSelectTool *ps_tool = GIMP_PAINT_SELECT_TOOL (tool);
if (g_strcmp0 (pspec->name, "stroke-width") == 0)
{
/* This triggers a redraw of the tool pointer, especially useful
* here when we change the pen size with on-canvas interaction.
*/
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
}
if (! tool->display)
return;
if (! strcmp (pspec->name, "stroke-width") && ps_tool->scribble)
{
g_object_unref (ps_tool->scribble);
ps_tool->scribble = NULL;
}
else if (! strcmp (pspec->name, "show-scribbles"))
{
gimp_paint_select_tool_toggle_scribbles_visibility (ps_tool);
}
}
static void
gimp_paint_select_tool_progress (GeglNode *ps_node,
gdouble value,
GimpProgress *progress)
{
gimp_progress_set_value (progress, value);
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
}
static void
gimp_paint_select_tool_halt (GimpPaintSelectTool *ps_tool)
{
GimpTool *tool = GIMP_TOOL (ps_tool);
g_clear_object (&ps_tool->trimap);
g_clear_object (&ps_tool->graph);
g_clear_object (&ps_tool->scribble);
ps_tool->drawable = NULL;
ps_tool->render_node = NULL;
ps_tool->ps_node = NULL;
ps_tool->image_mask = NULL;
if (tool->display)
{
gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
NULL, 0, 0, NULL, FALSE);
gimp_image_flush (gimp_display_get_image (tool->display));
}
tool->display = NULL;
g_list_free (tool->drawables);
tool->drawables = NULL;
}
static void
gimp_paint_select_tool_update_image_mask (GimpPaintSelectTool *ps_tool,
GeglBuffer *buffer,
gint offset_x,
gint offset_y)
{
GimpTool *tool = GIMP_TOOL (ps_tool);
if (tool->display)
{
GimpImage *image = gimp_display_get_image (tool->display);
gimp_channel_select_buffer (gimp_image_get_mask (image),
C_("command", "Paint Select"),
buffer,
offset_x,
offset_y,
ps_tool->painting_op,
FALSE,
0,
0);
gimp_image_flush (image);
}
}
static void
gimp_paint_select_tool_init_buffers (GimpPaintSelectTool *ps_tool,
GimpImage *image,
GimpDrawable *drawable)
{
GimpChannel *channel;
GeglColor *grey = gegl_color_new ("#888");
g_return_if_fail (ps_tool->trimap == NULL);
g_return_if_fail (ps_tool->drawable == NULL);
gimp_item_get_offset (GIMP_ITEM (drawable),
&ps_tool->drawable_off_x,
&ps_tool->drawable_off_y);
ps_tool->drawable_width = gimp_item_get_width (GIMP_ITEM (drawable));
ps_tool->drawable_height = gimp_item_get_height (GIMP_ITEM (drawable));
ps_tool->drawable = gimp_drawable_get_buffer (drawable);
channel = gimp_image_get_mask (image);
ps_tool->image_mask = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
ps_tool->trimap = gegl_buffer_new (gegl_buffer_get_extent (ps_tool->drawable),
babl_format ("Y float"));
gegl_buffer_set_color (ps_tool->trimap, NULL, grey);
g_object_unref (grey);
}
static void
gimp_paint_select_tool_init_scribble (GimpPaintSelectTool *ps_tool)
{
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (ps_tool);
GimpScanConvert *scan_convert;
GimpVector2 points[2];
gint size = options->stroke_width;
gint radius = size / 2;
GeglRectangle square = {0, 0, size, size};
if (ps_tool->scribble)
g_object_unref (ps_tool->scribble);
ps_tool->scribble = gegl_buffer_linear_new (&square, babl_format ("Y float"));
points[0].x = points[1].x = radius;
points[0].y = points[1].y = radius;
points[1].x += 0.01;
points[1].y += 0.01;
scan_convert = gimp_scan_convert_new ();
gimp_scan_convert_add_polyline (scan_convert, 2, points, FALSE);
gimp_scan_convert_stroke (scan_convert, size,
GIMP_JOIN_ROUND, GIMP_CAP_ROUND, 10.0,
0.0, NULL);
gimp_scan_convert_compose (scan_convert, ps_tool->scribble, 0, 0);
gimp_scan_convert_free (scan_convert);
}
static gboolean
gimp_paint_select_tool_paint_scribble (GimpPaintSelectTool *ps_tool)
{
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (ps_tool);
gint size = options->stroke_width;
gint radius = size / 2;
GeglRectangle trimap_area;
GeglRectangle mask_area;
GeglBufferIterator *iter;
gfloat scribble_value;
gboolean overlap = FALSE;
if (! ps_tool->scribble)
{
gimp_paint_select_tool_init_scribble (ps_tool);
}
/* add the scribble to the trimap buffer and check the image mask to see if
an optimization should be triggered.
*/
if (ps_tool->painting_op == GIMP_CHANNEL_OP_ADD)
{
scribble_value = 1.f;
}
else
{
scribble_value = 0.f;
}
iter = gegl_buffer_iterator_new (ps_tool->scribble, NULL, 0,
babl_format ("Y float"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
mask_area = *gegl_buffer_get_extent (ps_tool->scribble);
mask_area.x = ps_tool->last_pos.x - radius;
mask_area.y = ps_tool->last_pos.y - radius;
gegl_rectangle_copy (&trimap_area, &mask_area);
trimap_area.x = mask_area.x - ps_tool->drawable_off_x;
trimap_area.y = mask_area.y - ps_tool->drawable_off_y;
gegl_buffer_iterator_add (iter, ps_tool->trimap, &trimap_area, 0,
babl_format ("Y float"),
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE);
gegl_buffer_iterator_add (iter, ps_tool->image_mask, &mask_area, 0,
babl_format ("Y float"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (iter))
{
gfloat *scribble_pix = iter->items[0].data;
gfloat *trimap_pix = iter->items[1].data;
gfloat *mask_pix = iter->items[2].data;
gint n_pixels = iter->length;
while (n_pixels--)
{
if (*scribble_pix)
{
*trimap_pix = scribble_value;
if (*mask_pix != scribble_value)
overlap = TRUE;
}
scribble_pix++;
trimap_pix++;
mask_pix++;
}
}
gimp_paint_select_tool_toggle_scribbles_visibility (ps_tool);
return overlap;
}
static void
gimp_paint_select_tool_create_graph (GimpPaintSelectTool *ps_tool)
{
GeglNode *t; /* trimap */
GeglNode *m; /* mask */
GeglNode *d; /* drawable */
GeglNode *crop;
GeglNode *translate = NULL;
ps_tool->graph = gegl_node_new ();
m = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:buffer-source",
"buffer", ps_tool->image_mask,
NULL);
crop = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:crop",
"x", (gdouble) ps_tool->drawable_off_x,
"y", (gdouble) ps_tool->drawable_off_y,
"width", (gdouble) gegl_buffer_get_width (ps_tool->drawable),
"height", (gdouble) gegl_buffer_get_height (ps_tool->drawable),
NULL);
ps_tool->threshold_node = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:threshold",
NULL);
if (ps_tool->drawable_off_x || ps_tool->drawable_off_y)
{
translate = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:translate",
"x", -1.0 * ps_tool->drawable_off_x,
"y", -1.0 * ps_tool->drawable_off_y,
NULL);
}
d = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:buffer-source",
"buffer", ps_tool->drawable,
NULL);
t = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:buffer-source",
"buffer", ps_tool->trimap,
NULL);
ps_tool->ps_node = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:paint-select",
NULL);
ps_tool->render_node = gegl_node_new_child (ps_tool->graph,
"operation", "gegl:buffer-sink",
NULL);
gegl_node_link_many (m, ps_tool->threshold_node, crop, NULL);
if (translate)
gegl_node_link_many (crop, translate, ps_tool->ps_node, ps_tool->render_node, NULL);
else
gegl_node_link_many (crop, ps_tool->ps_node, ps_tool->render_node, NULL);
gegl_node_connect (d, "output", ps_tool->ps_node, "aux");
gegl_node_connect (t, "output", ps_tool->ps_node, "aux2");
}
static void
gimp_paint_select_tool_toggle_scribbles_visibility (GimpPaintSelectTool *ps_tool)
{
GimpTool *tool = GIMP_TOOL (ps_tool);
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (tool);
if (options->show_scribbles)
{
GeglColor *black = gegl_color_new ("black");
gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
ps_tool->trimap,
ps_tool->drawable_off_x,
ps_tool->drawable_off_y,
black,
TRUE);
g_object_unref (black);
}
else
{
gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
NULL, 0, 0, NULL, FALSE);
}
}
static GeglRectangle
gimp_paint_select_tool_get_local_region (GimpPaintSelectTool *ps_tool,
GimpDisplay *display)
{
GimpPaintSelectOptions *options = GIMP_PAINT_SELECT_TOOL_GET_OPTIONS (ps_tool);
GimpDisplayShell *shell;
GeglRectangle drawable_region;
GeglRectangle viewport;
GeglRectangle local_region;
gdouble x, y, w, h;
gint radius = ceil (options->stroke_width / 2.0);
shell = gimp_display_get_shell (display);
gimp_display_shell_scroll_get_viewport (shell, &x, &y, &w, &h);
viewport.x = (gint) x - radius;
viewport.y = (gint) y - radius;
viewport.width = (gint) w + options->stroke_width;
viewport.height = (gint) h + options->stroke_width;
drawable_region.x = ps_tool->drawable_off_x;
drawable_region.y = ps_tool->drawable_off_y;
drawable_region.width = ps_tool->drawable_width;
drawable_region.height = ps_tool->drawable_height;
gegl_rectangle_intersect (&local_region, &viewport, &drawable_region);
local_region.x -= ps_tool->drawable_off_x;
local_region.y -= ps_tool->drawable_off_y;
g_printerr ("local region: (%d,%d) %d x %d\n",
local_region.x, local_region.y,
local_region.width, local_region.height);
return local_region;
}
static gfloat
euclidean_distance (gint x1,
gint y1,
gint x2,
gint y2)
{
return sqrtf ((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}