vg_lite_flat.c 16 KB

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