diff options
-rw-r--r-- | libass/ass_drawing.c | 19 | ||||
-rw-r--r-- | libass/ass_drawing.h | 2 | ||||
-rw-r--r-- | libass/ass_render.c | 226 |
3 files changed, 224 insertions, 23 deletions
diff --git a/libass/ass_drawing.c b/libass/ass_drawing.c index f75bbd9..091ec87 100644 --- a/libass/ass_drawing.c +++ b/libass/ass_drawing.c @@ -106,7 +106,7 @@ static void drawing_prepare(ass_drawing_t *drawing) * \brief Finish a drawing. This only sets the horizontal advance according * to the glyph's bbox at the moment. */ -static void drawing_finish(ass_drawing_t *drawing) +static void drawing_finish(ass_drawing_t *drawing, int raw_mode) { int i, offset; FT_BBox bbox; @@ -127,6 +127,13 @@ static void drawing_finish(ass_drawing_t *drawing) printf("contour %d\n", ol->contours[i]); #endif + ass_msg(drawing->library, MSGL_V, + "Parsed drawing with %d points and %d contours", ol->n_points, + ol->n_contours); + + if (raw_mode) + return; + FT_Outline_Get_CBox(&drawing->glyph->outline, &bbox); drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin); @@ -138,10 +145,6 @@ static void drawing_finish(ass_drawing_t *drawing) drawing->scale_y); for (i = 0; i < ol->n_points; i++) ol->points[i].y += offset; - - ass_msg(drawing->library, MSGL_V, - "Parsed drawing with %d points and %d contours", ol->n_points, - ol->n_contours); } /* @@ -361,7 +364,7 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, ass_drawing_t* drawing; drawing = calloc(1, sizeof(*drawing)); - drawing->text = malloc(DRAWING_INITIAL_SIZE); + drawing->text = calloc(1, DRAWING_INITIAL_SIZE); drawing->size = DRAWING_INITIAL_SIZE; drawing->ftlibrary = lib; @@ -411,7 +414,7 @@ void ass_drawing_hash(ass_drawing_t* drawing) /* * \brief Convert token list to outline. Calls the line and curve evaluators. */ -FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing) +FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing, int raw_mode) { int started = 0; ass_drawing_token_t *token; @@ -474,7 +477,7 @@ FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing) } } - drawing_finish(drawing); + drawing_finish(drawing, raw_mode); drawing_free_tokens(drawing->tokens); return &drawing->glyph; } diff --git a/libass/ass_drawing.h b/libass/ass_drawing.h index dfd68f0..25cc4a7 100644 --- a/libass/ass_drawing.h +++ b/libass/ass_drawing.h @@ -72,6 +72,6 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, void ass_drawing_free(ass_drawing_t* drawing); void ass_drawing_add_char(ass_drawing_t* drawing, char symbol); void ass_drawing_hash(ass_drawing_t* drawing); -FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing); +FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing, int raw_mode); #endif /* LIBASS_DRAWING_H */ diff --git a/libass/ass_render.c b/libass/ass_render.c index ae65119..9e96345 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -59,6 +59,11 @@ typedef struct double_vector_s { double y; } double_vector_t; +typedef struct free_list_s { + void *object; + struct free_list_s *next; +} free_list_t; + typedef struct ass_settings_s { int frame_width; int frame_height; @@ -174,6 +179,8 @@ typedef struct render_context_s { double shadow_y; int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags ass_drawing_t *drawing; // current drawing + ass_drawing_t *clip_drawing;// clip vector + int clip_drawing_mode; // 0 = regular clip, 1 = inverse clip effect_t effect_type; int effect_timing; @@ -230,6 +237,9 @@ struct ass_renderer_s { render_context_t state; text_info_t text_info; cache_store_t cache; + + free_list_t *free_head; + free_list_t *free_tail; }; struct render_priv_s { @@ -663,6 +673,126 @@ render_overlap(ass_renderer_t *render_priv, ass_image_t **last_tail, cache_add_composite(render_priv->cache.composite_cache, &hk, &chv); } +static void free_list_add(ass_renderer_t *render_priv, void *object) +{ + if (!render_priv->free_head) { + render_priv->free_head = calloc(1, sizeof(free_list_t)); + render_priv->free_head->object = object; + render_priv->free_tail = render_priv->free_head; + } else { + free_list_t *l = calloc(1, sizeof(free_list_t)); + l->object = object; + render_priv->free_tail->next = l; + render_priv->free_tail = render_priv->free_tail->next; + } +} + +/** + * Iterate through a list of bitmaps and blend with clip vector, if + * applicable. The blended bitmaps are added to a free list which is freed + * at the start of a new frame. + */ +static void blend_vector_clip(ass_renderer_t *render_priv, + ass_image_t *head) +{ + FT_Glyph glyph; + FT_BitmapGlyph clip_bm; + ass_image_t *cur; + ass_drawing_t *drawing = render_priv->state.clip_drawing; + + if (!drawing) + return; + + // Rasterize it + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph); + FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); + clip_bm = (FT_BitmapGlyph) glyph; + clip_bm->top = -clip_bm->top; + + assert(clip_bm->bitmap.pitch >= 0); + + // Iterate through bitmaps and blend/clip them + for (cur = head; cur; cur = cur->next) { + int left, top, right, bottom, apos, bpos, y, x, w, h; + int ax, ay, aw, ah, as; + int bx, by, bw, bh, bs; + int aleft, atop, bleft, btop; + unsigned char *abuffer, *bbuffer, *nbuffer; + + abuffer = cur->bitmap; + bbuffer = clip_bm->bitmap.buffer; + ax = cur->dst_x; + ay = cur->dst_y; + aw = cur->w; + ah = cur->h; + as = cur->stride; + bx = clip_bm->left; + by = clip_bm->top; + bw = clip_bm->bitmap.width; + bh = clip_bm->bitmap.rows; + bs = clip_bm->bitmap.pitch; + + // Calculate overlap coordinates + left = (ax > bx) ? ax : bx; + top = (ay > by) ? ay : by; + right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); + bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); + aleft = left - ax; + atop = top - ay; + w = right - left; + h = bottom - top; + bleft = left - bx; + btop = top - by; + + if (render_priv->state.clip_drawing_mode) { + // Inverse clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + continue; + } + + // Allocate new buffer and add to free list + nbuffer = malloc(as * ah); + free_list_add(render_priv, nbuffer); + + // Blend together + memcpy(nbuffer, abuffer, as * ah); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]); + } + } else { + // Regular clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + cur->w = cur->h = 0; + continue; + } + + // Allocate new buffer and add to free list + nbuffer = calloc(as, ah); + free_list_add(render_priv, nbuffer); + + // Blend together + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8; + } + } + cur->bitmap = nbuffer; + } + + // Free clip vector and its bitmap, we don't need it anymore + FT_Done_Glyph((FT_Glyph) drawing->glyph); + FT_Done_Glyph(glyph); + ass_drawing_free(render_priv->state.clip_drawing); + render_priv->state.clip_drawing = 0; +} + /** * \brief Convert text_info_t struct to ass_image_t list * Splits glyphs in halves when needed (for \kf karaoke). @@ -760,6 +890,8 @@ static ass_image_t *render_text(ass_renderer_t *render_priv, int dst_x, } *tail = 0; + blend_vector_clip(render_priv, head); + return head; } @@ -1018,6 +1150,53 @@ interpolate_alpha(long long now, return a; } +#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} +#define skip(x) if (*p == (x)) ++p; else { return p; } +#define skipopt(x) if (*p == (x)) { ++p; } + +/** + * Parse a vector clip into an outline, using the proper scaling + * parameters. Translate it to correct for screen borders, if needed. + */ +static char *parse_vector_clip(ass_renderer_t *render_priv, char *p) +{ + int scale = 1; + int res = 0; + ass_drawing_t *drawing; + render_priv->state.clip_drawing = ass_drawing_new( + render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); + drawing = render_priv->state.clip_drawing; + skipopt('('); + res = mystrtoi(&p, &scale); + skipopt(',') + if (!res) + scale = 1; + drawing->scale = scale; + drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale; + drawing->scale_y = render_priv->font_scale; + while (*p != ')' && *p != '}' && p != 0) + ass_drawing_add_char(drawing, *p++); + skipopt(')'); + ass_drawing_parse(drawing, 1); + // We need to translate the clip according to screen borders + if (render_priv->settings.left_margin != 0 || + render_priv->settings.top_margin != 0) { + FT_Vector trans = { + .x = int_to_d6(render_priv->settings.left_margin), + .y = -int_to_d6(render_priv->settings.top_margin), + }; + FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y); + } + ass_msg(render_priv->library, MSGL_DBG2, + "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n", + scale, drawing->scale_x, drawing->scale_y, drawing->text); + + return p; +} + static void reset_render_context(ass_renderer_t *); /** @@ -1027,9 +1206,6 @@ static void reset_render_context(ass_renderer_t *); */ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) { -#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} -#define skip(x) if (*p == (x)) ++p; else { return p; } - skip_to('\\'); skip('\\'); if ((*p == '}') || (*p == 0)) @@ -1081,15 +1257,16 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) } else if (mystrcmp(&p, "iclip")) { int x0, y0, x1, y1; int res = 1; - skip('('); + char *start = p; + skipopt('('); res &= mystrtoi(&p, &x0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &x1); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y1); - skip(')'); + skipopt(')'); if (res) { render_priv->state.clip_x0 = render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; @@ -1100,6 +1277,9 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) render_priv->state.clip_y1 = render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; render_priv->state.clip_mode = 1; + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 1; } else render_priv->state.clip_mode = 0; } else if (mystrcmp(&p, "blur")) { @@ -1398,17 +1578,18 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) skip_to(')'); // in case there is some unknown tag or a comment skip(')'); } else if (mystrcmp(&p, "clip")) { + char *start = p; int x0, y0, x1, y1; int res = 1; - skip('('); + skipopt('('); res &= mystrtoi(&p, &x0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &x1); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y1); - skip(')'); + skipopt(')'); if (res) { render_priv->state.clip_x0 = render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; @@ -1418,6 +1599,10 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr; render_priv->state.clip_y1 = render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; + // Might be a vector clip + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 0; } else { render_priv->state.clip_x0 = 0; render_priv->state.clip_y0 = 0; @@ -1556,6 +1741,7 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) return p; #undef skip +#undef skipopt #undef skip_to } @@ -1948,7 +2134,7 @@ get_outline_glyph(ass_renderer_t *render_priv, int symbol, } else { glyph_hash_val_t v; if (drawing->hash) { - ass_drawing_parse(drawing); + ass_drawing_parse(drawing, 0); FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph); } else { info->glyph = @@ -3017,6 +3203,18 @@ ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track, if (render_priv->library != track->library) return 1; + // Clear the list of object to be freed. + if (render_priv->free_head) { + free_list_t *item = render_priv->free_head; + while(item) { + free_list_t *oi = item; + free(item->object); + item = item->next; + free(oi); + } + render_priv->free_head = NULL; + } + ass_settings_t *settings_priv = &render_priv->settings; if (!render_priv->settings.frame_width |