/* 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 . */ #include "config.h" #include #include #include "libgimpbase/gimpbase.h" #include "libgimpmath/gimpmath.h" #include "paint-types.h" #include "gegl/gimp-gegl-utils.h" #include "core/gimp.h" #include "core/gimpcontainer.h" #include "core/gimpdrawable.h" #include "core/gimpdynamics.h" #include "core/gimperror.h" #include "core/gimpimage.h" #include "core/gimpimage-new.h" #include "core/gimppickable.h" #include "core/gimpsymmetry.h" #include "gimpsourcecore.h" #include "gimpsourceoptions.h" #include "gimp-intl.h" enum { PROP_0, }; static gboolean gimp_source_core_start (GimpPaintCore *paint_core, GList *drawables, GimpPaintOptions *paint_options, const GimpCoords *coords, GError **error); static void gimp_source_core_paint (GimpPaintCore *paint_core, GList *drawables, GimpPaintOptions *paint_options, GimpSymmetry *sym, GimpPaintState paint_state, guint32 time); #if 0 static void gimp_source_core_motion (GimpSourceCore *source_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, gboolean self_drawable, GimpSymmetry *sym); #endif static gboolean gimp_source_core_real_use_source (GimpSourceCore *source_core, GimpSourceOptions *options); static GeglBuffer * gimp_source_core_real_get_source (GimpSourceCore *source_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, gboolean self_drawable, GimpPickable *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 (GimpSourceCore, gimp_source_core, GIMP_TYPE_BRUSH_CORE) #define parent_class gimp_source_core_parent_class static void gimp_source_core_class_init (GimpSourceCoreClass *klass) { GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass); GimpBrushCoreClass *brush_core_class = GIMP_BRUSH_CORE_CLASS (klass); paint_core_class->start = gimp_source_core_start; paint_core_class->paint = gimp_source_core_paint; brush_core_class->handles_changing_brush = TRUE; klass->use_source = gimp_source_core_real_use_source; klass->get_source = gimp_source_core_real_get_source; klass->motion = NULL; } static void gimp_source_core_init (GimpSourceCore *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 gimp_source_core_start (GimpPaintCore *paint_core, GList *drawables, GimpPaintOptions *paint_options, const GimpCoords *coords, GError **error) { GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core); GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options); if (! GIMP_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 && gimp_source_core_use_source (source_core, options)) { if (! options->src_drawables) { g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, _("Set a source image first.")); return FALSE; } else if (options->align_mode == GIMP_SOURCE_ALIGN_REGISTERED && g_list_length (drawables) > 1) { g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, _("\"Registered\" alignment cannot paint on multiple drawables.")); return FALSE; } if (options->sample_merged && g_list_length (drawables) == 1 && gimp_item_get_image (GIMP_ITEM (options->src_drawables->data)) == gimp_item_get_image (GIMP_ITEM (drawables->data))) { paint_core->use_saved_proj = TRUE; } } return TRUE; } static void gimp_source_core_paint (GimpPaintCore *paint_core, GList *drawables, GimpPaintOptions *paint_options, GimpSymmetry *sym, GimpPaintState paint_state, guint32 time) { GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core); GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options); const GimpCoords *coords; /* The source is based on the original stroke */ coords = gimp_symmetry_get_origin (sym); switch (paint_state) { case GIMP_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 == GIMP_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 GIMP_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 == GIMP_SOURCE_ALIGN_REGISTERED) { source_core->offset_x = 0; source_core->offset_y = 0; } else if (options->align_mode == GIMP_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) gimp_source_core_motion (source_core, iter->data, paint_options, (g_list_length (drawables) > 1), sym); } break; case GIMP_PAINT_STATE_FINISH: if (options->align_mode == GIMP_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 gimp_source_core_motion (GimpSourceCore *source_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, gboolean self_drawable, GimpSymmetry *sym) { GimpPaintCore *paint_core = GIMP_PAINT_CORE (source_core); GimpBrushCore *brush_core = GIMP_BRUSH_CORE (source_core); GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options); GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics; GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable)); GimpPickable *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; GimpLayerMode paint_mode; GeglNode *op; GimpCoords origin; GimpCoords 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 = gimp_paint_options_get_fade (paint_options, image, paint_core->pixel_dist); origin = *(gimp_symmetry_get_origin (sym)); gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y); coords = origin; coords.x -= off_x; coords.y -= off_y; gimp_symmetry_set_origin (sym, drawable, &coords); paint_core->sym = sym; /* Some settings are based on the original stroke. */ opacity = gimp_dynamics_get_linear_value (dynamics, GIMP_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 (gimp_source_core_use_source (source_core, options)) { if (self_drawable) { src_pickable = GIMP_PICKABLE (drawable); } else if (options->sample_merged) { GimpImage *src_image; src_image = gimp_pickable_get_image (options->src_drawables->data); if (! gimp_paint_core_get_show_all (paint_core)) { src_pickable = GIMP_PICKABLE (src_image); } else { src_pickable = GIMP_PICKABLE (gimp_image_get_projection (src_image)); } } else { src_pickable = options->src_pickable; } if (GIMP_IS_ITEM (src_pickable)) gimp_item_get_offset (GIMP_ITEM (src_pickable), &src_off_x, &src_off_y); } g_object_get (options, "src-x", &src_x, "src-y", &src_y, NULL); gimp_brush_core_eval_transform_dynamics (brush_core, image, paint_options, &origin); paint_mode = gimp_context_get_paint_mode (GIMP_CONTEXT (paint_options)); n_strokes = gimp_symmetry_get_size (sym); for (i = 0; i < n_strokes; i++) { coords = *(gimp_symmetry_get_coords (sym, i)); gimp_brush_core_eval_transform_symmetry (brush_core, sym, i); paint_buffer = gimp_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 (gimp_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 = GIMP_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 = gimp_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; } GIMP_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 gimp_source_core_use_source (GimpSourceCore *source_core, GimpSourceOptions *options) { return GIMP_SOURCE_CORE_GET_CLASS (source_core)->use_source (source_core, options); } static gboolean gimp_source_core_real_use_source (GimpSourceCore *source_core, GimpSourceOptions *options) { return TRUE; } static GeglBuffer * gimp_source_core_real_get_source (GimpSourceCore *source_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, gboolean self_drawable, GimpPickable *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) { GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options); GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable)); GimpImage *src_image = gimp_pickable_get_image (src_pickable); GeglBuffer *src_buffer = gimp_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 (! gimp_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 = gimp_paint_core_get_orig_proj (GIMP_PAINT_CORE (source_core)); else dest_buffer = gimp_paint_core_get_orig_image (GIMP_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); }