diff options
Diffstat (limited to 'app/paint/gimpsourcecore.c')
-rw-r--r-- | app/paint/gimpsourcecore.c | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/app/paint/gimpsourcecore.c b/app/paint/gimpsourcecore.c new file mode 100644 index 0000000..94238ad --- /dev/null +++ b/app/paint/gimpsourcecore.c @@ -0,0 +1,679 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "paint-types.h" + +#include "gegl/gimp-gegl-utils.h" + +#include "core/gimp.h" +#include "core/gimpdrawable.h" +#include "core/gimpdynamics.h" +#include "core/gimperror.h" +#include "core/gimpimage.h" +#include "core/gimppickable.h" +#include "core/gimpsymmetry.h" + +#include "gimpsourcecore.h" +#include "gimpsourceoptions.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_SRC_DRAWABLE, + PROP_SRC_X, + PROP_SRC_Y +}; + + +static void gimp_source_core_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_source_core_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_source_core_start (GimpPaintCore *paint_core, + GimpDrawable *drawable, + GimpPaintOptions *paint_options, + const GimpCoords *coords, + GError **error); +static void gimp_source_core_paint (GimpPaintCore *paint_core, + GimpDrawable *drawable, + 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, + 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, + 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); + +static void gimp_source_core_set_src_drawable (GimpSourceCore *source_core, + GimpDrawable *drawable); + + +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) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass); + GimpBrushCoreClass *brush_core_class = GIMP_BRUSH_CORE_CLASS (klass); + + object_class->set_property = gimp_source_core_set_property; + object_class->get_property = gimp_source_core_get_property; + + 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; + + g_object_class_install_property (object_class, PROP_SRC_DRAWABLE, + g_param_spec_object ("src-drawable", + NULL, NULL, + GIMP_TYPE_DRAWABLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SRC_X, + g_param_spec_int ("src-x", NULL, NULL, + 0, GIMP_MAX_IMAGE_SIZE, + 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SRC_Y, + g_param_spec_int ("src-y", NULL, NULL, + 0, GIMP_MAX_IMAGE_SIZE, + 0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_source_core_init (GimpSourceCore *source_core) +{ + source_core->set_source = FALSE; + + source_core->src_drawable = NULL; + source_core->src_x = 0; + source_core->src_y = 0; + + 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 void +gimp_source_core_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSourceCore *source_core = GIMP_SOURCE_CORE (object); + + switch (property_id) + { + case PROP_SRC_DRAWABLE: + gimp_source_core_set_src_drawable (source_core, + g_value_get_object (value)); + break; + case PROP_SRC_X: + source_core->src_x = g_value_get_int (value); + break; + case PROP_SRC_Y: + source_core->src_y = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_source_core_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSourceCore *source_core = GIMP_SOURCE_CORE (object); + + switch (property_id) + { + case PROP_SRC_DRAWABLE: + g_value_set_object (value, source_core->src_drawable); + break; + case PROP_SRC_X: + g_value_set_int (value, source_core->src_x); + break; + case PROP_SRC_Y: + g_value_set_int (value, source_core->src_y); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_source_core_start (GimpPaintCore *paint_core, + GimpDrawable *drawable, + 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, drawable, + 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 (! source_core->src_drawable) + { + g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, + _("Set a source image first.")); + return FALSE; + } + + if (options->sample_merged && + gimp_item_get_image (GIMP_ITEM (source_core->src_drawable)) == + gimp_item_get_image (GIMP_ITEM (drawable))) + { + paint_core->use_saved_proj = TRUE; + } + } + + return TRUE; +} + +static void +gimp_source_core_paint (GimpPaintCore *paint_core, + GimpDrawable *drawable, + 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) + { + gimp_source_core_set_src_drawable (source_core, drawable); + + /* FIXME(?): subpixel source sampling */ + source_core->src_x = floor (coords->x); + source_core->src_y = floor (coords->y); + + source_core->first_stroke = TRUE; + } + else if (options->align_mode == GIMP_SOURCE_ALIGN_NO) + { + source_core->orig_src_x = source_core->src_x; + source_core->orig_src_y = source_core->src_y; + + 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 */ + + source_core->src_x = floor (coords->x); + source_core->src_y = floor (coords->y); + + source_core->first_stroke = TRUE; + } + else + { + /* otherwise, update the target */ + + gint dest_x; + gint dest_y; + + dest_x = floor (coords->x); + dest_y = floor (coords->y); + + 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 = source_core->src_x - dest_x; + source_core->offset_y = source_core->src_y - dest_y; + } + else if (source_core->first_stroke) + { + source_core->offset_x = source_core->src_x - dest_x; + source_core->offset_y = source_core->src_y - dest_y; + + source_core->first_stroke = FALSE; + } + + source_core->src_x = dest_x + source_core->offset_x; + source_core->src_y = dest_y + source_core->offset_y; + + gimp_source_core_motion (source_core, drawable, paint_options, + sym); + } + break; + + case GIMP_PAINT_STATE_FINISH: + if (options->align_mode == GIMP_SOURCE_ALIGN_NO && + ! source_core->first_stroke) + { + source_core->src_x = source_core->orig_src_x; + source_core->src_y = source_core->orig_src_y; + } + break; + + default: + break; + } + + g_object_notify (G_OBJECT (source_core), "src-x"); + g_object_notify (G_OBJECT (source_core), "src-y"); +} + +void +gimp_source_core_motion (GimpSourceCore *source_core, + GimpDrawable *drawable, + GimpPaintOptions *paint_options, + 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 n_strokes; + gint i; + + fade_point = gimp_paint_options_get_fade (paint_options, image, + paint_core->pixel_dist); + + origin = gimp_symmetry_get_origin (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)) + { + src_pickable = GIMP_PICKABLE (source_core->src_drawable); + + if (options->sample_merged) + { + GimpImage *src_image = gimp_pickable_get_image (src_pickable); + gint off_x, off_y; + + 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)); + } + + gimp_item_get_offset (GIMP_ITEM (source_core->src_drawable), + &off_x, &off_y); + + base_src_offset_x += off_x; + base_src_offset_y += off_y; + } + } + + gimp_brush_core_eval_transform_dynamics (brush_core, + drawable, + 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_offset_y += floor (origin->y) - floor (coords->y); + src_buffer = + GIMP_SOURCE_CORE_GET_CLASS (source_core)->get_source (source_core, + drawable, + paint_options, + 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", -(source_core->src_x + 0.5), + "y", -(source_core->src_y + 0.5), + NULL); + + gegl_node_add_child (node, op); + + translate_after = gegl_node_new_child ( + node, + "operation", "gegl:translate", + "x", (source_core->src_x + 0.5) + + (paint_area_offset_x - src_rect.x), + "y", (source_core->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, + 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; + gint x, y; + gint width, height; + + 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 (( options->sample_merged && (src_image != image)) || + (! options->sample_merged && (source_core->src_drawable != drawable))) + { + dest_buffer = src_buffer; + } + else + { + /* get the original image */ + if (options->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)); + } + + *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); +} + +static void +gimp_source_core_src_drawable_removed (GimpDrawable *drawable, + GimpSourceCore *source_core) +{ + if (drawable == source_core->src_drawable) + { + source_core->src_drawable = NULL; + } + + g_signal_handlers_disconnect_by_func (drawable, + gimp_source_core_src_drawable_removed, + source_core); +} + +static void +gimp_source_core_set_src_drawable (GimpSourceCore *source_core, + GimpDrawable *drawable) +{ + if (source_core->src_drawable == drawable) + return; + + if (source_core->src_drawable) + g_signal_handlers_disconnect_by_func (source_core->src_drawable, + gimp_source_core_src_drawable_removed, + source_core); + + source_core->src_drawable = drawable; + + if (source_core->src_drawable) + g_signal_connect (source_core->src_drawable, "removed", + G_CALLBACK (gimp_source_core_src_drawable_removed), + source_core); + + g_object_notify (G_OBJECT (source_core), "src-drawable"); +} |