2 * Copyright (C) 2016 Vabishchevich Nikolay <vabnick@gmail.com>
4 * This file is part of libass.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include "ass_compat.h"
22 #include "ass_utils.h"
23 #include "ass_outline.h"
27 bool outline_alloc(ASS_Outline *outline, size_t n_points, size_t n_contours)
29 outline->contours = malloc(sizeof(size_t) * n_contours);
30 outline->points = malloc(sizeof(FT_Vector) * n_points);
31 outline->tags = malloc(n_points);
32 if (!outline->contours || !outline->points || !outline->tags) {
33 outline_free(outline);
37 outline->max_contours = n_contours;
38 outline->max_points = n_points;
42 static void outline_clear(ASS_Outline *outline)
44 outline->contours = NULL;
45 outline->points = NULL;
48 outline->n_contours = outline->max_contours = 0;
49 outline->n_points = outline->max_points = 0;
52 bool outline_convert(ASS_Outline *outline, const FT_Outline *source)
54 if (!source || !source->n_points) {
55 outline_clear(outline);
59 if (!outline_alloc(outline, source->n_points, source->n_contours))
63 outline->n_contours = outline->n_points = 0;
64 for (int i = 0; i < source->n_contours; i++) {
65 size_t n = source->contours[i] - start + 1;
66 // skip degenerate 2-point contours from broken fonts
68 memcpy(outline->points + outline->n_points, source->points + start, sizeof(FT_Vector) * n);
69 memcpy(outline->tags + outline->n_points, source->tags + start, n);
71 outline->n_points += n;
72 outline->contours[outline->n_contours++] = outline->n_points - 1;
74 start = source->contours[i] + 1;
79 bool outline_copy(ASS_Outline *outline, const ASS_Outline *source)
81 if (!source || !source->n_points) {
82 outline_clear(outline);
86 if (!outline_alloc(outline, source->n_points, source->n_contours))
89 memcpy(outline->contours, source->contours, sizeof(size_t) * source->n_contours);
90 memcpy(outline->points, source->points, sizeof(FT_Vector) * source->n_points);
91 memcpy(outline->tags, source->tags, source->n_points);
92 outline->n_contours = source->n_contours;
93 outline->n_points = source->n_points;
97 void outline_free(ASS_Outline *outline)
102 free(outline->contours);
103 free(outline->points);
106 outline_clear(outline);
111 * \brief Add a single point to a contour.
113 bool outline_add_point(ASS_Outline *outline, FT_Vector pt, char tag)
115 if (outline->n_points >= outline->max_points) {
116 size_t new_size = 2 * outline->max_points;
117 if (!ASS_REALLOC_ARRAY(outline->points, new_size))
119 if (!ASS_REALLOC_ARRAY(outline->tags, new_size))
121 outline->max_points = new_size;
123 outline->points[outline->n_points] = pt;
124 outline->tags[outline->n_points] = tag;
130 * \brief Close a contour.
132 bool outline_close_contour(ASS_Outline *outline)
134 if (outline->n_contours >= outline->max_contours) {
135 size_t new_size = 2 * outline->max_contours;
136 if (!ASS_REALLOC_ARRAY(outline->contours, new_size))
138 outline->max_contours = new_size;
140 outline->contours[outline->n_contours] = outline->n_points - 1;
141 outline->n_contours++;
146 void outline_translate(const ASS_Outline *outline, FT_Pos dx, FT_Pos dy)
148 for (size_t i = 0; i < outline->n_points; i++) {
149 outline->points[i].x += dx;
150 outline->points[i].y += dy;
154 void outline_transform(const ASS_Outline *outline, const FT_Matrix *matrix)
156 for (size_t i = 0; i < outline->n_points; i++) {
157 FT_Pos x = FT_MulFix(outline->points[i].x, matrix->xx) +
158 FT_MulFix(outline->points[i].y, matrix->xy);
159 FT_Pos y = FT_MulFix(outline->points[i].x, matrix->yx) +
160 FT_MulFix(outline->points[i].y, matrix->yy);
161 outline->points[i].x = x;
162 outline->points[i].y = y;
166 void outline_update_cbox(const ASS_Outline *outline, FT_BBox *cbox)
171 for (size_t i = 0; i < outline->n_points; i++) {
172 cbox->xMin = FFMIN(cbox->xMin, outline->points[i].x);
173 cbox->xMax = FFMAX(cbox->xMax, outline->points[i].x);
174 cbox->yMin = FFMIN(cbox->yMin, outline->points[i].y);
175 cbox->yMax = FFMAX(cbox->yMax, outline->points[i].y);
179 void outline_get_cbox(const ASS_Outline *outline, FT_BBox *cbox)
181 if (!outline->n_points) {
182 cbox->xMin = cbox->xMax = 0;
183 cbox->yMin = cbox->yMax = 0;
186 cbox->xMin = cbox->xMax = outline->points[0].x;
187 cbox->yMin = cbox->yMax = outline->points[0].y;
188 for (size_t i = 1; i < outline->n_points; i++) {
189 cbox->xMin = FFMIN(cbox->xMin, outline->points[i].x);
190 cbox->xMax = FFMAX(cbox->xMax, outline->points[i].x);
191 cbox->yMin = FFMIN(cbox->yMin, outline->points[i].y);
192 cbox->yMax = FFMAX(cbox->yMax, outline->points[i].y);
198 * Outline Stroke Algorithm
201 * Given source outline, construct two border outlines, such as that
202 * for any point inside any border outline (nonzero winding rule)
203 * minimal distance to points of source outline (same rule)
204 * is less than 1 with given precision, and for any point outside
205 * both border outlines minimal distance is more than approximately 1.
206 * Distance here is defined in normal space that is scaled by [1 / xbord, 1 / ybord].
207 * Correspondingly distance is equal to hypot(dx / xbord, dy / ybord) and
208 * approximate allowable error is eps / max(xbord, ybord).
210 * Two border outlines correspond to ±1 offset curves and
211 * are required in case of self-intersecting source outline.
213 * Each of source segment that can be line segment, quadratic or cubic spline,
214 * and also connection between them is stroked mostly independently.
215 * Line segments can be offset quite straightforwardly.
216 * For splines algorithm first tries to offset individual points,
217 * then estimates error of such approximation and subdivide recursively if necessary.
219 * List of problems that need to be solved:
220 * 1) Too close points lead to random derivatives or even division by zero.
221 * Algorithm solves that by merging such points into one.
222 * 2) Degenerate cases--near zero derivative in some spline points.
223 * Algorithm adds circular cap in such cases.
224 * 3) Negative curvative--offset amount is larger than spline curvative.
225 * Algorithm checks if produced splines can potentially have self-intersection
226 * and handles them accordingly. It's mostly done by skipping
227 * problematic spline and replacing it with polyline that covers only
228 * positive winging part of mathematical offset curve.
230 * Error estimation for splines is done by analyzing offset spline.
231 * Offset spline is the difference between result and source spline in normal space.
232 * Such spline should consist of vectors with length 1 and orthogonal to source spline.
233 * Correspondingly error estimator have radial and angular part.
235 * Useful facts about B-splines.
236 * 1) Derivative of B-spline of order N is B-spline of order N-1.
237 * 2) Multiplication of B-splines of order N and M is B-spline of order N+M.
238 * 3) B-spline is fully contained inside convex hull of its control points.
240 * So, for radial error its possible to check only control points of
241 * offset spline multiplication by itself. And for angular error its
242 * possible to check control points of cross and dot product between
243 * offset spline and derivative spline.
261 ASS_Outline *result[2]; // result outlines
262 double xbord, ybord; // border sizes
263 double xscale, yscale; // inverse border sizes
264 int eps; // allowable error in coordinate space
266 // true if where are points in current contour
268 // skip flags for first and last point
269 int first_skip, last_skip;
270 // normal at first and last point
271 Vector first_normal, last_normal;
272 // first and last point of current contour
273 OutlinePoint first_point, last_point;
275 // cosinus of maximal angle that do not require cap
277 // cosinus of maximal angle of circular arc that can be approximated with quadratic spline
279 // maximal distance between control points in normal space that triggers handling of degenerates
281 // constant that used in exact radial error checking in quadratic case
283 // constant that used in approximate radial error checking in cubic case
285 // tangent of maximal angular error
290 * \brief 2D vector dot product
292 static inline double vec_dot(Vector vec1, Vector vec2)
294 return vec1.x * vec2.x + vec1.y * vec2.y;
298 * \brief 2D vector cross product
300 static inline double vec_crs(Vector vec1, Vector vec2)
302 return vec1.x * vec2.y - vec1.y * vec2.x;
306 * \brief 2D vector length
308 static inline double vec_len(Vector vec)
310 return sqrt(vec.x * vec.x + vec.y * vec.y);
315 * \brief Add point to one or two border outlines
316 * \param str stroker state
317 * \param pt source point
318 * \param offs offset in normal space
319 * \param tag outline tag flag
320 * \param dir destination outline flags
321 * \return false on allocation failure
323 static bool emit_point(StrokerState *str, OutlinePoint pt,
324 Vector offs, char tag, int dir)
326 int32_t dx = (int32_t) (str->xbord * offs.x);
327 int32_t dy = (int32_t) (str->ybord * offs.y);
330 FT_Vector res = { pt.x + dx, pt.y + dy };
332 if (!outline_add_point(str->result[0], res, tag))
336 FT_Vector res = { pt.x - dx, pt.y - dy };
338 if (!outline_add_point(str->result[1], res, tag))
345 * \brief Replace first point of current contour
346 * \param str stroker state
347 * \param pt source point
348 * \param offs offset in normal space
349 * \param dir destination outline flags
351 static void fix_first_point(StrokerState *str, OutlinePoint pt,
352 Vector offs, int dir)
354 int32_t dx = (int32_t) (str->xbord * offs.x);
355 int32_t dy = (int32_t) (str->ybord * offs.y);
358 FT_Vector res = { pt.x + dx, pt.y + dy };
360 ASS_Outline *ol = str->result[0];
361 size_t first = ol->n_contours ?
362 ol->contours[ol->n_contours - 1] + 1 : 0;
363 ol->points[first] = res;
366 FT_Vector res = { pt.x - dx, pt.y - dy };
368 ASS_Outline *ol = str->result[1];
369 size_t first = ol->n_contours ?
370 ol->contours[ol->n_contours - 1] + 1 : 0;
371 ol->points[first] = res;
376 * \brief Helper function for circular arc construction
377 * \param str stroker state
378 * \param pt center point
379 * \param normal0 first normal
380 * \param normal1 last normal
381 * \param mul precalculated coefficients
382 * \param level subdivision level
383 * \param dir destination outline flags
384 * \return false on allocation failure
386 static bool process_arc(StrokerState *str, OutlinePoint pt,
387 Vector normal0, Vector normal1,
388 const double *mul, int level, int dir)
391 center.x = (normal0.x + normal1.x) * mul[level];
392 center.y = (normal0.y + normal1.y) * mul[level];
394 return process_arc(str, pt, normal0, center, mul, level - 1, dir) &&
395 process_arc(str, pt, center, normal1, mul, level - 1, dir);
396 return emit_point(str, pt, normal0, FT_CURVE_TAG_ON, dir) &&
397 emit_point(str, pt, center, FT_CURVE_TAG_CONIC, dir);
401 * \brief Construct circular arc
402 * \param str stroker state
403 * \param pt center point
404 * \param normal0 first normal
405 * \param normal1 last normal
406 * \param c dot product between normal0 and normal1
407 * \param dir destination outline flags
408 * \return false on allocation failure
410 static bool draw_arc(StrokerState *str, OutlinePoint pt,
411 Vector normal0, Vector normal1, double c, int dir)
413 const int max_subdiv = 15;
414 double mul[max_subdiv + 1];
417 bool small_angle = true;
419 double mul = dir & 2 ? -sqrt(0.5) : sqrt(0.5);
421 center.x = (normal1.y - normal0.y) * mul;
422 center.y = (normal0.x - normal1.x) * mul;
423 c = sqrt(FFMAX(0, 0.5 + 0.5 * c));
427 int pos = max_subdiv;
428 while (c < str->split_cos && pos) {
429 mul[pos] = sqrt(0.5) / sqrt(1 + c);
430 c = (1 + c) * mul[pos];
433 mul[pos] = 1 / (1 + c);
435 process_arc(str, pt, normal0, normal1, mul + pos, max_subdiv - pos, dir) :
436 process_arc(str, pt, normal0, center, mul + pos, max_subdiv - pos, dir) &&
437 process_arc(str, pt, center, normal1, mul + pos, max_subdiv - pos, dir);
441 * \brief Construct full circle
442 * \param str stroker state
443 * \param pt center point
444 * \param dir destination outline flags
445 * \return false on allocation failure
447 static bool draw_circle(StrokerState *str, OutlinePoint pt, int dir)
449 const int max_subdiv = 15;
450 double mul[max_subdiv + 1], c = 0;
452 int pos = max_subdiv;
453 while (c < str->split_cos && pos) {
454 mul[pos] = sqrt(0.5) / sqrt(1 + c);
455 c = (1 + c) * mul[pos];
458 mul[pos] = 1 / (1 + c);
461 { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }
463 return process_arc(str, pt, normal[0], normal[1], mul + pos, max_subdiv - pos, dir) &&
464 process_arc(str, pt, normal[1], normal[2], mul + pos, max_subdiv - pos, dir) &&
465 process_arc(str, pt, normal[2], normal[3], mul + pos, max_subdiv - pos, dir) &&
466 process_arc(str, pt, normal[3], normal[0], mul + pos, max_subdiv - pos, dir);
470 * \brief Start new segment and add circular cap if necessary
471 * \param str stroker state
472 * \param pt start point of the new segment
473 * \param normal normal at start of the new segment
474 * \param dir destination outline flags
475 * \return false on allocation failure
477 static bool start_segment(StrokerState *str, OutlinePoint pt,
478 Vector normal, int dir)
480 if (str->contour_start) {
481 str->contour_start = false;
482 str->first_skip = str->last_skip = 0;
483 str->first_normal = str->last_normal = normal;
484 str->first_point = pt;
488 Vector prev = str->last_normal;
489 double c = vec_dot(prev, normal);
490 if (c > str->merge_cos) { // merge without cap
491 double mul = 1 / (1 + c);
492 str->last_normal.x = (str->last_normal.x + normal.x) * mul;
493 str->last_normal.y = (str->last_normal.y + normal.y) * mul;
496 str->last_normal = normal;
498 // check for negative curvative
499 double s = vec_crs(prev, normal);
500 int skip_dir = s < 0 ? 1 : 2;
501 if (dir & skip_dir) {
502 if (!emit_point(str, pt, prev, FT_CURVE_TAG_ON, ~str->last_skip & skip_dir))
504 Vector zero_normal = {0, 0};
505 if (!emit_point(str, pt, zero_normal, FT_CURVE_TAG_ON, skip_dir))
508 str->last_skip = skip_dir;
511 return !dir || draw_arc(str, pt, prev, normal, c, dir);
515 * \brief Same as emit_point() but also updates skip flags
517 static bool emit_first_point(StrokerState *str, OutlinePoint pt, int dir)
519 str->last_skip &= ~dir;
520 return emit_point(str, pt, str->last_normal, FT_CURVE_TAG_ON, dir);
524 * \brief Prepare to skip part of curve
525 * \param str stroker state
526 * \param pt start point of the skipped part
527 * \param dir destination outline flags
528 * \param first true if the skipped part is at start of the segment
529 * \return false on allocation failure
531 static bool prepare_skip(StrokerState *str, OutlinePoint pt, int dir, bool first)
534 str->first_skip |= dir;
535 else if (!emit_point(str, pt, str->last_normal, FT_CURVE_TAG_ON, ~str->last_skip & dir))
537 str->last_skip |= dir;
542 * \brief Process source line segment
543 * \param str stroker state
544 * \param pt end point of the line segment
545 * \param dir destination outline flags
546 * \return false on allocation failure
548 static bool add_line(StrokerState *str, OutlinePoint pt, int dir)
550 int32_t dx = pt.x - str->last_point.x;
551 int32_t dy = pt.y - str->last_point.y;
552 if (dx > -str->eps && dx < str->eps && dy > -str->eps && dy < str->eps)
555 Vector deriv = { dy * str->yscale, -dx * str->xscale };
556 double scale = 1 / vec_len(deriv);
557 Vector normal = { deriv.x * scale, deriv.y * scale };
558 if (!start_segment(str, str->last_point, normal, dir))
560 if (!emit_first_point(str, str->last_point, dir))
562 str->last_normal = normal;
563 str->last_point = pt;
569 * \brief Estimate error for quadratic spline
570 * \param str stroker state
571 * \param c dot product between normal[0] and normal[1]
572 * \param s cross product between normal[0] and normal[1]
573 * \param normal first and last spline normal
574 * \param result best offset for central spline point
575 * \return false if error is too large
577 static bool estimate_quadratic_error(StrokerState *str, double c, double s,
578 const Normal *normal, Vector *result)
580 // check radial error
581 if (!((3 + c) * (3 + c) < str->err_q * (1 + c)))
584 double mul = 1 / (1 + c);
585 double l0 = 2 * normal[0].len, l1 = 2 * normal[1].len;
586 double dot0 = l0 + normal[1].len * c, crs0 = (l0 * mul - normal[1].len) * s;
587 double dot1 = l1 + normal[0].len * c, crs1 = (l1 * mul - normal[0].len) * s;
588 // check angular error
589 if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
592 result->x = (normal[0].v.x + normal[1].v.x) * mul;
593 result->y = (normal[0].v.y + normal[1].v.y) * mul;
598 * \brief Helper function for quadratic spline construction
599 * \param str stroker state
600 * \param pt array of 3 source spline points
601 * \param deriv array of 2 differences between adjacent points in normal space
602 * \param normal first and last spline normal
603 * \param dir destination outline flags
604 * \param first true if the current part is at start of the segment
605 * \return false on allocation failure
607 static bool process_quadratic(StrokerState *str, const OutlinePoint *pt,
608 const Vector *deriv, const Normal *normal,
611 double c = vec_dot(normal[0].v, normal[1].v);
612 double s = vec_crs(normal[0].v, normal[1].v);
613 int check_dir = dir, skip_dir = s < 0 ? 1 : 2;
614 if (dir & skip_dir) {
615 double abs_s = fabs(s);
616 double f0 = normal[0].len * c + normal[1].len;
617 double f1 = normal[1].len * c + normal[0].len;
618 double g0 = normal[0].len * abs_s;
619 double g1 = normal[1].len * abs_s;
620 // check for self-intersection
621 if (f0 < abs_s && f1 < abs_s) {
622 double d2 = (f0 * normal[1].len + f1 * normal[0].len) / 2;
623 if (d2 < g0 && d2 < g1) {
624 if (!prepare_skip(str, pt[0], skip_dir, first))
626 if (f0 < 0 || f1 < 0) {
627 Vector zero_normal = {0, 0};
628 if (!emit_point(str, pt[0], zero_normal, FT_CURVE_TAG_ON, skip_dir) ||
629 !emit_point(str, pt[2], zero_normal, FT_CURVE_TAG_ON, skip_dir))
632 double mul = f0 / abs_s;
633 Vector offs = { normal[0].v.x * mul, normal[0].v.y * mul };
634 if (!emit_point(str, pt[0], offs, FT_CURVE_TAG_ON, skip_dir))
639 str->last_normal = normal[1].v;
643 check_dir ^= skip_dir;
644 } else if (c + g0 < 1 && c + g1 < 1)
645 check_dir ^= skip_dir;
649 if (check_dir && estimate_quadratic_error(str, c, s, normal, &result)) {
650 if (!emit_first_point(str, pt[0], check_dir))
652 if (!emit_point(str, pt[1], result, FT_CURVE_TAG_CONIC, check_dir))
656 str->last_normal = normal[1].v;
661 OutlinePoint next[5];
662 next[1].x = pt[0].x + pt[1].x;
663 next[1].y = pt[0].y + pt[1].y;
664 next[3].x = pt[1].x + pt[2].x;
665 next[3].y = pt[1].y + pt[2].y;
666 next[2].x = (next[1].x + next[3].x + 2) >> 2;
667 next[2].y = (next[1].y + next[3].y + 2) >> 2;
675 Vector next_deriv[3];
676 next_deriv[0].x = deriv[0].x / 2;
677 next_deriv[0].y = deriv[0].y / 2;
678 next_deriv[2].x = deriv[1].x / 2;
679 next_deriv[2].y = deriv[1].y / 2;
680 next_deriv[1].x = (next_deriv[0].x + next_deriv[2].x) / 2;
681 next_deriv[1].y = (next_deriv[0].y + next_deriv[2].y) / 2;
683 double len = vec_len(next_deriv[1]);
684 if (len < str->min_len) { // check degenerate case
685 if (!emit_first_point(str, next[0], dir))
687 if (!start_segment(str, next[2], normal[1].v, dir))
689 str->last_skip &= ~dir;
690 return emit_point(str, next[2], normal[1].v, FT_CURVE_TAG_ON, dir);
693 double scale = 1 / len;
694 Normal next_normal[3] = {
695 { normal[0].v, normal[0].len / 2 },
696 { { next_deriv[1].x * scale, next_deriv[1].y * scale }, len },
697 { normal[1].v, normal[1].len / 2 }
699 return process_quadratic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) &&
700 process_quadratic(str, next + 2, next_deriv + 1, next_normal + 1, dir, false);
704 * \brief Process source quadratic spline
705 * \param str stroker state
706 * \param pt array of 3 source spline points
707 * \param dir destination outline flags
708 * \return false on allocation failure
710 static bool add_quadratic(StrokerState *str, const OutlinePoint *pt, int dir)
712 int32_t dx0 = pt[1].x - pt[0].x;
713 int32_t dy0 = pt[1].y - pt[0].y;
714 if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps)
715 return add_line(str, pt[2], dir);
717 int32_t dx1 = pt[2].x - pt[1].x;
718 int32_t dy1 = pt[2].y - pt[1].y;
719 if (dx1 > -str->eps && dx1 < str->eps && dy1 > -str->eps && dy1 < str->eps)
720 return add_line(str, pt[2], dir);
723 { dy0 * str->yscale, -dx0 * str->xscale },
724 { dy1 * str->yscale, -dx1 * str->xscale }
726 double len0 = vec_len(deriv[0]), scale0 = 1 / len0;
727 double len1 = vec_len(deriv[1]), scale1 = 1 / len1;
729 { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 },
730 { { deriv[1].x * scale1, deriv[1].y * scale1 }, len1 }
733 bool first = str->contour_start;
734 if (!start_segment(str, pt[0], normal[0].v, dir))
736 if (!process_quadratic(str, pt, deriv, normal, dir, first))
738 str->last_point = pt[2];
744 FLAG_INTERSECTION = 1,
753 MASK_INTERSECTION = FLAG_INTERSECTION << FLAG_COUNT,
754 MASK_ZERO_0 = FLAG_ZERO_0 << FLAG_COUNT,
755 MASK_ZERO_1 = FLAG_ZERO_1 << FLAG_COUNT,
756 MASK_CLIP_0 = FLAG_CLIP_0 << FLAG_COUNT,
757 MASK_CLIP_1 = FLAG_CLIP_1 << FLAG_COUNT,
761 * \brief Estimate error for cubic spline
762 * \param str stroker state
763 * \param c dot product between normal[0] and normal[1]
764 * \param s cross product between normal[0] and normal[1]
765 * \param dc dot products between normals and central points difference in normal space
766 * \param dc cross products between normals and central points difference in normal space
767 * \param normal first and last spline normal
768 * \param result best offsets for central spline points (second & third)
769 * \return flags for destination outlines that do not require subdivision
771 static int estimate_cubic_error(StrokerState *str, double c, double s,
772 const double *dc, const double *ds,
773 const Normal *normal, Vector *result,
774 int check_flags, int dir)
776 double t = (ds[0] + ds[1]) / (dc[0] + dc[1]), c1 = 1 + c, ss = s * s;
777 double ts = t * s, tt = t * t, ttc = tt * c1, ttcc = ttc * c1;
779 const double w = 0.4;
781 10 * w * (c - 1) + 9 * w * tt * c,
782 2 * (c - 1) + 3 * tt + 2 * ts,
783 2 * (c - 1) + 3 * tt - 2 * ts,
786 18 * w * (ss - ttc * c),
787 2 * ss - 6 * ttc - 2 * ts * (c + 4),
788 2 * ss - 6 * ttc + 2 * ts * (c + 4),
791 9 * w * (ttcc - ss) * c,
792 3 * ss + 3 * ttcc + 6 * ts * c1,
793 3 * ss + 3 * ttcc - 6 * ts * c1,
796 double aa = 0, ab = 0;
797 double ch = sqrt(c1 / 2);
798 double inv_ro0 = 1.5 * ch * (ch + 1);
799 for (int i = 0; i < 3; i++) {
800 double a = 2 * f2[i] + f1[i] * inv_ro0;
801 double b = f2[i] - f0[i] * inv_ro0 * inv_ro0;
802 aa += a * a; ab += a * b;
804 double ro = ab / (aa * inv_ro0 + 1e-9); // best fit
807 for (int i = 0; i < 3; i++) {
808 double err = f0[i] + ro * (f1[i] + ro * f2[i]);
811 if (!(err2 < str->err_c)) // check radial error
814 double r = ro * c1 - 1;
815 double ro0 = t * r - ro * s;
816 double ro1 = t * r + ro * s;
818 int check_dir = check_flags & FLAG_DIR_2 ? 2 : 1;
819 if (dir & check_dir) {
820 double test_s = s, test0 = ro0, test1 = ro1;
821 if (check_flags & FLAG_DIR_2) {
827 if (2 * test_s * r < dc[0] + dc[1]) flags |= FLAG_INTERSECTION;
828 if (normal[0].len - test0 < 0) flags |= FLAG_ZERO_0;
829 if (normal[1].len + test1 < 0) flags |= FLAG_ZERO_1;
830 if (normal[0].len + dc[0] + test_s - test1 * c < 0) flags |= FLAG_CLIP_0;
831 if (normal[1].len + dc[1] + test_s + test0 * c < 0) flags |= FLAG_CLIP_1;
832 if ((flags ^ check_flags) & (check_flags >> FLAG_COUNT)) {
839 double d0c = 2 * dc[0], d0s = 2 * ds[0];
840 double d1c = 2 * dc[1], d1s = 2 * ds[1];
841 double dot0 = d0c + 3 * normal[0].len, crs0 = d0s + 3 * ro0 * normal[0].len;
842 double dot1 = d1c + 3 * normal[1].len, crs1 = d1s + 3 * ro1 * normal[1].len;
843 // check angular error (stage 1)
844 if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
847 double cl0 = c * normal[0].len, sl0 = +s * normal[0].len;
848 double cl1 = c * normal[1].len, sl1 = -s * normal[1].len;
849 dot0 = d0c - ro0 * d0s + cl0 + ro1 * sl0 + cl1 / 3;
850 dot1 = d1c - ro1 * d1s + cl1 + ro0 * sl1 + cl0 / 3;
851 crs0 = d0s + ro0 * d0c - sl0 + ro1 * cl0 - sl1 / 3;
852 crs1 = d1s + ro1 * d1c - sl1 + ro0 * cl1 - sl0 / 3;
853 // check angular error (stage 2)
854 if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
857 result[0].x = normal[0].v.x + normal[0].v.y * ro0;
858 result[0].y = normal[0].v.y - normal[0].v.x * ro0;
859 result[1].x = normal[1].v.x + normal[1].v.y * ro1;
860 result[1].y = normal[1].v.y - normal[1].v.x * ro1;
865 * \brief Helper function for cubic spline construction
866 * \param str stroker state
867 * \param pt array of 4 source spline points
868 * \param deriv array of 3 differences between adjacent points in normal space
869 * \param normal first and last spline normal
870 * \param dir destination outline flags
871 * \param first true if the current part is at start of the segment
872 * \return false on allocation failure
874 static bool process_cubic(StrokerState *str, const OutlinePoint *pt,
875 const Vector *deriv, const Normal *normal,
878 double c = vec_dot(normal[0].v, normal[1].v);
879 double s = vec_crs(normal[0].v, normal[1].v);
880 double dc[] = { vec_dot(normal[0].v, deriv[1]), vec_dot(normal[1].v, deriv[1]) };
881 double ds[] = { vec_crs(normal[0].v, deriv[1]), vec_crs(normal[1].v, deriv[1]) };
882 double f0 = normal[0].len * c + normal[1].len + dc[1];
883 double f1 = normal[1].len * c + normal[0].len + dc[0];
884 double g0 = normal[0].len * s - ds[1];
885 double g1 = normal[1].len * s + ds[0];
888 int check_dir = dir, skip_dir = 2;
889 int flags = FLAG_INTERSECTION | FLAG_DIR_2;
898 if (!(dc[0] + dc[1] > 0))
900 else if (dir & skip_dir) {
901 if (f0 < abs_s && f1 < abs_s) { // check for self-intersection
902 double d2 = (f0 + dc[1]) * normal[1].len + (f1 + dc[0]) * normal[0].len;
903 d2 = (d2 + vec_dot(deriv[1], deriv[1])) / 2;
904 if (d2 < g0 && d2 < g1) {
905 double q = sqrt(d2 / (2 - d2));
906 double h0 = (f0 * q + g0) * normal[1].len;
907 double h1 = (f1 * q + g1) * normal[0].len;
909 if (h0 > q && h1 > q) {
910 if (!prepare_skip(str, pt[0], skip_dir, first))
912 if (f0 < 0 || f1 < 0) {
913 Vector zero_normal = {0, 0};
914 if (!emit_point(str, pt[0], zero_normal, FT_CURVE_TAG_ON, skip_dir) ||
915 !emit_point(str, pt[3], zero_normal, FT_CURVE_TAG_ON, skip_dir))
918 double mul = f0 / abs_s;
919 Vector offs = { normal[0].v.x * mul, normal[0].v.y * mul };
920 if (!emit_point(str, pt[0], offs, FT_CURVE_TAG_ON, skip_dir))
925 str->last_normal = normal[1].v;
930 check_dir ^= skip_dir;
933 flags ^= MASK_INTERSECTION;
935 flags ^= MASK_INTERSECTION | FLAG_INTERSECTION;
936 bool parallel = flags & MASK_INTERSECTION;
937 int badness = parallel ? 0 : 1;
940 flags ^= MASK_ZERO_0 | FLAG_ZERO_0;
942 flags ^= MASK_CLIP_0;
944 flags ^= FLAG_ZERO_0 | FLAG_CLIP_0;
948 flags ^= MASK_INTERSECTION | FLAG_INTERSECTION;
950 flags ^= MASK_ZERO_0;
952 flags ^= MASK_CLIP_0;
957 flags ^= MASK_ZERO_1 | FLAG_ZERO_1;
959 flags ^= MASK_CLIP_1;
961 flags ^= FLAG_ZERO_1 | FLAG_CLIP_1;
965 flags ^= MASK_INTERSECTION;
967 flags ^= MASK_ZERO_1;
969 flags ^= MASK_CLIP_1;
973 check_dir ^= skip_dir;
979 check_dir = estimate_cubic_error(str, c, s, dc, ds,
980 normal, result, flags, check_dir);
982 if (!emit_first_point(str, pt[0], check_dir))
984 if (!emit_point(str, pt[1], result[0], FT_CURVE_TAG_CUBIC, check_dir) ||
985 !emit_point(str, pt[2], result[1], FT_CURVE_TAG_CUBIC, check_dir))
989 str->last_normal = normal[1].v;
994 OutlinePoint next[7], center;
995 next[1].x = pt[0].x + pt[1].x;
996 next[1].y = pt[0].y + pt[1].y;
997 center.x = pt[1].x + pt[2].x + 2;
998 center.y = pt[1].y + pt[2].y + 2;
999 next[5].x = pt[2].x + pt[3].x;
1000 next[5].y = pt[2].y + pt[3].y;
1001 next[2].x = next[1].x + center.x;
1002 next[2].y = next[1].y + center.y;
1003 next[4].x = center.x + next[5].x;
1004 next[4].y = center.y + next[5].y;
1005 next[3].x = (next[2].x + next[4].x - 1) >> 3;
1006 next[3].y = (next[2].y + next[4].y - 1) >> 3;
1018 Vector next_deriv[5], center_deriv;
1019 next_deriv[0].x = deriv[0].x / 2;
1020 next_deriv[0].y = deriv[0].y / 2;
1021 center_deriv.x = deriv[1].x / 2;
1022 center_deriv.y = deriv[1].y / 2;
1023 next_deriv[4].x = deriv[2].x / 2;
1024 next_deriv[4].y = deriv[2].y / 2;
1025 next_deriv[1].x = (next_deriv[0].x + center_deriv.x) / 2;
1026 next_deriv[1].y = (next_deriv[0].y + center_deriv.y) / 2;
1027 next_deriv[3].x = (center_deriv.x + next_deriv[4].x) / 2;
1028 next_deriv[3].y = (center_deriv.y + next_deriv[4].y) / 2;
1029 next_deriv[2].x = (next_deriv[1].x + next_deriv[3].x) / 2;
1030 next_deriv[2].y = (next_deriv[1].y + next_deriv[3].y) / 2;
1032 double len = vec_len(next_deriv[2]);
1033 if (len < str->min_len) { // check degenerate case
1034 Normal next_normal[4];
1035 next_normal[0].v = normal[0].v;
1036 next_normal[0].len = normal[0].len / 2;
1037 next_normal[3].v = normal[1].v;
1038 next_normal[3].len = normal[1].len / 2;
1040 next_deriv[1].x += next_deriv[2].x;
1041 next_deriv[1].y += next_deriv[2].y;
1042 next_deriv[3].x += next_deriv[2].x;
1043 next_deriv[3].y += next_deriv[2].y;
1044 next_deriv[2].x = next_deriv[2].y = 0;
1046 double len1 = vec_len(next_deriv[1]);
1047 if (len1 < str->min_len) {
1048 next_normal[1] = normal[0];
1050 double scale = 1 / len1;
1051 next_normal[1].v.x = next_deriv[1].x * scale;
1052 next_normal[1].v.y = next_deriv[1].y * scale;
1053 next_normal[1].len = len1;
1056 double len2 = vec_len(next_deriv[3]);
1057 if (len2 < str->min_len) {
1058 next_normal[2] = normal[1];
1060 double scale = 1 / len2;
1061 next_normal[2].v.x = next_deriv[3].x * scale;
1062 next_normal[2].v.y = next_deriv[3].y * scale;
1063 next_normal[2].len = len2;
1066 if (len1 < str->min_len) {
1067 if (!emit_first_point(str, next[0], dir))
1070 if (!process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first))
1073 if (!start_segment(str, next[2], next_normal[2].v, dir))
1075 if (len2 < str->min_len) {
1076 if (!emit_first_point(str, next[3], dir))
1079 if (!process_cubic(str, next + 3, next_deriv + 2, next_normal + 2, dir, false))
1085 double scale = 1 / len;
1086 Normal next_normal[3] = {
1087 { normal[0].v, normal[0].len / 2 },
1088 { { next_deriv[2].x * scale, next_deriv[2].y * scale }, len },
1089 { normal[1].v, normal[1].len / 2 }
1091 return process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) &&
1092 process_cubic(str, next + 3, next_deriv + 2, next_normal + 1, dir, false);
1096 * \brief Process source cubic spline
1097 * \param str stroker state
1098 * \param pt array of 4 source spline points
1099 * \param dir destination outline flags
1100 * \return false on allocation failure
1102 static bool add_cubic(StrokerState *str, const OutlinePoint *pt, int dir)
1106 int32_t dx0 = pt[1].x - pt[0].x;
1107 int32_t dy0 = pt[1].y - pt[0].y;
1108 if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) {
1109 dx0 = pt[2].x - pt[0].x;
1110 dy0 = pt[2].y - pt[0].y;
1111 if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps)
1112 return add_line(str, pt[3], dir);
1116 int32_t dx2 = pt[3].x - pt[2].x;
1117 int32_t dy2 = pt[3].y - pt[2].y;
1118 if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) {
1119 dx2 = pt[3].x - pt[1].x;
1120 dy2 = pt[3].y - pt[1].y;
1121 if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps)
1122 return add_line(str, pt[3], dir);
1127 return add_line(str, pt[3], dir);
1129 int32_t dx1 = pt[flags >> 2].x - pt[flags & 3].x;
1130 int32_t dy1 = pt[flags >> 2].y - pt[flags & 3].y;
1133 { dy0 * str->yscale, -dx0 * str->xscale },
1134 { dy1 * str->yscale, -dx1 * str->xscale },
1135 { dy2 * str->yscale, -dx2 * str->xscale }
1137 double len0 = vec_len(deriv[0]), scale0 = 1 / len0;
1138 double len2 = vec_len(deriv[2]), scale2 = 1 / len2;
1139 Normal normal[2] = {
1140 { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 },
1141 { { deriv[2].x * scale2, deriv[2].y * scale2 }, len2 }
1144 bool first = str->contour_start;
1145 if (!start_segment(str, pt[0], normal[0].v, dir))
1147 if (!process_cubic(str, pt, deriv, normal, dir, first))
1149 str->last_point = pt[3];
1155 * \brief Process contour closing
1156 * \param str stroker state
1157 * \param dir destination outline flags
1158 * \return false on allocation failure
1160 static bool close_contour(StrokerState *str, int dir)
1162 if (str->contour_start) {
1165 if (!draw_circle(str, str->last_point, dir))
1168 if (!add_line(str, str->first_point, dir))
1170 if (!start_segment(str, str->first_point, str->first_normal, dir))
1172 if (!emit_point(str, str->first_point, str->first_normal, FT_CURVE_TAG_ON,
1173 ~str->last_skip & dir & str->first_skip))
1175 if (str->last_normal.x != str->first_normal.x ||
1176 str->last_normal.y != str->first_normal.y)
1177 fix_first_point(str, str->first_point, str->last_normal,
1178 ~str->last_skip & dir & ~str->first_skip);
1179 str->contour_start = true;
1181 if ((dir & 1) && !outline_close_contour(str->result[0]))
1183 if ((dir & 2) && !outline_close_contour(str->result[1]))
1190 * Stroke an outline glyph in x/y direction.
1191 * \param result first result outline
1192 * \param result1 second result outline
1193 * \param path source outline
1194 * \param xbord border size in X direction
1195 * \param ybord border size in Y direction
1196 * \param eps approximate allowable error
1197 * \return false on allocation failure
1199 bool outline_stroke(ASS_Outline *result, ASS_Outline *result1,
1200 const ASS_Outline *path, int xbord, int ybord, int eps)
1202 int rad = FFMAX(xbord, ybord);
1205 result->n_contours = result->n_points = 0;
1206 result1->n_contours = result1->n_points = 0;
1209 str.result[0] = result;
1210 str.result[1] = result1;
1213 str.xscale = 1.0 / FFMAX(eps, xbord);
1214 str.yscale = 1.0 / FFMAX(eps, ybord);
1217 str.contour_start = true;
1218 double rel_err = (double) eps / rad;
1219 str.merge_cos = 1 - rel_err;
1220 double e = sqrt(2 * rel_err);
1221 str.split_cos = 1 + 8 * rel_err - 4 * (1 + rel_err) * e;
1222 str.min_len = rel_err / 4;
1223 str.err_q = 8 * (1 + rel_err) * (1 + rel_err);
1224 str.err_c = 390 * rel_err * rel_err;
1228 S_ON, S_Q, S_C1, S_C2
1232 for (size_t i = 0, j = 0; i < path->n_contours; i++) {
1233 OutlinePoint start, p[4];
1234 int process_end = 1;
1237 int last = path->contours[i];
1241 if (path->points[j].x < -(1 << 28) || path->points[j].x >= (1 << 28))
1243 if (path->points[j].y <= -(1 << 28) || path->points[j].y > (1 << 28))
1246 switch (FT_CURVE_TAG(path->tags[j])) {
1247 case FT_CURVE_TAG_ON:
1248 p[0].x = path->points[j].x;
1249 p[0].y = -path->points[j].y;
1254 case FT_CURVE_TAG_CONIC:
1255 switch (FT_CURVE_TAG(path->tags[last])) {
1256 case FT_CURVE_TAG_ON:
1257 p[0].x = path->points[last].x;
1258 p[0].y = -path->points[last].y;
1259 p[1].x = path->points[j].x;
1260 p[1].y = -path->points[j].y;
1266 case FT_CURVE_TAG_CONIC:
1267 p[1].x = path->points[j].x;
1268 p[1].y = -path->points[j].y;
1269 p[0].x = (p[1].x + path->points[last].x) >> 1;
1270 p[0].y = (p[1].y - path->points[last].y) >> 1;
1283 str.last_point = start;
1285 for (j++; j <= last; j++) {
1286 if (path->points[j].x < -(1 << 28) || path->points[j].x >= (1 << 28))
1288 if (path->points[j].y <= -(1 << 28) || path->points[j].y > (1 << 28))
1291 switch (FT_CURVE_TAG(path->tags[j])) {
1292 case FT_CURVE_TAG_ON:
1295 p[1].x = path->points[j].x;
1296 p[1].y = -path->points[j].y;
1297 if (!add_line(&str, p[1], dir))
1303 p[2].x = path->points[j].x;
1304 p[2].y = -path->points[j].y;
1305 if (!add_quadratic(&str, p, dir))
1312 p[3].x = path->points[j].x;
1313 p[3].y = -path->points[j].y;
1314 if (!add_cubic(&str, p, dir))
1325 case FT_CURVE_TAG_CONIC:
1328 p[1].x = path->points[j].x;
1329 p[1].y = -path->points[j].y;
1334 p[3].x = path->points[j].x;
1335 p[3].y = -path->points[j].y;
1336 p[2].x = (p[1].x + p[3].x) >> 1;
1337 p[2].y = (p[1].y + p[3].y) >> 1;
1338 if (!add_quadratic(&str, p, dir))
1349 case FT_CURVE_TAG_CUBIC:
1352 p[1].x = path->points[j].x;
1353 p[1].y = -path->points[j].y;
1358 p[2].x = path->points[j].x;
1359 p[2].y = -path->points[j].y;
1376 if (!add_line(&str, start, dir))
1382 if (!add_quadratic(&str, p, dir))
1388 if (!add_cubic(&str, p, dir))
1395 if (!close_contour(&str, dir))