/* 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 . */ #include "config.h" #include #include #include #include #include #include "libpikamath/pikamath.h" #include "libpikacolor/pikacolor.h" #include "paint-types.h" #include "gegl/pika-gegl-utils.h" #include "core/pika.h" #include "core/pika-palettes.h" #include "core/pikadrawable.h" #include "core/pikaerror.h" #include "core/pikamybrush.h" #include "core/pikapickable.h" #include "core/pikasymmetry.h" #include "pikamybrushcore.h" #include "pikamybrushsurface.h" #include "pikamybrushoptions.h" #include "pika-intl.h" struct _PikaMybrushCorePrivate { PikaMybrush *mybrush; PikaMybrushSurface *surface; GList *brushes; gboolean synthetic; gint64 last_time; }; /* local function prototypes */ static void pika_mybrush_core_finalize (GObject *object); static gboolean pika_mybrush_core_start (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, const PikaCoords *coords, GError **error); static void pika_mybrush_core_interpolate (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, guint32 time); static void pika_mybrush_core_paint (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, PikaSymmetry *sym, PikaPaintState paint_state, guint32 time); static void pika_mybrush_core_motion (PikaPaintCore *paint_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, PikaSymmetry *sym, guint32 time); static void pika_mybrush_core_create_brushes (PikaMybrushCore *mybrush, PikaDrawable *drawable, PikaPaintOptions *paint_options, PikaSymmetry *sym); G_DEFINE_TYPE_WITH_PRIVATE (PikaMybrushCore, pika_mybrush_core, PIKA_TYPE_PAINT_CORE) #define parent_class pika_mybrush_core_parent_class void pika_mybrush_core_register (Pika *pika, PikaPaintRegisterCallback callback) { (* callback) (pika, PIKA_TYPE_MYBRUSH_CORE, PIKA_TYPE_MYBRUSH_OPTIONS, "pika-mybrush", _("Mybrush"), "pika-tool-mypaint-brush"); } static void pika_mybrush_core_class_init (PikaMybrushCoreClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass); object_class->finalize = pika_mybrush_core_finalize; paint_core_class->start = pika_mybrush_core_start; paint_core_class->paint = pika_mybrush_core_paint; paint_core_class->interpolate = pika_mybrush_core_interpolate; } static void pika_mybrush_core_init (PikaMybrushCore *mybrush) { mybrush->private = pika_mybrush_core_get_instance_private (mybrush); } static void pika_mybrush_core_finalize (GObject *object) { PikaMybrushCore *core = PIKA_MYBRUSH_CORE (object); if (core->private->brushes) { g_list_free_full (core->private->brushes, (GDestroyNotify) mypaint_brush_unref); core->private->brushes = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean pika_mybrush_core_start (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, const PikaCoords *coords, GError **error) { PikaMybrushCore *core = PIKA_MYBRUSH_CORE (paint_core); PikaContext *context = PIKA_CONTEXT (paint_options); core->private->mybrush = pika_context_get_mybrush (context); if (! core->private->mybrush) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("No MyPaint brushes available for use with this tool.")); return FALSE; } return TRUE; } static void pika_mybrush_core_interpolate (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, guint32 time) { PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core); /* If this is the first motion the brush has received then * we're being asked to draw a synthetic stroke in line mode */ if (mybrush->private->last_time < 0) { PikaCoords saved_coords = paint_core->cur_coords; paint_core->cur_coords = paint_core->last_coords; mybrush->private->synthetic = TRUE; pika_paint_core_paint (paint_core, drawables, paint_options, PIKA_PAINT_STATE_MOTION, time); paint_core->cur_coords = saved_coords; } pika_paint_core_paint (paint_core, drawables, paint_options, PIKA_PAINT_STATE_MOTION, time); paint_core->last_coords = paint_core->cur_coords; } static void pika_mybrush_core_paint (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, PikaSymmetry *sym, PikaPaintState paint_state, guint32 time) { PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core); PikaContext *context = PIKA_CONTEXT (paint_options); gint offset_x; gint offset_y; PikaRGB fg; g_return_if_fail (g_list_length (drawables) == 1); switch (paint_state) { case PIKA_PAINT_STATE_INIT: pika_context_get_foreground (context, &fg); pika_palettes_add_color_history (context->pika, &fg); pika_symmetry_set_stateful (sym, TRUE); pika_item_get_offset (drawables->data, &offset_x, &offset_y); mybrush->private->surface = pika_mypaint_surface_new (pika_drawable_get_buffer (drawables->data), pika_drawable_get_active_mask (drawables->data), paint_core->mask_buffer, -offset_x, -offset_y, PIKA_MYBRUSH_OPTIONS (paint_options)); pika_mybrush_core_create_brushes (mybrush, drawables->data, paint_options, sym); mybrush->private->last_time = -1; mybrush->private->synthetic = FALSE; break; case PIKA_PAINT_STATE_MOTION: pika_mybrush_core_motion (paint_core, drawables->data, paint_options, sym, time); break; case PIKA_PAINT_STATE_FINISH: pika_symmetry_set_stateful (sym, FALSE); mypaint_surface_unref ((MyPaintSurface *) mybrush->private->surface); mybrush->private->surface = NULL; g_list_free_full (mybrush->private->brushes, (GDestroyNotify) mypaint_brush_unref); mybrush->private->brushes = NULL; break; } } static void pika_mybrush_core_motion (PikaPaintCore *paint_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, PikaSymmetry *sym, guint32 time) { PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core); MyPaintRectangle rect; PikaCoords origin; GList *iter; gdouble dt = 0.0; gint off_x, off_y; gint n_strokes; gint i; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); n_strokes = pika_symmetry_get_size (sym); origin = *(pika_symmetry_get_origin (sym)); origin.x -= off_x; origin.y -= off_y; pika_symmetry_set_origin (sym, drawable, &origin); /* The number of strokes may change during a motion, depending on * the type of symmetry. When that happens, reset the brushes. */ if (g_list_length (mybrush->private->brushes) != n_strokes) { pika_mybrush_core_create_brushes (mybrush, drawable, paint_options, sym); } mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface); if (mybrush->private->last_time < 0) { /* First motion, so we need zero pressure events to start the strokes */ for (iter = mybrush->private->brushes, i = 0; iter; iter = g_list_next (iter), i++) { MyPaintBrush *brush = iter->data; PikaCoords coords = *(pika_symmetry_get_coords (sym, i)); mypaint_brush_stroke_to (brush, (MyPaintSurface *) mybrush->private->surface, coords.x, coords.y, 0.0f, coords.xtilt, coords.ytilt, 1.0f /* Pretend the cursor hasn't moved in a while */); } dt = 0.015; } else if (mybrush->private->synthetic) { PikaVector2 v = { paint_core->cur_coords.x - paint_core->last_coords.x, paint_core->cur_coords.y - paint_core->last_coords.y }; dt = 0.0005 * pika_vector2_length_val (v); } else { dt = (time - mybrush->private->last_time) * 0.001; } for (iter = mybrush->private->brushes, i = 0; iter; iter = g_list_next (iter), i++) { MyPaintBrush *brush = iter->data; PikaCoords coords = *(pika_symmetry_get_coords (sym, i)); gdouble pressure = coords.pressure; gboolean expanded; gfloat radius = 100; gint x1, x2, y1, y2; gint offset_change_x, offset_change_y; gint off_x_surf, off_y_surf; gint off_x, off_y; x1 = coords.x - radius; y1 = coords.y - radius; x2 = coords.x + radius; y2 = coords.y + radius; expanded = pika_paint_core_expand_drawable (paint_core, drawable, paint_options, x1, x2, y1, y2, &offset_change_x, &offset_change_y); pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); if (expanded) pika_mypaint_surface_set_buffer (mybrush->private->surface, pika_drawable_get_buffer (drawable), off_x, off_y); pika_mypaint_surface_get_offset (mybrush->private->surface, &off_x_surf, &off_y_surf); coords.x -= off_x_surf; coords.y -= off_y_surf; if (offset_change_x || offset_change_y) { pika_mypaint_surface_set_offset (mybrush->private->surface, off_x_surf + offset_change_x, off_y_surf + offset_change_y); origin = *(pika_symmetry_get_origin (sym)); origin.x += offset_change_x; origin.y += offset_change_y; pika_symmetry_set_origin (sym, drawable, &origin); } mypaint_brush_stroke_to (brush, (MyPaintSurface *) mybrush->private->surface, coords.x, coords.y, pressure, coords.xtilt, coords.ytilt, dt); } mybrush->private->last_time = time; mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface, &rect); if (rect.width > 0 && rect.height > 0) { paint_core->x1 = MIN (paint_core->x1, rect.x); paint_core->y1 = MIN (paint_core->y1, rect.y); paint_core->x2 = MAX (paint_core->x2, rect.x + rect.width); paint_core->y2 = MAX (paint_core->y2, rect.y + rect.height); pika_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height); } } static void pika_mybrush_core_create_brushes (PikaMybrushCore *mybrush, PikaDrawable *drawable, PikaPaintOptions *paint_options, PikaSymmetry *sym) { PikaMybrushOptions *options = PIKA_MYBRUSH_OPTIONS (paint_options); PikaContext *context = PIKA_CONTEXT (paint_options); PikaRGB fg; PikaHSV hsv; gint n_strokes; gint i; if (mybrush->private->brushes) { g_list_free_full (mybrush->private->brushes, (GDestroyNotify) mypaint_brush_unref); mybrush->private->brushes = NULL; } if (options->eraser) pika_context_get_background (context, &fg); else pika_context_get_foreground (context, &fg); pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable), &fg, &fg); pika_rgb_to_hsv (&fg, &hsv); n_strokes = pika_symmetry_get_size (sym); for (i = 0; i < n_strokes; i++) { MyPaintBrush *brush = mypaint_brush_new (); const gchar *brush_data; mypaint_brush_from_defaults (brush); brush_data = pika_mybrush_get_brush_json (mybrush->private->mybrush); if (brush_data) mypaint_brush_from_string (brush, brush_data); if (! mypaint_brush_get_base_value (brush, MYPAINT_BRUSH_SETTING_RESTORE_COLOR)) { mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_COLOR_H, hsv.h); mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_COLOR_S, hsv.s); mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_COLOR_V, hsv.v); } mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC, options->radius); mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_OPAQUE, options->opaque * pika_context_get_opacity (context)); mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_HARDNESS, options->hardness); mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_ERASER, (options->eraser && pika_drawable_has_alpha (drawable)) ? 1.0f : 0.0f); mypaint_brush_new_stroke (brush); mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush); } mybrush->private->brushes = g_list_reverse (mybrush->private->brushes); }