Mercurial > mplayer.hg
changeset 19638:a3473d990fed
Better collision detection algorithm. The idea is to keep a subtitle in place
when a lower placed one disappears, thus improving readability.
As a side effect, layers are supported now.
author | eugeni |
---|---|
date | Sat, 02 Sep 2006 19:17:32 +0000 |
parents | b79f65f98553 |
children | bbe600db7b83 |
files | libass/ass.c libass/ass.h libass/ass_render.c libass/ass_types.h |
diffstat | 4 files changed, 269 insertions(+), 93 deletions(-) [+] |
line wrap: on
line diff
--- a/libass/ass.c Sat Sep 02 19:00:30 2006 +0000 +++ b/libass/ass.c Sat Sep 02 19:17:32 2006 +0000 @@ -109,6 +109,8 @@ free(event->Effect); if (event->Text) free(event->Text); + if (event->render_priv) + free(event->render_priv); } void ass_free_style(ass_track_t* track, int sid) {
--- a/libass/ass.h Sat Sep 02 19:00:30 2006 +0000 +++ b/libass/ass.h Sat Sep 02 19:17:32 2006 +0000 @@ -52,35 +52,10 @@ void ass_configure(ass_instance_t* priv, const ass_settings_t* config); /** - * \brief start rendering a frame - * \param priv library - * \param track subtitle track - * \param now video timestamp in milliseconds - */ -int ass_start_frame(ass_instance_t *priv, ass_track_t* track, long long now); - -/** - * \brief render a single event - * uses library, track and timestamp from the previous call to ass_start_frame - */ -int ass_render_event(ass_event_t* event); - -/** - * \brief done rendering frame, give out the results - * \return a list of images for blending - */ -ass_image_t* ass_end_frame(void); // returns linked list of images to render - -/** * \brief render a frame, producing a list of ass_image_t * \param priv library * \param track subtitle track * \param now video timestamp in milliseconds - * This function is equivalent to - * ass_start_frame() - * for events: start <= now < end: - * ass_render_event() - * ass_end_frame() */ ass_image_t* ass_render_frame(ass_instance_t *priv, ass_track_t* track, long long now);
--- a/libass/ass_render.c Sat Sep 02 19:00:30 2006 +0000 +++ b/libass/ass_render.c Sat Sep 02 19:17:32 2006 +0000 @@ -30,14 +30,15 @@ static int font_fontconfig = 0; #endif +static int last_render_id = 0; + struct ass_instance_s { FT_Library library; fc_instance_t* fontconfig_priv; ass_settings_t settings; + int render_id; ass_image_t* images_root; // rendering result is stored here - int n_images; - int max_images; }; int no_more_font_messages = 0; // don't print font warnings @@ -128,8 +129,6 @@ int orig_height; // frame height ( = screen height - margins ) int orig_width; // frame width ( = screen width - margins ) ass_track_t* track; - int add_bottom_margin; // additional margin, used to shift subtitles in case of collision - int add_top_margin; long long time; // frame's timestamp, ms double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio } frame_context_t; @@ -140,6 +139,20 @@ static render_context_t render_context; static frame_context_t frame_context; +// a rendered event +typedef struct event_images_s { + ass_image_t* imgs; + int top, height; + int detect_collisions; + int shift_direction; + ass_event_t* event; +} event_images_t; + +struct render_priv_s { + int top, height; + int render_id; +}; + static void ass_lazy_track_init(void) { ass_track_t* track = frame_context.track; @@ -231,23 +244,13 @@ } /** - * \brief Create a new ass_image_t and append it to images_root + * \brief Create a new ass_image_t * Parameters are the same as ass_image_t fields. */ -static void my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color) +static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color) { - ass_instance_t* priv = ass_instance; - ass_image_t* img; + ass_image_t* img = calloc(1, sizeof(ass_image_t)); - assert(priv->n_images <= priv->max_images); - if (priv->n_images == priv->max_images) { - if (!priv->max_images) priv->max_images = 100; - else priv->max_images *= 2; - priv->images_root = (ass_image_t*)realloc(priv->images_root, priv->max_images * sizeof(ass_image_t)); - } - assert(priv->images_root); - img = priv->images_root + priv->n_images; - img->w = bitmap_w; img->h = bitmap_h; img->stride = stride; @@ -255,8 +258,8 @@ img->color = color; img->dst_x = dst_x; img->dst_y = dst_y; - - priv->n_images++; + + return img; } /** @@ -267,9 +270,11 @@ * \param color first color, RGBA * \param color2 second color, RGBA * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right + * \param tail pointer to the last image's next field, head of the generated list should be stored here + * \return pointer to the new list tail * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion. */ -static int render_glyph(FT_BitmapGlyph bit, int dst_x, int dst_y, uint32_t color, uint32_t color2, int brk) +static ass_image_t** render_glyph(FT_BitmapGlyph bit, int dst_x, int dst_y, uint32_t color, uint32_t color2, int brk, ass_image_t** tail) { // brk is relative to dst_x // color = color left of brk @@ -278,6 +283,7 @@ int clip_x0, clip_y0, clip_x1, clip_y1; int tmp; FT_Bitmap* bitmap; + ass_image_t* img; bitmap = &(bit->bitmap); dst_x += bit->left; @@ -286,7 +292,7 @@ if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY) { mp_msg(MSGT_GLOBAL, MSGL_WARN, "Unsupported pixel mode: %d\n", (int)(bitmap->pixel_mode)); - return 1; + return tail; } // clipping @@ -321,29 +327,32 @@ } if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) - return 0; + return tail; if (brk > b_x0) { // draw left part if (brk > b_x1) brk = b_x1; - my_draw_bitmap(bitmap->buffer + bitmap->pitch * b_y0 + b_x0, + img = my_draw_bitmap(bitmap->buffer + bitmap->pitch * b_y0 + b_x0, brk - b_x0, b_y1 - b_y0, bitmap->pitch, dst_x + b_x0, dst_y + b_y0, color); + *tail = img; + tail = &img->next; } if (brk < b_x1) { // draw right part if (brk < b_x0) brk = b_x0; - my_draw_bitmap(bitmap->buffer + bitmap->pitch * b_y0 + brk, + img = my_draw_bitmap(bitmap->buffer + bitmap->pitch * b_y0 + brk, b_x1 - brk, b_y1 - b_y0, bitmap->pitch, dst_x + brk, dst_y + b_y0, color2); - + *tail = img; + tail = &img->next; } - return 0; + return tail; } /** * \brief Render text_info_t struct into ass_images_t list * Rasterize glyphs and put them in glyph cache. */ -static int render_text(text_info_t* text_info, int dst_x, int dst_y) +static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y) { int pen_x, pen_y; int error, error2; @@ -351,6 +360,8 @@ FT_Glyph image; FT_BitmapGlyph bit; glyph_hash_val_t hash_val; + ass_image_t* head; + ass_image_t** tail = &head; for (i = 0; i < text_info->length; ++i) { if (text_info->glyphs[i].bitmap != 1) { @@ -391,7 +402,7 @@ if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) { // do nothing } else - render_glyph(bit, pen_x, pen_y, info->c3, 0, 1000000); + tail = render_glyph(bit, pen_x, pen_y, info->c3, 0, 1000000, tail); } for (i = 0; i < text_info->length; ++i) { glyph_info_t* info = text_info->glyphs + i; @@ -405,16 +416,17 @@ if ((info->effect_type == EF_KARAOKE) || (info->effect_type == EF_KARAOKE_KO)) { if (info->effect_timing > info->bbox.xMax) - render_glyph(bit, pen_x, pen_y, info->c1, 0, 1000000); + tail = render_glyph(bit, pen_x, pen_y, info->c1, 0, 1000000, tail); else - render_glyph(bit, pen_x, pen_y, info->c2, 0, 1000000); + tail = render_glyph(bit, pen_x, pen_y, info->c2, 0, 1000000, tail); } else if (info->effect_type == EF_KARAOKE_KF) { - render_glyph(bit, pen_x, pen_y, info->c1, info->c2, info->effect_timing); + tail = render_glyph(bit, pen_x, pen_y, info->c1, info->c2, info->effect_timing, tail); } else - render_glyph(bit, pen_x, pen_y, info->c1, 0, 1000000); + tail = render_glyph(bit, pen_x, pen_y, info->c1, 0, 1000000, tail); } - return 0; + *tail = 0; + return head; } /** @@ -1092,6 +1104,7 @@ render_context.clip_y0 = y0; render_context.clip_y1 = y1; render_context.evt_type = EVENT_VSCROLL; + render_context.detect_collisions = 0; } } @@ -1482,7 +1495,7 @@ * \param event event to render * Process event, appending resulting ass_image_t's to images_root. */ -int ass_render_event(ass_event_t* event) +static int ass_render_event(ass_event_t* event, event_images_t* event_images) { char* p; FT_UInt glyph_index; @@ -1514,7 +1527,7 @@ p = event->Text; if (!p) { mp_msg(MSGT_GLOBAL, MSGL_WARN, "Empty event!\n"); - return 0; + return 1; } // Event parsing. @@ -1528,7 +1541,7 @@ // face could have been changed in get_next_char if (!render_context.face) { free_render_context(); - return 0; + return 1; } if (code == 0) @@ -1605,7 +1618,7 @@ if (text_info.length == 0) { // no valid symbols in the event; this can be smth like {comment} free_render_context(); - return 0; + return 1; } // depends on glyph x coordinates being monotonous, so it should be done before line wrap @@ -1677,10 +1690,6 @@ render_context.evt_type == EVENT_HSCROLL) { if (valign == VALIGN_TOP) { // toptitle device_y = y2scr_top(MarginV) + (text_info.lines[0].asc >> 6); - if (render_context.detect_collisions) { - device_y += frame_context.add_top_margin; - frame_context.add_top_margin += (text_info.height >> 6); - } } else if (valign == VALIGN_CENTER) { // midtitle int scr_y = y2scr(frame_context.track->PlayResY / 2); device_y = scr_y - (bbox.yMax - bbox.yMin) / 2; @@ -1692,10 +1701,6 @@ device_y = scr_y; device_y -= (text_info.height >> 6); device_y += (text_info.lines[0].asc >> 6); - if (render_context.detect_collisions) { - device_y -= frame_context.add_bottom_margin; - frame_context.add_bottom_margin += (text_info.height >> 6); - } } } else if (render_context.evt_type == EVENT_VSCROLL) { if (render_context.scroll_direction == SCROLL_TB) @@ -1813,8 +1818,12 @@ } } - // render - render_text(&text_info, device_x, device_y); + event_images->top = device_y - (text_info.lines[0].asc >> 6); + event_images->height = text_info.height >> 6; + event_images->detect_collisions = render_context.detect_collisions; + event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; + event_images->event = event; + event_images->imgs = render_text(&text_info, device_x, device_y); free_render_context(); @@ -1827,6 +1836,8 @@ mp_msg(MSGT_GLOBAL, MSGL_V, "ass_configure: %d x %d; margins: l: %d, r: %d, t: %d, b: %d \n", config->frame_width, config->frame_height, config->left_margin, config->right_margin, config->top_margin, config->bottom_margin); + + priv->render_id = ++last_render_id; memcpy(&priv->settings, config, sizeof(ass_settings_t)); ass_glyph_cache_reset(); } @@ -1835,8 +1846,10 @@ /** * \brief Start a new frame */ -int ass_start_frame(ass_instance_t *priv, ass_track_t* track, long long now) +static int ass_start_frame(ass_instance_t *priv, ass_track_t* track, long long now) { + ass_image_t* img; + ass_instance = priv; global_settings = &priv->settings; @@ -1849,8 +1862,6 @@ frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin; frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin; frame_context.track = track; - frame_context.add_bottom_margin = 0; - frame_context.add_top_margin = 0; frame_context.time = now; ass_lazy_track_init(); @@ -1860,28 +1871,175 @@ else frame_context.font_scale_x = ((double)(frame_context.orig_width * track->PlayResY)) / (frame_context.orig_height * track->PlayResX); - priv->n_images = 0; + img = priv->images_root; + while (img) { + ass_image_t* next = img->next; + free(img); + img = next; + } + priv->images_root = 0; return 0; } -/** - * \brief End a frame, give out rendering results - * \return list of ass_image_t - */ -ass_image_t* ass_end_frame(void) +static ass_image_t** find_list_tail(ass_image_t** phead) +{ + ass_image_t* img = *phead; + if (!img) + return phead; + while (img->next) + img = img->next; + return &img->next; +} + +static int cmp_event_layer(const void* p1, const void* p2) +{ + ass_event_t* e1 = ((event_images_t*)p1)->event; + ass_event_t* e2 = ((event_images_t*)p2)->event; + if (e1->Layer < e2->Layer) + return -1; + if (e1->Layer > e2->Layer) + return 1; + if (e1->Start < e2->Start) + return -1; + if (e1->Start > e2->Start) + return 1; + return 0; +} + +#define MAX_EVENTS 100 + +static render_priv_t* get_render_priv(ass_event_t* event) { - ass_instance_t* priv = ass_instance; - if (priv->n_images) { - int i; - for (i = 0; i < priv->n_images - 1; ++i) - priv->images_root[i].next = priv->images_root + i + 1; - priv->images_root[priv->n_images - 1].next = 0; - return priv->images_root; - } else { + if (!event->render_priv) + event->render_priv = calloc(1, sizeof(render_priv_t)); + // FIXME: check render_id + if (ass_instance->render_id != event->render_priv->render_id) { + memset(event->render_priv, 0, sizeof(render_priv_t)); + event->render_priv->render_id = ass_instance->render_id; + } + return event->render_priv; +} + +typedef struct segment_s { + int a, b; // top and height +} segment_t; + +static int overlap(segment_t* s1, segment_t* s2) +{ + if (s1->a >= s2->b || s2->a >= s1->b) + return 0; + return 1; +} + +static int cmp_segment(const void* p1, const void* p2) +{ + return ((segment_t*)p1)->a - ((segment_t*)p1)->b; +} + +static void shift_event(event_images_t* ei, int shift) +{ + ass_image_t* cur = ei->imgs; + while (cur) { + cur->dst_y += shift; + cur = cur->next; + } + ei->top += shift; +} + +// dir: 1 - move down +// -1 - move up +static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir) +{ + int i; + int shift; + + if (*cnt == 0) { + *cnt = 1; + fixed[0].a = s->a; + fixed[0].b = s->b; return 0; } + + if (dir == 1) { // move down + if (s->b <= fixed[0].a) // all ok + return 0; + for (i = 0; i < *cnt; ++i) { + shift = fixed[i].b - s->a; + if (i == *cnt - 1 || fixed[i+1].a >= shift + s->b) { // here is a good place + fixed[i].b += s->b - s->a; + return shift; + } + } + } else { // dir == -1, move up + if (s->a >= fixed[*cnt-1].b) // all ok + return 0; + for (i = *cnt-1; i >= 0; --i) { + shift = fixed[i].a - s->b; + if (i == 0 || fixed[i-1].b <= shift + s->a) { // here is a good place + fixed[i].a -= s->b - s->a; + return shift; + } + } + } + assert(0); // unreachable } + +static void fix_collisions(event_images_t* imgs, int cnt) +{ + segment_t used[MAX_EVENTS]; + int cnt_used = 0; + int i, j; + + // fill used[] with fixed events + for (i = 0; i < cnt; ++i) { + render_priv_t* priv; + if (!imgs[i].detect_collisions) break; + priv = get_render_priv(imgs[i].event); + if (priv->height > 0) { // it's a fixed event + segment_t s; + s.a = priv->top; + s.b = priv->top + priv->height; + if (priv->height != imgs[i].height) { // no, it's not + mp_msg(MSGT_GLOBAL, MSGL_WARN, "Achtung! Event height has changed! \n"); + priv->top = 0; + priv->height = 0; + } + for (j = 0; j < cnt_used; ++j) + if (overlap(&s, used + j)) { // no, it's not + priv->top = 0; + priv->height = 0; + } + if (priv->height > 0) { // still a fixed event + used[cnt_used].a = priv->top; + used[cnt_used].b = priv->top + priv->height; + cnt_used ++; + shift_event(imgs + i, priv->top - imgs[i].top); + } + } + } + qsort(used, cnt_used, sizeof(segment_t), cmp_segment); + + // try to fit other events in free spaces + for (i = 0; i < cnt; ++i) { + render_priv_t* priv; + if (!imgs[i].detect_collisions) break; + priv = get_render_priv(imgs[i].event); + if (priv->height == 0) { // not a fixed event + int shift; + segment_t s; + s.a = imgs[i].top; + s.b = imgs[i].top + imgs[i].height; + shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); + if (shift) shift_event(imgs + i, shift); + // make it fixed + priv->top = imgs[i].top; + priv->height = imgs[i].height; + } + + } +} + /** * \brief render a frame * \param priv library handle @@ -1890,17 +2048,54 @@ */ ass_image_t* ass_render_frame(ass_instance_t *priv, ass_track_t* track, long long now) { - int i, rc; + int i, cnt, rc; + event_images_t eimg[MAX_EVENTS]; + event_images_t* last; + ass_image_t* head = 0; + ass_image_t** tail = &head; + // init frame rc = ass_start_frame(priv, track, now); - if (rc != 0) // some error + if (rc != 0) return 0; + + // render events separately + cnt = 0; for (i = 0; i < track->n_events; ++i) { ass_event_t* event = track->events + i; if ( (event->Start <= now) && (now < (event->Start + event->Duration)) ) { - ass_render_event(event); + if (cnt < MAX_EVENTS) { + rc = ass_render_event(event, eimg + cnt); + if (!rc) ++cnt; + } else { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "Too many simultaneous events \n"); + break; + } } } - return ass_end_frame(); + + // sort by layer + qsort(eimg, cnt, sizeof(event_images_t), cmp_event_layer); + + // call fix_collisions for each group of events with the same layer + last = eimg; + for (i = 1; i < cnt; ++i) + if (last->event->Layer != eimg[i].event->Layer) { + fix_collisions(last, eimg + i - last); + last = eimg + i; + } + if (cnt > 0) + fix_collisions(last, eimg + cnt - last); + + // concat lists + head = cnt ? eimg[0].imgs : 0; + tail = find_list_tail(&head); + for (i = 1; i < cnt; ++i) { + *tail = eimg[i].imgs; + tail = find_list_tail(&eimg[i].imgs); + } + + ass_instance->images_root = head; + return ass_instance->images_root; }
--- a/libass/ass_types.h Sat Sep 02 19:00:30 2006 +0000 +++ b/libass/ass_types.h Sat Sep 02 19:17:32 2006 +0000 @@ -36,6 +36,8 @@ int Encoding; } ass_style_t; +typedef struct render_priv_s render_priv_t; + /// ass_event_t corresponds to a single Dialogue line /// Text is stored as-is, style overrides will be parsed later typedef struct ass_event_s { @@ -51,6 +53,8 @@ int MarginV; char* Effect; char* Text; + + render_priv_t* render_priv; } ass_event_t; typedef struct parser_priv_s parser_priv_t;