gimp/app/gegl/gimpcageconfig.c

464 lines
14 KiB
C

/* GIMP - The GNU Image Manipulation Program
*
* gimpcageconfig.c
* Copyright (C) 2010 Michael Muré <batolettre@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include "gimp-gegl-types.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"
#include "gimpcageconfig.h"
/*#define DEBUG_CAGE */
#define N_ITEMS_PER_ALLOC 10
#define DELTA 0.010309278351
static void gimp_cage_config_finalize (GObject *object);
static void gimp_cage_config_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_cage_config_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_cage_config_compute_scaling_factor (GimpCageConfig *gcc);
static void gimp_cage_config_compute_edge_normal (GimpCageConfig *gcc);
G_DEFINE_TYPE_WITH_CODE (GimpCageConfig, gimp_cage_config,
GIMP_TYPE_IMAGE_MAP_CONFIG,
G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
NULL))
#define parent_class gimp_cage_config_parent_class
#ifdef DEBUG_CAGE
static void
print_cage (GimpCageConfig *gcc)
{
gint i;
GeglRectangle bounding_box;
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
bounding_box = gimp_cage_config_get_bounding_box (gcc);
for (i = 0; i < gcc->n_cage_vertices; i++)
{
printf("cgx: %.0f cgy: %.0f cvdx: %.0f cvdy: %.0f sf: %.2f normx: %.2f normy: %.2f\n", gcc->cage_vertices[i].x, gcc->cage_vertices[i].y, gcc->cage_vertices_d[i].x, gcc->cage_vertices_d[i].y, gcc->scaling_factor[i], gcc->normal_d[i].x, gcc->normal_d[i].y);
}
printf("bounding box: x: %d y: %d width: %d height: %d\n", bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height);
printf("offsets: (%d, %d)", gcc->offset_x, gcc->offset_y);
printf("done\n");
}
#endif
static void
gimp_cage_config_class_init (GimpCageConfigClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gimp_cage_config_set_property;
object_class->get_property = gimp_cage_config_get_property;
object_class->finalize = gimp_cage_config_finalize;
}
static void
gimp_cage_config_init (GimpCageConfig *self)
{
self->n_cage_vertices = 0;
self->max_cage_vertices = 50; /*pre-allocation for 50 vertices for the cage.*/
self->cage_vertices = g_new0 (GimpVector2, self->max_cage_vertices);
self->cage_vertices_d = g_new0 (GimpVector2, self->max_cage_vertices);
self->scaling_factor = g_malloc0 (self->max_cage_vertices * sizeof (gdouble));
self->normal_d = g_new0 (GimpVector2, self->max_cage_vertices);
}
static void
gimp_cage_config_finalize (GObject *object)
{
GimpCageConfig *gcc = GIMP_CAGE_CONFIG (object);
g_free (gcc->cage_vertices);
g_free (gcc->cage_vertices_d);
g_free (gcc->scaling_factor);
g_free (gcc->normal_d);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gimp_cage_config_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_cage_config_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
/**
* gimp_cage_config_add_cage_point:
* @gcc: the cage config
* @x: x value of the new point
* @y: y value of the new point
*
* Add a new point in the polygon of the cage, and make allocation if needed.
* Point is added in both source and destination cage
*/
void
gimp_cage_config_add_cage_point (GimpCageConfig *gcc,
gdouble x,
gdouble y)
{
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
/* reallocate memory if needed */
if (gcc->n_cage_vertices >= gcc->max_cage_vertices)
{
gcc->max_cage_vertices += N_ITEMS_PER_ALLOC;
gcc->cage_vertices = g_renew (GimpVector2,
gcc->cage_vertices,
gcc->max_cage_vertices);
gcc->cage_vertices_d = g_renew (GimpVector2,
gcc->cage_vertices_d,
gcc->max_cage_vertices);
gcc->scaling_factor = g_realloc (gcc->scaling_factor,
gcc->max_cage_vertices * sizeof (gdouble));
gcc->normal_d = g_renew (GimpVector2,
gcc->normal_d,
gcc->max_cage_vertices);
}
gcc->cage_vertices[gcc->n_cage_vertices].x = x + DELTA - gcc->offset_x;
gcc->cage_vertices[gcc->n_cage_vertices].y = y + DELTA - gcc->offset_y;
gcc->cage_vertices_d[gcc->n_cage_vertices].x = x + DELTA - gcc->offset_x;
gcc->cage_vertices_d[gcc->n_cage_vertices].y = y + DELTA - gcc->offset_y;
gcc->n_cage_vertices++;
gimp_cage_config_compute_scaling_factor (gcc);
gimp_cage_config_compute_edge_normal (gcc);
}
/**
* gimp_cage_config_remove_last_cage_point:
* @gcc: the cage config
*
* Remove the last point of the cage, in both source and destination cage
*/
void
gimp_cage_config_remove_last_cage_point (GimpCageConfig *gcc)
{
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
if (gcc->n_cage_vertices >= 1)
gcc->n_cage_vertices--;
gimp_cage_config_compute_scaling_factor (gcc);
gimp_cage_config_compute_edge_normal (gcc);
}
/**
* gimp_cage_config_move_cage_point:
* @gcc: the cage config
* @mode: the actual mode of the cage, GIMP_CAGE_MODE_CAGE_CHANGE or GIMP_CAGE_MODE_DEFORM
* @point_number: the point of the cage to move
* @x: new x value
* @y: new y value
*
* Move a point of the source or destination cage, according to the
* cage mode provided
*/
void
gimp_cage_config_move_cage_point (GimpCageConfig *gcc,
GimpCageMode mode,
gint point_number,
gdouble x,
gdouble y)
{
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
g_return_if_fail (point_number < gcc->n_cage_vertices);
g_return_if_fail (point_number >= 0);
if (mode == GIMP_CAGE_MODE_CAGE_CHANGE)
{
gcc->cage_vertices[point_number].x = x + DELTA - gcc->offset_x;
gcc->cage_vertices[point_number].y = y + DELTA - gcc->offset_y;
gcc->cage_vertices_d[point_number].x = x + DELTA - gcc->offset_x;
gcc->cage_vertices_d[point_number].y = y + DELTA - gcc->offset_y;
}
else
{
gcc->cage_vertices_d[point_number].x = x + DELTA - gcc->offset_x;
gcc->cage_vertices_d[point_number].y = y + DELTA - gcc->offset_y;
}
gimp_cage_config_compute_scaling_factor (gcc);
gimp_cage_config_compute_edge_normal (gcc);
}
/**
* gimp_cage_config_get_bounding_box:
* @gcc: the cage config
*
* Compute the bounding box of the destination cage
*
* Returns: the bounding box of the destination cage, as a GeglRectangle
*/
GeglRectangle
gimp_cage_config_get_bounding_box (GimpCageConfig *gcc)
{
GeglRectangle bounding_box = { 0, };
gint i;
g_return_val_if_fail (GIMP_IS_CAGE_CONFIG (gcc), bounding_box);
g_return_val_if_fail (gcc->n_cage_vertices >= 0, bounding_box);
bounding_box.x = gcc->cage_vertices[0].x;
bounding_box.y = gcc->cage_vertices[0].y;
bounding_box.height = 0;
bounding_box.width = 0;
for (i = 1; i < gcc->n_cage_vertices; i++)
{
gdouble x,y;
x = gcc->cage_vertices[i].x;
y = gcc->cage_vertices[i].y;
if (x < bounding_box.x)
{
bounding_box.width += bounding_box.x - x;
bounding_box.x = x;
}
if (y < bounding_box.y)
{
bounding_box.height += bounding_box.y - y;
bounding_box.y = y;
}
if (x > bounding_box.x + bounding_box.width)
{
bounding_box.width = x - bounding_box.x;
}
if (y > bounding_box.y + bounding_box.height)
{
bounding_box.height = y - bounding_box.y;
}
}
return bounding_box;
}
/**
* gimp_cage_config_reverse_cage:
* @gcc: the cage config
*
* When using non-simple cage (like a cage in 8), user may want to
* manually inverse inside and outside of the cage. This function
* reverse the cage
*/
void
gimp_cage_config_reverse_cage (GimpCageConfig *gcc)
{
GimpVector2 temp;
gint i;
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
for (i = 0; i < gcc->n_cage_vertices / 2; i++)
{
temp = gcc->cage_vertices[i];
gcc->cage_vertices[i] = gcc->cage_vertices[gcc->n_cage_vertices - i - 1];
gcc->cage_vertices[gcc->n_cage_vertices - i - 1] = temp;
temp = gcc->cage_vertices_d[i];
gcc->cage_vertices_d[i] = gcc->cage_vertices_d[gcc->n_cage_vertices - i - 1];
gcc->cage_vertices_d[gcc->n_cage_vertices - i - 1] = temp;
}
gimp_cage_config_compute_scaling_factor (gcc);
gimp_cage_config_compute_edge_normal (gcc);
}
/**
* gimp_cage_config_reverse_cage_if_needed:
* @gcc: the cage config
*
* Since the cage need to be defined counter-clockwise to have the
* topological inside in the actual 'physical' inside of the cage,
* this function compute if the cage is clockwise or not, and reverse
* the cage if needed.
*/
void
gimp_cage_config_reverse_cage_if_needed (GimpCageConfig *gcc)
{
gint i;
gdouble sum;
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
sum = 0.0;
/* this is a bit crappy, but should works most of the case */
/* we do the sum of the projection of each point to the previous
segment, and see the final sign */
for (i = 0; i < gcc->n_cage_vertices ; i++)
{
GimpVector2 P1, P2, P3;
gdouble z;
P1 = gcc->cage_vertices[i];
P2 = gcc->cage_vertices[(i+1) % gcc->n_cage_vertices];
P3 = gcc->cage_vertices[(i+2) % gcc->n_cage_vertices];
z = P1.x * (P2.y - P3.y) + P2.x * (P3.y - P1.y) + P3.x * (P1.y - P2.y);
sum += z;
}
/* sum > 0 mean a cage defined counter-clockwise, so we reverse it */
if (sum > 0)
{
gimp_cage_config_reverse_cage (gcc);
}
}
/**
* gimp_cage_config_point_inside:
* @gcc: the cage config
* @x: x coordinate of the point to test
* @y: y coordinate of the point to test
*
* Check if the given point is inside the cage. This test is done in
* the regard of the topological inside of the cage.
*
* Returns: TRUE if the point is inside, FALSE if not.
*/
static void
gimp_cage_config_compute_scaling_factor (GimpCageConfig *gcc)
{
GimpVector2 edge;
gdouble length, length_d;
gint i;
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
for (i = 0; i < gcc->n_cage_vertices; i++)
{
gimp_vector2_sub (&edge,
&gcc->cage_vertices[i],
&gcc->cage_vertices[(i + 1) % gcc->n_cage_vertices]);
length = gimp_vector2_length (&edge);
gimp_vector2_sub (&edge,
&gcc->cage_vertices_d[i],
&gcc->cage_vertices_d[(i + 1) % gcc->n_cage_vertices]);
length_d = gimp_vector2_length (&edge);
gcc->scaling_factor[i] = length_d / length;
}
#ifdef DEBUG_CAGE
print_cage (gcc);
#endif
}
static void
gimp_cage_config_compute_edge_normal (GimpCageConfig *gcc)
{
GimpVector2 normal;
gint i;
g_return_if_fail (GIMP_IS_CAGE_CONFIG (gcc));
for (i = 0; i < gcc->n_cage_vertices; i++)
{
gimp_vector2_sub (&normal,
&gcc->cage_vertices_d[(i + 1) % gcc->n_cage_vertices],
&gcc->cage_vertices_d[i]);
gcc->normal_d[i] = gimp_vector2_normal (&normal);
}
}
gboolean
gimp_cage_config_point_inside (GimpCageConfig *gcc,
gfloat x,
gfloat y)
{
GimpVector2 *cv;
gboolean inside = FALSE;
gint i, j;
g_return_val_if_fail (GIMP_IS_CAGE_CONFIG (gcc), FALSE);
cv = gcc->cage_vertices;
for (i = 0, j = gcc->n_cage_vertices - 1;
i < gcc->n_cage_vertices;
j = i++)
{
if ((((cv[i].y <= y) && (y < cv[j].y))
|| ((cv[j].y <= y) && (y < cv[i].y)))
&& (x < (cv[j].x - cv[i].x) * (y - cv[i].y) / (cv[j].y - cv[i].y) + cv[i].x))
{
inside = !inside;
}
}
return inside;
}