PIKApp/app/tools/pikatilehandleriscissors.c

337 lines
11 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
*
* 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 <gegl.h>
#include <gtk/gtk.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "tools-types.h"
#include "gegl/pika-gegl-loops.h"
#include "core/pikapickable.h"
#include "pikatilehandleriscissors.h"
enum
{
PROP_0,
PROP_PICKABLE
};
static void pika_tile_handler_iscissors_finalize (GObject *object);
static void pika_tile_handler_iscissors_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_tile_handler_iscissors_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_tile_handler_iscissors_validate (PikaTileHandlerValidate *validate,
const GeglRectangle *rect,
const Babl *format,
gpointer dest_buf,
gint dest_stride);
G_DEFINE_TYPE (PikaTileHandlerIscissors, pika_tile_handler_iscissors,
PIKA_TYPE_TILE_HANDLER_VALIDATE)
#define parent_class pika_tile_handler_iscissors_parent_class
static void
pika_tile_handler_iscissors_class_init (PikaTileHandlerIscissorsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaTileHandlerValidateClass *validate_class;
validate_class = PIKA_TILE_HANDLER_VALIDATE_CLASS (klass);
object_class->finalize = pika_tile_handler_iscissors_finalize;
object_class->set_property = pika_tile_handler_iscissors_set_property;
object_class->get_property = pika_tile_handler_iscissors_get_property;
validate_class->validate = pika_tile_handler_iscissors_validate;
g_object_class_install_property (object_class, PROP_PICKABLE,
g_param_spec_object ("pickable", NULL, NULL,
PIKA_TYPE_PICKABLE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
}
static void
pika_tile_handler_iscissors_init (PikaTileHandlerIscissors *iscissors)
{
}
static void
pika_tile_handler_iscissors_finalize (GObject *object)
{
PikaTileHandlerIscissors *iscissors = PIKA_TILE_HANDLER_ISCISSORS (object);
if (iscissors->pickable)
{
g_object_unref (iscissors->pickable);
iscissors->pickable = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_tile_handler_iscissors_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaTileHandlerIscissors *iscissors = PIKA_TILE_HANDLER_ISCISSORS (object);
switch (property_id)
{
case PROP_PICKABLE:
iscissors->pickable = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_tile_handler_iscissors_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaTileHandlerIscissors *iscissors = PIKA_TILE_HANDLER_ISCISSORS (object);
switch (property_id)
{
case PROP_PICKABLE:
g_value_set_object (value, iscissors->pickable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static const gfloat horz_deriv[9] =
{
1, 0, -1,
2, 0, -2,
1, 0, -1,
};
static const gfloat vert_deriv[9] =
{
1, 2, 1,
0, 0, 0,
-1, -2, -1,
};
static const gfloat blur_32[9] =
{
1, 1, 1,
1, 24, 1,
1, 1, 1,
};
#define MAX_GRADIENT 179.606 /* == sqrt (127^2 + 127^2) */
#define MIN_GRADIENT 63 /* gradients < this are directionless */
#define COST_WIDTH 2 /* number of bytes for each pixel in cost map */
static void
pika_tile_handler_iscissors_validate (PikaTileHandlerValidate *validate,
const GeglRectangle *rect,
const Babl *format,
gpointer dest_buf,
gint dest_stride)
{
PikaTileHandlerIscissors *iscissors = PIKA_TILE_HANDLER_ISCISSORS (validate);
GeglBuffer *src;
GeglBuffer *temp0;
GeglBuffer *temp1;
GeglBuffer *temp2;
gint stride1;
gint stride2;
gint i, j;
/* temporary convolution buffers -- */
guchar *maxgrad_conv1;
guchar *maxgrad_conv2;
#if 0
g_printerr ("validating at %d %d %d %d\n",
rect->x,
rect->y,
rect->width,
rect->height);
#endif
pika_pickable_flush (iscissors->pickable);
src = pika_pickable_get_buffer (iscissors->pickable);
temp0 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
rect->width,
rect->height),
babl_format ("R'G'B'A u8"));
temp1 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
rect->width,
rect->height),
babl_format ("R'G'B'A u8"));
temp2 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
rect->width,
rect->height),
babl_format ("R'G'B'A u8"));
/* XXX tile edges? */
/* Blur the source to get rid of noise */
pika_gegl_convolve (src, rect,
temp0, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
blur_32, 3, 32, PIKA_NORMAL_CONVOL, FALSE);
/* Use this blurred region as the new source */
/* Get the horizontal derivative */
pika_gegl_convolve (temp0, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
temp1, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
horz_deriv, 3, 1, PIKA_NEGATIVE_CONVOL, FALSE);
/* Get the vertical derivative */
pika_gegl_convolve (temp0, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
temp2, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
vert_deriv, 3, 1, PIKA_NEGATIVE_CONVOL, FALSE);
maxgrad_conv1 =
(guchar *) gegl_buffer_linear_open (temp1,
GEGL_RECTANGLE (0, 0,
rect->width,
rect->height),
&stride1, NULL);
maxgrad_conv2 =
(guchar *) gegl_buffer_linear_open (temp2,
GEGL_RECTANGLE (0, 0,
rect->width,
rect->height),
&stride2, NULL);
/* calculate overall gradient */
for (i = 0; i < rect->height; i++)
{
const guint8 *datah = maxgrad_conv1 + stride1 * i;
const guint8 *datav = maxgrad_conv2 + stride2 * i;
guint8 *gradmap = (guint8 *) dest_buf + dest_stride * i;
for (j = 0; j < rect->width; j++)
{
gint8 hmax = datah[0] - 128;
gint8 vmax = datav[0] - 128;
gfloat gradient;
gint b;
for (b = 1; b < 4; b++)
{
if (abs (datah[b] - 128) > abs (hmax))
hmax = datah[b] - 128;
if (abs (datav[b] - 128) > abs (vmax))
vmax = datav[b] - 128;
}
if (i == 0 || j == 0 || i == rect->height - 1 || j == rect->width - 1)
{
gradmap[j * COST_WIDTH + 0] = 0;
gradmap[j * COST_WIDTH + 1] = 255;
goto contin;
}
/* 1 byte absolute magnitude first */
gradient = sqrt (SQR (hmax) + SQR (vmax));
gradmap[j * COST_WIDTH] = gradient * 255 / MAX_GRADIENT;
/* then 1 byte direction */
if (gradient > MIN_GRADIENT)
{
gfloat direction;
if (! hmax)
direction = (vmax > 0) ? G_PI_2 : -G_PI_2;
else
direction = atan ((gdouble) vmax / (gdouble) hmax);
/* Scale the direction from between 0 and 254,
* corresponding to -PI/2, PI/2 255 is reserved for
* directionless pixels
*/
gradmap[j * COST_WIDTH + 1] =
(guint8) (254 * (direction + G_PI_2) / G_PI);
}
else
{
gradmap[j * COST_WIDTH + 1] = 255; /* reserved for weak gradient */
}
contin:
datah += 4;
datav += 4;
}
}
gegl_buffer_linear_close (temp1, maxgrad_conv1);
gegl_buffer_linear_close (temp2, maxgrad_conv2);
g_object_unref (temp0);
g_object_unref (temp1);
g_object_unref (temp2);
}
GeglTileHandler *
pika_tile_handler_iscissors_new (PikaPickable *pickable)
{
g_return_val_if_fail (PIKA_IS_PICKABLE (pickable), NULL);
return g_object_new (PIKA_TYPE_TILE_HANDLER_ISCISSORS,
"whole-tile", TRUE,
"pickable", pickable,
NULL);
}