summaryrefslogtreecommitdiffstats
path: root/app/tools/gimppainttool-paint.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/tools/gimppainttool-paint.c')
-rw-r--r--app/tools/gimppainttool-paint.c540
1 files changed, 540 insertions, 0 deletions
diff --git a/app/tools/gimppainttool-paint.c b/app/tools/gimppainttool-paint.c
new file mode 100644
index 0000000..88cd526
--- /dev/null
+++ b/app/tools/gimppainttool-paint.c
@@ -0,0 +1,540 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "tools-types.h"
+
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimpprojection.h"
+
+#include "paint/gimppaintcore.h"
+#include "paint/gimppaintoptions.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-utils.h"
+
+#include "gimppainttool.h"
+#include "gimppainttool-paint.h"
+
+
+#define DISPLAY_UPDATE_INTERVAL 10000 /* microseconds */
+
+
+#define PAINT_FINISH NULL
+
+
+typedef struct
+{
+ GimpPaintTool *paint_tool;
+ GimpPaintToolPaintFunc func;
+ union
+ {
+ gpointer data;
+ gboolean *finished;
+ };
+} PaintItem;
+
+typedef struct
+{
+ GimpCoords coords;
+ guint32 time;
+} InterpolateData;
+
+
+/* local function prototypes */
+
+static gboolean gimp_paint_tool_paint_use_thread (GimpPaintTool *paint_tool);
+static gpointer gimp_paint_tool_paint_thread (gpointer data);
+
+static gboolean gimp_paint_tool_paint_timeout (GimpPaintTool *paint_tool);
+
+static void gimp_paint_tool_paint_interpolate (GimpPaintTool *paint_tool,
+ InterpolateData *data);
+
+
+/* static variables */
+
+static GThread *paint_thread;
+
+static GMutex paint_mutex;
+static GCond paint_cond;
+
+static GQueue paint_queue = G_QUEUE_INIT;
+static GMutex paint_queue_mutex;
+static GCond paint_queue_cond;
+
+static guint paint_timeout_id;
+static volatile gboolean paint_timeout_pending;
+
+
+/* private functions */
+
+
+static gboolean
+gimp_paint_tool_paint_use_thread (GimpPaintTool *paint_tool)
+{
+ if (! paint_tool->draw_line)
+ {
+ if (! paint_thread)
+ {
+ static gint use_paint_thread = -1;
+
+ if (use_paint_thread < 0)
+ use_paint_thread = g_getenv ("GIMP_NO_PAINT_THREAD") == NULL;
+
+ if (use_paint_thread)
+ {
+ paint_thread = g_thread_new ("paint",
+ gimp_paint_tool_paint_thread, NULL);
+ }
+ }
+
+ return paint_thread != NULL;
+ }
+
+ return FALSE;
+}
+
+static gpointer
+gimp_paint_tool_paint_thread (gpointer data)
+{
+ g_mutex_lock (&paint_queue_mutex);
+
+ while (TRUE)
+ {
+ PaintItem *item;
+
+ while (! (item = g_queue_pop_head (&paint_queue)))
+ g_cond_wait (&paint_queue_cond, &paint_queue_mutex);
+
+ if (item->func == PAINT_FINISH)
+ {
+ *item->finished = TRUE;
+ g_cond_signal (&paint_queue_cond);
+ }
+ else
+ {
+ g_mutex_unlock (&paint_queue_mutex);
+ g_mutex_lock (&paint_mutex);
+
+ while (paint_timeout_pending)
+ g_cond_wait (&paint_cond, &paint_mutex);
+
+ item->func (item->paint_tool, item->data);
+
+ g_mutex_unlock (&paint_mutex);
+ g_mutex_lock (&paint_queue_mutex);
+ }
+
+ g_slice_free (PaintItem, item);
+ }
+
+ g_mutex_unlock (&paint_queue_mutex);
+
+ return NULL;
+}
+
+static gboolean
+gimp_paint_tool_paint_timeout (GimpPaintTool *paint_tool)
+{
+ GimpPaintCore *core = paint_tool->core;
+ GimpDrawable *drawable = paint_tool->drawable;
+ gboolean update;
+
+ paint_timeout_pending = TRUE;
+
+ g_mutex_lock (&paint_mutex);
+
+ paint_tool->paint_x = core->last_paint.x;
+ paint_tool->paint_y = core->last_paint.y;
+
+ update = gimp_drawable_flush_paint (drawable);
+
+ if (update && GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_flush)
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_flush (paint_tool);
+
+ paint_timeout_pending = FALSE;
+ g_cond_signal (&paint_cond);
+
+ g_mutex_unlock (&paint_mutex);
+
+ if (update)
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (paint_tool);
+ GimpDisplay *display = paint_tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (paint_tool->snap_brush)
+ gimp_draw_tool_pause (draw_tool);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (display);
+
+ if (paint_tool->snap_brush)
+ gimp_draw_tool_resume (draw_tool);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gimp_paint_tool_paint_interpolate (GimpPaintTool *paint_tool,
+ InterpolateData *data)
+{
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ GimpPaintCore *core = paint_tool->core;
+ GimpDrawable *drawable = paint_tool->drawable;
+
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &data->coords, data->time);
+
+ g_slice_free (InterpolateData, data);
+}
+
+
+/* public functions */
+
+
+gboolean
+gimp_paint_tool_paint_start (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ const GimpCoords *coords,
+ guint32 time,
+ gboolean constrain,
+ GError **error)
+{
+ GimpTool *tool;
+ GimpPaintOptions *paint_options;
+ GimpPaintCore *core;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GimpCoords curr_coords;
+ gint off_x, off_y;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_TOOL (paint_tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (paint_tool->display == NULL, FALSE);
+
+ tool = GIMP_TOOL (paint_tool);
+ paint_tool = GIMP_PAINT_TOOL (paint_tool);
+ paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ core = paint_tool->core;
+ shell = gimp_display_get_shell (display);
+ image = gimp_display_get_image (display);
+ drawable = gimp_image_get_active_drawable (image);
+
+ curr_coords = *coords;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ curr_coords.x -= off_x;
+ curr_coords.y -= off_y;
+
+ paint_tool->paint_x = curr_coords.x;
+ paint_tool->paint_y = curr_coords.y;
+
+ /* If we use a separate paint thread, enter paint mode before starting the
+ * paint core
+ */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ gimp_drawable_start_paint (drawable);
+
+ /* Prepare to start the paint core */
+ if (GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_prepare)
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_prepare (paint_tool, display);
+
+ /* Start the paint core */
+ if (! gimp_paint_core_start (core,
+ drawable, paint_options, &curr_coords,
+ error))
+ {
+ gimp_drawable_end_paint (drawable);
+
+ return FALSE;
+ }
+
+ paint_tool->display = display;
+ paint_tool->drawable = drawable;
+
+ if ((display != tool->display) || ! paint_tool->draw_line)
+ {
+ /* If this is a new display, reset the "last stroke's endpoint"
+ * because there is none
+ */
+ if (display != tool->display)
+ core->start_coords = core->cur_coords;
+
+ core->last_coords = core->cur_coords;
+
+ core->distance = 0.0;
+ core->pixel_dist = 0.0;
+ }
+ else if (paint_tool->draw_line)
+ {
+ gdouble offset_angle;
+ gdouble xres, yres;
+
+ gimp_display_shell_get_constrained_line_params (shell,
+ &offset_angle,
+ &xres, &yres);
+
+ /* If shift is down and this is not the first paint
+ * stroke, then draw a line from the last coords to the pointer
+ */
+ gimp_paint_core_round_line (core, paint_options,
+ constrain, offset_angle, xres, yres);
+ }
+
+ /* Notify subclasses */
+ if (gimp_paint_tool_paint_use_thread (paint_tool) &&
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_start)
+ {
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_start (paint_tool);
+ }
+
+ /* Let the specific painting function initialize itself */
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_INIT, time);
+
+ /* Paint to the image */
+ if (paint_tool->draw_line)
+ {
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &core->cur_coords, time);
+ }
+ else
+ {
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+ }
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (display);
+
+ /* Start the display update timeout */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ {
+ paint_timeout_id = g_timeout_add_full (
+ G_PRIORITY_HIGH_IDLE,
+ DISPLAY_UPDATE_INTERVAL / 1000,
+ (GSourceFunc) gimp_paint_tool_paint_timeout,
+ paint_tool, NULL);
+ }
+
+ return TRUE;
+}
+
+void
+gimp_paint_tool_paint_end (GimpPaintTool *paint_tool,
+ guint32 time,
+ gboolean cancel)
+{
+ GimpPaintOptions *paint_options;
+ GimpPaintCore *core;
+ GimpDrawable *drawable;
+
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (paint_tool));
+ g_return_if_fail (paint_tool->display != NULL);
+
+ paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ core = paint_tool->core;
+ drawable = paint_tool->drawable;
+
+ /* Process remaining paint items */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ {
+ PaintItem *item;
+ gboolean finished = FALSE;
+ guint64 end_time;
+
+ g_return_if_fail (gimp_paint_tool_paint_is_active (paint_tool));
+
+ g_source_remove (paint_timeout_id);
+ paint_timeout_id = 0;
+
+ item = g_slice_new (PaintItem);
+
+ item->paint_tool = paint_tool;
+ item->func = PAINT_FINISH;
+ item->finished = &finished;
+
+ g_mutex_lock (&paint_queue_mutex);
+
+ g_queue_push_tail (&paint_queue, item);
+ g_cond_signal (&paint_queue_cond);
+
+ end_time = g_get_monotonic_time () + DISPLAY_UPDATE_INTERVAL;
+
+ while (! finished)
+ {
+ if (! g_cond_wait_until (&paint_queue_cond, &paint_queue_mutex,
+ end_time))
+ {
+ g_mutex_unlock (&paint_queue_mutex);
+
+ gimp_paint_tool_paint_timeout (paint_tool);
+
+ g_mutex_lock (&paint_queue_mutex);
+
+ end_time = g_get_monotonic_time () + DISPLAY_UPDATE_INTERVAL;
+ }
+ }
+
+ g_mutex_unlock (&paint_queue_mutex);
+ }
+
+ /* Let the specific painting function finish up */
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_FINISH, time);
+
+ if (cancel)
+ gimp_paint_core_cancel (core, drawable);
+ else
+ gimp_paint_core_finish (core, drawable, TRUE);
+
+ /* Notify subclasses */
+ if (gimp_paint_tool_paint_use_thread (paint_tool) &&
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_end)
+ {
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_end (paint_tool);
+ }
+
+ /* Exit paint mode */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ gimp_drawable_end_paint (drawable);
+
+ paint_tool->display = NULL;
+ paint_tool->drawable = NULL;
+}
+
+gboolean
+gimp_paint_tool_paint_is_active (GimpPaintTool *paint_tool)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_TOOL (paint_tool), FALSE);
+
+ return paint_tool->drawable != NULL &&
+ gimp_drawable_is_painting (paint_tool->drawable);
+}
+
+void
+gimp_paint_tool_paint_push (GimpPaintTool *paint_tool,
+ GimpPaintToolPaintFunc func,
+ gpointer data)
+{
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (paint_tool));
+ g_return_if_fail (func != NULL);
+
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ {
+ PaintItem *item;
+
+ g_return_if_fail (gimp_paint_tool_paint_is_active (paint_tool));
+
+ /* Push an item to the queue, to be processed by the paint thread */
+
+ item = g_slice_new (PaintItem);
+
+ item->paint_tool = paint_tool;
+ item->func = func;
+ item->data = data;
+
+ g_mutex_lock (&paint_queue_mutex);
+
+ g_queue_push_tail (&paint_queue, item);
+ g_cond_signal (&paint_queue_cond);
+
+ g_mutex_unlock (&paint_queue_mutex);
+ }
+ else
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (paint_tool);
+ GimpDisplay *display = paint_tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ /* Paint directly */
+
+ gimp_draw_tool_pause (draw_tool);
+
+ func (paint_tool, data);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (display);
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+}
+
+void
+gimp_paint_tool_paint_motion (GimpPaintTool *paint_tool,
+ const GimpCoords *coords,
+ guint32 time)
+{
+ GimpPaintOptions *paint_options;
+ GimpPaintCore *core;
+ GimpDrawable *drawable;
+ InterpolateData *data;
+ gint off_x, off_y;
+
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (paint_tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (paint_tool->display != NULL);
+
+ paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ core = paint_tool->core;
+ drawable = paint_tool->drawable;
+
+ data = g_slice_new (InterpolateData);
+
+ data->coords = *coords;
+ data->time = time;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ data->coords.x -= off_x;
+ data->coords.y -= off_y;
+
+ paint_tool->cursor_x = data->coords.x;
+ paint_tool->cursor_y = data->coords.y;
+
+ gimp_paint_core_smooth_coords (core, paint_options, &data->coords);
+
+ /* Don't paint while the Shift key is pressed for line drawing */
+ if (paint_tool->draw_line)
+ {
+ gimp_paint_core_set_current_coords (core, &data->coords);
+
+ g_slice_free (InterpolateData, data);
+
+ return;
+ }
+
+ gimp_paint_tool_paint_push (
+ paint_tool,
+ (GimpPaintToolPaintFunc) gimp_paint_tool_paint_interpolate,
+ data);
+}