backends/native: Change the MetaBezier API to sampling and lookup
This keeps the existing ClutterBezier implementation but changes the visible API to match the needs of the tablet tool pressure curve: a bezier defined within a [0.0/0.0, 1.0/1.0] box,(sampled into a set of x->y mappings for each possible pressure input x, and a lookup function to get those values out of the curve. This patch moves the internally-only functions to be statics and changes meta_bezier_init() to take only the second and third control point, as normalized doubles. Because internally we still work with integers, the bezier curve now has an integer "precision" that defines how many points between 0.0 and 1.0 we can sample. The meta_bezier_rasterize() function calculates the x->y mapping for each point on the bezier curve given the initial scale of the curve. That value is then available to the caller via meta_bezier_lookup(). Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3399>
This commit is contained in:
parent
f2ed377f48
commit
5ec67a8e90
@ -29,6 +29,11 @@
|
||||
* (private; a building block for the public bspline object) *
|
||||
****************************************************************************/
|
||||
|
||||
/* This is used in meta_bezier_advance to represent the full
|
||||
length of the bezier curve. Anything less than that represents a
|
||||
fraction of the length */
|
||||
#define META_BEZIER_MAX_LENGTH (1 << 18)
|
||||
|
||||
/*
|
||||
* The t parameter of the bezier is from interval <0,1>, so we can use
|
||||
* 14.18 format and special multiplication functions that preserve
|
||||
@ -55,11 +60,26 @@
|
||||
|
||||
typedef gint32 _FixedT;
|
||||
|
||||
typedef struct _MetaBezierKnot MetaBezierKnot;
|
||||
|
||||
struct _MetaBezierKnot
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
/*
|
||||
* This is a private type representing a single cubic bezier
|
||||
*/
|
||||
struct _MetaBezier
|
||||
{
|
||||
/* The precision, i.e. the number of possible points between 0.0 and 1.0.
|
||||
* We require a normalized bezier curve but then later sample to a given
|
||||
* precision. The bezier coefficients are scaled up by this factor and later
|
||||
* scaled down again during sampling.
|
||||
*/
|
||||
unsigned int precision;
|
||||
|
||||
/*
|
||||
* bezier coefficients -- these are calculated using multiplication and
|
||||
* addition from integer input, so these are also integers
|
||||
@ -76,19 +96,38 @@ struct _MetaBezier
|
||||
|
||||
/* length of the bezier */
|
||||
unsigned int length;
|
||||
|
||||
/* the sampled points on the curve */
|
||||
double *points;
|
||||
};
|
||||
|
||||
/**
|
||||
* meta_bezier_new:
|
||||
* @precision: the number of points we can sample between 0.0 and 1.0
|
||||
*
|
||||
* Create a new bezier curve with the given precision. This precision defines
|
||||
* the maximum number of points we can sample and thus thus how much linear
|
||||
* interpolation needs to be done when looking up a point on the curve.
|
||||
*/
|
||||
MetaBezier *
|
||||
meta_bezier_new (void)
|
||||
meta_bezier_new (unsigned int precision)
|
||||
{
|
||||
return g_new0 (MetaBezier, 1);
|
||||
MetaBezier *b;
|
||||
|
||||
g_return_val_if_fail (precision > 0, NULL);
|
||||
|
||||
b = g_new0 (MetaBezier, 1);
|
||||
b->precision = precision;
|
||||
b->points = g_new0 (double, precision);
|
||||
return b;
|
||||
}
|
||||
|
||||
void
|
||||
meta_bezier_free (MetaBezier * b)
|
||||
meta_bezier_free (MetaBezier *b)
|
||||
{
|
||||
if (G_LIKELY (b))
|
||||
{
|
||||
g_free (b->points);
|
||||
g_free (b);
|
||||
}
|
||||
}
|
||||
@ -121,8 +160,10 @@ meta_bezier_t2y (const MetaBezier *b,
|
||||
* Advances along the bezier to relative length L and returns the coordinances
|
||||
* in knot
|
||||
*/
|
||||
void
|
||||
meta_bezier_advance (const MetaBezier *b, gint L, MetaBezierKnot * knot)
|
||||
static void
|
||||
meta_bezier_advance (const MetaBezier *b,
|
||||
int L,
|
||||
MetaBezierKnot *knot)
|
||||
{
|
||||
_FixedT t = L;
|
||||
|
||||
@ -137,6 +178,113 @@ meta_bezier_advance (const MetaBezier *b, gint L, MetaBezierKnot * knot)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
meta_bezier_sample (MetaBezier *b)
|
||||
{
|
||||
const size_t N = b->precision;
|
||||
const double MIN_EPSILON = 0.00001;
|
||||
MetaBezierKnot pos;
|
||||
int i;
|
||||
double epsilon;
|
||||
double t;
|
||||
double ts[N]; /* maps pos.x -> t */
|
||||
double *points = b->points;
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
points[i] = -1.0;
|
||||
ts[i] = -1.0;
|
||||
}
|
||||
|
||||
ts[0] = 0;
|
||||
ts[N - 1] = 1;
|
||||
points[0] = 0;
|
||||
/* Fill in the last point so linear interpolation (see below) is
|
||||
* guaranteed. This should always yield 1.0 anyway. */
|
||||
meta_bezier_advance (b, META_BEZIER_MAX_LENGTH, &pos);
|
||||
points[N - 1] = pos.y / N;
|
||||
|
||||
epsilon = 1.0 / N;
|
||||
t = epsilon;
|
||||
|
||||
/* We walk forward from t=0 to t=1.0, calculating every bezier
|
||||
* point on the curve and for all x values we remember our matching t.
|
||||
* If any x coordinate is missing, we reduce the epsilon and restart
|
||||
* with this higher granularity from the last t that gave us a value
|
||||
* below the missing x.
|
||||
*/
|
||||
do
|
||||
{
|
||||
for (i = 1; i < N; i++)
|
||||
{
|
||||
if (ts[i] == -1.0)
|
||||
{
|
||||
t = ts[i - 1];
|
||||
epsilon /= 2.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (t < 1.0)
|
||||
{
|
||||
meta_bezier_advance (b, t * META_BEZIER_MAX_LENGTH, &pos);
|
||||
if (pos.x < N)
|
||||
{
|
||||
if (points[MAX (pos.x - 1, 0)] == -1)
|
||||
{
|
||||
/* Skipped over at least one x coordinate, let's restart
|
||||
* as long as we have have a sensible epsilon. Some curves
|
||||
* may never find all points. */
|
||||
if (epsilon > MIN_EPSILON)
|
||||
break;
|
||||
}
|
||||
|
||||
if (points[pos.x] == -1)
|
||||
{
|
||||
points[pos.x] = (double)pos.y / N;
|
||||
ts[pos.x] = t;
|
||||
}
|
||||
}
|
||||
|
||||
t += epsilon;
|
||||
}
|
||||
}
|
||||
while (t < 1.0 && epsilon > MIN_EPSILON);
|
||||
|
||||
/* Do linear interpolation of the remaining points, if any */
|
||||
i = 0;
|
||||
while (i < N - 1)
|
||||
{
|
||||
double *current = &points[++i];
|
||||
int prev = points[i - 1];
|
||||
|
||||
if (*current == -1.0)
|
||||
{
|
||||
int next_idx = i;
|
||||
int next;
|
||||
double delta;
|
||||
|
||||
do
|
||||
{
|
||||
next = points[++next_idx];
|
||||
} while (next == -1.0 && next_idx < N); /* N-1 is guaranteed valid */
|
||||
|
||||
delta = (next - prev) / (next_idx - i + 1);
|
||||
|
||||
while (i < next_idx)
|
||||
{
|
||||
*current = prev + delta;
|
||||
prev = *current;
|
||||
current++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
g_warn_if_fail (points[i] != -1.0);
|
||||
}
|
||||
|
||||
static int
|
||||
sqrti (int number)
|
||||
{
|
||||
@ -223,17 +371,33 @@ sqrti (int number)
|
||||
|
||||
void
|
||||
meta_bezier_init (MetaBezier *b,
|
||||
gint x_0, gint y_0,
|
||||
gint x_1, gint y_1,
|
||||
gint x_2, gint y_2,
|
||||
gint x_3, gint y_3)
|
||||
double x_1,
|
||||
double y_1,
|
||||
double x_2,
|
||||
double y_2)
|
||||
{
|
||||
double x_0 = 0.0,
|
||||
y_0 = 0.0,
|
||||
x_3 = 1.0,
|
||||
y_3 = 1.0;
|
||||
_FixedT t;
|
||||
int i;
|
||||
int xp = x_0;
|
||||
int yp = y_0;
|
||||
_FixedT length[CBZ_T_SAMPLES + 1];
|
||||
|
||||
g_warn_if_fail (x_1 >= 0.0 && x_1 <= 1.0);
|
||||
g_warn_if_fail (x_2 >= 0.0 && x_2 <= 1.0);
|
||||
g_warn_if_fail (y_1 >= 0.0 && y_1 <= 1.0);
|
||||
g_warn_if_fail (y_2 >= 0.0 && y_2 <= 1.0);
|
||||
|
||||
x_1 *= b->precision;
|
||||
y_1 *= b->precision;
|
||||
x_2 *= b->precision;
|
||||
y_2 *= b->precision;
|
||||
x_3 *= b->precision;
|
||||
y_3 *= b->precision;
|
||||
|
||||
#if 0
|
||||
g_debug ("Initializing bezier at {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
|
||||
x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
@ -291,13 +455,33 @@ meta_bezier_init (MetaBezier *b,
|
||||
|
||||
b->length = length[CBZ_T_SAMPLES];
|
||||
|
||||
meta_bezier_sample (b);
|
||||
|
||||
#if 0
|
||||
g_debug ("length %d", b->length);
|
||||
#endif
|
||||
}
|
||||
|
||||
guint
|
||||
meta_bezier_get_length (const MetaBezier *b)
|
||||
/**
|
||||
* meta_bezier_lookup:
|
||||
* @b: a #MetaBezier curve
|
||||
* @pos: the position on the bezier curve in the [0.0, 1.0] range
|
||||
*
|
||||
* Returns the value of this normalized point on the curve.
|
||||
*/
|
||||
double
|
||||
meta_bezier_lookup (const MetaBezier *b,
|
||||
double pos)
|
||||
{
|
||||
return b->length;
|
||||
/* The position may be fine-grained than our calculated bezier curve,
|
||||
* find the two closest points and do a linear interpolation between
|
||||
* those two points.
|
||||
*/
|
||||
int low_idx = CLAMP ((int)(pos * b->precision), 0, b->precision - 1);
|
||||
int high_idx = CLAMP (low_idx + 1, 0, b->precision - 1);
|
||||
double low = b->points[low_idx];
|
||||
double high = b->points[high_idx];
|
||||
double rval = low + (high - low) * (pos - (int)pos);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
@ -21,38 +21,28 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "core/util-private.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* This is used in meta_bezier_advance to represent the full
|
||||
length of the bezier curve. Anything less than that represents a
|
||||
fraction of the length */
|
||||
#define META_BEZIER_MAX_LENGTH (1 << 18)
|
||||
|
||||
typedef struct _MetaBezier MetaBezier;
|
||||
typedef struct _MetaBezierKnot MetaBezierKnot;
|
||||
|
||||
struct _MetaBezierKnot
|
||||
{
|
||||
gint x;
|
||||
gint y;
|
||||
};
|
||||
|
||||
MetaBezier *meta_bezier_new (void);
|
||||
META_EXPORT_TEST
|
||||
MetaBezier * meta_bezier_new (unsigned int precision);
|
||||
|
||||
META_EXPORT_TEST
|
||||
void meta_bezier_free (MetaBezier *b);
|
||||
|
||||
void meta_bezier_advance (const MetaBezier *b,
|
||||
gint L,
|
||||
MetaBezierKnot *knot);
|
||||
|
||||
META_EXPORT_TEST
|
||||
void meta_bezier_init (MetaBezier *b,
|
||||
gint x_0, gint y_0,
|
||||
gint x_1, gint y_1,
|
||||
gint x_2, gint y_2,
|
||||
gint x_3, gint y_3);
|
||||
double x_1,
|
||||
double y_1,
|
||||
double x_2,
|
||||
double y_2);
|
||||
|
||||
guint meta_bezier_get_length (const MetaBezier *b);
|
||||
META_EXPORT_TEST
|
||||
double meta_bezier_lookup (const MetaBezier *b,
|
||||
double pos);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MetaBezier, meta_bezier_free);
|
||||
|
||||
|
@ -353,6 +353,7 @@ if have_native_tests
|
||||
'native-screen-cast.h',
|
||||
'native-virtual-monitor.c',
|
||||
'native-virtual-monitor.h',
|
||||
'native-bezier-tests.c',
|
||||
],
|
||||
'depends': [
|
||||
screen_cast_client,
|
||||
|
127
src/tests/native-bezier-tests.c
Normal file
127
src/tests/native-bezier-tests.c
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Red Hat 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "native-bezier-tests.h"
|
||||
|
||||
#include "backends/native/meta-bezier.h"
|
||||
|
||||
static void
|
||||
meta_test_bezier_linear (void)
|
||||
{
|
||||
const int precision = 256;
|
||||
g_autoptr (MetaBezier) b = meta_bezier_new (precision);
|
||||
double i;
|
||||
double point;
|
||||
|
||||
meta_bezier_init (b, 0.0, 0.0, 1.0, 1.0);
|
||||
|
||||
/* The bezier curve code has a slight bug: the last point is always enforced
|
||||
* to be 1.0. For low scales this can lead to a jump from N-2 to N-1*/
|
||||
for (i = 0.0; i < 1.0; i += 1.0 / precision)
|
||||
{
|
||||
point = meta_bezier_lookup (b, i);
|
||||
g_assert_cmpfloat_with_epsilon (i, point, 0.01);
|
||||
}
|
||||
|
||||
point = meta_bezier_lookup (b, 1.0);
|
||||
g_assert_cmpfloat (point, ==, 1.0);
|
||||
}
|
||||
|
||||
static void
|
||||
meta_test_bezier_steep (void)
|
||||
{
|
||||
const int precision = 1000;
|
||||
g_autoptr (MetaBezier) b = meta_bezier_new (precision);
|
||||
double i;
|
||||
|
||||
meta_bezier_init (b, 0.0, 1.0, 0.0, 1.0);
|
||||
|
||||
/* ^ _____________
|
||||
* | /
|
||||
* || steep
|
||||
* ||
|
||||
* ||
|
||||
* +---------------t>
|
||||
*/
|
||||
for (i = 0.2; i < 1.0; i += 1.0 / precision)
|
||||
{
|
||||
double point = meta_bezier_lookup (b, i);
|
||||
g_assert_cmpfloat (point, >, 0.90);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
meta_test_bezier_flat (void)
|
||||
{
|
||||
const int precision = 1000;
|
||||
g_autoptr (MetaBezier) b = meta_bezier_new (precision);
|
||||
double i;
|
||||
|
||||
/* ^ |
|
||||
* | |
|
||||
* | flat |
|
||||
* | /
|
||||
* |____________/
|
||||
* +-------------->
|
||||
*/
|
||||
meta_bezier_init (b, 1.0, 0.0, 1.0, 0.0);
|
||||
|
||||
for (i = 0.0; i < 0.8; i++)
|
||||
{
|
||||
double point = meta_bezier_lookup (b, i);
|
||||
g_assert_cmpfloat (point, <, 0.20);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
meta_test_bezier_snake (void)
|
||||
{
|
||||
const int precision = 1000;
|
||||
g_autoptr (MetaBezier) b = meta_bezier_new (precision);
|
||||
double i;
|
||||
|
||||
/* ^ _______
|
||||
* | /
|
||||
* | | snake
|
||||
* | |
|
||||
* |________/
|
||||
* +--------------->
|
||||
*/
|
||||
meta_bezier_init (b, 1.0, 0.0, 0.0, 1.0);
|
||||
|
||||
for (i = 0.0; i < 1.0; i += 1.0 / precision)
|
||||
{
|
||||
double point = meta_bezier_lookup (b, i);
|
||||
if (i < 0.33)
|
||||
g_assert_cmpfloat (point, <=, 0.1);
|
||||
else if (i > 0.66)
|
||||
g_assert_cmpfloat (point, >=, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
init_bezier_tests (void)
|
||||
{
|
||||
g_test_add_func ("/backends/bezier/linear", meta_test_bezier_linear);
|
||||
g_test_add_func ("/backends/bezier/steep", meta_test_bezier_steep);
|
||||
g_test_add_func ("/backends/bezier/flat", meta_test_bezier_flat);
|
||||
g_test_add_func ("/backends/bezier/snake", meta_test_bezier_snake);
|
||||
}
|
||||
|
21
src/tests/native-bezier-tests.h
Normal file
21
src/tests/native-bezier-tests.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Red Hat 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void init_bezier_tests (void);
|
@ -19,6 +19,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "meta-test/meta-context-test.h"
|
||||
#include "tests/native-bezier-tests.h"
|
||||
#include "tests/native-screen-cast.h"
|
||||
#include "tests/native-virtual-monitor.h"
|
||||
|
||||
@ -27,6 +28,7 @@ init_tests (MetaContext *context)
|
||||
{
|
||||
init_virtual_monitor_tests (context);
|
||||
init_screen_cast_tests ();
|
||||
init_bezier_tests ();
|
||||
}
|
||||
|
||||
int
|
||||
|
Loading…
x
Reference in New Issue
Block a user