PIKApp/app/operations/pikaoperationoffset.c

502 lines
17 KiB
C

/* 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
*
* pikaoperationoffset.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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <cairo.h>
#include <gegl-plugin.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "operations-types.h"
#include "gegl/pika-gegl-loops.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pikacontext.h"
#include "pikaoperationoffset.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_CONTEXT,
PROP_TYPE,
PROP_X,
PROP_Y
};
static void pika_operation_offset_dispose (GObject *object);
static void pika_operation_offset_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_operation_offset_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static GeglRectangle pika_operation_offset_get_required_for_output (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *output_roi);
static GeglRectangle pika_operation_offset_get_invalidated_by_change (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *input_roi);
static void pika_operation_offset_prepare (GeglOperation *operation);
static gboolean pika_operation_offset_parent_process (GeglOperation *operation,
GeglOperationContext *context,
const gchar *output_pad,
const GeglRectangle *result,
gint level);
static gboolean pika_operation_offset_process (GeglOperation *operation,
GeglBuffer *input,
GeglBuffer *output,
const GeglRectangle *roi,
gint level);
static void pika_operation_offset_get_offset (PikaOperationOffset *offset,
gboolean invert,
gint *x,
gint *y);
static void pika_operation_offset_get_rect (PikaOperationOffset *offset,
gboolean invert,
const GeglRectangle *roi,
GeglRectangle *rect);
G_DEFINE_TYPE (PikaOperationOffset, pika_operation_offset,
GEGL_TYPE_OPERATION_FILTER)
#define parent_class pika_operation_offset_parent_class
static void
pika_operation_offset_class_init (PikaOperationOffsetClass *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 = pika_operation_offset_dispose;
object_class->set_property = pika_operation_offset_set_property;
object_class->get_property = pika_operation_offset_get_property;
operation_class->get_required_for_output = pika_operation_offset_get_required_for_output;
operation_class->get_invalidated_by_change = pika_operation_offset_get_invalidated_by_change;
operation_class->prepare = pika_operation_offset_prepare;
operation_class->process = pika_operation_offset_parent_process;
operation_class->threaded = FALSE;
operation_class->cache_policy = GEGL_CACHE_POLICY_NEVER;
filter_class->process = pika_operation_offset_process;
gegl_operation_class_set_keys (operation_class,
"name", "pika:offset",
"categories", "transform",
"description", _("Shift the pixels, optionally wrapping them at the borders"),
NULL);
g_object_class_install_property (object_class, PROP_CONTEXT,
g_param_spec_object ("context",
"Context",
"A PikaContext",
PIKA_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",
PIKA_TYPE_OFFSET_TYPE,
PIKA_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
pika_operation_offset_init (PikaOperationOffset *self)
{
}
static void
pika_operation_offset_dispose (GObject *object)
{
PikaOperationOffset *offset = PIKA_OPERATION_OFFSET (object);
g_clear_object (&offset->context);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
pika_operation_offset_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaOperationOffset *offset = PIKA_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
pika_operation_offset_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaOperationOffset *offset = PIKA_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
pika_operation_offset_get_required_for_output (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *output_roi)
{
PikaOperationOffset *offset = PIKA_OPERATION_OFFSET (operation);
GeglRectangle rect;
pika_operation_offset_get_rect (offset, TRUE, output_roi, &rect);
return rect;
}
static GeglRectangle
pika_operation_offset_get_invalidated_by_change (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *input_roi)
{
PikaOperationOffset *offset = PIKA_OPERATION_OFFSET (operation);
GeglRectangle rect;
pika_operation_offset_get_rect (offset, FALSE, input_roi, &rect);
return rect;
}
static void
pika_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
pika_operation_offset_parent_process (GeglOperation *operation,
GeglOperationContext *context,
const gchar *output_pad,
const GeglRectangle *result,
gint level)
{
PikaOperationOffset *offset = PIKA_OPERATION_OFFSET (operation);
GObject *input;
gint x;
gint y;
input = gegl_operation_context_get_object (context, "input");
pika_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 == PIKA_OFFSET_TRANSPARENT ||
(offset->type == PIKA_OFFSET_BACKGROUND &&
! offset->context))
{
GObject *output = NULL;
if (input)
{
GeglRectangle bounds;
GeglRectangle extent;
bounds = gegl_operation_get_bounding_box (GEGL_OPERATION (offset));
extent = *gegl_buffer_get_extent (GEGL_BUFFER (input));
extent.x += x;
extent.y += y;
if (gegl_rectangle_intersect (&extent, &extent, &bounds))
{
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
pika_operation_offset_process (GeglOperation *operation,
GeglBuffer *input,
GeglBuffer *output,
const GeglRectangle *roi,
gint level)
{
PikaOperationOffset *offset = PIKA_OPERATION_OFFSET (operation);
GeglColor *color = NULL;
GeglRectangle bounds;
gint x;
gint y;
gint i;
bounds = gegl_operation_get_bounding_box (GEGL_OPERATION (offset));
pika_operation_offset_get_offset (offset, FALSE, &x, &y);
if (offset->type == PIKA_OFFSET_BACKGROUND && offset->context)
{
PikaRGB bg;
pika_context_get_background (offset->context, &bg);
color = pika_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 == PIKA_OFFSET_WRAP_AROUND)
{
GeglRectangle offset_roi = offset_bounds;
offset_roi.x -= offset_x;
offset_roi.y -= offset_y;
pika_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
pika_operation_offset_get_offset (PikaOperationOffset *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 == PIKA_OFFSET_WRAP_AROUND)
{
*x %= bounds.width;
if (*x < 0)
*x += bounds.width;
*y %= bounds.height;
if (*y < 0)
*y += bounds.height;
}
else
{
*x = CLAMP (*x, -bounds.width, +bounds.width);
*y = CLAMP (*y, -bounds.height, +bounds.height);
}
}
static void
pika_operation_offset_get_rect (PikaOperationOffset *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;
}
pika_operation_offset_get_offset (offset, invert, &x, &y);
*rect = *roi;
rect->x += x;
rect->y += y;
if (offset->type == PIKA_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);
}