diff --git a/app/operations/Makefile.am b/app/operations/Makefile.am index e46c944674..dad1ec9ebd 100644 --- a/app/operations/Makefile.am +++ b/app/operations/Makefile.am @@ -64,6 +64,8 @@ libappoperations_a_sources = \ gimpoperationhistogramsink.h \ gimpoperationmaskcomponents.cc \ gimpoperationmaskcomponents.h \ + gimpoperationoffset.c \ + gimpoperationoffset.h \ gimpoperationprofiletransform.c \ gimpoperationprofiletransform.h \ gimpoperationscalarmultiply.c \ diff --git a/app/operations/gimp-operations.c b/app/operations/gimp-operations.c index dd9390bf14..7cb30fd2dd 100644 --- a/app/operations/gimp-operations.c +++ b/app/operations/gimp-operations.c @@ -42,6 +42,7 @@ #include "gimpoperationgrow.h" #include "gimpoperationhistogramsink.h" #include "gimpoperationmaskcomponents.h" +#include "gimpoperationoffset.h" #include "gimpoperationprofiletransform.h" #include "gimpoperationscalarmultiply.h" #include "gimpoperationsemiflatten.h" @@ -136,6 +137,7 @@ gimp_operations_init (Gimp *gimp) g_type_class_ref (GIMP_TYPE_OPERATION_GROW); g_type_class_ref (GIMP_TYPE_OPERATION_HISTOGRAM_SINK); g_type_class_ref (GIMP_TYPE_OPERATION_MASK_COMPONENTS); + g_type_class_ref (GIMP_TYPE_OPERATION_OFFSET); g_type_class_ref (GIMP_TYPE_OPERATION_PROFILE_TRANSFORM); g_type_class_ref (GIMP_TYPE_OPERATION_SCALAR_MULTIPLY); g_type_class_ref (GIMP_TYPE_OPERATION_SEMI_FLATTEN); diff --git a/app/operations/gimpoperationoffset.c b/app/operations/gimpoperationoffset.c new file mode 100644 index 0000000000..bec2449b87 --- /dev/null +++ b/app/operations/gimpoperationoffset.c @@ -0,0 +1,480 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoperationoffset.c + * Copyright (C) 2019 Ell + * + * 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 + +#include "operations-types.h" + +#include "gegl/gimp-gegl-utils.h" + +#include "core/gimpcontext.h" + +#include "gimpoperationoffset.h" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_TYPE, + PROP_X, + PROP_Y +}; + + +static void gimp_operation_offset_dispose (GObject *object); +static void gimp_operation_offset_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_operation_offset_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static GeglRectangle gimp_operation_offset_get_required_for_output (GeglOperation *operation, + const gchar *input_pad, + const GeglRectangle *output_roi); +static GeglRectangle gimp_operation_offset_get_invalidated_by_change (GeglOperation *operation, + const gchar *input_pad, + const GeglRectangle *input_roi); +static void gimp_operation_offset_prepare (GeglOperation *operation); +static gboolean gimp_operation_offset_parent_process (GeglOperation *operation, + GeglOperationContext *context, + const gchar *output_pad, + const GeglRectangle *result, + gint level); + +static gboolean gimp_operation_offset_process (GeglOperation *operation, + GeglBuffer *input, + GeglBuffer *output, + const GeglRectangle *roi, + gint level); + +static void gimp_operation_offset_get_offset (GimpOperationOffset *offset, + gboolean invert, + gint *x, + gint *y); +static void gimp_operation_offset_get_rect (GimpOperationOffset *offset, + gboolean invert, + const GeglRectangle *roi, + GeglRectangle *rect); + + +G_DEFINE_TYPE (GimpOperationOffset, gimp_operation_offset, + GEGL_TYPE_OPERATION_FILTER) + +#define parent_class gimp_operation_offset_parent_class + + +static void +gimp_operation_offset_class_init (GimpOperationOffsetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeglOperationClass *operation_class = GEGL_OPERATION_CLASS (klass); + GeglOperationFilterClass *filter_class = GEGL_OPERATION_FILTER_CLASS (klass); + + object_class->dispose = gimp_operation_offset_dispose; + object_class->set_property = gimp_operation_offset_set_property; + object_class->get_property = gimp_operation_offset_get_property; + + operation_class->get_required_for_output = gimp_operation_offset_get_required_for_output; + operation_class->get_invalidated_by_change = gimp_operation_offset_get_invalidated_by_change; + operation_class->prepare = gimp_operation_offset_prepare; + operation_class->process = gimp_operation_offset_parent_process; + + operation_class->threaded = FALSE; + operation_class->cache_policy = GEGL_CACHE_POLICY_NEVER; + + filter_class->process = gimp_operation_offset_process; + + gegl_operation_class_set_keys (operation_class, + "name", "gimp:offset", + "categories", "gimp", + "description", "GIMP Offset operation", + NULL); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + "Context", + "A GimpContext", + GIMP_TYPE_CONTEXT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", + "Type", + "Offset type", + GIMP_TYPE_OFFSET_TYPE, + GIMP_OFFSET_WRAP_AROUND, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_int ("x", + "X Offset", + "X offset", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_int ("y", + "Y Offset", + "Y offset", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_operation_offset_init (GimpOperationOffset *self) +{ +} + +static void +gimp_operation_offset_dispose (GObject *object) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (object); + + g_clear_object (&offset->context); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_operation_offset_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, offset->context); + break; + + case PROP_TYPE: + g_value_set_enum (value, offset->type); + break; + + case PROP_X: + g_value_set_int (value, offset->x); + break; + + case PROP_Y: + g_value_set_int (value, offset->y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_operation_offset_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_set_object (&offset->context, g_value_get_object (value)); + break; + + case PROP_TYPE: + offset->type = g_value_get_enum (value); + break; + + case PROP_X: + offset->x = g_value_get_int (value); + break; + + case PROP_Y: + offset->y = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GeglRectangle +gimp_operation_offset_get_required_for_output (GeglOperation *operation, + const gchar *input_pad, + const GeglRectangle *output_roi) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (operation); + GeglRectangle rect; + + gimp_operation_offset_get_rect (offset, TRUE, output_roi, &rect); + + return rect; +} + +static GeglRectangle +gimp_operation_offset_get_invalidated_by_change (GeglOperation *operation, + const gchar *input_pad, + const GeglRectangle *input_roi) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (operation); + GeglRectangle rect; + + gimp_operation_offset_get_rect (offset, FALSE, input_roi, &rect); + + return rect; +} + +static void +gimp_operation_offset_prepare (GeglOperation *operation) +{ + const Babl *format; + + format = gegl_operation_get_source_format (operation, "input"); + + if (! format) + format = babl_format ("RGBA float"); + + gegl_operation_set_format (operation, "input", format); + gegl_operation_set_format (operation, "output", format); +} + +static gboolean +gimp_operation_offset_parent_process (GeglOperation *operation, + GeglOperationContext *context, + const gchar *output_pad, + const GeglRectangle *result, + gint level) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (operation); + GObject *input; + gint x; + gint y; + + input = gegl_operation_context_get_object (context, "input"); + + gimp_operation_offset_get_offset (offset, FALSE, &x, &y); + + if (x == 0 && y == 0) + { + gegl_operation_context_set_object (context, "output", input); + + return TRUE; + } + else if (offset->type == GIMP_OFFSET_TRANSPARENT || + (offset->type == GIMP_OFFSET_BACKGROUND && + ! offset->context)) + { + GObject *output = NULL; + + if (input) + { + const GeglRectangle *extent; + + extent = gegl_buffer_get_extent (GEGL_BUFFER (input)); + + output = g_object_new (GEGL_TYPE_BUFFER, + "source", input, + "x", extent->x, + "y", extent->y, + "width", extent->width, + "height", extent->height, + "shift-x", -x, + "shift-y", -y, + NULL); + + if (gegl_object_get_has_forked (input)) + gegl_object_set_has_forked (output); + } + + gegl_operation_context_take_object (context, "output", output); + + return TRUE; + } + + return GEGL_OPERATION_CLASS (parent_class)->process (operation, context, + output_pad, result, + level); +} + +static gboolean +gimp_operation_offset_process (GeglOperation *operation, + GeglBuffer *input, + GeglBuffer *output, + const GeglRectangle *roi, + gint level) +{ + GimpOperationOffset *offset = GIMP_OPERATION_OFFSET (operation); + GeglColor *color = NULL; + GeglRectangle bounds; + gint x; + gint y; + gint i; + + bounds = gegl_operation_get_bounding_box (GEGL_OPERATION (offset)); + + gimp_operation_offset_get_offset (offset, FALSE, &x, &y); + + if (offset->type == GIMP_OFFSET_BACKGROUND && offset->context) + { + GimpRGB bg; + + gimp_context_get_background (offset->context, &bg); + + color = gimp_gegl_color_new (&bg, NULL); + } + + for (i = 0; i < 4; i++) + { + GeglRectangle offset_bounds = bounds; + gint offset_x = x; + gint offset_y = y; + + if (i & 1) + offset_x += x < 0 ? bounds.width : -bounds.width; + if (i & 2) + offset_y += y < 0 ? bounds.height : -bounds.height; + + offset_bounds.x += offset_x; + offset_bounds.y += offset_y; + + if (gegl_rectangle_intersect (&offset_bounds, &offset_bounds, roi)) + { + if (i == 0 || offset->type == GIMP_OFFSET_WRAP_AROUND) + { + GeglRectangle offset_roi = offset_bounds; + + offset_roi.x -= offset_x; + offset_roi.y -= offset_y; + + gegl_buffer_copy (input, &offset_roi, GEGL_ABYSS_NONE, + output, &offset_bounds); + } + else if (color) + { + gegl_buffer_set_color (output, &offset_bounds, color); + } + } + } + + g_clear_object (&color); + + return TRUE; +} + +static void +gimp_operation_offset_get_offset (GimpOperationOffset *offset, + gboolean invert, + gint *x, + gint *y) +{ + GeglRectangle bounds; + + bounds = gegl_operation_get_bounding_box (GEGL_OPERATION (offset)); + + if (gegl_rectangle_is_empty (&bounds)) + { + *x = 0; + *y = 0; + + return; + } + + *x = offset->x; + *y = offset->y; + + if (invert) + { + *x = -*x; + *y = -*y; + } + + if (offset->type == GIMP_OFFSET_WRAP_AROUND) + { + *x %= bounds.width; + + if (*x < 0) + *x += bounds.width; + + *y %= bounds.height; + + if (*y < 0) + *y += bounds.height; + } +} + +static void +gimp_operation_offset_get_rect (GimpOperationOffset *offset, + gboolean invert, + const GeglRectangle *roi, + GeglRectangle *rect) +{ + GeglRectangle bounds; + gint x; + gint y; + + bounds = gegl_operation_get_bounding_box (GEGL_OPERATION (offset)); + + if (gegl_rectangle_is_empty (&bounds)) + { + rect->x = 0; + rect->y = 0; + rect->width = 0; + rect->height = 0; + + return; + } + + gimp_operation_offset_get_offset (offset, invert, &x, &y); + + *rect = *roi; + + rect->x += x; + rect->y += y; + + if (offset->type == GIMP_OFFSET_WRAP_AROUND) + { + if (rect->x + rect->width > bounds.x + bounds.width) + { + rect->x = bounds.x; + rect->width = bounds.width; + } + + if (rect->y + rect->height > bounds.y + bounds.height) + { + rect->y = bounds.y; + rect->height = bounds.height; + } + } + + gegl_rectangle_intersect (rect, rect, &bounds); +} diff --git a/app/operations/gimpoperationoffset.h b/app/operations/gimpoperationoffset.h new file mode 100644 index 0000000000..dcd0e0b320 --- /dev/null +++ b/app/operations/gimpoperationoffset.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoperationoffset.h + * Copyright (C) 2019 Ell + * + * 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 . + */ + +#ifndef __GIMP_OPERATION_OFFSET_H__ +#define __GIMP_OPERATION_OFFSET_H__ + + +#define GIMP_TYPE_OPERATION_OFFSET (gimp_operation_offset_get_type ()) +#define GIMP_OPERATION_OFFSET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OPERATION_OFFSET, GimpOperationOffset)) +#define GIMP_OPERATION_OFFSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OPERATION_OFFSET, GimpOperationOffsetClass)) +#define GIMP_IS_OPERATION_OFFSET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OPERATION_OFFSET)) +#define GIMP_IS_OPERATION_OFFSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OPERATION_OFFSET)) +#define GIMP_OPERATION_OFFSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OPERATION_OFFSET, GimpOperationOffsetClass)) + + +typedef struct _GimpOperationOffset GimpOperationOffset; +typedef struct _GimpOperationOffsetClass GimpOperationOffsetClass; + +struct _GimpOperationOffset +{ + GeglOperationFilter parent_instance; + + GimpContext *context; + GimpOffsetType type; + gint x; + gint y; +}; + +struct _GimpOperationOffsetClass +{ + GeglOperationFilterClass parent_class; +}; + + +GType gimp_operation_offset_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_OPERATION_OFFSET_H__ */ diff --git a/libgimpbase/gimpbaseenums.c b/libgimpbase/gimpbaseenums.c index f29e9c6340..0f102aaf63 100644 --- a/libgimpbase/gimpbaseenums.c +++ b/libgimpbase/gimpbaseenums.c @@ -1151,6 +1151,7 @@ gimp_offset_type_get_type (void) { { GIMP_OFFSET_BACKGROUND, "GIMP_OFFSET_BACKGROUND", "background" }, { GIMP_OFFSET_TRANSPARENT, "GIMP_OFFSET_TRANSPARENT", "transparent" }, + { GIMP_OFFSET_WRAP_AROUND, "GIMP_OFFSET_WRAP_AROUND", "wrap-around" }, { 0, NULL, NULL } }; @@ -1158,6 +1159,7 @@ gimp_offset_type_get_type (void) { { GIMP_OFFSET_BACKGROUND, "GIMP_OFFSET_BACKGROUND", NULL }, { GIMP_OFFSET_TRANSPARENT, "GIMP_OFFSET_TRANSPARENT", NULL }, + { GIMP_OFFSET_WRAP_AROUND, "GIMP_OFFSET_WRAP_AROUND", NULL }, { 0, NULL, NULL } }; diff --git a/libgimpbase/gimpbaseenums.h b/libgimpbase/gimpbaseenums.h index a4e2caf425..546e8b5b85 100644 --- a/libgimpbase/gimpbaseenums.h +++ b/libgimpbase/gimpbaseenums.h @@ -773,6 +773,7 @@ typedef enum * GimpOffsetType: * @GIMP_OFFSET_BACKGROUND: Background * @GIMP_OFFSET_TRANSPARENT: Transparent + * @GIMP_OFFSET_WRAP_AROUND: Wrap image around * * Background fill types for the offset operation. **/ @@ -783,7 +784,8 @@ GType gimp_offset_type_get_type (void) G_GNUC_CONST; typedef enum { GIMP_OFFSET_BACKGROUND, - GIMP_OFFSET_TRANSPARENT + GIMP_OFFSET_TRANSPARENT, + GIMP_OFFSET_WRAP_AROUND } GimpOffsetType; diff --git a/pdb/enums.pl b/pdb/enums.pl index 8365336e73..0a1f5d75f5 100644 --- a/pdb/enums.pl +++ b/pdb/enums.pl @@ -370,9 +370,11 @@ package Gimp::CodeGen::enums; GimpOffsetType => { contig => 1, header => 'libgimpbase/gimpbaseenums.h', - symbols => [ qw(GIMP_OFFSET_BACKGROUND GIMP_OFFSET_TRANSPARENT) ], + symbols => [ qw(GIMP_OFFSET_BACKGROUND GIMP_OFFSET_TRANSPARENT + GIMP_OFFSET_WRAP_AROUND) ], mapping => { GIMP_OFFSET_BACKGROUND => '0', - GIMP_OFFSET_TRANSPARENT => '1' } + GIMP_OFFSET_TRANSPARENT => '1', + GIMP_OFFSET_WRAP_AROUND => '2' } }, GimpOrientationType => { contig => 1,