837 lines
23 KiB
C
837 lines
23 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):
|
|
*
|
|
* pikacageconfig.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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
#include <gegl.h>
|
|
|
|
#include "libpikaconfig/pikaconfig.h"
|
|
#include "libpikamath/pikamath.h"
|
|
#include "libpikabase/pikabase.h"
|
|
|
|
#include "operations-types.h"
|
|
|
|
#include "pikacageconfig.h"
|
|
|
|
|
|
/*#define DEBUG_CAGE */
|
|
|
|
/* This DELTA is aimed to not have handle on exact pixel during computation,
|
|
* to avoid particular case. It shouldn't be so useful, but it's a double
|
|
* safety. */
|
|
#define DELTA 0.010309278351
|
|
|
|
|
|
static void pika_cage_config_finalize (GObject *object);
|
|
static void pika_cage_config_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_cage_config_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void pika_cage_config_compute_scaling_factor (PikaCageConfig *gcc);
|
|
static void pika_cage_config_compute_edges_normal (PikaCageConfig *gcc);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (PikaCageConfig, pika_cage_config,
|
|
PIKA_TYPE_OPERATION_SETTINGS,
|
|
G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG,
|
|
NULL))
|
|
|
|
#define parent_class pika_cage_config_parent_class
|
|
|
|
#ifdef DEBUG_CAGE
|
|
static void
|
|
print_cage (PikaCageConfig *gcc)
|
|
{
|
|
gint i;
|
|
GeglRectangle bounding_box;
|
|
PikaCagePoint *point;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
bounding_box = pika_cage_config_get_bounding_box (gcc);
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
g_printerr ("cgx: %.0f cgy: %.0f cvdx: %.0f cvdy: %.0f sf: %.2f normx: %.2f normy: %.2f %s\n",
|
|
point->src_point.x + ((gcc->cage_mode==PIKA_CAGE_MODE_CAGE_CHANGE)?gcc->displacement_x:0),
|
|
point->src_point.y + ((gcc->cage_mode==PIKA_CAGE_MODE_CAGE_CHANGE)?gcc->displacement_y:0),
|
|
point->dest_point.x + ((gcc->cage_mode==PIKA_CAGE_MODE_DEFORM)?gcc->displacement_x:0),
|
|
point->dest_point.y + ((gcc->cage_mode==PIKA_CAGE_MODE_DEFORM)?gcc->displacement_y:0),
|
|
point->edge_scaling_factor,
|
|
point->edge_normal.x,
|
|
point->edge_normal.y,
|
|
((point->selected) ? "S" : "NS"));
|
|
}
|
|
|
|
g_printerr ("bounding box: x: %d y: %d width: %d height: %d\n", bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height);
|
|
g_printerr ("disp x: %f disp y: %f\n", gcc->displacement_x, gcc->displacement_y);
|
|
g_printerr ("done\n");
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
pika_cage_config_class_init (PikaCageConfigClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->set_property = pika_cage_config_set_property;
|
|
object_class->get_property = pika_cage_config_get_property;
|
|
|
|
object_class->finalize = pika_cage_config_finalize;
|
|
}
|
|
|
|
static void
|
|
pika_cage_config_init (PikaCageConfig *self)
|
|
{
|
|
/*pre-allocation for 50 vertices for the cage.*/
|
|
self->cage_points = g_array_sized_new (FALSE, FALSE, sizeof(PikaCagePoint), 50);
|
|
}
|
|
|
|
static void
|
|
pika_cage_config_finalize (GObject *object)
|
|
{
|
|
PikaCageConfig *gcc = PIKA_CAGE_CONFIG (object);
|
|
|
|
g_array_free (gcc->cage_points, TRUE);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
pika_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
|
|
pika_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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_get_n_points:
|
|
* @gcc: the cage config
|
|
*
|
|
* Returns: the number of points of the cage
|
|
*/
|
|
guint
|
|
pika_cage_config_get_n_points (PikaCageConfig *gcc)
|
|
{
|
|
return gcc->cage_points->len;
|
|
}
|
|
|
|
/**
|
|
* pika_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 last index of the polygon of the cage.
|
|
* Point is added in both source and destination cage
|
|
*/
|
|
void
|
|
pika_cage_config_add_cage_point (PikaCageConfig *gcc,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
pika_cage_config_insert_cage_point (gcc, gcc->cage_points->len, x, y);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_insert_cage_point:
|
|
* @gcc: the cage config
|
|
* @point_number: index where the point will be inserted
|
|
* @x: x value of the new point
|
|
* @y: y value of the new point
|
|
*
|
|
* Insert a new point in the polygon of the cage at the given index.
|
|
* Point is added in both source and destination cage
|
|
*/
|
|
void
|
|
pika_cage_config_insert_cage_point (PikaCageConfig *gcc,
|
|
gint point_number,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
PikaCagePoint point;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
g_return_if_fail (point_number <= gcc->cage_points->len);
|
|
g_return_if_fail (point_number >= 0);
|
|
|
|
point.src_point.x = x + DELTA;
|
|
point.src_point.y = y + DELTA;
|
|
|
|
point.dest_point.x = x + DELTA;
|
|
point.dest_point.y = y + DELTA;
|
|
|
|
g_array_insert_val (gcc->cage_points, point_number, point);
|
|
|
|
pika_cage_config_compute_scaling_factor (gcc);
|
|
pika_cage_config_compute_edges_normal (gcc);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_remove_last_cage_point:
|
|
* @gcc: the cage config
|
|
*
|
|
* Remove the last point of the cage, in both source and destination cage
|
|
*/
|
|
void
|
|
pika_cage_config_remove_last_cage_point (PikaCageConfig *gcc)
|
|
{
|
|
pika_cage_config_remove_cage_point (gcc, gcc->cage_points->len - 1);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_remove_cage_point:
|
|
* @gcc: the cage config
|
|
* @point_number: the index of the point to remove
|
|
*
|
|
* Remove the given point from the cage
|
|
*/
|
|
void
|
|
pika_cage_config_remove_cage_point (PikaCageConfig *gcc,
|
|
gint point_number)
|
|
{
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
g_return_if_fail (point_number < gcc->cage_points->len);
|
|
g_return_if_fail (point_number >= 0);
|
|
|
|
if (gcc->cage_points->len > 0)
|
|
g_array_remove_index (gcc->cage_points, gcc->cage_points->len - 1);
|
|
|
|
pika_cage_config_compute_scaling_factor (gcc);
|
|
pika_cage_config_compute_edges_normal (gcc);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_remove_selected_points:
|
|
* @gcc: the cage config
|
|
*
|
|
* Remove all the selected points from the cage
|
|
*/
|
|
void
|
|
pika_cage_config_remove_selected_points (PikaCageConfig *gcc)
|
|
{
|
|
gint i;
|
|
PikaCagePoint *point;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
|
|
if (point->selected)
|
|
{
|
|
g_array_remove_index (gcc->cage_points, i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
pika_cage_config_compute_scaling_factor (gcc);
|
|
pika_cage_config_compute_edges_normal (gcc);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_get_point_coordinate:
|
|
* @gcc: the cage config
|
|
* @mode: the actual mode of the cage, PIKA_CAGE_MODE_CAGE_CHANGE or PIKA_CAGE_MODE_DEFORM
|
|
* @point_number: the index of the point to return
|
|
*
|
|
* Returns: the real position of the given point, as a PikaVector2
|
|
*/
|
|
PikaVector2
|
|
pika_cage_config_get_point_coordinate (PikaCageConfig *gcc,
|
|
PikaCageMode mode,
|
|
gint point_number)
|
|
{
|
|
PikaVector2 result = { 0.0, 0.0 };
|
|
PikaCagePoint *point;
|
|
|
|
g_return_val_if_fail (PIKA_IS_CAGE_CONFIG (gcc), result);
|
|
g_return_val_if_fail (point_number < gcc->cage_points->len, result);
|
|
g_return_val_if_fail (point_number >= 0, result);
|
|
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, point_number);
|
|
|
|
if (point->selected)
|
|
{
|
|
if (mode == PIKA_CAGE_MODE_CAGE_CHANGE)
|
|
{
|
|
result.x = point->src_point.x + gcc->displacement_x;
|
|
result.y = point->src_point.y + gcc->displacement_y;
|
|
}
|
|
else
|
|
{
|
|
result.x = point->dest_point.x + gcc->displacement_x;
|
|
result.y = point->dest_point.y + gcc->displacement_y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mode == PIKA_CAGE_MODE_CAGE_CHANGE)
|
|
{
|
|
result.x = point->src_point.x;
|
|
result.y = point->src_point.y;
|
|
}
|
|
else
|
|
{
|
|
result.x = point->dest_point.x;
|
|
result.y = point->dest_point.y;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_add_displacement:
|
|
* @gcc: the cage config
|
|
* @mode: the actual mode of the cage, PIKA_CAGE_MODE_CAGE_CHANGE or PIKA_CAGE_MODE_DEFORM
|
|
* @point_number: the point of the cage to move
|
|
* @x: x displacement value
|
|
* @y: y displacement value
|
|
*
|
|
* Add a displacement for all selected points of the cage.
|
|
* This displacement need to be committed to become effective.
|
|
*/
|
|
void
|
|
pika_cage_config_add_displacement (PikaCageConfig *gcc,
|
|
PikaCageMode mode,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
gcc->cage_mode = mode;
|
|
gcc->displacement_x = x;
|
|
gcc->displacement_y = y;
|
|
|
|
#ifdef DEBUG_CAGE
|
|
print_cage (gcc);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_commit_displacement:
|
|
* @gcc: the cage config
|
|
*
|
|
* Apply the displacement to the cage
|
|
*/
|
|
void
|
|
pika_cage_config_commit_displacement (PikaCageConfig *gcc)
|
|
{
|
|
gint i;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
PikaCagePoint *point;
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
|
|
if (point->selected)
|
|
{
|
|
if (gcc->cage_mode == PIKA_CAGE_MODE_CAGE_CHANGE)
|
|
{
|
|
point->src_point.x += gcc->displacement_x;
|
|
point->src_point.y += gcc->displacement_y;
|
|
point->dest_point.x += gcc->displacement_x;
|
|
point->dest_point.y += gcc->displacement_y;
|
|
}
|
|
else
|
|
{
|
|
point->dest_point.x += gcc->displacement_x;
|
|
point->dest_point.y += gcc->displacement_y;
|
|
}
|
|
}
|
|
}
|
|
|
|
pika_cage_config_compute_scaling_factor (gcc);
|
|
pika_cage_config_compute_edges_normal (gcc);
|
|
pika_cage_config_reset_displacement (gcc);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_reset_displacement:
|
|
* @gcc: the cage config
|
|
*
|
|
* Set the displacement to zero.
|
|
*/
|
|
void
|
|
pika_cage_config_reset_displacement (PikaCageConfig *gcc)
|
|
{
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
gcc->displacement_x = 0.0;
|
|
gcc->displacement_y = 0.0;
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_get_bounding_box:
|
|
* @gcc: the cage config
|
|
*
|
|
* Compute the bounding box of the source cage
|
|
*
|
|
* Returns: the bounding box of the source cage, as a GeglRectangle
|
|
*/
|
|
GeglRectangle
|
|
pika_cage_config_get_bounding_box (PikaCageConfig *gcc)
|
|
{
|
|
GeglRectangle bounding_box = { 0, 0, 0, 0};
|
|
gint i;
|
|
PikaCagePoint *point;
|
|
|
|
g_return_val_if_fail (PIKA_IS_CAGE_CONFIG (gcc), bounding_box);
|
|
|
|
if (gcc->cage_points->len == 0)
|
|
return bounding_box;
|
|
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, 0);
|
|
|
|
if (point->selected)
|
|
{
|
|
bounding_box.x = point->src_point.x + gcc->displacement_x;
|
|
bounding_box.y = point->src_point.y + gcc->displacement_y;
|
|
}
|
|
else
|
|
{
|
|
bounding_box.x = point->src_point.x;
|
|
bounding_box.y = point->src_point.y;
|
|
}
|
|
|
|
for (i = 1; i < gcc->cage_points->len; i++)
|
|
{
|
|
gdouble x,y;
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
|
|
if (point->selected)
|
|
{
|
|
x = point->src_point.x + gcc->displacement_x;
|
|
y = point->src_point.y + gcc->displacement_y;
|
|
}
|
|
else
|
|
{
|
|
x = point->src_point.x;
|
|
y = point->src_point.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;
|
|
}
|
|
|
|
/**
|
|
* pika_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
|
|
pika_cage_config_reverse_cage (PikaCageConfig *gcc)
|
|
{
|
|
PikaCagePoint temp;
|
|
gint i;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
for (i = 0; i < gcc->cage_points->len / 2; i++)
|
|
{
|
|
temp = g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
|
|
g_array_index (gcc->cage_points, PikaCagePoint, i) =
|
|
g_array_index (gcc->cage_points, PikaCagePoint, gcc->cage_points->len - i - 1);
|
|
|
|
g_array_index (gcc->cage_points, PikaCagePoint, gcc->cage_points->len - i - 1) = temp;
|
|
}
|
|
|
|
pika_cage_config_compute_scaling_factor (gcc);
|
|
pika_cage_config_compute_edges_normal (gcc);
|
|
}
|
|
|
|
/**
|
|
* pika_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.
|
|
*
|
|
* This function does not take into account an eventual displacement
|
|
*/
|
|
void
|
|
pika_cage_config_reverse_cage_if_needed (PikaCageConfig *gcc)
|
|
{
|
|
gint i;
|
|
gdouble sum;
|
|
|
|
g_return_if_fail (PIKA_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->cage_points->len ; i++)
|
|
{
|
|
PikaVector2 P1, P2, P3;
|
|
gdouble z;
|
|
|
|
P1 = (g_array_index (gcc->cage_points, PikaCagePoint, i)).src_point;
|
|
P2 = (g_array_index (gcc->cage_points, PikaCagePoint, (i+1) % gcc->cage_points->len)).src_point;
|
|
P3 = (g_array_index (gcc->cage_points, PikaCagePoint, (i+2) % gcc->cage_points->len)).src_point;
|
|
|
|
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)
|
|
{
|
|
pika_cage_config_reverse_cage (gcc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_compute_scaling_factor:
|
|
* @gcc: the cage config
|
|
*
|
|
* Update Green Coordinate scaling factor for the destination cage.
|
|
* This function does not take into account an eventual displacement.
|
|
*/
|
|
static void
|
|
pika_cage_config_compute_scaling_factor (PikaCageConfig *gcc)
|
|
{
|
|
PikaVector2 edge;
|
|
gdouble length, length_d;
|
|
gint i;
|
|
PikaCagePoint *current, *last;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
if (gcc->cage_points->len < 2)
|
|
return;
|
|
|
|
last = &g_array_index (gcc->cage_points, PikaCagePoint, 0);
|
|
|
|
for (i = 1; i <= gcc->cage_points->len; i++)
|
|
{
|
|
current = &g_array_index (gcc->cage_points, PikaCagePoint, i % gcc->cage_points->len);
|
|
|
|
pika_vector2_sub (&edge,
|
|
&(last->src_point),
|
|
&(current->src_point));
|
|
length = pika_vector2_length (&edge);
|
|
|
|
pika_vector2_sub (&edge,
|
|
&(last->dest_point),
|
|
&(current->dest_point));
|
|
length_d = pika_vector2_length (&edge);
|
|
|
|
last->edge_scaling_factor = length_d / length;
|
|
last = current;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_compute_edges_normal:
|
|
* @gcc: the cage config
|
|
*
|
|
* Update edges normal for the destination cage.
|
|
* This function does not take into account an eventual displacement.
|
|
*/
|
|
static void
|
|
pika_cage_config_compute_edges_normal (PikaCageConfig *gcc)
|
|
{
|
|
PikaVector2 normal;
|
|
gint i;
|
|
PikaCagePoint *current, *last;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
last = &g_array_index (gcc->cage_points, PikaCagePoint, 0);
|
|
|
|
for (i = 1; i <= gcc->cage_points->len; i++)
|
|
{
|
|
current = &g_array_index (gcc->cage_points, PikaCagePoint, i % gcc->cage_points->len);
|
|
|
|
pika_vector2_sub (&normal,
|
|
&(current->dest_point),
|
|
&(last->dest_point));
|
|
|
|
last->edge_normal = pika_vector2_normal (&normal);
|
|
last = current;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_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 source cage.
|
|
*
|
|
* Returns: TRUE if the point is inside, FALSE if not.
|
|
* This function does not take into account an eventual displacement.
|
|
*/
|
|
gboolean
|
|
pika_cage_config_point_inside (PikaCageConfig *gcc,
|
|
gfloat x,
|
|
gfloat y)
|
|
{
|
|
PikaVector2 *last, *current;
|
|
gboolean inside = FALSE;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (PIKA_IS_CAGE_CONFIG (gcc), FALSE);
|
|
|
|
last = &((g_array_index (gcc->cage_points, PikaCagePoint, gcc->cage_points->len - 1)).src_point);
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
current = &((g_array_index (gcc->cage_points, PikaCagePoint, i)).src_point);
|
|
|
|
if ((((current->y <= y) && (y < last->y))
|
|
|| ((last->y <= y) && (y < current->y)))
|
|
&& (x < (last->x - current->x) * (y - current->y) / (last->y - current->y) + current->x))
|
|
{
|
|
inside = !inside;
|
|
}
|
|
|
|
last = current;
|
|
}
|
|
|
|
return inside;
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_select_point:
|
|
* @gcc: the cage config
|
|
* @point_number: the index of the point to select
|
|
*
|
|
* Select the given point of the cage, and deselect the others.
|
|
*/
|
|
void
|
|
pika_cage_config_select_point (PikaCageConfig *gcc,
|
|
gint point_number)
|
|
{
|
|
gint i;
|
|
PikaCagePoint *point;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
g_return_if_fail (point_number < gcc->cage_points->len);
|
|
g_return_if_fail (point_number >= 0);
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
|
|
if (i == point_number)
|
|
{
|
|
point->selected = TRUE;
|
|
}
|
|
else
|
|
{
|
|
point->selected = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_select_area:
|
|
* @gcc: the cage config
|
|
* @mode: the actual mode of the cage, PIKA_CAGE_MODE_CAGE_CHANGE or PIKA_CAGE_MODE_DEFORM
|
|
* @area: the area to select
|
|
*
|
|
* Select cage's point inside the given area and deselect others
|
|
*/
|
|
void
|
|
pika_cage_config_select_area (PikaCageConfig *gcc,
|
|
PikaCageMode mode,
|
|
GeglRectangle area)
|
|
{
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
pika_cage_config_deselect_points (gcc);
|
|
pika_cage_config_select_add_area (gcc, mode, area);
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_select_add_area:
|
|
* @gcc: the cage config
|
|
* @mode: the actual mode of the cage, PIKA_CAGE_MODE_CAGE_CHANGE or PIKA_CAGE_MODE_DEFORM
|
|
* @area: the area to select
|
|
*
|
|
* Select cage's point inside the given area. Already selected point stay selected.
|
|
*/
|
|
void
|
|
pika_cage_config_select_add_area (PikaCageConfig *gcc,
|
|
PikaCageMode mode,
|
|
GeglRectangle area)
|
|
{
|
|
gint i;
|
|
PikaCagePoint *point;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, i);
|
|
|
|
if (mode == PIKA_CAGE_MODE_CAGE_CHANGE)
|
|
{
|
|
if (point->src_point.x >= area.x &&
|
|
point->src_point.x <= area.x + area.width &&
|
|
point->src_point.y >= area.y &&
|
|
point->src_point.y <= area.y + area.height)
|
|
{
|
|
point->selected = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (point->dest_point.x >= area.x &&
|
|
point->dest_point.x <= area.x + area.width &&
|
|
point->dest_point.y >= area.y &&
|
|
point->dest_point.y <= area.y + area.height)
|
|
{
|
|
point->selected = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_toggle_point_selection:
|
|
* @gcc: the cage config
|
|
* @point_number: the index of the point to toggle selection
|
|
*
|
|
* Toggle the selection of the given cage point
|
|
*/
|
|
void
|
|
pika_cage_config_toggle_point_selection (PikaCageConfig *gcc,
|
|
gint point_number)
|
|
{
|
|
PikaCagePoint *point;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
g_return_if_fail (point_number < gcc->cage_points->len);
|
|
g_return_if_fail (point_number >= 0);
|
|
|
|
point = &g_array_index (gcc->cage_points, PikaCagePoint, point_number);
|
|
point->selected = ! point->selected;
|
|
}
|
|
|
|
/**
|
|
* pika_cage_deselect_points:
|
|
* @gcc: the cage config
|
|
*
|
|
* Deselect all cage points.
|
|
*/
|
|
void
|
|
pika_cage_config_deselect_points (PikaCageConfig *gcc)
|
|
{
|
|
gint i;
|
|
|
|
g_return_if_fail (PIKA_IS_CAGE_CONFIG (gcc));
|
|
|
|
for (i = 0; i < gcc->cage_points->len; i++)
|
|
{
|
|
(g_array_index (gcc->cage_points, PikaCagePoint, i)).selected = FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_cage_config_point_is_selected:
|
|
* @gcc: the cage config
|
|
* @point_number: the index of the point to test
|
|
*
|
|
* Returns: TRUE if the point is selected, FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
pika_cage_config_point_is_selected (PikaCageConfig *gcc,
|
|
gint point_number)
|
|
{
|
|
PikaCagePoint *point;
|
|
|
|
g_return_val_if_fail (PIKA_IS_CAGE_CONFIG (gcc), FALSE);
|
|
g_return_val_if_fail (point_number < gcc->cage_points->len, FALSE);
|
|
g_return_val_if_fail (point_number >= 0, FALSE);
|
|
|
|
point = &(g_array_index (gcc->cage_points, PikaCagePoint, point_number));
|
|
|
|
return point->selected;
|
|
}
|