Skip to content

Commit 766565d

Browse files
committed
Find optimal shift value to decompose the spline
De Casteljau's algorithm is a common approach to rendering spline. There are many studies that aim to improve the spline. Here, we only consider three metrics to evaluate which method is more suitable. The first metric is the number of generated points when rendering a curve. The second metric is the number of '_de_casteljau' function calls. The third metric is the computation cost of the '_de_casteljau' function. In order to generate fewer points when rendering a curve, we first apply a flexible update 't' that employs the bisection method to identify the positions that are best suited for curve fitting. Most of these points have maximum curvature on the segment. When we try to find the point at which curvature is as maximum as possible, we will reduce the scope of the unhandled segment of spline. Therefore, we can generate fewer points while rendering an unhandled segment of spline. However, the drawback of this method is the high cost of finding points and the high cost of the bisection method. Next, to solve the drawback mentioned above, we apply a flexible update 't' with early stopping, which aims to stop the call of the '_de_casteljau' function. However, the improvement is little. Based on the previous experiments, we can see that the root of the problem is that there is a must to call '_de_casteljau' function twice to generate a point even in the original setting, which has the most fewer numbers of call '_de_casteljau'. The ideal method is that the rendering process only needs to call '_de_casteljau' once to generate a point. To achieve this goal, we reduce the initial 't' from 0.5 to 0.25 because the average number of '_de_casteljau' calls per point is 1.99. This means that the first call of the function '_de_casteljau' is redundant. In the experiment, we can see that the average points per character of the original method that shift 2 is almost the same with the original method. Experiment : Use font-edit as the evaluation testbed to do experiments as follows: Original - Average number of _de_casteljau calls per point: 1.99 Original - Average points per character: 18.89 Original (shift2) - Average number of _de_casteljau calls per point: 1.51 Original (shift2) - Average points per character: 18.98 Flexible - Average number of _de_casteljau calls per point: 4.53 Flexible - Average points per character: 16.30 Flexible (shift2) - Average number of _de_casteljau calls per point: 4.40 Flexible (shift2) - Average points per character: 21.16 Flexible (early stopping) - Average number of _de_casteljau calls per point: 4.23 Flexible (early stopping) - Average points per character: 16.18 Flexible (early stopping) (shift2) - Average number of _de_casteljau calls per point: 3.99 Flexible (early stopping) (shift2) - Average points per character: 21.09 After considering the computation cost of the '_de_casteljau' function and the increase in average number of _de_casteljau calls per point, we abandon the method flexible update 't' with the bisection method. Then, Choose to apply the original method with shift 2 finally. Take t = 0.25 for example (3/4) * A + (1/4) * B Original: A + (B - A) >> 4 We can directly use (B - A) to get the coefficients 1/4 and 3/4. Bisection: we need to multiply (B - A) with 0.25. Also, the bisection has many possible values of t rather than the original method. When using the original method, the '_de_casteljau' function can all use shift operations because the t is always the power of 2.
1 parent 98a2579 commit 766565d

File tree

1 file changed

+26
-57
lines changed

1 file changed

+26
-57
lines changed

src/spline.c

Lines changed: 26 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,36 @@ typedef struct _twin_spline {
1111
} twin_spline_t;
1212

1313
/*
14-
* Linearly interpolate between points 'a' and 'b' with a 't' factor.
15-
* The 't' factor determines the position between 'a' and 'b'.
14+
* Linearly interpolate between points 'a' and 'b' with a 'shift' factor.
15+
* The 'shift' factor determines the position between 'a' and 'b'.
1616
* The result is stored in 'result'.
1717
*/
1818
static void _lerp(const twin_spoint_t *a,
1919
const twin_spoint_t *b,
20-
twin_dfixed_t t,
20+
int shift,
2121
twin_spoint_t *result)
2222
{
23-
result->x = a->x + twin_dfixed_to_sfixed(twin_dfixed_mul(
24-
twin_sfixed_to_dfixed(b->x - a->x), t));
25-
result->y = a->y + twin_dfixed_to_sfixed(twin_dfixed_mul(
26-
twin_sfixed_to_dfixed(b->y - a->y), t));
23+
result->x = a->x + ((b->x - a->x) >> shift);
24+
result->y = a->y + ((b->y - a->y) >> shift);
2725
}
2826

