/* 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 "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "paint-types.h" #include "gegl/pika-gegl-utils.h" #include "core/pika.h" #include "core/pikacontainer.h" #include "core/pikadrawable.h" #include "core/pikadynamics.h" #include "core/pikaerror.h" #include "core/pikaimage.h" #include "core/pikaimage-new.h" #include "core/pikapickable.h" #include "core/pikasymmetry.h" #include "pikasourcecore.h" #include "pikasourceoptions.h" #include "pika-intl.h" enum { PROP_0, }; static gboolean pika_source_core_start (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, const PikaCoords *coords, GError **error); static void pika_source_core_paint (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, PikaSymmetry *sym, PikaPaintState paint_state, guint32 time); #if 0 static void pika_source_core_motion (PikaSourceCore *source_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, gboolean self_drawable, PikaSymmetry *sym); #endif static gboolean pika_source_core_real_use_source (PikaSourceCore *source_core, PikaSourceOptions *options); static GeglBuffer * pika_source_core_real_get_source (PikaSourceCore *source_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, gboolean self_drawable, PikaPickable *src_pickable, gint src_offset_x, gint src_offset_y, GeglBuffer *paint_buffer, gint paint_buffer_x, gint paint_buffer_y, gint *paint_area_offset_x, gint *paint_area_offset_y, gint *paint_area_width, gint *paint_area_height, GeglRectangle *src_rect); G_DEFINE_TYPE (PikaSourceCore, pika_source_core, PIKA_TYPE_BRUSH_CORE) #define parent_class pika_source_core_parent_class static void pika_source_core_class_init (PikaSourceCoreClass *klass) { PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass); PikaBrushCoreClass *brush_core_class = PIKA_BRUSH_CORE_CLASS (klass); paint_core_class->start = pika_source_core_start; paint_core_class->paint = pika_source_core_paint; brush_core_class->handles_changing_brush = TRUE; klass->use_source = pika_source_core_real_use_source; klass->get_source = pika_source_core_real_get_source; klass->motion = NULL; } static void pika_source_core_init (PikaSourceCore *source_core) { source_core->set_source = FALSE; source_core->orig_src_x = 0; source_core->orig_src_y = 0; source_core->offset_x = 0; source_core->offset_y = 0; source_core->first_stroke = TRUE; } static gboolean pika_source_core_start (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, const PikaCoords *coords, GError **error) { PikaSourceCore *source_core = PIKA_SOURCE_CORE (paint_core); PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options); if (! PIKA_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawables, paint_options, coords, error)) { return FALSE; } paint_core->use_saved_proj = FALSE; if (! source_core->set_source && pika_source_core_use_source (source_core, options)) { if (! options->src_drawables) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Set a source image first.")); return FALSE; } else if (options->align_mode == PIKA_SOURCE_ALIGN_REGISTERED && g_list_length (drawables) > 1) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("\"Registered\" alignment cannot paint on multiple drawables.")); return FALSE; } if (options->sample_merged && g_list_length (drawables) == 1 && pika_item_get_image (PIKA_ITEM (options->src_drawables->data)) == pika_item_get_image (PIKA_ITEM (drawables->data))) { paint_core->use_saved_proj = TRUE; } } return TRUE; } static void pika_source_core_paint (PikaPaintCore *paint_core, GList *drawables, PikaPaintOptions *paint_options, PikaSymmetry *sym, PikaPaintState paint_state, guint32 time) { PikaSourceCore *source_core = PIKA_SOURCE_CORE (paint_core); PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options); const PikaCoords *coords; /* The source is based on the original stroke */ coords = pika_symmetry_get_origin (sym); switch (paint_state) { case PIKA_PAINT_STATE_INIT: if (source_core->set_source) { g_object_set (options, "src-drawables", drawables, "src-x", (gint) floor (coords->x), "src-y", (gint) floor (coords->y), NULL); /* FIXME(?): subpixel source sampling */ source_core->first_stroke = TRUE; } else if (options->align_mode == PIKA_SOURCE_ALIGN_NO) { g_object_get (options, "src-x", &source_core->orig_src_x, "src-y", &source_core->orig_src_y, NULL); source_core->first_stroke = TRUE; } break; case PIKA_PAINT_STATE_MOTION: if (source_core->set_source) { /* If the control key is down, move the src target and return */ g_object_set (options, "src-drawables", drawables, "src-x", (gint) floor (coords->x), "src-y", (gint) floor (coords->y), NULL); source_core->first_stroke = TRUE; } else { /* otherwise, update the target */ gint src_x; gint src_y; gint dest_x; gint dest_y; dest_x = floor (coords->x); dest_y = floor (coords->y); g_object_get (options, "src-x", &src_x, "src-y", &src_y, NULL); if (options->align_mode == PIKA_SOURCE_ALIGN_REGISTERED) { source_core->offset_x = 0; source_core->offset_y = 0; } else if (options->align_mode == PIKA_SOURCE_ALIGN_FIXED) { source_core->offset_x = src_x - dest_x; source_core->offset_y = src_y - dest_y; } else if (source_core->first_stroke) { source_core->offset_x = src_x - dest_x; source_core->offset_y = src_y - dest_y; source_core->first_stroke = FALSE; } g_object_set (options, "src-x", dest_x + source_core->offset_x, "src-y", dest_y + source_core->offset_y, NULL); for (GList *iter = drawables; iter; iter = iter->next) pika_source_core_motion (source_core, iter->data, paint_options, (g_list_length (drawables) > 1), sym); } break; case PIKA_PAINT_STATE_FINISH: if (options->align_mode == PIKA_SOURCE_ALIGN_NO && ! source_core->first_stroke) { g_object_set (options, "src-x", source_core->orig_src_x, "src-y", source_core->orig_src_y, NULL); } break; default: break; } } void pika_source_core_motion (PikaSourceCore *source_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, gboolean self_drawable, PikaSymmetry *sym) { PikaPaintCore *paint_core = PIKA_PAINT_CORE (source_core); PikaBrushCore *brush_core = PIKA_BRUSH_CORE (source_core); PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options); PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics; PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); PikaPickable *src_pickable = NULL; GeglBuffer *src_buffer = NULL; GeglRectangle src_rect; gint base_src_offset_x; gint base_src_offset_y; gint src_offset_x; gint src_offset_y; GeglBuffer *paint_buffer; gint paint_buffer_x; gint paint_buffer_y; gint paint_area_offset_x; gint paint_area_offset_y; gint paint_area_width; gint paint_area_height; gdouble fade_point; gdouble opacity; PikaLayerMode paint_mode; GeglNode *op; PikaCoords origin; PikaCoords coords; gint src_off_x = 0; gint src_off_y = 0; gint src_x, src_y; gint off_x, off_y; gint n_strokes; gint i; fade_point = pika_paint_options_get_fade (paint_options, image, paint_core->pixel_dist); origin = *(pika_symmetry_get_origin (sym)); pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); coords = origin; coords.x -= off_x; coords.y -= off_y; pika_symmetry_set_origin (sym, drawable, &coords); paint_core->sym = sym; /* Some settings are based on the original stroke. */ opacity = pika_dynamics_get_linear_value (dynamics, PIKA_DYNAMICS_OUTPUT_OPACITY, &origin, paint_options, fade_point); if (opacity == 0.0) return; base_src_offset_x = source_core->offset_x; base_src_offset_y = source_core->offset_y; if (pika_source_core_use_source (source_core, options)) { if (self_drawable) { src_pickable = PIKA_PICKABLE (drawable); } else if (options->sample_merged) { PikaImage *src_image; src_image = pika_pickable_get_image (options->src_drawables->data); if (! pika_paint_core_get_show_all (paint_core)) { src_pickable = PIKA_PICKABLE (src_image); } else { src_pickable = PIKA_PICKABLE (pika_image_get_projection (src_image)); } } else { src_pickable = options->src_pickable; } if (PIKA_IS_ITEM (src_pickable)) pika_item_get_offset (PIKA_ITEM (src_pickable), &src_off_x, &src_off_y); } g_object_get (options, "src-x", &src_x, "src-y", &src_y, NULL); pika_brush_core_eval_transform_dynamics (brush_core, image, paint_options, &origin); paint_mode = pika_context_get_paint_mode (PIKA_CONTEXT (paint_options)); n_strokes = pika_symmetry_get_size (sym); for (i = 0; i < n_strokes; i++) { coords = *(pika_symmetry_get_coords (sym, i)); pika_brush_core_eval_transform_symmetry (brush_core, sym, i); paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable, paint_options, paint_mode, &coords, &paint_buffer_x, &paint_buffer_y, NULL, NULL); if (! paint_buffer) continue; paint_area_offset_x = 0; paint_area_offset_y = 0; paint_area_width = gegl_buffer_get_width (paint_buffer); paint_area_height = gegl_buffer_get_height (paint_buffer); src_offset_x = base_src_offset_x; src_offset_y = base_src_offset_y; if (pika_source_core_use_source (source_core, options)) { /* When using a source, use the same for every stroke. */ src_offset_x += floor (origin.x) - floor (coords.x) - src_off_x; src_offset_y += floor (origin.y) - floor (coords.y) - src_off_y; src_buffer = PIKA_SOURCE_CORE_GET_CLASS (source_core)->get_source (source_core, drawable, paint_options, self_drawable, src_pickable, src_offset_x, src_offset_y, paint_buffer, paint_buffer_x, paint_buffer_y, &paint_area_offset_x, &paint_area_offset_y, &paint_area_width, &paint_area_height, &src_rect); if (! src_buffer) continue; } /* Set the paint buffer to transparent */ gegl_buffer_clear (paint_buffer, NULL); op = pika_symmetry_get_operation (sym, i); if (op) { GeglNode *node; GeglNode *input; GeglNode *translate_before; GeglNode *translate_after; GeglNode *output; node = gegl_node_new (); input = gegl_node_get_input_proxy (node, "input"); translate_before = gegl_node_new_child ( node, "operation", "gegl:translate", "x", -((gdouble) src_x + 0.5), "y", -((gdouble) src_y + 0.5), NULL); gegl_node_add_child (node, op); translate_after = gegl_node_new_child ( node, "operation", "gegl:translate", "x", ((gdouble) src_x + 0.5) + (paint_area_offset_x - src_rect.x), "y", ((gdouble) src_y + 0.5) + (paint_area_offset_y - src_rect.y), NULL); output = gegl_node_get_output_proxy (node, "output"); gegl_node_link_many (input, translate_before, op, translate_after, output, NULL); g_object_unref (op); op = node; } PIKA_SOURCE_CORE_GET_CLASS (source_core)->motion (source_core, drawable, paint_options, &coords, op, opacity, src_pickable, src_buffer, &src_rect, src_offset_x, src_offset_y, paint_buffer, paint_buffer_x, paint_buffer_y, paint_area_offset_x, paint_area_offset_y, paint_area_width, paint_area_height); g_clear_object (&op); g_clear_object (&src_buffer); } } gboolean pika_source_core_use_source (PikaSourceCore *source_core, PikaSourceOptions *options) { return PIKA_SOURCE_CORE_GET_CLASS (source_core)->use_source (source_core, options); } static gboolean pika_source_core_real_use_source (PikaSourceCore *source_core, PikaSourceOptions *options) { return TRUE; } static GeglBuffer * pika_source_core_real_get_source (PikaSourceCore *source_core, PikaDrawable *drawable, PikaPaintOptions *paint_options, gboolean self_drawable, PikaPickable *src_pickable, gint src_offset_x, gint src_offset_y, GeglBuffer *paint_buffer, gint paint_buffer_x, gint paint_buffer_y, gint *paint_area_offset_x, gint *paint_area_offset_y, gint *paint_area_width, gint *paint_area_height, GeglRectangle *src_rect) { PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options); PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); PikaImage *src_image = pika_pickable_get_image (src_pickable); GeglBuffer *src_buffer = pika_pickable_get_buffer (src_pickable); GeglBuffer *dest_buffer; gboolean sample_merged; gint x, y; gint width, height; /* As a special case, we bypass sample merged value when we request * each drawable to be its own source. */ sample_merged = options->sample_merged && (! self_drawable); if (! pika_rectangle_intersect (paint_buffer_x + src_offset_x, paint_buffer_y + src_offset_y, gegl_buffer_get_width (paint_buffer), gegl_buffer_get_height (paint_buffer), gegl_buffer_get_x (src_buffer), gegl_buffer_get_y (src_buffer), gegl_buffer_get_width (src_buffer), gegl_buffer_get_height (src_buffer), &x, &y, &width, &height)) { return FALSE; } /* If the source image is different from the destination, * then we should copy straight from the source image * to the canvas. * Otherwise, we need a call to get_orig_image to make sure * we get a copy of the unblemished (offset) image */ if (( sample_merged && (src_image != image)) || (! sample_merged && (g_list_length (options->src_drawables) != 1 || options->src_drawables->data != drawable))) { dest_buffer = src_buffer; } else { /* get the original image */ if (sample_merged) dest_buffer = pika_paint_core_get_orig_proj (PIKA_PAINT_CORE (source_core)); else dest_buffer = pika_paint_core_get_orig_image (PIKA_PAINT_CORE (source_core), drawable); } *paint_area_offset_x = x - (paint_buffer_x + src_offset_x); *paint_area_offset_y = y - (paint_buffer_y + src_offset_y); *paint_area_width = width; *paint_area_height = height; *src_rect = *GEGL_RECTANGLE (x, y, width, height); return g_object_ref (dest_buffer); }