578 lines
16 KiB
C
578 lines
16 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* 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 "libpikamath/pikamath.h"
|
|
|
|
#include "tools-types.h"
|
|
|
|
#include "core/pikadrawable.h"
|
|
#include "core/pikaimage.h"
|
|
#include "core/pikalayer.h"
|
|
#include "core/pikalayermask.h"
|
|
#include "core/pikaprojection.h"
|
|
|
|
#include "paint/pikapaintcore.h"
|
|
#include "paint/pikapaintoptions.h"
|
|
|
|
#include "display/pikadisplay.h"
|
|
#include "display/pikadisplayshell.h"
|
|
#include "display/pikadisplayshell-utils.h"
|
|
|
|
#include "pikapainttool.h"
|
|
#include "pikapainttool-paint.h"
|
|
#include "pikatools-utils.h"
|
|
|
|
|
|
#define DISPLAY_UPDATE_INTERVAL 10000 /* microseconds */
|
|
|
|
|
|
#define PAINT_FINISH NULL
|
|
|
|
|
|
typedef struct
|
|
{
|
|
PikaPaintTool *paint_tool;
|
|
PikaPaintToolPaintFunc func;
|
|
union
|
|
{
|
|
gpointer data;
|
|
gboolean *finished;
|
|
};
|
|
} PaintItem;
|
|
|
|
typedef struct
|
|
{
|
|
GList *drawables;
|
|
PikaCoords coords;
|
|
guint32 time;
|
|
} InterpolateData;
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static gboolean pika_paint_tool_paint_use_thread (PikaPaintTool *paint_tool);
|
|
static gpointer pika_paint_tool_paint_thread (gpointer data);
|
|
|
|
static gboolean pika_paint_tool_paint_timeout (PikaPaintTool *paint_tool);
|
|
|
|
static void pika_paint_tool_paint_interpolate (PikaPaintTool *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
|
|
pika_paint_tool_paint_use_thread (PikaPaintTool *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 ("PIKA_NO_PAINT_THREAD") == NULL;
|
|
|
|
if (use_paint_thread)
|
|
{
|
|
paint_thread = g_thread_new ("paint",
|
|
pika_paint_tool_paint_thread, NULL);
|
|
}
|
|
}
|
|
|
|
return paint_thread != NULL;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gpointer
|
|
pika_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
|
|
pika_paint_tool_paint_timeout (PikaPaintTool *paint_tool)
|
|
{
|
|
PikaPaintCore *core = paint_tool->core;
|
|
gboolean update = FALSE;
|
|
|
|
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;
|
|
|
|
for (GList *iter = paint_tool->drawables; iter; iter = iter->next)
|
|
{
|
|
update |= pika_drawable_flush_paint (iter->data);
|
|
if (update)
|
|
break;
|
|
}
|
|
|
|
if (update && PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_flush)
|
|
PIKA_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)
|
|
{
|
|
PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (paint_tool);
|
|
PikaDisplay *display = paint_tool->display;
|
|
PikaImage *image = pika_display_get_image (display);
|
|
|
|
if (paint_tool->snap_brush)
|
|
pika_draw_tool_pause (draw_tool);
|
|
|
|
pika_projection_flush_now (pika_image_get_projection (image), TRUE);
|
|
pika_display_flush_now (display);
|
|
|
|
if (paint_tool->snap_brush)
|
|
pika_draw_tool_resume (draw_tool);
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
pika_paint_tool_paint_interpolate (PikaPaintTool *paint_tool,
|
|
InterpolateData *data)
|
|
{
|
|
PikaPaintOptions *paint_options = PIKA_PAINT_TOOL_GET_OPTIONS (paint_tool);
|
|
PikaPaintCore *core = paint_tool->core;
|
|
|
|
pika_paint_core_interpolate (core, data->drawables, paint_options,
|
|
&data->coords, data->time);
|
|
|
|
/* Blink the lock box if required */
|
|
if (core->lock_blink_state == PIKA_PAINT_LOCK_BLINK_PENDING)
|
|
{
|
|
GList *iter;
|
|
PikaLayer *layer;
|
|
|
|
/* Blink the lock only once per stroke */
|
|
core->lock_blink_state = PIKA_PAINT_LOCK_BLINKED;
|
|
|
|
for (iter = data->drawables; iter; iter = g_list_next (iter))
|
|
{
|
|
layer = PIKA_IS_LAYER_MASK (iter->data) ?
|
|
PIKA_LAYER_MASK (iter->data)->layer :
|
|
PIKA_LAYER (iter->data);
|
|
|
|
if (pika_item_get_lock_position (PIKA_ITEM (layer)))
|
|
pika_tools_blink_lock_box (PIKA_CONTEXT (paint_options)->pika,
|
|
PIKA_ITEM (layer));
|
|
}
|
|
}
|
|
|
|
g_list_free (data->drawables);
|
|
g_slice_free (InterpolateData, data);
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
|
|
gboolean
|
|
pika_paint_tool_paint_start (PikaPaintTool *paint_tool,
|
|
PikaDisplay *display,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
gboolean constrain,
|
|
GError **error)
|
|
{
|
|
PikaTool *tool;
|
|
PikaPaintOptions *paint_options;
|
|
PikaPaintCore *core;
|
|
PikaDisplayShell *shell;
|
|
PikaImage *image;
|
|
GList *drawables;
|
|
GList *iter;
|
|
PikaCoords curr_coords;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PAINT_TOOL (paint_tool), FALSE);
|
|
g_return_val_if_fail (PIKA_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 = PIKA_TOOL (paint_tool);
|
|
paint_tool = PIKA_PAINT_TOOL (paint_tool);
|
|
paint_options = PIKA_PAINT_TOOL_GET_OPTIONS (paint_tool);
|
|
core = paint_tool->core;
|
|
shell = pika_display_get_shell (display);
|
|
image = pika_display_get_image (display);
|
|
drawables = pika_image_get_selected_drawables (image);
|
|
|
|
g_return_val_if_fail (g_list_length (drawables) == 1 ||
|
|
(g_list_length (drawables) > 1 && paint_tool->can_multi_paint),
|
|
FALSE);
|
|
|
|
curr_coords = *coords;
|
|
|
|
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 (pika_paint_tool_paint_use_thread (paint_tool))
|
|
for (iter = drawables; iter; iter = iter->next)
|
|
pika_drawable_start_paint (iter->data);
|
|
|
|
/* Prepare to start the paint core */
|
|
if (PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_prepare)
|
|
PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_prepare (paint_tool, display);
|
|
|
|
/* Start the paint core */
|
|
if (! pika_paint_core_start (core,
|
|
drawables, paint_options, &curr_coords,
|
|
error))
|
|
{
|
|
for (iter = drawables; iter; iter = iter->next)
|
|
pika_drawable_end_paint (iter->data);
|
|
g_list_free (drawables);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
paint_tool->display = display;
|
|
g_list_free (paint_tool->drawables);
|
|
paint_tool->drawables = drawables;
|
|
|
|
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;
|
|
|
|
pika_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
|
|
*/
|
|
pika_paint_core_round_line (core, paint_options,
|
|
constrain, offset_angle, xres, yres);
|
|
}
|
|
|
|
/* Notify subclasses */
|
|
if (pika_paint_tool_paint_use_thread (paint_tool) &&
|
|
PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_start)
|
|
{
|
|
PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_start (paint_tool);
|
|
}
|
|
|
|
/* Let the specific painting function initialize itself */
|
|
pika_paint_core_paint (core, drawables, paint_options,
|
|
PIKA_PAINT_STATE_INIT, time);
|
|
|
|
/* Paint to the image */
|
|
if (paint_tool->draw_line)
|
|
{
|
|
pika_paint_core_interpolate (core, drawables, paint_options,
|
|
&core->cur_coords, time);
|
|
}
|
|
else
|
|
{
|
|
pika_paint_core_paint (core, drawables, paint_options,
|
|
PIKA_PAINT_STATE_MOTION, time);
|
|
}
|
|
|
|
pika_projection_flush_now (pika_image_get_projection (image), TRUE);
|
|
pika_display_flush_now (display);
|
|
|
|
/* Start the display update timeout */
|
|
if (pika_paint_tool_paint_use_thread (paint_tool))
|
|
{
|
|
paint_timeout_id = g_timeout_add_full (
|
|
G_PRIORITY_HIGH_IDLE,
|
|
DISPLAY_UPDATE_INTERVAL / 1000,
|
|
(GSourceFunc) pika_paint_tool_paint_timeout,
|
|
paint_tool, NULL);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
pika_paint_tool_paint_end (PikaPaintTool *paint_tool,
|
|
guint32 time,
|
|
gboolean cancel)
|
|
{
|
|
PikaPaintOptions *paint_options;
|
|
PikaPaintCore *core;
|
|
GList *drawables;
|
|
|
|
g_return_if_fail (PIKA_IS_PAINT_TOOL (paint_tool));
|
|
g_return_if_fail (paint_tool->display != NULL);
|
|
|
|
paint_options = PIKA_PAINT_TOOL_GET_OPTIONS (paint_tool);
|
|
core = paint_tool->core;
|
|
drawables = paint_tool->drawables;
|
|
|
|
/* Process remaining paint items */
|
|
if (pika_paint_tool_paint_use_thread (paint_tool))
|
|
{
|
|
PaintItem *item;
|
|
gboolean finished = FALSE;
|
|
guint64 end_time;
|
|
|
|
g_return_if_fail (pika_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);
|
|
|
|
pika_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 */
|
|
pika_paint_core_paint (core, drawables, paint_options,
|
|
PIKA_PAINT_STATE_FINISH, time);
|
|
|
|
if (cancel)
|
|
pika_paint_core_cancel (core, drawables);
|
|
else
|
|
pika_paint_core_finish (core, drawables, TRUE);
|
|
|
|
/* Notify subclasses */
|
|
if (pika_paint_tool_paint_use_thread (paint_tool) &&
|
|
PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_end)
|
|
{
|
|
PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->paint_end (paint_tool);
|
|
}
|
|
|
|
/* Exit paint mode */
|
|
if (pika_paint_tool_paint_use_thread (paint_tool))
|
|
for (GList *iter = drawables; iter; iter = iter->next)
|
|
pika_drawable_end_paint (iter->data);
|
|
|
|
paint_tool->display = NULL;
|
|
g_clear_pointer (&paint_tool->drawables, g_list_free);
|
|
}
|
|
|
|
gboolean
|
|
pika_paint_tool_paint_is_active (PikaPaintTool *paint_tool)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_PAINT_TOOL (paint_tool), FALSE);
|
|
|
|
for (GList *iter = paint_tool->drawables; iter; iter = iter->next)
|
|
{
|
|
if (pika_drawable_is_painting (iter->data))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
pika_paint_tool_paint_push (PikaPaintTool *paint_tool,
|
|
PikaPaintToolPaintFunc func,
|
|
gpointer data)
|
|
{
|
|
g_return_if_fail (PIKA_IS_PAINT_TOOL (paint_tool));
|
|
g_return_if_fail (func != NULL);
|
|
|
|
if (pika_paint_tool_paint_use_thread (paint_tool))
|
|
{
|
|
PaintItem *item;
|
|
|
|
g_return_if_fail (pika_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
|
|
{
|
|
PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (paint_tool);
|
|
PikaDisplay *display = paint_tool->display;
|
|
PikaImage *image = pika_display_get_image (display);
|
|
|
|
/* Paint directly */
|
|
|
|
pika_draw_tool_pause (draw_tool);
|
|
|
|
func (paint_tool, data);
|
|
|
|
pika_projection_flush_now (pika_image_get_projection (image), TRUE);
|
|
pika_display_flush_now (display);
|
|
|
|
pika_draw_tool_resume (draw_tool);
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_paint_tool_paint_motion (PikaPaintTool *paint_tool,
|
|
const PikaCoords *coords,
|
|
guint32 time)
|
|
{
|
|
PikaPaintOptions *paint_options;
|
|
PikaPaintCore *core;
|
|
GList *drawables;
|
|
InterpolateData *data;
|
|
|
|
g_return_if_fail (PIKA_IS_PAINT_TOOL (paint_tool));
|
|
g_return_if_fail (coords != NULL);
|
|
g_return_if_fail (paint_tool->display != NULL);
|
|
|
|
paint_options = PIKA_PAINT_TOOL_GET_OPTIONS (paint_tool);
|
|
core = paint_tool->core;
|
|
drawables = paint_tool->drawables;
|
|
|
|
data = g_slice_new (InterpolateData);
|
|
|
|
data->drawables = g_list_copy (drawables);
|
|
data->coords = *coords;
|
|
data->time = time;
|
|
|
|
paint_tool->cursor_x = data->coords.x;
|
|
paint_tool->cursor_y = data->coords.y;
|
|
|
|
pika_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)
|
|
{
|
|
pika_paint_core_set_current_coords (core, &data->coords);
|
|
g_list_free (data->drawables);
|
|
|
|
g_slice_free (InterpolateData, data);
|
|
|
|
return;
|
|
}
|
|
|
|
pika_paint_tool_paint_push (paint_tool,
|
|
(PikaPaintToolPaintFunc) pika_paint_tool_paint_interpolate,
|
|
data);
|
|
}
|