/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* 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 2 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
#include
#include "gth-curve.h"
/* -- GthCurve -- */
G_DEFINE_TYPE (GthCurve, gth_curve, G_TYPE_OBJECT)
GthCurve *
gth_curve_new (GType curve_type,
GthPoints *points)
{
GthCurve *curve;
curve = g_object_new (curve_type, NULL);
gth_curve_set_points (curve, points);
return curve;
}
GthCurve *
gth_curve_new_for_points (GType curve_type,
int n_points,
...)
{
GthCurve *curve;
va_list args;
GthPoints points;
curve = g_object_new (curve_type, NULL);
va_start (args, n_points);
gth_points_init (&points, 0);
gth_points_set_pointv (&points, args, n_points);
gth_curve_set_points (curve, &points);
va_end (args);
return curve;
}
void
gth_curve_set_points (GthCurve *curve,
GthPoints *points)
{
gth_points_copy (points, gth_curve_get_points (curve));
gth_curve_setup (curve);
}
static void
gth_curve_finalize (GObject *object)
{
GthCurve *spline;
spline = GTH_CURVE (object);
gth_points_dispose (&spline->points);
G_OBJECT_CLASS (gth_curve_parent_class)->finalize (object);
}
static void
gth_curve_setup_base (GthCurve *self)
{
/* void */
}
static double
gth_curve_eval_base (GthCurve *self,
double x)
{
return x;
}
static void
gth_curve_class_init (GthCurveClass *class)
{
GObjectClass *object_class;
object_class = (GObjectClass*) class;
object_class->finalize = gth_curve_finalize;
class->setup = gth_curve_setup_base;
class->eval = gth_curve_eval_base;
}
static void
gth_curve_init (GthCurve *self)
{
gth_points_init (&self->points, 0);
}
GthPoints *
gth_curve_get_points (GthCurve *curve)
{
return &curve->points;
}
void
gth_curve_setup (GthCurve *self)
{
return GTH_CURVE_GET_CLASS (self)->setup (self);
}
double
gth_curve_eval (GthCurve *self,
double x)
{
if (self->points.n > 0) {
x = MAX (x, self->points.p[0].x);
x = MIN (x, self->points.p[self->points.n - 1].x);
}
return GTH_CURVE_GET_CLASS (self)->eval (self, x);
}
/* -- Gauss-Jordan linear equation solver (for splines) -- */
typedef struct {
double **v;
int r;
int c;
} Matrix;
static Matrix *
GJ_matrix_new (int r, int c)
{
Matrix *m;
int i, j;
m = g_new (Matrix, 1);
m->r = r;
m->c = c;
m->v = g_new (double *, r);
for (i = 0; i < r; i++) {
m->v[i] = g_new (double, c);
for (j = 0; j < c; j++)
m->v[i][j] = 0.0;
}
return m;
}
static void
GJ_matrix_free (Matrix *m)
{
int i;
for (i = 0; i < m->r; i++)
g_free (m->v[i]);
g_free (m->v);
g_free (m);
}
static void
GJ_swap_rows (double **m, int k, int l)
{
double *t = m[k];
m[k] = m[l];
m[l] = t;
}
static gboolean
GJ_matrix_solve (Matrix *m, double *x)
{
double **A = m->v;
int r = m->r;
int k, i, j;
for (k = 0; k < r; k++) { // column
// pivot for column
int i_max = 0;
double vali = 0;
for (i = k; i < r; i++) {
if ((i == k) || (A[i][k] > vali)) {
i_max = i;
vali = A[i][k];
}
}
GJ_swap_rows (A, k, i_max);
if (A[i_max][i] == 0) {
g_print ("matrix is singular!\n");
return TRUE;
}
// for all rows below pivot
for (i = k + 1; i < r; i++) {
for (j = k + 1; j < r + 1; j++)
A[i][j] = A[i][j] - A[k][j] * (A[i][k] / A[k][k]);
A[i][k] = 0.0;
}
}
for (i = r - 1; i >= 0; i--) { // rows = columns
double v = A[i][r] / A[i][i];
x[i] = v;
for (j = i - 1; j >= 0; j--) { // rows
A[j][r] -= A[j][i] * v;
A[j][i] = 0.0;
}
}
return FALSE;
}
/* -- GthSpline (http://en.wikipedia.org/wiki/Spline_interpolation#Algorithm_to_find_the_interpolating_cubic_spline) -- */
G_DEFINE_TYPE (GthSpline, gth_spline, GTH_TYPE_CURVE)
static void
gth_spline_finalize (GObject *object)
{
GthSpline *spline;
spline = GTH_SPLINE (object);
g_free (spline->k);
G_OBJECT_CLASS (gth_spline_parent_class)->finalize (object);
}
static void
gth_spline_setup (GthCurve *curve)
{
GthSpline *spline;
GthPoints *points;
int n;
GthPoint *p;
Matrix *m;
double **A;
int i;
spline = GTH_SPLINE (curve);
points = gth_curve_get_points (GTH_CURVE (spline));
n = points->n;
p = points->p;
spline->k = g_new (double, n + 1);
for (i = 0; i < n + 1; i++)
spline->k[i] = 1.0;
m = GJ_matrix_new (n+1, n+2);
A = m->v;
for (i = 1; i < n; i++) {
A[i][i-1] = 1.0 / (p[i].x - p[i-1].x);
A[i][i ] = 2.0 * (1.0 / (p[i].x - p[i-1].x) + 1.0 / (p[i+1].x - p[i].x));
A[i][i+1] = 1.0 / (p[i+1].x - p[i].x);
A[i][n+1] = 3.0 * ( (p[i].y - p[i-1].y) / ((p[i].x - p[i-1].x) * (p[i].x - p[i-1].x)) + (p[i+1].y - p[i].y) / ((p[i+1].x - p[i].x) * (p[i+1].x - p[i].x)) );
}
A[0][0 ] = 2.0 / (p[1].x - p[0].x);
A[0][1 ] = 1.0 / (p[1].x - p[0].x);
A[0][n+1] = 3.0 * (p[1].y - p[0].y) / ((p[1].x - p[0].x) * (p[1].x - p[0].x));
A[n][n-1] = 1.0 / (p[n].x - p[n-1].x);
A[n][n ] = 2.0 / (p[n].x - p[n-1].x);
A[n][n+1] = 3.0 * (p[n].y - p[n-1].y) / ((p[n].x - p[n-1].x) * (p[n].x - p[n-1].x));
spline->is_singular = GJ_matrix_solve (m, spline->k);
GJ_matrix_free (m);
}
static double
gth_spline_eval (GthCurve *curve,
double x)
{
GthSpline *spline;
GthPoints *points;
GthPoint *p;
double *k;
int i;
spline = GTH_SPLINE (curve);
points = gth_curve_get_points (GTH_CURVE (spline));
p = points->p;
k = spline->k;
if (spline->is_singular)
return x;
for (i = 1; p[i].x < x; i++)
/* void */;
double t = (x - p[i-1].x) / (p[i].x - p[i-1].x);
double a = k[i-1] * (p[i].x - p[i-1].x) - (p[i].y - p[i-1].y);
double b = - k[i] * (p[i].x - p[i-1].x) + (p[i].y - p[i-1].y);
double y = round ( ((1-t) * p[i-1].y) + (t * p[i].y) + (t * (1-t) * (a * (1-t) + b * t)) );
return CLAMP (y, 0, 255);
}
static void
gth_spline_class_init (GthSplineClass *class)
{
GObjectClass *object_class;
GthCurveClass *curve_class;
object_class = (GObjectClass*) class;
object_class->finalize = gth_spline_finalize;
curve_class = (GthCurveClass*) class;
curve_class->setup = gth_spline_setup;
curve_class->eval = gth_spline_eval;
}
static void
gth_spline_init (GthSpline *spline)
{
spline->k = NULL;
spline->is_singular = FALSE;
}
/* -- GthCSpline (https://en.wikipedia.org/wiki/Cubic_Hermite_spline) -- */
G_DEFINE_TYPE (GthCSpline, gth_cspline, GTH_TYPE_CURVE)
static void
gth_cspline_finalize (GObject *object)
{
GthCSpline *spline;
spline = GTH_CSPLINE (object);
g_free (spline->tangents);
G_OBJECT_CLASS (gth_cspline_parent_class)->finalize (object);
}
#if 0
static int number_sign (double x) {
return (x < 0) ? -1 : (x > 0) ? 1 : 0;
}
#endif
static void
gth_cspline_setup (GthCurve *curve)
{
GthCSpline *spline;
GthPoints *points;
int n;
GthPoint *p;
double *t;
int k;
spline = GTH_CSPLINE (curve);
points = gth_curve_get_points (GTH_CURVE (spline));
n = points->n;
p = points->p;
t = spline->tangents = g_new (double, n);
#if 0
/* Finite difference */
for (k = 0; k < n; k++) {
t[k] = 0;
if (k > 0)
t[k] += (p[k].y - p[k-1].y) / (2 * (p[k].x - p[k-1].x));
if (k < n - 1)
t[k] += (p[k+1].y - p[k].y) / (2 * (p[k+1].x - p[k].x));
}
#endif
#if 1
/* Catmull–Rom spline */
for (k = 0; k < n; k++) {
t[k] = 0;
if (k == 0) {
t[k] = (p[k+1].y - p[k].y) / (p[k+1].x - p[k].x);
}
else if (k == n - 1) {
t[k] = (p[k].y - p[k-1].y) / (p[k].x - p[k-1].x);
}
else
t[k] = (p[k+1].y - p[k-1].y) / (p[k+1].x - p[k-1].x);
}
#endif
#if 0
/* Monotonic spline (Fritsch–Carlson method) (https://en.wikipedia.org/wiki/Monotone_cubic_interpolation) */
double *delta = g_new (double, n);
double *alfa = g_new (double, n);
double *beta = g_new (double, n);
for (k = 0; k < n - 1; k++)
delta[k] = (p[k+1].y - p[k].y) / (p[k+1].x - p[k].x);
t[0] = delta[0];
for (k = 1; k < n - 1; k++) {
if (number_sign (delta[k-1]) == number_sign (delta[k]))
t[k] = (delta[k-1] + delta[k]) / 2.0;
else
t[k] = 0;
}
t[n-1] = delta[n-2];
for (k = 0; k < n - 1; k++) {
if (delta[k] == 0) {
t[k] = 0;
t[k+1] = 0;
}
}
for (k = 0; k < n - 1; k++) {
if (delta[k] == 0)
continue;
alfa[k] = t[k] / delta[k];
beta[k] = t[k+1] / delta[k];
if (alfa[k] > 3) {
t[k] = 3 * delta[k];
alfa[k] = 3;
}
if (beta[k] > 3) {
t[k+1] = 3 * delta[k];
beta[k] = 3;
}
}
for (k = 0; k < n - 1; k++) {
if (delta[k] == 0)
continue;
if ((alfa[k] < 0) && (beta[k-1] < 0))
t[k] = 0;
double v = SQR (alfa[k]) + SQR (beta[k]);
if (v > 9) {
double tau = 3.0 / sqrt (v);
t[k] = tau * alfa[k] * delta[k];
t[k+1] = tau * beta[k] * delta[k];
}
}
g_free (beta);
g_free (alfa);
g_free (delta);
#endif
}
static inline double h00 (double x, double x2, double x3)
{
return 2*x3 - 3*x2 + 1;
}
static inline double h10 (double x, double x2, double x3)
{
return x3 - 2*x2 + x;
}
static inline double h01 (double x, double x2, double x3)
{
return -2*x3 + 3*x2;
}
static inline double h11 (double x, double x2, double x3)
{
return x3 - x2;
}
static double
gth_cspline_eval (GthCurve *curve,
double x)
{
GthCSpline *spline;
GthPoints *points;
GthPoint *p;
double *t;
int k;
double d, z, z2, z3;
double y;
spline = GTH_CSPLINE (curve);
points = gth_curve_get_points (GTH_CURVE (spline));
p = points->p;
t = spline->tangents;
for (k = 1; p[k].x < x; k++)
/* void */;
k--;
d = p[k+1].x - p[k].x;
z = (x - p[k].x) / d;
z2 = z * z;
z3 = z2 * z;
y = (h00(z, z2, z3) * p[k].y) + (h10(z, z2, z3) * d * t[k]) + (h01(z, z2, z3) * p[k+1].y) + (h11(z, z2, z3) * d * t[k+1]);
return CLAMP (y, 0, 255);
}
static void
gth_cspline_class_init (GthCSplineClass *class)
{
GObjectClass *object_class;
GthCurveClass *curve_class;
object_class = (GObjectClass*) class;
object_class->finalize = gth_cspline_finalize;
curve_class = (GthCurveClass*) class;
curve_class->setup = gth_cspline_setup;
curve_class->eval = gth_cspline_eval;
}
static void
gth_cspline_init (GthCSpline *spline)
{
spline->tangents = NULL;
}
/* -- GthBezier -- */
G_DEFINE_TYPE (GthBezier, gth_bezier, GTH_TYPE_CURVE)
/* ---- */
static void
gth_bezier_finalize (GObject *object)
{
GthBezier *spline;
spline = GTH_BEZIER (object);
g_free (spline->k);
G_OBJECT_CLASS (gth_bezier_parent_class)->finalize (object);
}
/*
* calculate the control points coordinates (only the y) relative to
* the interval [p1, p2]
*
* a: the point to the left of p1
* b: the point to the right of p2
* k: (output) the y values of the 4 points in the [p1, p2] interval
*
**/
static void
bezier_set_control_points (GthPoint *a,
GthPoint *p1,
GthPoint *p2,
GthPoint *b,
double *k)
{
double slope;
double c1_y = 0;
double c2_y = 0;
/*
* the x coordinates of the control points is fixed to:
*
* c1_x = 2/3 * p1->x + 1/3 * p2->x;
* c2_x = 1/3 * p1->x + 2/3 * p2->x;
*
* for a more complete explanation see here: https://git.gnome.org/browse/gimp/tree/app/core/gimpcurve.c?h=gimp-2-8#n976
*
**/
if ((a != NULL) && (b != NULL)) {
slope = (p2->y - a->y) / (p2->x - a->x);
c1_y = p1->y + slope * (p2->x - p1->x) / 3.0;
slope = (b->y - p1->y) / (b->x - p1->x);
c2_y = p2->y - slope * (p2->x - p1->x) / 3.0;
}
else if ((a == NULL) && (b != NULL)) {
slope = (b->y - p1->y) / (b->x - p1->x);
c2_y = p2->y - slope * (p2->x - p1->x) / 3.0;
c1_y = p1->y + (c2_y - p1->y) / 2.0;
}
else if ((a != NULL) && (b == NULL)) {
slope = (p2->y - a->y) / (p2->x - a->x);
c1_y = p1->y + slope * (p2->x - p1->x) / 3.0;
c2_y = p2->y + (c1_y - p2->y) / 2.0;
}
else { /* if ((a == NULL) && (b == NULL)) */
c1_y = p1->y + (p2->y - p1->y) / 3.0;
c2_y = p1->y + (p2->y - p1->y) * 2.0 / 3.0;
}
k[0] = p1->y;
k[1] = c1_y;
k[2] = c2_y;
k[3] = p2->y;
}
static void
gth_bezier_setup (GthCurve *curve)
{
GthBezier *spline;
GthPoints *points;
int n;
GthPoint *p;
int i;
spline = GTH_BEZIER (curve);
points = gth_curve_get_points (GTH_CURVE (spline));
n = points->n;
p = points->p;
spline->linear = (n <= 1);
if (spline->linear)
return;
spline->k = g_new (double, 4 * (n - 1)); /* 4 points for each interval */
for (i = 0; i < n - 1; i++) {
double *k = spline->k + (i*4);
GthPoint *a;
GthPoint *b;
a = (i == 0) ? NULL: p + (i-1);
b = (i == n-2) ? NULL : p + (i+2);
bezier_set_control_points (a, p + i, p + (i+1), b, k);
}
}
static double
gth_bezier_eval (GthCurve *curve,
double x)
{
GthBezier *spline;
GthPoints *points;
GthPoint *p;
double *k;
int i;
spline = GTH_BEZIER (curve);
if (spline->linear)
return x;
points = gth_curve_get_points (GTH_CURVE (spline));
p = points->p;
for (i = 1; p[i].x < x; i++)
/* void */;
i--;
k = spline->k + (i*4); /* k: the 4 bezier points of the interval i */
double t = (x - p[i].x) / (p[i+1].x - p[i].x);
double t2 = t*t;
double s = 1.0 - t;
double s2 = s * s;
double y = round (s2*s*k[0] + 3*s2*t*k[1] + 3*s*t2*k[2] + t2*t*k[3]);
return CLAMP (y, 0, 255);
}
static void
gth_bezier_class_init (GthBezierClass *class)
{
GObjectClass *object_class;
GthCurveClass *curve_class;
object_class = (GObjectClass*) class;
object_class->finalize = gth_bezier_finalize;
curve_class = (GthCurveClass*) class;
curve_class->setup = gth_bezier_setup;
curve_class->eval = gth_bezier_eval;
}
static void
gth_bezier_init (GthBezier *spline)
{
spline->k = NULL;
spline->linear = TRUE;
}