/* 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 "paint-types.h" #include "core/pika.h" #include "core/pikabrush.h" #include "core/pikadrawable.h" #include "core/pikadynamics.h" #include "core/pikagradient.h" #include "core/pikaimage.h" #include "core/pikasymmetry.h" #include "pikaairbrush.h" #include "pikaairbrushoptions.h" #include "pika-intl.h" #define STAMP_MAX_FPS 60 enum { STAMP, LAST_SIGNAL }; static void pika_airbrush_finalize (GObject *object); static void pika_airbrush_paint (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, PikaSymmetry *sym, PikaPaintState paint_state, guint32 time); static void pika_airbrush_motion (PikaPaintCore *paint_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, PikaSymmetry *sym); static gboolean pika_airbrush_timeout (gpointer data); G_DEFINE_TYPE (PikaAirbrush, pika_airbrush, PIKA_TYPE_PAINTBRUSH) #define parent_class pika_airbrush_parent_class static guint airbrush_signals[LAST_SIGNAL] = { 0 }; void pika_airbrush_register (Pika *pika, PikaPaintRegisterCallback callback) { (* callback) (pika, PIKA_TYPE_AIRBRUSH, PIKA_TYPE_AIRBRUSH_OPTIONS, "pika-airbrush", _("Airbrush"), "pika-tool-airbrush"); } static void pika_airbrush_class_init (PikaAirbrushClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass); object_class->finalize = pika_airbrush_finalize; paint_core_class->paint = pika_airbrush_paint; airbrush_signals[STAMP] = g_signal_new ("stamp", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaAirbrushClass, stamp), NULL, NULL, NULL, G_TYPE_NONE, 0); } static void pika_airbrush_init (PikaAirbrush *airbrush) { } static void pika_airbrush_finalize (GObject *object) { PikaAirbrush *airbrush = PIKA_AIRBRUSH (object); if (airbrush->timeout_id) { g_source_remove (airbrush->timeout_id); airbrush->timeout_id = 0; } g_clear_object (&airbrush->sym); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_airbrush_paint (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, PikaSymmetry *sym, PikaPaintState paint_state, guint32 time) { PikaAirbrush *airbrush = PIKA_AIRBRUSH (paint_core); PikaAirbrushOptions *options = PIKA_AIRBRUSH_OPTIONS (paint_options); PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics; PikaCoords coords; g_return_if_fail (g_list_length (drawables) == 1); if (airbrush->timeout_id) { g_source_remove (airbrush->timeout_id); airbrush->timeout_id = 0; } switch (paint_state) { case PIKA_PAINT_STATE_INIT: PIKA_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawables, paint_options, sym, paint_state, time); break; case PIKA_PAINT_STATE_MOTION: coords = *(pika_symmetry_get_origin (sym)); pika_airbrush_motion (paint_core, drawables->data, paint_options, sym); if ((options->rate != 0.0) && ! options->motion_only) { PikaImage *image = pika_item_get_image (PIKA_ITEM (drawables->data)); gdouble fade_point; gdouble dynamic_rate; gint timeout; fade_point = pika_paint_options_get_fade (paint_options, image, paint_core->pixel_dist); airbrush->drawable = drawables->data; airbrush->paint_options = paint_options; pika_symmetry_set_origin (sym, drawables->data, &coords); if (airbrush->sym) g_object_unref (airbrush->sym); airbrush->sym = g_object_ref (sym); /* Base our timeout on the original stroke. */ airbrush->coords = coords; dynamic_rate = pika_dynamics_get_linear_value (dynamics, PIKA_DYNAMICS_OUTPUT_RATE, &coords, paint_options, fade_point); timeout = (1000.0 / STAMP_MAX_FPS) / ((options->rate / 100.0) * dynamic_rate); airbrush->timeout_id = g_timeout_add_full (G_PRIORITY_HIGH, timeout, pika_airbrush_timeout, airbrush, NULL); } break; case PIKA_PAINT_STATE_FINISH: PIKA_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawables, paint_options, sym, paint_state, time); g_clear_object (&airbrush->sym); break; } } static void pika_airbrush_motion (PikaPaintCore *paint_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, PikaSymmetry *sym) { PikaAirbrushOptions *options = PIKA_AIRBRUSH_OPTIONS (paint_options); PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics; PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); gdouble opacity; gdouble fade_point; PikaCoords *coords; fade_point = pika_paint_options_get_fade (paint_options, image, paint_core->pixel_dist); coords = pika_symmetry_get_origin (sym); opacity = (options->flow / 100.0 * pika_dynamics_get_linear_value (dynamics, PIKA_DYNAMICS_OUTPUT_FLOW, coords, paint_options, fade_point)); _pika_paintbrush_motion (paint_core, drawable, paint_options, sym, opacity); } static gboolean pika_airbrush_timeout (gpointer data) { PikaAirbrush *airbrush = PIKA_AIRBRUSH (data); airbrush->timeout_id = 0; g_signal_emit (airbrush, airbrush_signals[STAMP], 0); return G_SOURCE_REMOVE; } /* public functions */ void pika_airbrush_stamp (PikaAirbrush *airbrush) { GList *drawables; g_return_if_fail (PIKA_IS_AIRBRUSH (airbrush)); pika_symmetry_set_origin (airbrush->sym, airbrush->drawable, &airbrush->coords); drawables = g_list_prepend (NULL, airbrush->drawable), pika_airbrush_paint (PIKA_PAINT_CORE (airbrush), drawables, airbrush->paint_options, airbrush->sym, PIKA_PAINT_STATE_MOTION, 0); g_list_free (drawables); pika_symmetry_clear_origin (airbrush->sym); }