diff options
Diffstat (limited to 'app/paint/gimpperspectiveclone.c')
-rw-r--r-- | app/paint/gimpperspectiveclone.c | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/app/paint/gimpperspectiveclone.c b/app/paint/gimpperspectiveclone.c new file mode 100644 index 0000000..abb9fab --- /dev/null +++ b/app/paint/gimpperspectiveclone.c @@ -0,0 +1,537 @@ +/* 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 <stdlib.h> +#include <string.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-nodes.h" +#include "gegl/gimp-gegl-utils.h" + +#include "core/gimp.h" +#include "core/gimp-utils.h" +#include "core/gimpdrawable.h" +#include "core/gimperror.h" +#include "core/gimpimage.h" +#include "core/gimppattern.h" +#include "core/gimppickable.h" +#include "core/gimpsymmetry.h" + +#include "gimpperspectiveclone.h" +#include "gimpperspectivecloneoptions.h" + +#include "gimp-intl.h" + + +static void gimp_perspective_clone_paint (GimpPaintCore *paint_core, + GimpDrawable *drawable, + GimpPaintOptions *paint_options, + GimpSymmetry *sym, + GimpPaintState paint_state, + guint32 time); + +static gboolean gimp_perspective_clone_use_source (GimpSourceCore *source_core, + GimpSourceOptions *options); +static GeglBuffer * gimp_perspective_clone_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_perspective_clone_get_matrix (GimpPerspectiveClone *clone, + GimpMatrix3 *matrix); + + +G_DEFINE_TYPE (GimpPerspectiveClone, gimp_perspective_clone, + GIMP_TYPE_CLONE) + +#define parent_class gimp_perspective_clone_parent_class + + +void +gimp_perspective_clone_register (Gimp *gimp, + GimpPaintRegisterCallback callback) +{ + (* callback) (gimp, + GIMP_TYPE_PERSPECTIVE_CLONE, + GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS, + "gimp-perspective-clone", + _("Perspective Clone"), + "gimp-tool-perspective-clone"); +} + +static void +gimp_perspective_clone_class_init (GimpPerspectiveCloneClass *klass) +{ + GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass); + GimpSourceCoreClass *source_core_class = GIMP_SOURCE_CORE_CLASS (klass); + + paint_core_class->paint = gimp_perspective_clone_paint; + + source_core_class->use_source = gimp_perspective_clone_use_source; + source_core_class->get_source = gimp_perspective_clone_get_source; +} + +static void +gimp_perspective_clone_init (GimpPerspectiveClone *clone) +{ + clone->src_x_fv = 0.0; /* source coords in front_view perspective */ + clone->src_y_fv = 0.0; + + clone->dest_x_fv = 0.0; /* destination coords in front_view perspective */ + clone->dest_y_fv = 0.0; + + gimp_matrix3_identity (&clone->transform); + gimp_matrix3_identity (&clone->transform_inv); +} + +static void +gimp_perspective_clone_paint (GimpPaintCore *paint_core, + GimpDrawable *drawable, + GimpPaintOptions *paint_options, + GimpSymmetry *sym, + GimpPaintState paint_state, + guint32 time) +{ + GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core); + GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (paint_core); + GimpContext *context = GIMP_CONTEXT (paint_options); + GimpCloneOptions *clone_options = GIMP_CLONE_OPTIONS (paint_options); + 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 (source_core, "src-drawable", drawable, NULL); + + source_core->src_x = floor (coords->x); + source_core->src_y = floor (coords->y); + + /* get source coordinates in front view perspective */ + gimp_matrix3_transform_point (&clone->transform_inv, + source_core->src_x, + source_core->src_y, + &clone->src_x_fv, + &clone->src_y_fv); + + source_core->first_stroke = TRUE; + } + else + { + GeglBuffer *orig_buffer = NULL; + GeglNode *tile = NULL; + GeglNode *src_node; + + 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; + } + + clone->node = gegl_node_new (); + + g_object_set (clone->node, + "cache-policy", GEGL_CACHE_POLICY_NEVER, + NULL); + + switch (clone_options->clone_type) + { + case GIMP_CLONE_IMAGE: + { + GimpPickable *src_pickable; + GimpImage *src_image; + GimpImage *dest_image; + + /* 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 + */ + src_pickable = GIMP_PICKABLE (source_core->src_drawable); + src_image = gimp_pickable_get_image (src_pickable); + + if (options->sample_merged) + src_pickable = GIMP_PICKABLE (src_image); + + dest_image = gimp_item_get_image (GIMP_ITEM (drawable)); + + if ((options->sample_merged && + (src_image != dest_image)) || + (! options->sample_merged && + (source_core->src_drawable != drawable))) + { + orig_buffer = gimp_pickable_get_buffer (src_pickable); + } + else + { + if (options->sample_merged) + orig_buffer = gimp_paint_core_get_orig_proj (paint_core); + else + orig_buffer = gimp_paint_core_get_orig_image (paint_core); + } + } + break; + + case GIMP_CLONE_PATTERN: + { + GimpPattern *pattern = gimp_context_get_pattern (context); + + orig_buffer = gimp_pattern_create_buffer (pattern); + + tile = gegl_node_new_child (clone->node, + "operation", "gegl:tile", + NULL); + clone->crop = gegl_node_new_child (clone->node, + "operation", "gegl:crop", + NULL); + } + break; + } + + src_node = gegl_node_new_child (clone->node, + "operation", "gegl:buffer-source", + "buffer", orig_buffer, + NULL); + + clone->transform_node = + gegl_node_new_child (clone->node, + "operation", "gegl:transform", + "sampler", GIMP_INTERPOLATION_LINEAR, + NULL); + + clone->dest_node = + gegl_node_new_child (clone->node, + "operation", "gegl:write-buffer", + NULL); + + if (tile) + { + gegl_node_link_many (src_node, + tile, + clone->crop, + clone->transform_node, + clone->dest_node, + NULL); + + g_object_unref (orig_buffer); + } + else + { + gegl_node_link_many (src_node, + clone->transform_node, + clone->dest_node, + NULL); + } + } + 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); + + /* get source coordinates in front view perspective */ + gimp_matrix3_transform_point (&clone->transform_inv, + source_core->src_x, + source_core->src_y, + &clone->src_x_fv, + &clone->src_y_fv); + + source_core->first_stroke = TRUE; + } + else + { + /* otherwise, update the target */ + + gint dest_x; + gint dest_y; + gint n_strokes; + gint i; + + n_strokes = gimp_symmetry_get_size (sym); + for (i = 0; i < n_strokes; i++) + { + coords = gimp_symmetry_get_coords (sym, i); + + 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; + + /* get destination coordinates in front view perspective */ + gimp_matrix3_transform_point (&clone->transform_inv, + dest_x, + dest_y, + &clone->dest_x_fv, + &clone->dest_y_fv); + + source_core->first_stroke = FALSE; + } + } + + gimp_source_core_motion (source_core, drawable, paint_options, sym); + } + break; + + case GIMP_PAINT_STATE_FINISH: + g_clear_object (&clone->node); + clone->crop = NULL; + clone->transform_node = NULL; + clone->dest_node = NULL; + break; + + default: + break; + } + + g_object_notify (G_OBJECT (clone), "src-x"); + g_object_notify (G_OBJECT (clone), "src-y"); +} + +static gboolean +gimp_perspective_clone_use_source (GimpSourceCore *source_core, + GimpSourceOptions *options) +{ + return TRUE; +} + +static GeglBuffer * +gimp_perspective_clone_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) +{ + GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (source_core); + GimpCloneOptions *clone_options = GIMP_CLONE_OPTIONS (paint_options); + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + const Babl *src_format_alpha; + gint x1d, y1d, x2d, y2d; + gdouble x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s; + gint xmin, ymin, xmax, ymax; + GimpMatrix3 matrix; + GimpMatrix3 gegl_matrix; + + src_buffer = gimp_pickable_get_buffer (src_pickable); + src_format_alpha = gimp_pickable_get_format_with_alpha (src_pickable); + + /* Destination coordinates that will be painted */ + x1d = paint_buffer_x; + y1d = paint_buffer_y; + x2d = paint_buffer_x + gegl_buffer_get_width (paint_buffer); + y2d = paint_buffer_y + gegl_buffer_get_height (paint_buffer); + + /* Boundary box for source pixels to copy: Convert all the vertex of + * the box to paint in destination area to its correspondent in + * source area bearing in mind perspective + */ + gimp_perspective_clone_get_source_point (clone, x1d, y1d, &x1s, &y1s); + gimp_perspective_clone_get_source_point (clone, x1d, y2d, &x2s, &y2s); + gimp_perspective_clone_get_source_point (clone, x2d, y1d, &x3s, &y3s); + gimp_perspective_clone_get_source_point (clone, x2d, y2d, &x4s, &y4s); + + xmin = floor (MIN4 (x1s, x2s, x3s, x4s)); + ymin = floor (MIN4 (y1s, y2s, y3s, y4s)); + xmax = ceil (MAX4 (x1s, x2s, x3s, x4s)); + ymax = ceil (MAX4 (y1s, y2s, y3s, y4s)); + + switch (clone_options->clone_type) + { + case GIMP_CLONE_IMAGE: + if (! gimp_rectangle_intersect (xmin, ymin, + xmax - xmin, ymax - ymin, + gegl_buffer_get_x (src_buffer), + gegl_buffer_get_y (src_buffer), + gegl_buffer_get_width (src_buffer), + gegl_buffer_get_height (src_buffer), + NULL, NULL, NULL, NULL)) + { + /* if the source area is completely out of the image */ + return NULL; + } + break; + + case GIMP_CLONE_PATTERN: + gegl_node_set (clone->crop, + "x", (gdouble) xmin, + "y", (gdouble) ymin, + "width", (gdouble) xmax - xmin, + "height", (gdouble) ymax - ymin, + NULL); + break; + } + + dest_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, x2d - x1d, y2d - y1d), + src_format_alpha); + + gimp_perspective_clone_get_matrix (clone, &matrix); + + gimp_matrix3_identity (&gegl_matrix); + gimp_matrix3_mult (&matrix, &gegl_matrix); + gimp_matrix3_translate (&gegl_matrix, -x1d, -y1d); + + gimp_gegl_node_set_matrix (clone->transform_node, &gegl_matrix); + + gegl_node_set (clone->dest_node, + "buffer", dest_buffer, + NULL); + + gegl_node_blit (clone->dest_node, 1.0, + GEGL_RECTANGLE (0, 0, x2d - x1d, y2d - y1d), + NULL, NULL, 0, GEGL_BLIT_DEFAULT); + + *src_rect = *GEGL_RECTANGLE (0, 0, x2d - x1d, y2d - y1d); + + return dest_buffer; +} + + +/* public functions */ + +void +gimp_perspective_clone_set_transform (GimpPerspectiveClone *clone, + GimpMatrix3 *transform) +{ + g_return_if_fail (GIMP_IS_PERSPECTIVE_CLONE (clone)); + g_return_if_fail (transform != NULL); + + clone->transform = *transform; + + clone->transform_inv = clone->transform; + gimp_matrix3_invert (&clone->transform_inv); + +#if 0 + g_printerr ("%f\t%f\t%f\n%f\t%f\t%f\n%f\t%f\t%f\n\n", + clone->transform.coeff[0][0], + clone->transform.coeff[0][1], + clone->transform.coeff[0][2], + clone->transform.coeff[1][0], + clone->transform.coeff[1][1], + clone->transform.coeff[1][2], + clone->transform.coeff[2][0], + clone->transform.coeff[2][1], + clone->transform.coeff[2][2]); +#endif +} + +void +gimp_perspective_clone_get_source_point (GimpPerspectiveClone *clone, + gdouble x, + gdouble y, + gdouble *newx, + gdouble *newy) +{ + gdouble temp_x, temp_y; + + g_return_if_fail (GIMP_IS_PERSPECTIVE_CLONE (clone)); + g_return_if_fail (newx != NULL); + g_return_if_fail (newy != NULL); + + gimp_matrix3_transform_point (&clone->transform_inv, + x, y, &temp_x, &temp_y); + +#if 0 + /* Get the offset of each pixel in destination area from the + * destination pixel in front view perspective + */ + offset_x_fv = temp_x - clone->dest_x_fv; + offset_y_fv = temp_y - clone->dest_y_fv; + + /* Get the source pixel in front view perspective */ + temp_x = offset_x_fv + clone->src_x_fv; + temp_y = offset_y_fv + clone->src_y_fv; +#endif + + temp_x += clone->src_x_fv - clone->dest_x_fv; + temp_y += clone->src_y_fv - clone->dest_y_fv; + + /* Convert the source pixel to perspective view */ + gimp_matrix3_transform_point (&clone->transform, + temp_x, temp_y, newx, newy); +} + + +/* private functions */ + +static void +gimp_perspective_clone_get_matrix (GimpPerspectiveClone *clone, + GimpMatrix3 *matrix) +{ + GimpMatrix3 temp; + + gimp_matrix3_identity (&temp); + gimp_matrix3_translate (&temp, + clone->dest_x_fv - clone->src_x_fv, + clone->dest_y_fv - clone->src_y_fv); + + *matrix = clone->transform_inv; + gimp_matrix3_mult (&temp, matrix); + gimp_matrix3_mult (&clone->transform, matrix); +} |