2927
/*
30-
* Perform the de Casteljau algorithm to split a spline at a given 't'
28+
* Perform the de Casteljau algorithm to split a spline at a given 'shift'
3129
* factor. The spline is split into two new splines 's1' and 's2'.
3230
*/
3331
static void _de_casteljau(twin_spline_t *spline,
34-
twin_dfixed_t t,
32+
int shift,
3533
twin_spline_t *s1,
3634
twin_spline_t *s2)
3735
{
3836
twin_spoint_t ab, bc, cd, abbc, bccd, final;
3937

40-
_lerp(&spline->a, &spline->b, t, &ab);
41-
_lerp(&spline->b, &spline->c, t, &bc);
42-
_lerp(&spline->c, &spline->d, t, &cd);
43-
_lerp(&ab, &bc, t, &abbc);
44-
_lerp(&bc, &cd, t, &bccd);
45-
_lerp(&abbc, &bccd, t, &final);
38+
_lerp(&spline->a, &spline->b, shift, &ab);
39+
_lerp(&spline->b, &spline->c, shift, &bc);
40+
_lerp(&spline->c, &spline->d, shift, &cd);
41+
_lerp(&ab, &bc, shift, &abbc);
42+
_lerp(&bc, &cd, shift, &bccd);
43+
_lerp(&abbc, &bccd, shift, &final);
4644

4745
s1->a = spline->a;
4846
s1->b = ab;
@@ -64,14 +62,14 @@ static void _de_casteljau(twin_spline_t *spline,
6462
*/
6563
static twin_dfixed_t _twin_spline_distance_squared(twin_spline_t *spline)
6664
{
67-
twin_dfixed_t berr, cerr;
65+
twin_dfixed_t bdist, cdist;
6866

69-
berr = _twin_distance_to_line_squared(&spline->b, &spline->a, &spline->d);
70-
cerr = _twin_distance_to_line_squared(&spline->c, &spline->a, &spline->d);
67+
bdist = _twin_distance_to_line_squared(&spline->b, &spline->a, &spline->d);
68+
cdist = _twin_distance_to_line_squared(&spline->c, &spline->a, &spline->d);
7169

72-
if (berr > cerr)
73-
return berr;
74-
return cerr;
70+
if (bdist > cdist)
71+
return bdist;
72+
return cdist;
7573
}
7674

7775
/*
@@ -94,49 +92,20 @@ static void _twin_spline_decompose(twin_path_t *path,
9492
{
9593
/* Draw starting point */
9694
_twin_path_sdraw(path, spline->a.x, spline->a.y);
97-
95+
int shift = 2;
9896
while (!is_flat(spline, tolerance_squared)) {
99-
twin_dfixed_t hi = TWIN_SFIXED_ONE * TWIN_SFIXED_ONE, lo = 0,
100-
t_optimal = 0, t, max_distance = 0, left_dist, right_dist;
10197
twin_spline_t left, right;
10298

10399
while (true) {
104-
t = (hi + lo) >> 1;
105-
if (t_optimal == t)
100+
_de_casteljau(spline, shift, &left, &right);
101+
if (is_flat(&left, tolerance_squared)) {
102+
if (shift > 1)
103+
shift--;
106104
break;
107-
108-
_de_casteljau(spline, t, &left, &right);
109-
110-
left_dist = _twin_spline_distance_squared(&left);
111-
right_dist = _twin_spline_distance_squared(&right);
112-
113-
/* The left segment is close enough to fit the original spline. */
114-
if (left_dist <= tolerance_squared) {
115-
max_distance = left_dist;
116-
t_optimal = t;
117-
if (right_dist <= tolerance_squared)
118-
break;
119-
/*
120-
* Try to find a better point such that the line segment
121-
* connecting it to the previous point can fit the spline, while
122-
* maximizing the distance from the spline as much as possible.
123-
*/
124-
lo = t;
125-
} else {
126-
/*
127-
* The line segment connecting the previous point to the
128-
* currently tested point with value 't' cannot fit the original
129-
* spline. The point with value 't_optimal' is sufficient to fit
130-
* the original spline.
131-
*/
132-
if (t_optimal)
133-
break;
134-
hi = t;
135-
}
105+
} else
106+
shift++;
136107
}
137108

138-
_de_casteljau(spline, t_optimal, &left, &right);
139-
140109
/* Draw the left segment */
141110
_twin_path_sdraw(path, left.d.x, left.d.y);
142111

0 commit comments

Comments
 (0)