1 
2 /****************************************************************************
3 *
4 *  Copyright Raph Levien 2022
5 *  Copyright Nicolas Silva 2022
6 *  Copyright NXP 2022
7 *
8 *  Licensed under the Apache License, Version 2.0 (the "License");
9 *  you may not use this file except in compliance with the License.
10 *  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *  Unless required by applicable law or agreed to in writing, software
15 *  distributed under the License is distributed on an "AS IS" BASIS,
16 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 *  See the License for the specific language governing permissions and
18 *  limitations under the License.
19 *
20 *****************************************************************************/
21 
22 #include <math.h>
23 
24 #include "vg_lite_flat.h"
25 
26 /*
27  * Stop IAR compiler from warning about implicit conversions from float to
28  * double
29  */
30 #if (defined(__ICCARM__))
31 #pragma diag_suppress = Pa205
32 #endif
33 
34 #ifndef VG_CURVE_FLATTENING_TOLERANCE
35 #define VG_CURVE_FLATTENING_TOLERANCE           0.25
36 #endif /* defined(VG_CURVE_FLATTENING_TOLERANCE) */
37 
38 #define FABSF(x)                                ((vg_lite_float_t) fabs(x))
39 #define SQRTF(x)                                ((vg_lite_float_t) sqrt(x))
40 #define CEILF(x)                                ((vg_lite_float_t) ceil(x))
41 
42 #define VG_LITE_ERROR_HANDLER(func) \
43 if ((error = func) != VG_LITE_SUCCESS) \
44 goto ErrorHandler
45 
46 /* Point flatten type for flattened line segments. */
47 #define vgcFLATTEN_NO                           0
48 #define vgcFLATTEN_START                        1
49 #define vgcFLATTEN_MIDDLE                       2
50 #define vgcFLATTEN_END                          3
51 
52 /*
53  * Algorithm originally created by Raph Levien:
54  * https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
55  */
56 
57 #define FHYPOTF(x, y) ((vg_lite_float_t) hypotf(x, y))
58 #define FPOWF(x, y) ((vg_lite_float_t) powf(x, y))
59 
60 /*
61  * Contains the fields that are used to represent the quadratic Bezier curve
62  * as a 'y = x^2' parabola.
63  */
64 typedef struct parabola_approx {
65     vg_lite_float_t x0;
66     vg_lite_float_t x2;
67     vg_lite_float_t scale;
68     vg_lite_float_t cross;
69 } parabola_approx_t;
70 
71 /*
72  * Keeps the quadratic Bezier's control points. This makes life easier when
73  * passing quadratics as parameters, so we don't have to give 6 floats every
74  * time.
75  */
76 typedef struct quad_bezier {
77     vg_lite_float_t X0;
78     vg_lite_float_t Y0;
79     vg_lite_float_t X1;
80     vg_lite_float_t Y1;
81     vg_lite_float_t X2;
82     vg_lite_float_t Y2;
83 } quad_bezier_t;
84 
85 /*
86  * Parameters which are used by the flattening algorithm.
87  */
88 typedef struct quad_bezier_flatten_params {
89     vg_lite_float_t a0;
90     vg_lite_float_t a2;
91     int num_points;
92     vg_lite_float_t u0;
93     vg_lite_float_t u2;
94 } quad_bezier_flatten_params_t;
95 
96 /*
97  * Keeps the cubic Bezier's control points.
98  */
99 typedef struct cubic_bezier {
100     vg_lite_float_t X0;
101     vg_lite_float_t Y0;
102     vg_lite_float_t X1;
103     vg_lite_float_t Y1;
104     vg_lite_float_t X2;
105     vg_lite_float_t Y2;
106     vg_lite_float_t X3;
107     vg_lite_float_t Y3;
108 } cubic_bezier_t;
109 
110 
111 vg_lite_error_t _add_point_to_point_list(
112                                 vg_lite_stroke_conversion_t * stroke_conversion,
113                                 vg_lite_float_t X,
114                                 vg_lite_float_t Y,
115                                 uint8_t flatten_flag);
116 
117 vg_lite_error_t _add_point_to_point_list_wdelta(
118                                 vg_lite_stroke_conversion_t * stroke_conversion,
119                                 vg_lite_float_t X,
120                                 vg_lite_float_t Y,
121                                 vg_lite_float_t DX,
122                                 vg_lite_float_t DY,
123                                 uint8_t flatten_flag);
124 
125 
126 /*
127  * Evaluates the Bernstein polynomial that represents the curve, at 't'.
128  * 't' should be a value between 0.0 and 1.0 (though it can be any float, but
129  *      the relevant values are between 0 and 1).
130  * 'x' and 'y' will contain the coordinates of the evaluated point.
131  */
quad_bezier_eval(const quad_bezier_t * q,vg_lite_float_t t,vg_lite_float_t * x,vg_lite_float_t * y)132 static void quad_bezier_eval(
133     const quad_bezier_t *q,
134     vg_lite_float_t t,
135     vg_lite_float_t *x,
136     vg_lite_float_t *y
137     )
138 {
139     const vg_lite_float_t omt = 1.0 - t;
140     *x = q->X0 * omt * omt + 2.0 * q->X1 * t * omt + q->X2 * t * t;
141     *y = q->Y0 * omt * omt + 2.0 * q->Y1 * t * omt + q->Y2 * t * t;
142 }
143 
144 /*
145  * Approximates the integral which uses the arclength and curvature of the
146  * parabola.
147  */
approx_integral(vg_lite_float_t x)148 static vg_lite_float_t approx_integral(vg_lite_float_t x)
149 {
150     const vg_lite_float_t D = 0.67;
151     return x / (1.0 - D + FPOWF(FPOWF(D, 4) + 0.25 * x * x, 0.25));
152 }
153 
154 /*
155  * Approximates the inverse of the previous integral.
156  */
approx_inverse_integral(vg_lite_float_t x)157 static vg_lite_float_t approx_inverse_integral(vg_lite_float_t x)
158 {
159     const vg_lite_float_t B = 0.39;
160     return x * (1.0 - B + SQRTF(B * B + 0.25 * x * x));
161 }
162 
163 /*
164  * Represents a quadratic Bezier curve as a parabola.
165  */
map_to_parabola(const quad_bezier_t * q)166 static parabola_approx_t map_to_parabola(const quad_bezier_t *q)
167 {
168     const vg_lite_float_t ddx = 2 * q->X1 - q->X0 - q->X2;
169     const vg_lite_float_t ddy = 2 * q->Y1 - q->Y0 - q->Y2;
170     const vg_lite_float_t u0 = (q->X1 - q->X0) * ddx + (q->Y1 - q->Y0) * ddy;
171     const vg_lite_float_t u2 = (q->X2 - q->X1) * ddx + (q->Y2 - q->Y1) * ddy;
172     const vg_lite_float_t cross = (q->X2 - q->X0) * ddy - (q->Y2 - q->Y0) * ddx;
173     const vg_lite_float_t x0 = u0 / cross;
174     const vg_lite_float_t x2 = u2 / cross;
175     const vg_lite_float_t scale = FABSF(cross) / (FHYPOTF(ddx, ddy) * FABSF(x2 - x0));
176 
177     return (parabola_approx_t) {
178         .x0 = x0,
179         .x2 = x2,
180         .scale = scale,
181         .cross = cross
182     };
183 }
184 
185 /*
186  * Tolerance influences the number of lines generated. The lower the tolerance,
187  * the more lines it generates, thus the flattening will have a higher quality,
188  * but it will also consume more memory. The bigger the tolerance, the less lines
189  * will be generated, so the quality will be worse, but the memory consumption
190  * will be better.
191  *
192  * A good default value could be 0.25.
193  */
quad_bezier_flatten_params_init(const quad_bezier_t * q,vg_lite_float_t tolerance)194 static quad_bezier_flatten_params_t quad_bezier_flatten_params_init(
195     const quad_bezier_t *q,
196     vg_lite_float_t tolerance
197     )
198 {
199     const parabola_approx_t params = map_to_parabola(q);
200     const vg_lite_float_t a0 = approx_integral(params.x0);
201     const vg_lite_float_t a2 = approx_integral(params.x2);
202     const vg_lite_float_t count = 0.5 * FABSF(a2 - a0) * SQRTF(params.scale / tolerance);
203     const int num_points = (int)CEILF(count);
204     const vg_lite_float_t u0 = approx_inverse_integral(a0);
205     const vg_lite_float_t u2 = approx_inverse_integral(a2);
206 
207     return (quad_bezier_flatten_params_t) {
208         .a0 = a0,
209         .a2 = a2,
210         .num_points = num_points,
211         .u0 = u0,
212         .u2 = u2
213     };
214 }
215 
216 /*
217  * Puts into (x, y) the coordinate to which a line should be drawn given the step.
218  * This should be used in a loop to flatten a curve, like this:
219  * ```
220  * params = quad_bezier_flatten_params_init(&q, tolerance);
221  * for (int i = 1; i < params.num_points; ++i) {
222  *  vg_lite_float_t x, y;
223  *  quad_bezier_flatten_at(&q, &params, i, &x, &y);
224  *  draw_line_to(x, y);
225  * }
226  * ```
227  */
quad_bezier_flatten_at(const quad_bezier_t * q,const quad_bezier_flatten_params_t * params,int step,vg_lite_float_t * x,vg_lite_float_t * y)228 static void quad_bezier_flatten_at(
229     const quad_bezier_t *q,
230     const quad_bezier_flatten_params_t *params,
231     int step,
232     vg_lite_float_t *x,
233     vg_lite_float_t *y
234     )
235 {
236     const vg_lite_float_t a0 = params->a0, a2 = params->a2, u0 = params->u0, u2 = params->u2;
237     const int num_points = params->num_points;
238     const vg_lite_float_t u = approx_inverse_integral(a0 + ((a2 - a0) * step) / num_points);
239     const vg_lite_float_t t = (u - u0) / (u2 - u0);
240 
241     quad_bezier_eval(q, t, x, y);
242 }
243 
244 vg_lite_error_t
_flatten_quad_bezier(vg_lite_stroke_conversion_t * stroke_conversion,vg_lite_float_t X0,vg_lite_float_t Y0,vg_lite_float_t X1,vg_lite_float_t Y1,vg_lite_float_t X2,vg_lite_float_t Y2)245 _flatten_quad_bezier(
246     vg_lite_stroke_conversion_t *stroke_conversion,
247     vg_lite_float_t X0,
248     vg_lite_float_t Y0,
249     vg_lite_float_t X1,
250     vg_lite_float_t Y1,
251     vg_lite_float_t X2,
252     vg_lite_float_t Y2
253     )
254 {
255     vg_lite_error_t error = VG_LITE_SUCCESS;
256     vg_lite_path_point_ptr point0, point1;
257     vg_lite_float_t x, y;
258 
259     const vg_lite_float_t tolerance = VG_CURVE_FLATTENING_TOLERANCE;
260     const quad_bezier_t q = {
261         .X0 = X0,
262         .Y0 = Y0,
263         .X1 = X1,
264         .Y1 = Y1,
265         .X2 = X2,
266         .Y2 = Y2
267     };
268     const quad_bezier_flatten_params_t params = quad_bezier_flatten_params_init(&q, tolerance);
269 
270     if(!stroke_conversion)
271         return VG_LITE_INVALID_ARGUMENT;
272 
273     /* Add extra P0 for incoming tangent. */
274     point0 = stroke_conversion->path_last_point;
275     /* First add P1 to calculate incoming tangent, which is saved in P0. */
276     VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X1, Y1, vgcFLATTEN_START));
277 
278     point1 = stroke_conversion->path_last_point;
279     /* Change the point1's coordinates back to P0. */
280     point1->x = X0;
281     point1->y = Y0;
282     point0->length = 0.0f;
283 
284     for (int i = 1; i < params.num_points; ++i) {
285         quad_bezier_flatten_at(&q, &params, i, &x, &y);
286         _add_point_to_point_list(stroke_conversion, x, y, vgcFLATTEN_MIDDLE);
287     }
288 
289     /* Add point 2 separately to avoid cumulative errors. */
290     VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X2, Y2, vgcFLATTEN_END));
291 
292     /* Add extra P2 for outgoing tangent. */
293     /* First change P2(point0)'s coordinates to P1. */
294     point0 = stroke_conversion->path_last_point;
295     point0->x = X1;
296     point0->y = Y1;
297 
298     /* Add P2 to calculate outgoing tangent. */
299     VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X2, Y2, vgcFLATTEN_NO));
300 
301     point1 = stroke_conversion->path_last_point;
302 
303     /* Change point0's coordinates back to P2. */
304     point0->x = X2;
305     point0->y = Y2;
306     point0->length = 0.0f;
307 
308 ErrorHandler:
309     return error;
310 }
311 
312 /*
313  * Like eval_quad_bezier, computes the coordinates of the point which resides at
314  * `t` for the cubic.
315  */
cubic_bezier_eval(const cubic_bezier_t * c,vg_lite_float_t t,vg_lite_float_t * x,vg_lite_float_t * y)316 static void cubic_bezier_eval(
317     const cubic_bezier_t *c,
318     vg_lite_float_t t,
319     vg_lite_float_t *x,
320     vg_lite_float_t *y
321     )
322 {
323     const vg_lite_float_t omt = 1.0 - t;
324     const vg_lite_float_t omt2 = omt * omt;
325     const vg_lite_float_t omt3 = omt * omt2;
326     const vg_lite_float_t t2 = t * t;
327     const vg_lite_float_t t3 = t * t2;
328 
329     *x = omt3 * c->X0 + 3.0 * t * omt2 * c->X1 + 3.0 * t2 * omt * c->X2 + t3 * c->X3;
330     *y = omt3 * c->Y0 + 3.0 * t * omt2 * c->Y1 + 3.0 * t2 * omt * c->Y2 + t3 * c->Y3;
331 }
332 
cubic_bezier_derivative(const cubic_bezier_t * c)333 static quad_bezier_t cubic_bezier_derivative(const cubic_bezier_t *c)
334 {
335     const vg_lite_float_t x0 = 3.0 * (c->X1 - c->X0);
336     const vg_lite_float_t y0 = 3.0 * (c->Y1 - c->Y0);
337     const vg_lite_float_t x1 = 3.0 * (c->X2 - c->X1);
338     const vg_lite_float_t y1 = 3.0 * (c->Y2 - c->Y1);
339     const vg_lite_float_t x2 = 3.0 * (c->X3 - c->X2);
340     const vg_lite_float_t y2 = 3.0 * (c->Y3 - c->Y2);
341 
342     return (quad_bezier_t) {
343         .X0 = x0,
344         .Y0 = y0,
345         .X1 = x1,
346         .Y1 = y1,
347         .X2 = x2,
348         .Y2 = y2
349     };
350 }
351 
352 /*
353  * Returns the cubic bezier that is between t0 and t1 of c.
354  */
cubic_bezier_split_at(const cubic_bezier_t * c,vg_lite_float_t t0,vg_lite_float_t t1)355 static cubic_bezier_t cubic_bezier_split_at(
356     const cubic_bezier_t *c,
357     vg_lite_float_t t0,
358     vg_lite_float_t t1
359     )
360 {
361     vg_lite_float_t p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, d1x, d1y, d2x, d2y;
362     vg_lite_float_t scale;
363     quad_bezier_t derivative;
364 
365     cubic_bezier_eval(c, t0, &p0x, &p0y);
366     cubic_bezier_eval(c, t1, &p3x, &p3y);
367     derivative = cubic_bezier_derivative(c);
368     scale = (t1 - t0) * (1.0 / 3.0);
369     quad_bezier_eval(&derivative, t0, &d1x, &d1y);
370     quad_bezier_eval(&derivative, t1, &d2x, &d2y);
371     p1x = p0x + scale * d1x;
372     p1y = p0y + scale * d1y;
373     p2x = p3x - scale * d2x;
374     p2y = p3y - scale * d2y;
375 
376     return (cubic_bezier_t) {
377         .X0 = p0x,
378         .Y0 = p0y,
379         .X1 = p1x,
380         .Y1 = p1y,
381         .X2 = p2x,
382         .Y2 = p2y,
383         .X3 = p3x,
384         .Y3 = p3y
385     };
386 }
387 
388 /*
389  * This function returns the number of quadratic Bezier curves that are needed to
390  * represent the given cubic, respecting the tolerance.
391  *
392  * As with the flattening of quadratics, the lower the tolerance, the better the
393  * quality. The higher the tolerance, the worse the quality, but better performance
394  * or memory consumption.
395  *
396  * The algorithm comes from:
397  * https://web.archive.org/web/20210108052742/http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
398  *
399  * Implementation adapted from:
400  * https://github.com/linebender/kurbo/blob/master/src/cubicbez.rs
401  * and:
402  * https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=1000&context=facpub#section.10.6
403  */
cubic_bezier_get_flatten_count(const cubic_bezier_t * c,vg_lite_float_t tolerance)404 static int cubic_bezier_get_flatten_count(
405     const cubic_bezier_t *c,
406     vg_lite_float_t tolerance
407     )
408 {
409     const vg_lite_float_t x = c->X0 - 3.0 * c->X1 + 3.0 * c->X2 - c->X3;
410     const vg_lite_float_t y = c->Y0 - 3.0 * c->Y1 + 3.0 * c->Y2 - c->Y3;
411     const vg_lite_float_t err = x * x + y * y;
412     vg_lite_float_t result;
413 
414     result = FPOWF(err / (432.0 * tolerance * tolerance), 1.0 / 6.0);
415     result = CEILF(result);
416 
417     return result > 1.0 ? (int)result : 1;
418 }
419 
420 vg_lite_error_t
_flatten_cubic_bezier(vg_lite_stroke_conversion_t * stroke_conversion,vg_lite_float_t X0,vg_lite_float_t Y0,vg_lite_float_t X1,vg_lite_float_t Y1,vg_lite_float_t X2,vg_lite_float_t Y2,vg_lite_float_t X3,vg_lite_float_t Y3)421 _flatten_cubic_bezier(
422     vg_lite_stroke_conversion_t *  stroke_conversion,
423     vg_lite_float_t X0,
424     vg_lite_float_t Y0,
425     vg_lite_float_t X1,
426     vg_lite_float_t Y1,
427     vg_lite_float_t X2,
428     vg_lite_float_t Y2,
429     vg_lite_float_t X3,
430     vg_lite_float_t Y3
431     )
432 {
433     vg_lite_error_t error = VG_LITE_SUCCESS;
434     vg_lite_path_point_ptr point0, point1;
435     const cubic_bezier_t c = {
436         .X0 = X0,
437         .Y0 = Y0,
438         .X1 = X1,
439         .Y1 = Y1,
440         .X2 = X2,
441         .Y2 = Y2,
442         .X3 = X3,
443         .Y3 = Y3
444     };
445     const vg_lite_float_t tolerance = VG_CURVE_FLATTENING_TOLERANCE;
446     int num_curves = cubic_bezier_get_flatten_count(&c, tolerance);
447     vg_lite_float_t fnum_curves = (vg_lite_float_t)num_curves;
448     vg_lite_float_t fi, t0, t1, p1x, p1y, p2x, p2y, x, y;
449     cubic_bezier_t subsegment;
450     quad_bezier_t current_curve;
451     quad_bezier_flatten_params_t params;
452 
453     if(!stroke_conversion)
454         return VG_LITE_INVALID_ARGUMENT;
455 
456     /* Add extra P0 for incoming tangent. */
457     point0 = stroke_conversion->path_last_point;
458     /* First add P1/P2/P3 to calculate incoming tangent, which is saved in P0. */
459     if (X0 != X1 || Y0 != Y1)
460     {
461         VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X1, Y1, vgcFLATTEN_START));
462     }
463     else if (X0 != X2 || Y0 != Y2)
464     {
465         VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X2, Y2, vgcFLATTEN_START));
466     }
467     else
468     {
469         VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X3, Y3, vgcFLATTEN_START));
470     }
471     point1 = stroke_conversion->path_last_point;
472     /* Change the point1's coordinates back to P0. */
473     point1->x = X0;
474     point1->y = Y0;
475     point0->length = 0.0f;
476 
477     for (int i = 0; i < num_curves; ++i) {
478         fi = (vg_lite_float_t)i;
479         t0 = fi / fnum_curves;
480         t1 = (fi + 1.0) / fnum_curves;
481         subsegment = cubic_bezier_split_at(&c, t0, t1);
482         p1x = 3.0 * subsegment.X1 - subsegment.X0;
483         p1y = 3.0 * subsegment.Y1 - subsegment.Y0;
484         p2x = 3.0 * subsegment.X2 - subsegment.X3;
485         p2y = 3.0 * subsegment.Y2 - subsegment.Y3;
486         current_curve = (quad_bezier_t) {
487             .X0 = subsegment.X0,
488             .Y0 = subsegment.Y0,
489             .X1 = (p1x + p2x) / 4.0,
490             .Y1 = (p1y + p2y) / 4.0,
491             .X2 = subsegment.X3,
492             .Y2 = subsegment.Y3
493         };
494         params = quad_bezier_flatten_params_init(&current_curve, tolerance);
495         for (int j = 0; j < params.num_points; ++j) {
496             quad_bezier_flatten_at(&current_curve, &params, j, &x, &y);
497             _add_point_to_point_list(stroke_conversion, x, y, vgcFLATTEN_MIDDLE);
498         }
499     }
500 
501     /* Add point 3 separately to avoid cumulative errors. */
502     VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X3, Y3, vgcFLATTEN_END));
503 
504     /* Add extra P3 for outgoing tangent. */
505     /* First change P3(point0)'s coordinates to P0/P1/P2. */
506     point0 = stroke_conversion->path_last_point;
507     if (X3 != X2 || Y3 != Y2)
508     {
509         point0->x = X2;
510         point0->y = Y2;
511     }
512     else if (X3 != X1 || Y3 != Y1)
513     {
514         point0->x = X1;
515         point0->y = Y1;
516     }
517     else
518     {
519         point0->x = X0;
520         point0->y = Y0;
521     }
522 
523     /* Add P3 to calculate outgoing tangent. */
524     VG_LITE_ERROR_HANDLER(_add_point_to_point_list(stroke_conversion, X3, Y3, vgcFLATTEN_NO));
525 
526     point1 = stroke_conversion->path_last_point;
527 
528     /* Change point0's coordinates back to P3. */
529     point0->x = X3;
530     point0->y = Y3;
531     point0->length = 0.0f;
532 
533 ErrorHandler:
534     return error;
535 }
536