osd: draw the OSD bar with ASS vector drawings

Drawing the bar with vector drawings (instead with characters from the
OSD font) offers more flexibility and looks better. This also adds
chapter marks to the OSD bar, which are visible as small triangles on
the top and bottom inner border of the bar.

Change the default position of the OSD bar below the center of the
screen. This is less annoying than putting the bar directly into the
center of the view, where it obscures the video. The new position is
not quite on the bottom of the screen to avoid collisions with
subtitles.

The old centered position can be forced with ``--osd-bar-align-y=0``.

Also make it possible to change the OSD bar width/height with the new
--osd-bar-w and --osd-bar-h options.

It's possible that the new OSD bar renders much slower than the old
one. There are two reasons for this: 1. the character based bar
allowed libass to cache each character, while the vector drawing forces
it to redraw every time the bar position changes. 2., the bar position
is updated at a much higher granularity (the bar position is passed
along as float instead of as integer in the range 0-100, so the bar
will be updated on every single video frame).
This commit is contained in:
wm4 2013-03-30 20:08:56 +01:00
parent d39b131bde
commit ef3c0e6eda
7 changed files with 213 additions and 72 deletions

View File

@ -1315,12 +1315,19 @@
prefixes, see ``Input command prefixes``. If you want to disable the OSD
completely, use ``--osd-level=0``.
--osd-bar-align-x=<-1..1>
--osd-bar-align-x=<-1-1>
Position of the OSD bar. -1 is far left, 0 is centered, 1 is far right.
--osd-bar-align-y=<-1..1>
--osd-bar-align-y=<-1-1>
Position of the OSD bar. -1 is top, 0 is centered, 1 is bottom.
--osd-bar-w=<1-100>
Width of the OSD bar, in percentage of the screen width (default: 75).
A value of 0.5 means the bar is half the screen wide.
--osd-bar-h=<0.1-50>
Height of the OSD bar, in percentage of the screen height (default: 3.125).
--osd-back-color=<#RRGGBB>, --sub-text-back-color=<#RRGGBB>
See ``--osd-color``. Color used for OSD/sub text background.

View File

