]> granicus.if.org Git - libass/commitdiff
Better collision detection algorithm. The idea is to keep a subtitle in place
authoreugeni <eugeni@b3059339-0415-0410-9bf9-f77b7e298cf2>
Sat, 2 Sep 2006 19:17:32 +0000 (19:17 +0000)
committereugeni <eugeni@b3059339-0415-0410-9bf9-f77b7e298cf2>
Sat, 2 Sep 2006 19:17:32 +0000 (19:17 +0000)
when a lower placed one disappears, thus improving readability.
As a side effect, layers are supported now.

git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@19644 b3059339-0415-0410-9bf9-f77b7e298cf2

libass/ass.c
libass/ass.h
libass/ass_render.c
libass/ass_types.h

index c75fbf0e89932741f6db6001e5b6125a3320c66e..fcc205dec0e6e23d56f587ace896ca89197e0d32 100644 (file)
@@ -109,6 +109,8 @@ void ass_free_event(ass_track_t* track, int eid) {
                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) {
index e97b0f6d6c073b75c8319e7547da89a59f090ff1..a99b7540a6308853bfbb7a835f8cc408f8400ef5 100644 (file)
@@ -51,36 +51,11 @@ void ass_done(ass_instance_t* priv);
  */
 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);
 
index 53ef9b222da634501f37c803419f913e9e5e2b98..088db2eda5d890e9bd8f82e5607c911defaa8847 100644 (file)
@@ -30,14 +30,15 @@ extern int font_fontconfig;
 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 @@ typedef struct frame_context_s {
        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 text_info_t text_info;
 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 @@ void ass_done(ass_instance_t* priv)
 }
 
 /**
- * \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 @@ static void my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, in
        img->color = color;
        img->dst_x = dst_x;
        img->dst_y = dst_y;
-       
-       priv->n_images++;
+
+       return img;
 }
 
 /**
@@ -267,9 +270,11 @@ static void my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, in
  * \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 @@ static int render_glyph(FT_BitmapGlyph bit, int dst_x, int dst_y, uint32_t color
        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 @@ static int render_glyph(FT_BitmapGlyph bit, int dst_x, int dst_y, uint32_t color
        
        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 @@ static int render_glyph(FT_BitmapGlyph bit, int dst_x, int dst_y, uint32_t color
        }
        
        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 @@ static int render_text(text_info_t* text_info, int dst_x, int dst_y)
        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 @@ static int render_text(text_info_t* text_info, int dst_x, int dst_y)
                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 @@ static int render_text(text_info_t* text_info, int dst_x, int dst_y)
 
                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 @@ static void apply_transition_effects(ass_event_t* event)
                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 @@ static int get_face_descender(FT_Face face)
  * \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 @@ int ass_render_event(ass_event_t* event)
        p = event->Text;
        if (!p) {
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "Empty event!\n");
-               return 0;
+               return 1;
        }
 
        // Event parsing.
@@ -1528,7 +1541,7 @@ int ass_render_event(ass_event_t* event)
                // 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 @@ int ass_render_event(ass_event_t* event)
        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 @@ int ass_render_event(ass_event_t* event)
            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 @@ int ass_render_event(ass_event_t* event)
                        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 @@ int ass_render_event(ass_event_t* event)
                }
        }
 
-       // 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 @@ void ass_configure(ass_instance_t* priv, const ass_settings_t* config)
                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 @@ void ass_configure(ass_instance_t* priv, const ass_settings_t* config)
 /**
  * \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 @@ int ass_start_frame(ass_instance_t *priv, ass_track_t* track, long long now)
        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 @@ int ass_start_frame(ass_instance_t *priv, ass_track_t* track, long long now)
        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_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 {
+       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)
+{
+       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_end_frame(void)
  */
 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;
 }
 
index 1743bfec108b0b9e280d3b54464502ca008d35d6..5ec32a01693c5a74b8ddc6651a89111c3c840ece 100644 (file)
@@ -36,6 +36,8 @@ typedef struct ass_style_s {
        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 @@ typedef struct ass_event_s {
        int MarginV;
        char* Effect;
        char* Text;
+
+       render_priv_t* render_priv;
 } ass_event_t;
 
 typedef struct parser_priv_s parser_priv_t;