From 305463c29a402298911f812b222f54a9f9457920 Mon Sep 17 00:00:00 2001 From: "Dr.Smile" Date: Mon, 31 Jul 2017 05:33:00 +0300 Subject: stroker: implement fast two-outline stroker --- libass/ass_drawing.c | 58 +-- libass/ass_outline.c | 1170 ++++++++++++++++++++++++++++++++++++++++++++------ libass/ass_outline.h | 7 +- libass/ass_parse.c | 35 -- libass/ass_parse.h | 3 - libass/ass_render.c | 134 +----- libass/ass_render.h | 3 - 7 files changed, 1062 insertions(+), 348 deletions(-) diff --git a/libass/ass_drawing.c b/libass/ass_drawing.c index 3428d21..23fe8cd 100644 --- a/libass/ass_drawing.c +++ b/libass/ass_drawing.c @@ -34,47 +34,6 @@ #define GLYPH_INITIAL_POINTS 100 #define GLYPH_INITIAL_CONTOURS 5 -/* - * \brief Add a single point to a contour. - */ -static inline bool drawing_add_point(ASS_Drawing *drawing, - const FT_Vector *point, char tags) -{ - ASS_Outline *ol = &drawing->outline; - if (ol->n_points >= ol->max_points) { - size_t new_size = 2 * ol->max_points; - if (!ASS_REALLOC_ARRAY(ol->points, new_size)) - return false; - if (!ASS_REALLOC_ARRAY(ol->tags, new_size)) - return false; - ol->max_points = new_size; - } - - ol->points[ol->n_points].x = point->x; - ol->points[ol->n_points].y = point->y; - ol->tags[ol->n_points] = tags; - ol->n_points++; - return true; -} - -/* - * \brief Close a contour and check outline size overflow. - */ -static inline bool drawing_close_shape(ASS_Drawing *drawing) -{ - ASS_Outline *ol = &drawing->outline; - if (ol->n_contours >= ol->max_contours) { - size_t new_size = 2 * ol->max_contours; - if (!ASS_REALLOC_ARRAY(ol->contours, new_size)) - return false; - ol->max_contours = new_size; - } - - ol->contours[ol->n_contours] = ol->n_points - 1; - ol->n_contours++; - return true; -} - /* * \brief Prepare drawing for parsing. This just sets a few parameters. */ @@ -278,10 +237,11 @@ static bool drawing_evaluate_curve(ASS_Drawing *drawing, p[2].y -= y12; } - return (started || drawing_add_point(drawing, &p[0], FT_CURVE_TAG_ON)) && - drawing_add_point(drawing, &p[1], FT_CURVE_TAG_CUBIC) && - drawing_add_point(drawing, &p[2], FT_CURVE_TAG_CUBIC) && - drawing_add_point(drawing, &p[3], FT_CURVE_TAG_ON); + return (started || + outline_add_point(&drawing->outline, p[0], FT_CURVE_TAG_ON)) && + outline_add_point(&drawing->outline, p[1], FT_CURVE_TAG_CUBIC) && + outline_add_point(&drawing->outline, p[2], FT_CURVE_TAG_CUBIC) && + outline_add_point(&drawing->outline, p[3], FT_CURVE_TAG_ON); } /* @@ -362,7 +322,7 @@ ASS_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode) pen = token->point; translate_point(drawing, &pen); if (started) { - if (!drawing_close_shape(drawing)) + if (!outline_close_contour(&drawing->outline)) goto error; started = 0; } @@ -372,9 +332,9 @@ ASS_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode) FT_Vector to; to = token->point; translate_point(drawing, &to); - if (!started && !drawing_add_point(drawing, &pen, FT_CURVE_TAG_ON)) + if (!started && !outline_add_point(&drawing->outline, pen, FT_CURVE_TAG_ON)) goto error; - if (!drawing_add_point(drawing, &to, FT_CURVE_TAG_ON)) + if (!outline_add_point(&drawing->outline, to, FT_CURVE_TAG_ON)) goto error; started = 1; token = token->next; @@ -409,7 +369,7 @@ ASS_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode) } // Close the last contour - if (started && !drawing_close_shape(drawing)) + if (started && !outline_close_contour(&drawing->outline)) goto error; drawing_finish(drawing, raw_mode); diff --git a/libass/ass_outline.c b/libass/ass_outline.c index 7c1d784..1facc6d 100644 --- a/libass/ass_outline.c +++ b/libass/ass_outline.c @@ -37,21 +37,30 @@ bool outline_alloc(ASS_Outline *outline, size_t n_points, size_t n_contours) return true; } -ASS_Outline *outline_convert(const FT_Outline *source) +ASS_Outline *outline_create(size_t n_points, size_t n_contours) { - if (!source) - return NULL; - ASS_Outline *ol = calloc(1, sizeof(*ol)); if (!ol) return NULL; - if (!outline_alloc(ol, source->n_points, source->n_contours)) { + if (!outline_alloc(ol, n_points, n_contours)) { outline_free(ol); free(ol); return NULL; } + return ol; +} + +ASS_Outline *outline_convert(const FT_Outline *source) +{ + if (!source) + return NULL; + + ASS_Outline *ol = outline_create(source->n_points, source->n_contours); + if (!ol) + return NULL; + for (int i = 0; i < source->n_contours; i++) ol->contours[i] = source->contours[i]; memcpy(ol->points, source->points, sizeof(FT_Vector) * source->n_points); @@ -66,16 +75,10 @@ ASS_Outline *outline_copy(const ASS_Outline *source) if (!source) return NULL; - ASS_Outline *ol = calloc(1, sizeof(*ol)); + ASS_Outline *ol = outline_create(source->n_points, source->n_contours); if (!ol) return NULL; - if (!outline_alloc(ol, source->n_points, source->n_contours)) { - outline_free(ol); - free(ol); - return NULL; - } - memcpy(ol->contours, source->contours, sizeof(size_t) * source->n_contours); memcpy(ol->points, source->points, sizeof(FT_Vector) * source->n_points); memcpy(ol->tags, source->tags, source->n_points); @@ -95,6 +98,42 @@ void outline_free(ASS_Outline *outline) } +/* + * \brief Add a single point to a contour. + */ +bool outline_add_point(ASS_Outline *outline, FT_Vector pt, char tag) +{ + if (outline->n_points >= outline->max_points) { + size_t new_size = 2 * outline->max_points; + if (!ASS_REALLOC_ARRAY(outline->points, new_size)) + return false; + if (!ASS_REALLOC_ARRAY(outline->tags, new_size)) + return false; + outline->max_points = new_size; + } + outline->points[outline->n_points] = pt; + outline->tags[outline->n_points] = tag; + outline->n_points++; + return true; +} + +/* + * \brief Close a contour. + */ +bool outline_close_contour(ASS_Outline *outline) +{ + if (outline->n_contours >= outline->max_contours) { + size_t new_size = 2 * outline->max_contours; + if (!ASS_REALLOC_ARRAY(outline->contours, new_size)) + return false; + outline->max_contours = new_size; + } + outline->contours[outline->n_contours] = outline->n_points - 1; + outline->n_contours++; + return true; +} + + void outline_translate(const ASS_Outline *outline, FT_Pos dx, FT_Pos dy) { for (size_t i = 0; i < outline->n_points; i++) { @@ -133,143 +172,1008 @@ void outline_get_cbox(const ASS_Outline *outline, FT_BBox *cbox) } -/** - * \brief Calculate the cbox of a series of points - */ -static void -get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end) + +typedef struct { + int32_t x, y; +} OutlinePoint; + +typedef struct { + double x, y; +} Vector; + +typedef struct { + Vector v; + double len; +} Normal; + +typedef struct { + ASS_Outline *result[2]; + double xbord, ybord; + double xscale, yscale; + int eps; + + bool contour_start; + int first_skip, last_skip; + Vector first_normal, last_normal; + OutlinePoint first_point, last_point; + + double merge_cos, split_cos, min_len; + double err_q, err_c, err_a; +} StrokerState; + +static inline double vec_dot(Vector vec1, Vector vec2) { - box->xMin = box->yMin = INT_MAX; - box->xMax = box->yMax = INT_MIN; - for (int i = start; i <= end; i++) { - box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin; - box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax; - box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin; - box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax; + return vec1.x * vec2.x + vec1.y * vec2.y; +} + +static inline double vec_crs(Vector vec1, Vector vec2) +{ + return vec1.x * vec2.y - vec1.y * vec2.x; +} + +static inline double vec_len(Vector vec) +{ + return sqrt(vec.x * vec.x + vec.y * vec.y); +} + + +static bool emit_point(StrokerState *str, OutlinePoint pt, + Vector offs, char tag, int dir) +{ + int32_t dx = (int32_t)(str->xbord * offs.x); + int32_t dy = (int32_t)(str->ybord * offs.y); + + if (dir & 1) { + FT_Vector res = { pt.x + dx, pt.y + dy }; + res.y = -res.y; + if (!outline_add_point(str->result[0], res, tag)) + return false; + } + if (dir & 2) { + FT_Vector res = { pt.x - dx, pt.y - dy }; + res.y = -res.y; + if (!outline_add_point(str->result[1], res, tag)) + return false; } + return true; } -/** - * \brief Determine signed area of a contour - * \return area doubled - */ -static long long get_contour_area(FT_Vector *points, int start, int end) +static void fix_first_point(StrokerState *str, OutlinePoint pt, + Vector offs, int dir) +{ + int32_t dx = (int32_t)(str->xbord * offs.x); + int32_t dy = (int32_t)(str->ybord * offs.y); + + if (dir & 1) { + FT_Vector res = { pt.x + dx, pt.y + dy }; + res.y = -res.y; + ASS_Outline *ol = str->result[0]; + size_t first = ol->n_contours ? + ol->contours[ol->n_contours - 1] + 1 : 0; + ol->points[first] = res; + } + if (dir & 2) { + FT_Vector res = { pt.x - dx, pt.y - dy }; + res.y = -res.y; + ASS_Outline *ol = str->result[1]; + size_t first = ol->n_contours ? + ol->contours[ol->n_contours - 1] + 1 : 0; + ol->points[first] = res; + } +} + +static bool process_arc(StrokerState *str, OutlinePoint pt, + Vector normal0, Vector normal1, + const double *mul, int level, int dir) { - long long area = 0; - int x = points[end].x; - int y = points[end].y; - for (int i = start; i <= end; i++) { - area += (long long)(points[i].x + x) * (points[i].y - y); - x = points[i].x; - y = points[i].y; - } - return area; + Vector center; + center.x = (normal0.x + normal1.x) * mul[level]; + center.y = (normal0.y + normal1.y) * mul[level]; + if (level) + return process_arc(str, pt, normal0, center, mul, level - 1, dir) && + process_arc(str, pt, center, normal1, mul, level - 1, dir); + return emit_point(str, pt, normal0, FT_CURVE_TAG_ON, dir) && + emit_point(str, pt, center, FT_CURVE_TAG_CONIC, dir); } -/** - * \brief Apply fixups to please the FreeType stroker and improve the - * rendering result, especially in case the outline has some anomalies. - * At the moment, the following fixes are done: - * - * 1. Reverse contours that have "inside" winding direction but are not - * contained in any other contours' cbox. - * 2. Remove "inside" contours depending on border size, so that large - * borders do not reverse the winding direction, which leads to "holes" - * inside the border. The inside will be filled by the border of the - * outside contour anyway in this case. - * - * \param outline FreeType outline, modified in-place - * \param border_x border size, x direction, d6 format - * \param border_x border size, y direction, d6 format - */ -void fix_freetype_stroker(ASS_Outline *outline, int border_x, int border_y) +static bool draw_arc(StrokerState *str, OutlinePoint pt, + Vector normal0, Vector normal1, double c, int dir) { - int nc = outline->n_contours; - int begin, stop; - char modified = 0; - char *valid_cont = malloc(nc); - int start = 0; - int end = -1; - FT_BBox *boxes = malloc(nc * sizeof(FT_BBox)); - int i, j; - - long long area = 0; - // create a list of cboxes of the contours - for (i = 0; i < nc; i++) { - start = end + 1; - end = outline->contours[i]; - get_contour_cbox(&boxes[i], outline->points, start, end); - area += get_contour_area(outline->points, start, end); - } - int inside_direction = area < 0; - - // for each contour, check direction and whether it's "outside" - // or contained in another contour - end = -1; - for (i = 0; i < nc; i++) { - start = end + 1; - end = outline->contours[i]; - int dir = get_contour_area(outline->points, start, end) > 0; - valid_cont[i] = 1; - if (dir == inside_direction) { - for (j = 0; j < nc; j++) { - if (i == j) - continue; - if (boxes[i].xMin >= boxes[j].xMin && - boxes[i].xMax <= boxes[j].xMax && - boxes[i].yMin >= boxes[j].yMin && - boxes[i].yMax <= boxes[j].yMax) - goto check_inside; - } - /* "inside" contour but we can't find anything it could be - * inside of - assume the font is buggy and it should be - * an "outside" contour, and reverse it */ - for (j = 0; j < (end - start) / 2; j++) { - FT_Vector temp = outline->points[start + 1 + j]; - char temp2 = outline->tags[start + 1 + j]; - outline->points[start + 1 + j] = outline->points[end - j]; - outline->points[end - j] = temp; - outline->tags[start + 1 + j] = outline->tags[end - j]; - outline->tags[end - j] = temp2; + const int max_subdiv = 15; + double mul[max_subdiv + 1]; + + Vector center; + bool small_angle = true; + if (c < 0) { + double mul = dir & 2 ? -sqrt(0.5) : sqrt(0.5); + mul /= sqrt(1 - c); + center.x = (normal1.y - normal0.y) * mul; + center.y = (normal0.x - normal1.x) * mul; + c = sqrt(FFMAX(0, 0.5 + 0.5 * c)); + small_angle = false; + } + + int pos = max_subdiv; + while (c < str->split_cos && pos) { + mul[pos] = sqrt(0.5) / sqrt(1 + c); + c = (1 + c) * mul[pos]; + pos--; + } + mul[pos] = 1 / (1 + c); + return small_angle ? + process_arc(str, pt, normal0, normal1, mul + pos, max_subdiv - pos, dir) : + process_arc(str, pt, normal0, center, mul + pos, max_subdiv - pos, dir) && + process_arc(str, pt, center, normal1, mul + pos, max_subdiv - pos, dir); +} + +static bool draw_circle(StrokerState *str, OutlinePoint pt, int dir) +{ + const int max_subdiv = 15; + double mul[max_subdiv + 1], c = 0; + + int pos = max_subdiv; + while (c < str->split_cos && pos) { + mul[pos] = sqrt(0.5) / sqrt(1 + c); + c = (1 + c) * mul[pos]; + pos--; + } + mul[pos] = 1 / (1 + c); + + Vector normal[4] = { + { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } + }; + return process_arc(str, pt, normal[0], normal[1], mul + pos, max_subdiv - pos, dir) && + process_arc(str, pt, normal[1], normal[2], mul + pos, max_subdiv - pos, dir) && + process_arc(str, pt, normal[2], normal[3], mul + pos, max_subdiv - pos, dir) && + process_arc(str, pt, normal[3], normal[0], mul + pos, max_subdiv - pos, dir); +} + +static bool start_segment(StrokerState *str, OutlinePoint pt, + Vector normal, int dir) +{ + if (str->contour_start) { + str->contour_start = false; + str->first_skip = str->last_skip = 0; + str->first_normal = str->last_normal = normal; + str->first_point = pt; + return true; + } + + Vector prev = str->last_normal; + double c = vec_dot(prev, normal); + if (c > str->merge_cos) { // merge without cap + double mul = 1 / (1 + c); + str->last_normal.x = (str->last_normal.x + normal.x) * mul; + str->last_normal.y = (str->last_normal.y + normal.y) * mul; + return true; + } + str->last_normal = normal; + + // check for negative curvative + double s = vec_crs(prev, normal); + int skip_dir = s < 0 ? 1 : 2; + if (dir & skip_dir) { + if (!emit_point(str, pt, prev, FT_CURVE_TAG_ON, ~str->last_skip & skip_dir)) + return false; + Vector zero_normal = {0, 0}; + if (!emit_point(str, pt, zero_normal, FT_CURVE_TAG_ON, skip_dir)) + return false; + } + str->last_skip = skip_dir; + + dir &= ~skip_dir; + return !dir || draw_arc(str, pt, prev, normal, c, dir); +} + +static bool emit_first_point(StrokerState *str, OutlinePoint pt, int dir) +{ + str->last_skip &= ~dir; + return emit_point(str, pt, str->last_normal, FT_CURVE_TAG_ON, dir); +} + +static bool prepare_skip(StrokerState *str, OutlinePoint pt, int dir, bool first) +{ + if (first) + str->first_skip |= dir; + else if (!emit_point(str, pt, str->last_normal, FT_CURVE_TAG_ON, ~str->last_skip & dir)) + return false; + str->last_skip |= dir; + return true; +} + +static bool add_line(StrokerState *str, OutlinePoint pt, int dir) +{ + int32_t dx = pt.x - str->last_point.x; + int32_t dy = pt.y - str->last_point.y; + if (dx > -str->eps && dx < str->eps && dy > -str->eps && dy < str->eps) + return true; + + Vector deriv = { dy * str->yscale, -dx * str->xscale }; + double scale = 1 / vec_len(deriv); + Vector normal = { deriv.x * scale, deriv.y * scale }; + if (!start_segment(str, str->last_point, normal, dir)) + return false; + if (!emit_first_point(str, str->last_point, dir)) + return false; + str->last_normal = normal; + str->last_point = pt; + return true; +} + + +static bool estimate_quadratic_error(StrokerState *str, double c, double s, + const Normal *normal, Vector *result) +{ + // check radial error + if (!((3 + c) * (3 + c) < str->err_q * (1 + c))) + return false; + + double mul = 1 / (1 + c); + double l0 = 2 * normal[0].len, l1 = 2 * normal[1].len; + double dot0 = l0 + normal[1].len * c, crs0 = (l0 * mul - normal[1].len) * s; + double dot1 = l1 + normal[0].len * c, crs1 = (l1 * mul - normal[0].len) * s; + // check angular error + if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1)) + return false; + + result->x = (normal[0].v.x + normal[1].v.x) * mul; + result->y = (normal[0].v.y + normal[1].v.y) * mul; + return true; +} + +static bool process_quadratic(StrokerState *str, const OutlinePoint *pt, + const Vector *deriv, const Normal *normal, + int dir, bool first) +{ + double c = vec_dot(normal[0].v, normal[1].v); + double s = vec_crs(normal[0].v, normal[1].v); + int check_dir = dir, skip_dir = s < 0 ? 1 : 2; + if (dir & skip_dir) { + double abs_s = fabs(s); + double f0 = normal[0].len * c + normal[1].len; + double f1 = normal[1].len * c + normal[0].len; + double g0 = normal[0].len * abs_s; + double g1 = normal[1].len * abs_s; + // check for self-intersection + if (f0 < abs_s && f1 < abs_s) { + double d2 = (f0 * normal[1].len + f1 * normal[0].len) / 2; + if (d2 < g0 && d2 < g1) { + if (!prepare_skip(str, pt[0], skip_dir, first)) + return false; + if (f0 < 0 || f1 < 0) { + Vector zero_normal = {0, 0}; + if (!emit_point(str, pt[0], zero_normal, FT_CURVE_TAG_ON, skip_dir) || + !emit_point(str, pt[2], zero_normal, FT_CURVE_TAG_ON, skip_dir)) + return false; + } else { + double mul = f0 / abs_s; + Vector offs = { normal[0].v.x * mul, normal[0].v.y * mul }; + if (!emit_point(str, pt[0], offs, FT_CURVE_TAG_ON, skip_dir)) + return false; + } + dir &= ~skip_dir; + if (!dir) { + str->last_normal = normal[1].v; + return true; + } } - dir ^= 1; + check_dir ^= skip_dir; + } else if (c + g0 < 1 && c + g1 < 1) + check_dir ^= skip_dir; + } + + Vector result; + if (check_dir && estimate_quadratic_error(str, c, s, normal, &result)) { + if (!emit_first_point(str, pt[0], check_dir)) + return false; + if (!emit_point(str, pt[1], result, FT_CURVE_TAG_CONIC, check_dir)) + return false; + dir &= ~check_dir; + if (!dir) { + str->last_normal = normal[1].v; + return true; } - check_inside: - if (dir == inside_direction) { - FT_BBox box; - get_contour_cbox(&box, outline->points, start, end); - int width = box.xMax - box.xMin; - int height = box.yMax - box.yMin; - if (width < border_x * 2 || height < border_y * 2) { - valid_cont[i] = 0; - modified = 1; - } + } + + OutlinePoint next[5]; + next[1].x = pt[0].x + pt[1].x; + next[1].y = pt[0].y + pt[1].y; + next[3].x = pt[1].x + pt[2].x; + next[3].y = pt[1].y + pt[2].y; + next[2].x = (next[1].x + next[3].x + 2) >> 2; + next[2].y = (next[1].y + next[3].y + 2) >> 2; + next[1].x >>= 1; + next[1].y >>= 1; + next[3].x >>= 1; + next[3].y >>= 1; + next[0] = pt[0]; + next[4] = pt[2]; + + Vector next_deriv[3]; + next_deriv[0].x = deriv[0].x / 2; + next_deriv[0].y = deriv[0].y / 2; + next_deriv[2].x = deriv[1].x / 2; + next_deriv[2].y = deriv[1].y / 2; + next_deriv[1].x = (next_deriv[0].x + next_deriv[2].x) / 2; + next_deriv[1].y = (next_deriv[0].y + next_deriv[2].y) / 2; + + double len = vec_len(next_deriv[1]); + if (len < str->min_len) { // check degenerate case + if (!emit_first_point(str, next[0], dir)) + return false; + if (!start_segment(str, next[2], normal[1].v, dir)) + return false; + str->last_skip &= ~dir; + return emit_point(str, next[2], normal[1].v, FT_CURVE_TAG_ON, dir); + } + + double scale = 1 / len; + Normal next_normal[3] = { + { normal[0].v, normal[0].len / 2 }, + { { next_deriv[1].x * scale, next_deriv[1].y * scale }, len }, + { normal[1].v, normal[1].len / 2 } + }; + return process_quadratic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) && + process_quadratic(str, next + 2, next_deriv + 1, next_normal + 1, dir, false); +} + +static bool add_quadratic(StrokerState *str, const OutlinePoint *pt, int dir) +{ + int32_t dx0 = pt[1].x - pt[0].x; + int32_t dy0 = pt[1].y - pt[0].y; + if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) + return add_line(str, pt[2], dir); + + int32_t dx1 = pt[2].x - pt[1].x; + int32_t dy1 = pt[2].y - pt[1].y; + if (dx1 > -str->eps && dx1 < str->eps && dy1 > -str->eps && dy1 < str->eps) + return add_line(str, pt[2], dir); + + Vector deriv[2] = { + { dy0 * str->yscale, -dx0 * str->xscale }, + { dy1 * str->yscale, -dx1 * str->xscale } + }; + double len0 = vec_len(deriv[0]), scale0 = 1 / len0; + double len1 = vec_len(deriv[1]), scale1 = 1 / len1; + Normal normal[2] = { + { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 }, + { { deriv[1].x * scale1, deriv[1].y * scale1 }, len1 } + }; + + bool first = str->contour_start; + if (!start_segment(str, pt[0], normal[0].v, dir)) + return false; + if (!process_quadratic(str, pt, deriv, normal, dir, first)) + return false; + str->last_point = pt[2]; + return true; +} + + +enum { + FLAG_INTERSECTION = 1, + FLAG_ZERO_0 = 2, + FLAG_ZERO_1 = 4, + FLAG_CLIP_0 = 8, + FLAG_CLIP_1 = 16, + FLAG_DIR_2 = 32, + + FLAG_COUNT = 6, + + MASK_INTERSECTION = FLAG_INTERSECTION << FLAG_COUNT, + MASK_ZERO_0 = FLAG_ZERO_0 << FLAG_COUNT, + MASK_ZERO_1 = FLAG_ZERO_1 << FLAG_COUNT, + MASK_CLIP_0 = FLAG_CLIP_0 << FLAG_COUNT, + MASK_CLIP_1 = FLAG_CLIP_1 << FLAG_COUNT, +}; + +static int estimate_cubic_error(StrokerState *str, double c, double s, + const double *dc, const double *ds, + const Normal *normal, Vector *result, + int check_flags, int dir) +{ + double t = (ds[0] + ds[1]) / (dc[0] + dc[1]), c1 = 1 + c, ss = s * s; + double ts = t * s, tt = t * t, ttc = tt * c1, ttcc = ttc * c1; + + const double w = 0.4; + double f0[] = { + 10 * w * (c - 1) + 9 * w * tt * c, + 2 * (c - 1) + 3 * tt + 2 * ts, + 2 * (c - 1) + 3 * tt - 2 * ts, + }; + double f1[] = { + 18 * w * (ss - ttc * c), + 2 * ss - 6 * ttc - 2 * ts * (c + 4), + 2 * ss - 6 * ttc + 2 * ts * (c + 4), + }; + double f2[] = { + 9 * w * (ttcc - ss) * c, + 3 * ss + 3 * ttcc + 6 * ts * c1, + 3 * ss + 3 * ttcc - 6 * ts * c1, + }; + + double aa = 0, ab = 0; + double ch = sqrt(c1 / 2); + double inv_ro0 = 1.5 * ch * (ch + 1); + for (int i = 0; i < 3; i++) { + double a = 2 * f2[i] + f1[i] * inv_ro0; + double b = f2[i] - f0[i] * inv_ro0 * inv_ro0; + aa += a * a; ab += a * b; + } + double ro = ab / (aa * inv_ro0 + 1e-9); // best fit + + double err2 = 0; + for (int i = 0; i < 3; i++) { + double err = f0[i] + ro * (f1[i] + ro * f2[i]); + err2 += err * err; + } + if (!(err2 < str->err_c)) // check radial error + return 0; + + double r = ro * c1 - 1; + double ro0 = t * r - ro * s; + double ro1 = t * r + ro * s; + + int check_dir = check_flags & FLAG_DIR_2 ? 2 : 1; + if (dir & check_dir) { + double test_s = s, test0 = ro0, test1 = ro1; + if (check_flags & FLAG_DIR_2) { + test_s = -test_s; + test0 = -test0; + test1 = -test1; + } + int flags = 0; + if (2 * test_s * r < dc[0] + dc[1]) flags |= FLAG_INTERSECTION; + if (normal[0].len - test0 < 0) flags |= FLAG_ZERO_0; + if (normal[1].len + test1 < 0) flags |= FLAG_ZERO_1; + if (normal[0].len + dc[0] + test_s - test1 * c < 0) flags |= FLAG_CLIP_0; + if (normal[1].len + dc[1] + test_s + test0 * c < 0) flags |= FLAG_CLIP_1; + if ((flags ^ check_flags) & (check_flags >> FLAG_COUNT)) { + dir &= ~check_dir; + if (!dir) + return 0; } } - // if we need to modify the outline, rewrite it and skip - // the contours that we determined should be removed. - if (modified) { - int p = 0, c = 0; - for (i = 0; i < nc; i++) { - if (!valid_cont[i]) - continue; - begin = (i == 0) ? 0 : outline->contours[i - 1] + 1; - stop = outline->contours[i]; - for (j = begin; j <= stop; j++) { - outline->points[p].x = outline->points[j].x; - outline->points[p].y = outline->points[j].y; - outline->tags[p] = outline->tags[j]; - p++; + double d0c = 2 * dc[0], d0s = 2 * ds[0]; + double d1c = 2 * dc[1], d1s = 2 * ds[1]; + double dot0 = d0c + 3 * normal[0].len, crs0 = d0s + 3 * ro0 * normal[0].len; + double dot1 = d1c + 3 * normal[1].len, crs1 = d1s + 3 * ro1 * normal[1].len; + // check angular error (stage 1) + if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1)) + return 0; + + double cl0 = c * normal[0].len, sl0 = +s * normal[0].len; + double cl1 = c * normal[1].len, sl1 = -s * normal[1].len; + dot0 = d0c - ro0 * d0s + cl0 + ro1 * sl0 + cl1 / 3; + dot1 = d1c - ro1 * d1s + cl1 + ro0 * sl1 + cl0 / 3; + crs0 = d0s + ro0 * d0c - sl0 + ro1 * cl0 - sl1 / 3; + crs1 = d1s + ro1 * d1c - sl1 + ro0 * cl1 - sl0 / 3; + // check angular error (stage 2) + if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1)) + return 0; + + result[0].x = normal[0].v.x + normal[0].v.y * ro0; + result[0].y = normal[0].v.y - normal[0].v.x * ro0; + result[1].x = normal[1].v.x + normal[1].v.y * ro1; + result[1].y = normal[1].v.y - normal[1].v.x * ro1; + return dir; +} + +static bool process_cubic(StrokerState *str, const OutlinePoint *pt, + const Vector *deriv, const Normal *normal, + int dir, bool first) +{ + double c = vec_dot(normal[0].v, normal[1].v); + double s = vec_crs(normal[0].v, normal[1].v); + double dc[] = { vec_dot(normal[0].v, deriv[1]), vec_dot(normal[1].v, deriv[1]) }; + double ds[] = { vec_crs(normal[0].v, deriv[1]), vec_crs(normal[1].v, deriv[1]) }; + double f0 = normal[0].len * c + normal[1].len + dc[1]; + double f1 = normal[1].len * c + normal[0].len + dc[0]; + double g0 = normal[0].len * s - ds[1]; + double g1 = normal[1].len * s + ds[0]; + + double abs_s = s; + int check_dir = dir, skip_dir = 2; + int flags = FLAG_INTERSECTION | FLAG_DIR_2; + if (s < 0) { + abs_s = -s; + skip_dir = 1; + flags = 0; + g0 = -g0; + g1 = -g1; + } + + if (!(dc[0] + dc[1] > 0)) + check_dir = 0; + else if (dir & skip_dir) { + if (f0 < abs_s && f1 < abs_s) { // check for self-intersection + double d2 = (f0 + dc[1]) * normal[1].len + (f1 + dc[0]) * normal[0].len; + d2 = (d2 + vec_dot(deriv[1], deriv[1])) / 2; + if (d2 < g0 && d2 < g1) { + double q = sqrt(d2 / (2 - d2)); + double h0 = (f0 * q + g0) * normal[1].len; + double h1 = (f1 * q + g1) * normal[0].len; + q *= (4.0 / 3) * d2; + if (h0 > q && h1 > q) { + if (!prepare_skip(str, pt[0], skip_dir, first)) + return false; + if (f0 < 0 || f1 < 0) { + Vector zero_normal = {0, 0}; + if (!emit_point(str, pt[0], zero_normal, FT_CURVE_TAG_ON, skip_dir) || + !emit_point(str, pt[3], zero_normal, FT_CURVE_TAG_ON, skip_dir)) + return false; + } else { + double mul = f0 / abs_s; + Vector offs = { normal[0].v.x * mul, normal[0].v.y * mul }; + if (!emit_point(str, pt[0], offs, FT_CURVE_TAG_ON, skip_dir)) + return false; + } + dir &= ~skip_dir; + if (!dir) { + str->last_normal = normal[1].v; + return true; + } + } + } + check_dir ^= skip_dir; + } else { + if (ds[0] < 0) + flags ^= MASK_INTERSECTION; + if (ds[1] < 0) + flags ^= MASK_INTERSECTION | FLAG_INTERSECTION; + bool parallel = flags & MASK_INTERSECTION; + int badness = parallel ? 0 : 1; + if (c + g0 < 1) { + if (parallel) { + flags ^= MASK_ZERO_0 | FLAG_ZERO_0; + if (c < 0) + flags ^= MASK_CLIP_0; + if (f0 > abs_s) + flags ^= FLAG_ZERO_0 | FLAG_CLIP_0; + } + badness++; + } else { + flags ^= MASK_INTERSECTION | FLAG_INTERSECTION; + if (!parallel) { + flags ^= MASK_ZERO_0; + if (c > 0) + flags ^= MASK_CLIP_0; + } } - outline->contours[c] = p - 1; - c++; + if (c + g1 < 1) { + if (parallel) { + flags ^= MASK_ZERO_1 | FLAG_ZERO_1; + if (c < 0) + flags ^= MASK_CLIP_1; + if (f1 > abs_s) + flags ^= FLAG_ZERO_1 | FLAG_CLIP_1; + } + badness++; + } else { + flags ^= MASK_INTERSECTION; + if (!parallel) { + flags ^= MASK_ZERO_1; + if (c > 0) + flags ^= MASK_CLIP_1; + } + } + if (badness > 2) + check_dir ^= skip_dir; + } + } + + Vector result[2]; + if (check_dir) + check_dir = estimate_cubic_error(str, c, s, dc, ds, + normal, result, flags, check_dir); + if (check_dir) { + if (!emit_first_point(str, pt[0], check_dir)) + return false; + if (!emit_point(str, pt[1], result[0], FT_CURVE_TAG_CUBIC, check_dir) || + !emit_point(str, pt[2], result[1], FT_CURVE_TAG_CUBIC, check_dir)) + return false; + dir &= ~check_dir; + if (!dir) { + str->last_normal = normal[1].v; + return true; } - outline->n_points = p; - outline->n_contours = c; } - free(boxes); - free(valid_cont); + OutlinePoint next[7], center; + next[1].x = pt[0].x + pt[1].x; + next[1].y = pt[0].y + pt[1].y; + center.x = pt[1].x + pt[2].x + 2; + center.y = pt[1].y + pt[2].y + 2; + next[5].x = pt[2].x + pt[3].x; + next[5].y = pt[2].y + pt[3].y; + next[2].x = next[1].x + center.x; + next[2].y = next[1].y + center.y; + next[4].x = center.x + next[5].x; + next[4].y = center.y + next[5].y; + next[3].x = (next[2].x + next[4].x - 1) >> 3; + next[3].y = (next[2].y + next[4].y - 1) >> 3; + next[2].x >>= 2; + next[2].y >>= 2; + next[4].x >>= 2; + next[4].y >>= 2; + next[1].x >>= 1; + next[1].y >>= 1; + next[5].x >>= 1; + next[5].y >>= 1; + next[0] = pt[0]; + next[6] = pt[3]; + + Vector next_deriv[5], center_deriv; + next_deriv[0].x = deriv[0].x / 2; + next_deriv[0].y = deriv[0].y / 2; + center_deriv.x = deriv[1].x / 2; + center_deriv.y = deriv[1].y / 2; + next_deriv[4].x = deriv[2].x / 2; + next_deriv[4].y = deriv[2].y / 2; + next_deriv[1].x = (next_deriv[0].x + center_deriv.x) / 2; + next_deriv[1].y = (next_deriv[0].y + center_deriv.y) / 2; + next_deriv[3].x = (center_deriv.x + next_deriv[4].x) / 2; + next_deriv[3].y = (center_deriv.y + next_deriv[4].y) / 2; + next_deriv[2].x = (next_deriv[1].x + next_deriv[3].x) / 2; + next_deriv[2].y = (next_deriv[1].y + next_deriv[3].y) / 2; + + double len = vec_len(next_deriv[2]); + if (len < str->min_len) { // check degenerate case + Normal next_normal[4]; + next_normal[0].v = normal[0].v; + next_normal[0].len = normal[0].len / 2; + next_normal[3].v = normal[1].v; + next_normal[3].len = normal[1].len / 2; + + next_deriv[1].x += next_deriv[2].x; + next_deriv[1].y += next_deriv[2].y; + next_deriv[3].x += next_deriv[2].x; + next_deriv[3].y += next_deriv[2].y; + next_deriv[2].x = next_deriv[2].y = 0; + + double len1 = vec_len(next_deriv[1]); + if (len1 < str->min_len) { + next_normal[1] = normal[0]; + } else { + double scale = 1 / len1; + next_normal[1].v.x = next_deriv[1].x * scale; + next_normal[1].v.y = next_deriv[1].y * scale; + next_normal[1].len = len1; + } + + double len2 = vec_len(next_deriv[3]); + if (len2 < str->min_len) { + next_normal[2] = normal[1]; + } else { + double scale = 1 / len2; + next_normal[2].v.x = next_deriv[3].x * scale; + next_normal[2].v.y = next_deriv[3].y * scale; + next_normal[2].len = len2; + } + + if (len1 < str->min_len) { + if (!emit_first_point(str, next[0], dir)) + return false; + } else { + if (!process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first)) + return false; + } + if (!start_segment(str, next[2], next_normal[2].v, dir)) + return false; + if (len2 < str->min_len) { + if (!emit_first_point(str, next[3], dir)) + return false; + } else { + if (!process_cubic(str, next + 3, next_deriv + 2, next_normal + 2, dir, false)) + return false; + } + return true; + } + + double scale = 1 / len; + Normal next_normal[3] = { + { normal[0].v, normal[0].len / 2 }, + { { next_deriv[2].x * scale, next_deriv[2].y * scale }, len }, + { normal[1].v, normal[1].len / 2 } + }; + return process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) && + process_cubic(str, next + 3, next_deriv + 2, next_normal + 1, dir, false); +} + +static bool add_cubic(StrokerState *str, const OutlinePoint *pt, int dir) +{ + int flags = 9; + + int32_t dx0 = pt[1].x - pt[0].x; + int32_t dy0 = pt[1].y - pt[0].y; + if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) { + dx0 = pt[2].x - pt[0].x; + dy0 = pt[2].y - pt[0].y; + if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) + return add_line(str, pt[3], dir); + flags ^= 1; + } + + int32_t dx2 = pt[3].x - pt[2].x; + int32_t dy2 = pt[3].y - pt[2].y; + if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) { + dx2 = pt[3].x - pt[1].x; + dy2 = pt[3].y - pt[1].y; + if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) + return add_line(str, pt[3], dir); + flags ^= 4; + } + + if (flags == 12) + return add_line(str, pt[3], dir); + + int32_t dx1 = pt[flags >> 2].x - pt[flags & 3].x; + int32_t dy1 = pt[flags >> 2].y - pt[flags & 3].y; + + Vector deriv[3] = { + { dy0 * str->yscale, -dx0 * str->xscale }, + { dy1 * str->yscale, -dx1 * str->xscale }, + { dy2 * str->yscale, -dx2 * str->xscale } + }; + double len0 = vec_len(deriv[0]), scale0 = 1 / len0; + double len2 = vec_len(deriv[2]), scale2 = 1 / len2; + Normal normal[2] = { + { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 }, + { { deriv[2].x * scale2, deriv[2].y * scale2 }, len2 } + }; + + bool first = str->contour_start; + if (!start_segment(str, pt[0], normal[0].v, dir)) + return false; + if (!process_cubic(str, pt, deriv, normal, dir, first)) + return false; + str->last_point = pt[3]; + return true; +} + + +static bool close_contour(StrokerState *str, int dir) +{ + if (str->contour_start) { + if ((dir & 3) == 3) + dir = 1; + if (!draw_circle(str, str->last_point, dir)) + return false; + } else { + if (!add_line(str, str->first_point, dir)) + return false; + if (!start_segment(str, str->first_point, str->first_normal, dir)) + return false; + if (!emit_point(str, str->first_point, str->first_normal, FT_CURVE_TAG_ON, + ~str->last_skip & dir & str->first_skip)) + return false; + if (str->last_normal.x != str->first_normal.x || + str->last_normal.y != str->first_normal.y) + fix_first_point(str, str->first_point, str->last_normal, + ~str->last_skip & dir & ~str->first_skip); + str->contour_start = true; + } + if ((dir & 1) && !outline_close_contour(str->result[0])) + return false; + if ((dir & 2) && !outline_close_contour(str->result[1])) + return false; + return true; } + +/* + * Stroke an outline glyph in x/y direction. + */ +bool outline_stroke(ASS_Outline *result, ASS_Outline *result1, + const ASS_Outline *path, int xbord, int ybord, int eps) +{ + if (result1) + result1->n_contours = result1->n_points = 0; + + int rad = FFMAX(xbord, ybord); + if (rad < eps) { + assert(result->max_contours >= path->n_contours); + assert(result->max_points >= path->n_points); + memcpy(result->contours, path->contours, sizeof(size_t) * path->n_contours); + memcpy(result->points, path->points, sizeof(FT_Vector) * path->n_points); + memcpy(result->tags, path->tags, path->n_points); + result->n_contours = path->n_contours; + result->n_points = path->n_points; + return true; + } + + result->n_contours = result->n_points = 0; + + int dir = result1 ? 3 : 1; + StrokerState str; + str.result[0] = result; + str.result[1] = result1; + str.xbord = xbord; + str.ybord = ybord; + str.xscale = 1.0 / FFMAX(eps, xbord); + str.yscale = 1.0 / FFMAX(eps, ybord); + str.eps = eps; + + str.contour_start = true; + double rel_err = (double)eps / rad; + str.merge_cos = 1 - rel_err; + double e = sqrt(2 * rel_err); + str.split_cos = 1 + 8 * rel_err - 4 * (1 + rel_err) * e; + str.min_len = rel_err / 4; + str.err_q = 8 * (1 + rel_err) * (1 + rel_err); + str.err_c = 390 * rel_err * rel_err; + str.err_a = e; + + enum Status { + S_ON, S_Q, S_C1, S_C2 + }; + + for (size_t i = 0, j = 0; i < path->n_contours; i++) { + OutlinePoint start, p[4]; + int process_end = 1; + enum Status st; + + int last = path->contours[i]; + if (j > last) + return false; + + if (path->points[j].x < -(1 << 28) || path->points[j].x >= (1 << 28)) + return false; + if (path->points[j].y <= -(1 << 28) || path->points[j].y > (1 << 28)) + return false; + + switch (FT_CURVE_TAG(path->tags[j])) { + case FT_CURVE_TAG_ON: + p[0].x = path->points[j].x; + p[0].y = -path->points[j].y; + start = p[0]; + st = S_ON; + break; + + case FT_CURVE_TAG_CONIC: + switch (FT_CURVE_TAG(path->tags[last])) { + case FT_CURVE_TAG_ON: + p[0].x = path->points[last].x; + p[0].y = -path->points[last].y; + p[1].x = path->points[j].x; + p[1].y = -path->points[j].y; + process_end = 0; + start = p[0]; + st = S_Q; + break; + + case FT_CURVE_TAG_CONIC: + p[1].x = path->points[j].x; + p[1].y = -path->points[j].y; + p[0].x = (p[1].x + path->points[last].x) >> 1; + p[0].y = (p[1].y - path->points[last].y) >> 1; + start = p[0]; + st = S_Q; + break; + + default: + return false; + } + break; + + default: + return false; + } + str.last_point = start; + + for (j++; j <= last; j++) { + if (path->points[j].x < -(1 << 28) || path->points[j].x >= (1 << 28)) + return false; + if (path->points[j].y <= -(1 << 28) || path->points[j].y > (1 << 28)) + return false; + + switch (FT_CURVE_TAG(path->tags[j])) { + case FT_CURVE_TAG_ON: + switch (st) { + case S_ON: + p[1].x = path->points[j].x; + p[1].y = -path->points[j].y; + if (!add_line(&str, p[1], dir)) + return false; + p[0] = p[1]; + break; + + case S_Q: + p[2].x = path->points[j].x; + p[2].y = -path->points[j].y; + if (!add_quadratic(&str, p, dir)) + return false; + p[0] = p[2]; + st = S_ON; + break; + + case S_C2: + p[3].x = path->points[j].x; + p[3].y = -path->points[j].y; + if (!add_cubic(&str, p, dir)) + return false; + p[0] = p[3]; + st = S_ON; + break; + + default: + return false; + } + break; + + case FT_CURVE_TAG_CONIC: + switch (st) { + case S_ON: + p[1].x = path->points[j].x; + p[1].y = -path->points[j].y; + st = S_Q; + break; + + case S_Q: + p[3].x = path->points[j].x; + p[3].y = -path->points[j].y; + p[2].x = (p[1].x + p[3].x) >> 1; + p[2].y = (p[1].y + p[3].y) >> 1; + if (!add_quadratic(&str, p, dir)) + return false; + p[0] = p[2]; + p[1] = p[3]; + break; + + default: + return false; + } + break; + + case FT_CURVE_TAG_CUBIC: + switch (st) { + case S_ON: + p[1].x = path->points[j].x; + p[1].y = -path->points[j].y; + st = S_C1; + break; + + case S_C1: + p[2].x = path->points[j].x; + p[2].y = -path->points[j].y; + st = S_C2; + break; + + default: + return false; + } + break; + + default: + return false; + } + } + + if (process_end) + switch (st) { + case S_ON: + if (!add_line(&str, start, dir)) + return false; + break; + + case S_Q: + p[2] = start; + if (!add_quadratic(&str, p, dir)) + return false; + break; + + case S_C2: + p[3] = start; + if (!add_cubic(&str, p, dir)) + return false; + break; + + default: + return false; + } + if (!close_contour(&str, dir)) + return false; + } + return true; +} diff --git a/libass/ass_outline.h b/libass/ass_outline.h index a03f082..86c6b3e 100644 --- a/libass/ass_outline.h +++ b/libass/ass_outline.h @@ -35,15 +35,20 @@ typedef struct ass_outline { #define EFFICIENT_CONTOUR_COUNT 8 bool outline_alloc(ASS_Outline *outline, size_t n_points, size_t n_contours); +ASS_Outline *outline_create(size_t n_points, size_t n_contours); ASS_Outline *outline_convert(const FT_Outline *source); ASS_Outline *outline_copy(const ASS_Outline *source); void outline_free(ASS_Outline *outline); +bool outline_add_point(ASS_Outline *outline, FT_Vector pt, char tag); +bool outline_close_contour(ASS_Outline *outline); + void outline_translate(const ASS_Outline *outline, FT_Pos dx, FT_Pos dy); void outline_transform(const ASS_Outline *outline, const FT_Matrix *matrix); void outline_get_cbox(const ASS_Outline *outline, FT_BBox *cbox); -void fix_freetype_stroker(ASS_Outline *outline, int border_x, int border_y); +bool outline_stroke(ASS_Outline *result, ASS_Outline *result1, + const ASS_Outline *path, int xbord, int ybord, int eps); #endif /* LIBASS_OUTLINE_H */ diff --git a/libass/ass_parse.c b/libass/ass_parse.c index dc27bf5..80d4bfc 100644 --- a/libass/ass_parse.c +++ b/libass/ass_parse.c @@ -137,41 +137,6 @@ void update_font(ASS_Renderer *render_priv) change_font_size(render_priv, render_priv->state.font_size); } -/** - * \brief Change border width - * - * \param render_priv renderer state object - * \param info glyph state object - */ -void change_border(ASS_Renderer *render_priv, double border_x, double border_y) -{ - int bord = 64 * border_x * render_priv->border_scale; - - if (bord > 0 && border_x == border_y) { - if (!render_priv->state.stroker) { - int error; - error = - FT_Stroker_New(render_priv->ftlibrary, - &render_priv->state.stroker); - if (error) { - ass_msg(render_priv->library, MSGL_V, - "failed to get stroker"); - render_priv->state.stroker = 0; - } - render_priv->state.stroker_radius = -1.0; - } - if (render_priv->state.stroker && render_priv->state.stroker_radius != bord) { - FT_Stroker_Set(render_priv->state.stroker, bord, - FT_STROKER_LINECAP_ROUND, - FT_STROKER_LINEJOIN_ROUND, 0); - render_priv->state.stroker_radius = bord; - } - } else { - FT_Stroker_Done(render_priv->state.stroker); - render_priv->state.stroker = 0; - } -} - /** * \brief Calculate a weighted average of two colors * calculates c1*(1-a) + c2*a, but separately for each component except alpha diff --git a/libass/ass_parse.h b/libass/ass_parse.h index 2b4cee0..947f7ae 100644 --- a/libass/ass_parse.h +++ b/libass/ass_parse.h @@ -28,9 +28,6 @@ void update_font(ASS_Renderer *render_priv); double ensure_font_size(ASS_Renderer *priv, double size); -void calc_border(ASS_Renderer *priv, double border_x, double border_y); -void change_border(ASS_Renderer *render_priv, double border_x, - double border_y); void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event); void process_karaoke_effects(ASS_Renderer *render_priv); unsigned get_next_char(ASS_Renderer *render_priv, char **str); diff --git a/libass/ass_render.c b/libass/ass_render.c index ac73ec7..4379c09 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -133,10 +133,6 @@ void ass_renderer_done(ASS_Renderer *render_priv) rasterizer_done(&render_priv->rasterizer); #endif - if (render_priv->state.stroker) { - FT_Stroker_Done(render_priv->state.stroker); - render_priv->state.stroker = 0; - } if (render_priv->fontselect) ass_fontselect_free(render_priv->fontselect); if (render_priv->ftlibrary) @@ -854,7 +850,6 @@ void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style) render_priv->state.border_style = style->BorderStyle; render_priv->state.border_x = style->Outline; render_priv->state.border_y = style->Outline; - change_border(render_priv, render_priv->state.border_x, render_priv->state.border_y); render_priv->state.scale_x = style->ScaleX; render_priv->state.scale_y = style->ScaleY; render_priv->state.hspacing = style->Spacing; @@ -962,120 +957,6 @@ static void draw_opaque_box(ASS_Renderer *render_priv, GlyphInfo *info, ol->contours[ol->n_contours++] = ol->n_points - 1; } -/* - * Stroke an outline glyph in x/y direction. Applies various fixups to get - * around limitations of the FreeType stroker. - */ -static void stroke_outline(ASS_Renderer *render_priv, ASS_Outline *outline, - int sx, int sy) -{ - if (sx <= 0 && sy <= 0) - return; - - fix_freetype_stroker(outline, sx, sy); - - size_t n_points = outline->n_points; - if (n_points > SHRT_MAX) { - ass_msg(render_priv->library, MSGL_WARN, "Too many outline points: %d", - outline->n_points); - n_points = SHRT_MAX; - } - - size_t n_contours = FFMIN(outline->n_contours, SHRT_MAX); - short contours_small[EFFICIENT_CONTOUR_COUNT]; - short *contours = contours_small; - short *contours_large = NULL; - if (n_contours > EFFICIENT_CONTOUR_COUNT) { - contours_large = malloc(n_contours * sizeof(short)); - if (!contours_large) - return; - contours = contours_large; - } - for (size_t i = 0; i < n_contours; ++i) - contours[i] = FFMIN(outline->contours[i], n_points - 1); - - FT_Outline ftol; - ftol.n_points = n_points; - ftol.n_contours = n_contours; - ftol.points = outline->points; - ftol.tags = outline->tags; - ftol.contours = contours; - ftol.flags = 0; - - // Borders are equal; use the regular stroker - if (sx == sy && render_priv->state.stroker) { - int error; - FT_StrokerBorder border = FT_Outline_GetOutsideBorder(&ftol); - error = FT_Stroker_ParseOutline(render_priv->state.stroker, &ftol, 0); - if (error) { - ass_msg(render_priv->library, MSGL_WARN, - "FT_Stroker_ParseOutline failed, error: %d", error); - } - unsigned new_points, new_contours; - error = FT_Stroker_GetBorderCounts(render_priv->state.stroker, border, - &new_points, &new_contours); - if (error) { - ass_msg(render_priv->library, MSGL_WARN, - "FT_Stroker_GetBorderCounts failed, error: %d", error); - } - outline_free(outline); - outline->n_points = outline->n_contours = 0; - if (new_contours > FFMAX(EFFICIENT_CONTOUR_COUNT, n_contours)) { - if (!ASS_REALLOC_ARRAY(contours_large, new_contours)) { - free(contours_large); - return; - } - } - n_points = new_points; - n_contours = new_contours; - if (!outline_alloc(outline, n_points, n_contours)) { - ass_msg(render_priv->library, MSGL_WARN, - "Not enough memory for border outline"); - free(contours_large); - return; - } - ftol.n_points = ftol.n_contours = 0; - ftol.points = outline->points; - ftol.tags = outline->tags; - - FT_Stroker_ExportBorder(render_priv->state.stroker, border, &ftol); - - outline->n_points = n_points; - outline->n_contours = n_contours; - for (size_t i = 0; i < n_contours; ++i) - outline->contours[i] = (unsigned short) contours[i]; - - // "Stroke" with the outline emboldener (in two passes if needed). - // The outlines look uglier, but the emboldening never adds any points - } else { -#if (FREETYPE_MAJOR > 2) || \ - ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 4)) || \ - ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 4) && (FREETYPE_PATCH >= 10)) - FT_Outline_EmboldenXY(&ftol, sx * 2, sy * 2); - FT_Outline_Translate(&ftol, -sx, -sy); -#else - int i; - FT_Outline nol; - - FT_Outline_New(render_priv->ftlibrary, ftol.n_points, - ftol.n_contours, &nol); - FT_Outline_Copy(&ftol, &nol); - - FT_Outline_Embolden(&ftol, sx * 2); - FT_Outline_Translate(&ftol, -sx, -sx); - FT_Outline_Embolden(&nol, sy * 2); - FT_Outline_Translate(&nol, -sy, -sy); - - for (i = 0; i < ftol.n_points; i++) - ftol.points[i].y = nol.points[i].y; - - FT_Outline_Done(render_priv->ftlibrary, &nol); -#endif - } - - free(contours_large); -} - /** * \brief Prepare glyph hash */ @@ -1133,7 +1014,7 @@ static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info) * \brief Get normal and outline (border) glyphs * \param info out: struct filled with extracted data * Tries to get both glyphs from cache. - * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker, + * If they can't be found, gets a glyph from font face, generates outline, * and add them to cache. * The glyphs are returned in info->glyph and info->outline_glyph */ @@ -1218,11 +1099,16 @@ get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info) } else if ((info->border_x > 0 || info->border_y > 0) && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) { - change_border(priv, info->border_x, info->border_y); - val->border = outline_copy(val->outline); - stroke_outline(priv, val->border, + val->border = outline_create(2 * val->outline->n_points, + val->outline->n_contours); + if (val->border && !outline_stroke(val->border, NULL, val->outline, double_to_d6(info->border_x * priv->border_scale), - double_to_d6(info->border_y * priv->border_scale)); + double_to_d6(info->border_y * priv->border_scale), 16)) { + ass_msg(priv->library, MSGL_WARN, "Cannot stoke outline"); + outline_free(val->border); + free(val->border); + val->border = NULL; + } } ass_cache_commit(val, 1); diff --git a/libass/ass_render.h b/libass/ass_render.h index 65422e3..e8004e9 100644 --- a/libass/ass_render.h +++ b/libass/ass_render.h @@ -23,7 +23,6 @@ #include #include #include FT_FREETYPE_H -#include FT_STROKER_H #include FT_GLYPH_H #include FT_SYNTHESIS_H #ifdef CONFIG_HARFBUZZ @@ -218,8 +217,6 @@ typedef struct { double font_size; int flags; // decoration flags (underline/strike-through) - FT_Stroker stroker; - int stroker_radius; // last stroker radius, for caching stroker objects int alignment; // alignment overrides go here; if zero, style value will be used int justify; // justify instructions double frx, fry, frz; -- cgit v1.2.3