@ -501,6 +501,8 @@ const m_option_t common_opts[] = {
OPT_FLAG("osd-bar", osd_bar_visible, 0),
OPT_FLOATRANGE("osd-bar-align-x", osd_bar_align_x, 0, -1.0, +1.0),
OPT_FLOATRANGE("osd-bar-align-y", osd_bar_align_y, 0, -1.0, +1.0),
OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
OPT_SUBSTRUCT("sub-text", sub_text_style, osd_style_conf, 0),
{NULL, NULL, 0, 0, 0, 0, NULL}

View File

@ -43,6 +43,9 @@ void set_default_mplayer_options(struct MPOpts *opts)
.gamma_hue = 1000,
.osd_level = 1,
.osd_duration = 1000,
.osd_bar_align_y = 0.5,
.osd_bar_w = 75.0,
.osd_bar_h = 3.125,
.loop_times = -1,
.ordered_chapters = 1,
.chapter_merge_threshold = 100,

View File

@ -1330,7 +1330,8 @@ void set_osd_bar(struct MPContext *mpctx, int type, const char *name,
if (mpctx->sh_video && opts->term_osd != 1) {
mpctx->osd_visible = (GetTimerMS() + opts->osd_duration) | 1;
mpctx->osd->progbar_type = type;
mpctx->osd->progbar_value = 256 * (val - min) / (max - min);
mpctx->osd->progbar_value = (val - min) / (max - min);
mpctx->osd->progbar_num_stops = 0;
vo_osd_changed(OSDTYPE_PROGBAR);
return;
}
@ -1345,7 +1346,7 @@ static void update_osd_bar(struct MPContext *mpctx, int type,
double min, double max, double val)
{
if (mpctx->osd->progbar_type == type) {
int new_value = 256 * (val - min) / (max - min);
float new_value = (val - min) / (max - min);
if (new_value != mpctx->osd->progbar_value) {
mpctx->osd->progbar_value = new_value;
vo_osd_changed(OSDTYPE_PROGBAR);
@ -1353,6 +1354,26 @@ static void update_osd_bar(struct MPContext *mpctx, int type,
}
}
static void set_osd_bar_chapters(struct MPContext *mpctx, int type)
{
struct osd_state *osd = mpctx->osd;
osd->progbar_num_stops = 0;
if (osd->progbar_type == type) {
double len = get_time_length(mpctx);
if (len > 0) {
int num = get_chapter_count(mpctx);
for (int n = 0; n < num; n++) {
double time = chapter_start_time(mpctx, n);
if (time >= 0) {
float pos = time / len;
MP_TARRAY_APPEND(osd, osd->progbar_stops,
osd->progbar_num_stops, pos);
}
}
}
}
}
void set_osd_function(struct MPContext *mpctx, int osd_function)
{
struct MPOpts *opts = &mpctx->opts;
@ -1432,6 +1453,7 @@ static void add_seek_osd_messages(struct MPContext *mpctx)
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_BAR) {
set_osd_bar(mpctx, OSD_BAR_SEEK, "Position", 0, 1,
av_clipf(get_current_pos_ratio(mpctx), 0, 1));
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
}
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_TEXT) {
mp_osd_msg_t *msg = add_osd_msg(mpctx, OSD_MSG_TEXT, 1,

View File

@ -161,6 +161,8 @@ typedef struct MPOpts {
int osd_bar_visible;
float osd_bar_align_x;
float osd_bar_align_y;
float osd_bar_w;
float osd_bar_h;
struct osd_style_opts *osd_style;
struct osd_style_opts *sub_text_style;
float sub_scale;

View File

@ -61,14 +61,19 @@ void osd_destroy_backend(struct osd_state *osd)
osd->osd_ass_library = NULL;
}
static ASS_Track *create_osd_ass_track(struct osd_state *osd)
static void create_osd_ass_track(struct osd_state *osd, struct osd_object *obj)
{
ASS_Track *track = ass_new_track(osd->osd_ass_library);
ASS_Track *track = obj->osd_track;
if (!track)
track = ass_new_track(osd->osd_ass_library);
double aspect = 1.0 * obj->vo_res.w / FFMAX(obj->vo_res.h, 1) /
obj->vo_res.display_par;
track->track_type = TRACK_TYPE_ASS;
track->Timer = 100.;
track->PlayResY = MP_ASS_FONT_PLAYRESY;
track->PlayResX = track->PlayResY * 1.33333;
track->PlayResX = track->PlayResY * aspect;
track->WrapStyle = 1; // end-of-line wrapping instead of smart wrapping
if (track->n_styles == 0) {
@ -83,18 +88,19 @@ static ASS_Track *create_osd_ass_track(struct osd_state *osd)
style->Encoding = -1;
}
return track;
obj->osd_track = track;
}
static ASS_Event *get_osd_ass_event(ASS_Track *track)
static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *text)
{
ass_flush_events(track);
ass_alloc_event(track);
ASS_Event *event = track->events + 0;
int n = ass_alloc_event(track);
ASS_Event *event = track->events + n;
event->Start = 0;
event->Duration = 100;
event->Style = track->default_style;
assert(event->Text == NULL);
if (text)
event->Text = strdup(text);
return event;
}
@ -135,67 +141,135 @@ static char *mangle_ass(const char *in)
static void update_osd(struct osd_state *osd, struct osd_object *obj)
{
if (!osd->osd_text[0]) {
clear_obj(obj);
create_osd_ass_track(osd, obj);
clear_obj(obj);
if (!osd->osd_text[0])
return;
}
if (!obj->osd_track)
obj->osd_track = create_osd_ass_track(osd);
ASS_Event *event = get_osd_ass_event(obj->osd_track);
char *text = mangle_ass(osd->osd_text);
event->Text = strdup(text);
add_osd_ass_event(obj->osd_track, text);
talloc_free(text);
}
static int get_align(float val, int res, int *out_margin)
// align: -1 .. +1
// frame: size of the containing area
// obj: size of the object that should be positioned inside the area
// margin: min. distance from object to frame (as long as -1 <= align <= +1)
static float get_align(float align, float frame, float obj, float margin)
{
*out_margin = FFMAX(0, (1.0 - fabs(val)) * res / 2);
if (fabs(val) < 0.1)
return 1; // centered
return val > 0 ? 2 : 0; // bottom / top (or right / left)
frame -= margin * 2;
return margin + frame / 2 - obj / 2 + (frame - obj) / 2 * align;
}
static const int ass_align_x[3] = {1, 2, 3};
static const int ass_align_y[3] = {4, 8, 0};
struct ass_draw {
int scale;
char *text;
};
#define OSDBAR_ELEMS 46
static void ass_draw_start(struct ass_draw *d)
{
d->scale = FFMAX(d->scale, 1);
d->text = talloc_asprintf_append(d->text, "{\\p%d}", d->scale);
}
static void update_progbar(struct osd_state *osd, struct osd_object *obj)
static void ass_draw_stop(struct ass_draw *d)
{
d->text = talloc_strdup_append(d->text, "{\\p0}");
}
static void ass_draw_c(struct ass_draw *d, float x, float y)
{
int ix = round(x * (1 << (d->scale - 1)));
int iy = round(y * (1 << (d->scale - 1)));
d->text = talloc_asprintf_append(d->text, " %d %d", ix, iy);
}
static void ass_draw_append(struct ass_draw *d, const char *t)
{
d->text = talloc_strdup_append(d->text, t);
}
static void ass_draw_move_to(struct ass_draw *d, float x, float y)
{
ass_draw_append(d, " m");
ass_draw_c(d, x, y);
}
static void ass_draw_line_to(struct ass_draw *d, float x, float y)
{
ass_draw_append(d, " l");
ass_draw_c(d, x, y);
}
static void ass_draw_rect_ccw(struct ass_draw *d, float x0, float y0,
float x1, float y1)
{
ass_draw_move_to(d, x0, y0);
ass_draw_line_to(d, x0, y1);
ass_draw_line_to(d, x1, y1);
ass_draw_line_to(d, x1, y0);
}
static void ass_draw_rect_cw(struct ass_draw *d, float x0, float y0,
float x1, float y1)
{
ass_draw_move_to(d, x0, y0);
ass_draw_line_to(d, x1, y0);
ass_draw_line_to(d, x1, y1);
ass_draw_line_to(d, x0, y1);
}
static void ass_draw_reset(struct ass_draw *d)
{
talloc_free(d->text);
d->text = NULL;
}
static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj,
float *o_x, float *o_y, float *o_w, float *o_h,
float *o_border)
{
struct MPOpts *opts = osd->opts;
if (osd->progbar_type < 0) {
clear_obj(obj);
return;
bool new_track = !obj->osd_track;
create_osd_ass_track(osd, obj);
ASS_Track *track = obj->osd_track;
ASS_Style *style = track->styles + track->default_style;
*o_w = track->PlayResX * (opts->osd_bar_w / 100.0);
*o_h = track->PlayResY * (opts->osd_bar_h / 100.0);
if (new_track) {
float base_size = 0.03125;
style->Outline *= *o_h / track->PlayResY / base_size;
// So that the chapter marks have space between them
style->Outline = FFMIN(style->Outline, *o_h / 5.2);
// So that the border is not 0
style->Outline = FFMAX(style->Outline, *o_h / 32.0);
// Rendering with shadow is broken (because there's more than one shape)
style->Shadow = 0;
}
if (!obj->osd_track)
obj->osd_track = create_osd_ass_track(osd);
*o_border = style->Outline;
ASS_Style *style = obj->osd_track->styles + obj->osd_track->default_style;
*o_x = get_align(opts->osd_bar_align_x, track->PlayResX, *o_w, *o_border);
*o_y = get_align(opts->osd_bar_align_y, track->PlayResY, *o_h, *o_border);
}
int ax = get_align(opts->osd_bar_align_x, obj->osd_track->PlayResX,
&style->MarginR);
int ay = get_align(opts->osd_bar_align_y, obj->osd_track->PlayResY,
&style->MarginV);
style->Alignment = ass_align_x[ax] + ass_align_y[ay];
style->MarginL = style->MarginR;
static void update_progbar(struct osd_state *osd, struct osd_object *obj)
{
float px, py, width, height, border;
get_osd_bar_box(osd, obj, &px, &py, &width, &height, &border);
// We need a fixed font size with respect to the OSD width.
// Assume the OSD bar takes 2/3 of the OSD width at PlayResY=288 and
// FontSize=22 with an OSD aspect ratio of 16:9. Rescale as needed.
// xxx can fail when unknown fonts are involved
double asp = (double)obj->vo_res.w / obj->vo_res.h;
double scale = (asp / 1.77777) * (obj->osd_track->PlayResY / 288.0);
style->ScaleX = style->ScaleY = scale;
style->FontSize = 22.0;
style->Outline = style->FontSize / 16 * scale;
clear_obj(obj);
int active = (osd->progbar_value * OSDBAR_ELEMS + 255) / 256;
active = FFMIN(OSDBAR_ELEMS, FFMAX(active, 0));
if (osd->progbar_type < 0)
return;
char *text = talloc_strdup(NULL, "{\\q2}");
float sx = px - border * 2 - height / 4; // includes additional spacing
float sy = py + height / 2;
char *text = talloc_asprintf(NULL, "{\\an6\\pos(%f,%f)}", sx, sy);
if (osd->progbar_type == 0 || osd->progbar_type >= 256) {
// no sym
@ -207,30 +281,57 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
text = talloc_strdup_append_buffer(text, "{\\r}");
}
//xxx space in normal font, because OSD font doesn't have a space
text = talloc_strdup_append_buffer(text, "\\h");
text = talloc_strdup_append_buffer(text, ASS_USE_OSD_FONT);
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_START);
for (int n = 0; n < active; n++)
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_0);
for (int n = 0; n < OSDBAR_ELEMS - active; n++)
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_1);
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_END);
ASS_Event *event = get_osd_ass_event(obj->osd_track);
event->Text = strdup(text);
add_osd_ass_event(obj->osd_track, text);
talloc_free(text);
struct ass_draw *d = &(struct ass_draw) { .scale = 4 };
// filled area
d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py);
ass_draw_start(d);
float pos = osd->progbar_value * width - border / 2;
ass_draw_rect_cw(d, 0, 0, pos, height);
ass_draw_stop(d);
add_osd_ass_event(obj->osd_track, d->text);
ass_draw_reset(d);
d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py);
ass_draw_start(d);
// the box
ass_draw_rect_cw(d, -border, -border, width + border, height + border);
// the "hole"
ass_draw_rect_ccw(d, 0, 0, width, height);
// chapter marks
for (int n = 0; n < osd->progbar_num_stops; n++) {
float s = osd->progbar_stops[n] * width;
float dent = border * 1.3;
if (s > dent && s < width - dent) {
ass_draw_move_to(d, s + dent, 0);
ass_draw_line_to(d, s, dent);
ass_draw_line_to(d, s - dent, 0);
ass_draw_move_to(d, s - dent, height);
ass_draw_line_to(d, s, height - dent);
ass_draw_line_to(d, s + dent, height);
}
}
ass_draw_stop(d);
add_osd_ass_event(obj->osd_track, d->text);
ass_draw_reset(d);
}
static void update_sub(struct osd_state *osd, struct osd_object *obj)
{
struct MPOpts *opts = osd->opts;
if (!(vo_sub && opts->sub_visibility)) {
clear_obj(obj);
clear_obj(obj);
if (!(vo_sub && opts->sub_visibility))
return;
}
if (!obj->osd_track)
obj->osd_track = mp_ass_default_track(osd->osd_ass_library, osd->opts);
@ -250,9 +351,8 @@ static void update_sub(struct osd_state *osd, struct osd_object *obj)
for (int n = 0; n < vo_sub->lines; n++)
text = talloc_asprintf_append_buffer(text, "%s\n", vo_sub->text[n]);
ASS_Event *event = get_osd_ass_event(obj->osd_track);
char *escaped_text = mangle_ass(text);
event->Text = strdup(escaped_text);
add_osd_ass_event(obj->osd_track, escaped_text);
talloc_free(escaped_text);
talloc_free(text);
}

View File

@ -129,8 +129,13 @@ struct osd_state {
bool want_redraw;
char *osd_text; // OSDTYPE_OSD
int progbar_type, progbar_value; // OSDTYPE_PROGBAR
// OSDTYPE_OSD
char *osd_text;
// OSDTYPE_PROGBAR
int progbar_type; // <0: disabled, 1-255: symbol, else: no symbol
float progbar_value; // range 0.0-1.0
float *progbar_stops; // used for chapter indicators (0.0-1.0 each)
int progbar_num_stops;
int switch_sub_id;