mirror of
https://github.com/mpv-player/mpv
synced 2024-12-26 09:02:38 +00:00
draw_bmp: add a function to return a single-texture OSD overlay
Maybe this is useful for some of the lesser VOs. It's preferable over bad ad-hoc solutions based on the more complex sub_bitmap data structures (as observed e.g. in vo_vaapi.c), and does not use that much more code since draw_bmp already created such an overlay internally. But I still wanted something that avoids having to upload/render a full screen-sized overlay if for example there's only a tiny subtitle line on the bottom of the screen. So the new API can return a list of modified pixels (for upload) and non-transparent pixels (for display). The way these pixel rectangles are computed is a bit dumb and returns dumb results, but it should be usable, and the implementation can change.
This commit is contained in:
parent
6db890ebab
commit
55e1f15cdb
265
sub/draw_bmp.c
265
sub/draw_bmp.c
@ -100,6 +100,8 @@ struct mp_draw_sub_cache
|
||||
|
||||
// Function that works on the _f32 data.
|
||||
void (*blend_line)(void *dst, void *src, void *src_a, int w);
|
||||
|
||||
struct mp_image res_overlay; // returned by mp_draw_sub_overlay()
|
||||
};
|
||||
|
||||
static void blend_line_f32(void *dst, void *src, void *src_a, int w)
|
||||
@ -112,7 +114,7 @@ static void blend_line_f32(void *dst, void *src, void *src_a, int w)
|
||||
dst_f[x] = src_f[x] + dst_f[x] * (1.0f - src_a_f[x]);
|
||||
}
|
||||
|
||||
static void blend_slice(struct mp_draw_sub_cache *p, int rgb_y)
|
||||
static void blend_slice(struct mp_draw_sub_cache *p)
|
||||
{
|
||||
struct mp_image *ov = p->overlay_tmp;
|
||||
struct mp_image *ca = p->calpha_tmp;
|
||||
@ -164,7 +166,7 @@ static bool blend_overlay_with_video(struct mp_draw_sub_cache *p,
|
||||
if (p->calpha_to_f32)
|
||||
repack_line(p->calpha_to_f32, 0, 0, x >> xs, y >> ys, w >> xs);
|
||||
|
||||
blend_slice(p, y);
|
||||
blend_slice(p);
|
||||
|
||||
repack_line(p->video_from_f32, x, y, 0, 0, w);
|
||||
}
|
||||
@ -478,10 +480,21 @@ static void clear_rgba_overlay(struct mp_draw_sub_cache *p)
|
||||
p->any_osd = false;
|
||||
}
|
||||
|
||||
static bool reinit(struct mp_draw_sub_cache *p, struct mp_image_params *params)
|
||||
static void init_general(struct mp_draw_sub_cache *p)
|
||||
{
|
||||
talloc_free_children(p);
|
||||
*p = (struct mp_draw_sub_cache){.params = *params};
|
||||
p->sub_scale = mp_sws_alloc(p);
|
||||
|
||||
p->s_w = MP_ALIGN_UP(p->rgba_overlay->w, SLICE_W) / SLICE_W;
|
||||
|
||||
p->slices = talloc_zero_array(p, struct slice, p->s_w * p->rgba_overlay->h);
|
||||
|
||||
mp_image_clear(p->rgba_overlay, 0, 0, p->w, p->h);
|
||||
clear_rgba_overlay(p);
|
||||
}
|
||||
|
||||
static bool reinit_to_video(struct mp_draw_sub_cache *p)
|
||||
{
|
||||
struct mp_image_params *params = &p->params;
|
||||
|
||||
bool need_premul = params->alpha != MP_ALPHA_PREMUL &&
|
||||
(mp_imgfmt_get_desc(params->imgfmt).flags & MP_IMGFLAG_ALPHA);
|
||||
@ -683,15 +696,6 @@ static bool reinit(struct mp_draw_sub_cache *p, struct mp_image_params *params)
|
||||
}
|
||||
}
|
||||
|
||||
p->sub_scale = mp_sws_alloc(p);
|
||||
|
||||
p->s_w = MP_ALIGN_UP(p->rgba_overlay->w, SLICE_W) / SLICE_W;
|
||||
|
||||
p->slices = talloc_zero_array(p, struct slice, p->s_w * p->rgba_overlay->h);
|
||||
|
||||
mp_image_clear(p->rgba_overlay, 0, 0, w, h);
|
||||
clear_rgba_overlay(p);
|
||||
|
||||
if (need_premul) {
|
||||
p->premul = mp_sws_alloc(p);
|
||||
p->unpremul = mp_sws_alloc(p);
|
||||
@ -707,6 +711,56 @@ static bool reinit(struct mp_draw_sub_cache *p, struct mp_image_params *params)
|
||||
p->unpremul->force_scaler = MP_SWS_ZIMG;
|
||||
}
|
||||
|
||||
init_general(p);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reinit_to_overlay(struct mp_draw_sub_cache *p)
|
||||
{
|
||||
p->align_x = 1;
|
||||
p->align_y = 1;
|
||||
|
||||
p->w = p->params.w;
|
||||
p->h = p->params.h;
|
||||
|
||||
p->rgba_overlay = talloc_steal(p, mp_image_alloc(IMGFMT_BGRA, p->w, p->h));
|
||||
if (!p->rgba_overlay)
|
||||
return false;
|
||||
|
||||
mp_image_params_guess_csp(&p->rgba_overlay->params);
|
||||
p->rgba_overlay->params.alpha = MP_ALPHA_PREMUL;
|
||||
|
||||
// Some non-sense with the intention to somewhat isolate the returned image.
|
||||
mp_image_setfmt(&p->res_overlay, p->rgba_overlay->imgfmt);
|
||||
mp_image_set_size(&p->res_overlay, p->rgba_overlay->w, p->rgba_overlay->h);
|
||||
mp_image_copy_attributes(&p->res_overlay, p->rgba_overlay);
|
||||
p->res_overlay.planes[0] = p->rgba_overlay->planes[0];
|
||||
p->res_overlay.stride[0] = p->rgba_overlay->stride[0];
|
||||
|
||||
init_general(p);
|
||||
|
||||
// Mark all dirty (for full reinit of user state).
|
||||
for (int y = 0; y < p->rgba_overlay->h; y++) {
|
||||
for (int sx = 0; sx < p->s_w; sx++)
|
||||
p->slices[y * p->s_w + sx] = (struct slice){0, SLICE_W};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_reinit(struct mp_draw_sub_cache *p,
|
||||
struct mp_image_params *params, bool to_video)
|
||||
{
|
||||
if (!mp_image_params_equal(&p->params, params) || !p->rgba_overlay) {
|
||||
talloc_free_children(p);
|
||||
*p = (struct mp_draw_sub_cache){.params = *params};
|
||||
if (!(to_video ? reinit_to_video(p) : reinit_to_overlay(p))) {
|
||||
talloc_free_children(p);
|
||||
*p = (struct mp_draw_sub_cache){0};
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -725,10 +779,12 @@ char *mp_draw_sub_get_dbg_info(struct mp_draw_sub_cache *p)
|
||||
mp_imgfmt_to_name(p->calpha_tmp ? p->calpha_tmp->imgfmt : 0));
|
||||
}
|
||||
|
||||
// p_cache: if not NULL, the function will set *p to a talloc-allocated p
|
||||
// containing scaled versions of sbs contents - free the p with
|
||||
// talloc_free()
|
||||
bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **p_cache, struct mp_image *dst,
|
||||
struct mp_draw_sub_cache *mp_draw_sub_alloc(void *ta_parent)
|
||||
{
|
||||
return talloc_zero(ta_parent, struct mp_draw_sub_cache);
|
||||
}
|
||||
|
||||
bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache *p, struct mp_image *dst,
|
||||
struct sub_bitmap_list *sbs_list)
|
||||
{
|
||||
bool ok = false;
|
||||
@ -738,18 +794,8 @@ bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **p_cache, struct mp_image *ds
|
||||
assert(dst->w >= sbs_list->w);
|
||||
assert(dst->h >= sbs_list->h);
|
||||
|
||||
struct mp_draw_sub_cache *p = p_cache ? *p_cache : NULL;
|
||||
if (!p)
|
||||
p = talloc_zero(NULL, struct mp_draw_sub_cache);
|
||||
|
||||
if (!mp_image_params_equal(&p->params, &dst->params) || !p->video_tmp)
|
||||
{
|
||||
if (!reinit(p, &dst->params)) {
|
||||
talloc_free_children(p);
|
||||
*p = (struct mp_draw_sub_cache){0};
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (!check_reinit(p, &dst->params, true))
|
||||
return false;
|
||||
|
||||
if (p->change_id != sbs_list->change_id) {
|
||||
p->change_id = sbs_list->change_id;
|
||||
@ -765,31 +811,156 @@ bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **p_cache, struct mp_image *ds
|
||||
goto done;
|
||||
}
|
||||
|
||||
struct mp_image *target = dst;
|
||||
if (p->any_osd && p->premul_tmp) {
|
||||
if (mp_sws_scale(p->premul, p->premul_tmp, dst) < 0)
|
||||
goto done;
|
||||
target = p->premul_tmp;
|
||||
}
|
||||
if (p->any_osd) {
|
||||
struct mp_image *target = dst;
|
||||
if (p->premul_tmp) {
|
||||
if (mp_sws_scale(p->premul, p->premul_tmp, dst) < 0)
|
||||
goto done;
|
||||
target = p->premul_tmp;
|
||||
}
|
||||
|
||||
if (!blend_overlay_with_video(p, target))
|
||||
goto done;
|
||||
|
||||
if (p->any_osd && p->premul_tmp) {
|
||||
if (mp_sws_scale(p->unpremul, dst, p->premul_tmp) < 0)
|
||||
if (!blend_overlay_with_video(p, target))
|
||||
goto done;
|
||||
|
||||
if (target != dst) {
|
||||
if (mp_sws_scale(p->unpremul, dst, p->premul_tmp) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
ok = true;
|
||||
|
||||
done:
|
||||
if (p_cache) {
|
||||
*p_cache = p;
|
||||
} else {
|
||||
talloc_free(p);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Bounding boxes for mp_draw_sub_overlay() API. For simplicity, each rectangle
|
||||
// covers a fixed tile on the screen, starts out empty, but is not extended
|
||||
// beyond the tile. In the simplest case, there's only 1 rect/tile for everything.
|
||||
struct rc_grid {
|
||||
unsigned w, h; // size in grid tiles
|
||||
unsigned r_w, r_h; // size of a grid tile in pixels
|
||||
struct mp_rect *rcs; // rcs[x * w + y]
|
||||
};
|
||||
|
||||
static void init_rc_grid(struct rc_grid *gr, struct mp_draw_sub_cache *p,
|
||||
struct mp_rect *rcs, int max_rcs)
|
||||
{
|
||||
*gr = (struct rc_grid){ .w = max_rcs ? 1 : 0, .h = max_rcs ? 1 : 0,
|
||||
.rcs = rcs, .r_w = p->s_w * SLICE_W, .r_h = p->h, };
|
||||
|
||||
// Dumb iteration to figure out max. size because I'm stupid.
|
||||
bool more = true;
|
||||
while (more) {
|
||||
more = false;
|
||||
if (gr->r_h >= 128) {
|
||||
if (gr->w * gr->h * 2 > max_rcs)
|
||||
break;
|
||||
gr->h *= 2;
|
||||
gr->r_h = (p->h + gr->h - 1) / gr->h;
|
||||
more = true;
|
||||
}
|
||||
if (gr->r_w >= SLICE_W * 2) {
|
||||
if (gr->w * gr->h * 2 > max_rcs)
|
||||
break;
|
||||
gr->w *= 2;
|
||||
gr->r_w = (p->s_w + gr->w - 1) / gr->w * SLICE_W;
|
||||
more = true;
|
||||
}
|
||||
}
|
||||
|
||||
assert(gr->r_h * gr->h >= p->h);
|
||||
assert(!(gr->r_w & (SLICE_W - 1)));
|
||||
assert(gr->r_w * gr->w >= p->w);
|
||||
|
||||
// Init with empty (degenerate) rectangles.
|
||||
for (int y = 0; y < gr->h; y++) {
|
||||
for (int x = 0; x < gr->w; x++) {
|
||||
struct mp_rect *rc = &gr->rcs[y * gr->w + x];
|
||||
rc->x1 = x * gr->r_w;
|
||||
rc->y1 = y * gr->r_h;
|
||||
rc->x0 = rc->x1 + gr->r_w;
|
||||
rc->y0 = rc->y1 + gr->r_h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extend given grid with contents of p->slices.
|
||||
static void mark_rcs(struct mp_draw_sub_cache *p, struct rc_grid *gr)
|
||||
{
|
||||
for (int y = 0; y < p->h; y++) {
|
||||
struct slice *line = &p->slices[y * p->s_w];
|
||||
struct mp_rect *rcs = &gr->rcs[y / gr->r_h * gr->w];
|
||||
|
||||
for (int sx = 0; sx < p->s_w; sx++) {
|
||||
struct slice *s = &line[sx];
|
||||
if (s->x0 < s->x1) {
|
||||
unsigned xpos = sx * SLICE_W;
|
||||
struct mp_rect *rc = &rcs[xpos / gr->r_w];
|
||||
rc->y0 = MPMIN(rc->y0, y);
|
||||
rc->y1 = MPMAX(rc->y1, y + 1);
|
||||
rc->x0 = MPMIN(rc->x0, xpos + s->x0);
|
||||
rc->x1 = MPMAX(rc->x1, xpos + s->x1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty RCs, and return rc count.
|
||||
static int return_rcs(struct rc_grid *gr)
|
||||
{
|
||||
int num = 0, cnt = gr->w * gr->h;
|
||||
for (int n = 0; n < cnt; n++) {
|
||||
struct mp_rect *rc = &gr->rcs[n];
|
||||
if (rc->x0 < rc->x1 && rc->y0 < rc->y1)
|
||||
gr->rcs[num++] = *rc;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
struct mp_image *mp_draw_sub_overlay(struct mp_draw_sub_cache *p,
|
||||
struct sub_bitmap_list *sbs_list,
|
||||
struct mp_rect *act_rcs,
|
||||
int max_act_rcs,
|
||||
int *num_act_rcs,
|
||||
struct mp_rect *mod_rcs,
|
||||
int max_mod_rcs,
|
||||
int *num_mod_rcs)
|
||||
{
|
||||
*num_act_rcs = 0;
|
||||
*num_mod_rcs = 0;
|
||||
|
||||
struct mp_image_params params = {.w = sbs_list->w, .h = sbs_list->h};
|
||||
if (!check_reinit(p, ¶ms, false))
|
||||
return NULL;
|
||||
|
||||
struct rc_grid gr_act, gr_mod;
|
||||
init_rc_grid(&gr_act, p, act_rcs, max_act_rcs);
|
||||
init_rc_grid(&gr_mod, p, mod_rcs, max_mod_rcs);
|
||||
|
||||
if (p->change_id != sbs_list->change_id) {
|
||||
p->change_id = sbs_list->change_id;
|
||||
|
||||
mark_rcs(p, &gr_mod);
|
||||
|
||||
clear_rgba_overlay(p);
|
||||
|
||||
for (int n = 0; n < sbs_list->num_items; n++) {
|
||||
if (!render_sb(p, sbs_list->items[n])) {
|
||||
p->change_id = 0;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
mark_rcs(p, &gr_mod);
|
||||
}
|
||||
|
||||
mark_rcs(p, &gr_act);
|
||||
|
||||
*num_act_rcs = return_rcs(&gr_act);
|
||||
*num_mod_rcs = return_rcs(&gr_mod);
|
||||
|
||||
return &p->res_overlay;
|
||||
}
|
||||
|
||||
// vim: ts=4 sw=4 et tw=80
|
||||
|
@ -3,12 +3,53 @@
|
||||
|
||||
#include "osd.h"
|
||||
|
||||
struct mp_rect;
|
||||
struct mp_image;
|
||||
struct mp_draw_sub_cache;
|
||||
bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache **cache, struct mp_image *dst,
|
||||
|
||||
struct mp_draw_sub_cache *mp_draw_sub_alloc(void *ta_parent);
|
||||
|
||||
// Render the sub-bitmaps in sbs_list to dst. sbs_list must have been rendered
|
||||
// for an OSD resolution equivalent to dst's size (UB if not).
|
||||
// Warning: if dst is a format with alpha, and dst is not set to MP_ALPHA_PREMUL
|
||||
// (not done by default), this will be extremely slow.
|
||||
// Warning: the caller is responsible for ensuring that dst is writable.
|
||||
// cache: allocated instance; caches non-changing OSD parts etc.
|
||||
// dst: image to draw to
|
||||
// sbs_list: source sub-bitmaps
|
||||
// returns: success
|
||||
bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache *cache, struct mp_image *dst,
|
||||
struct sub_bitmap_list *sbs_list);
|
||||
|
||||
char *mp_draw_sub_get_dbg_info(struct mp_draw_sub_cache *c);
|
||||
|
||||
// Return a RGBA overlay with subtitles. The returned image uses IMGFMT_BGRA and
|
||||
// premultiplied alpha, and the size specified by sbs_list.w/h.
|
||||
// This can return a list of active (act_) and modified (mod_) rectangles.
|
||||
// Active rectangles are regions that contain visible OSD pixels. Modified
|
||||
// rectangles are regions that were changed since the last call. This function
|
||||
// always makes the act region a subset of the mod region. Rectangles within a
|
||||
// list never overlap with rectangles within the same list.
|
||||
// If the user-provided lists are too small (max_*_rcs too small), multiple
|
||||
// rectangles are merged until they fit in the list.
|
||||
// You can pass max_act_rcs=0, which implies you render the whole overlay.
|
||||
// cache: allocated instance; keeps track of changed regions
|
||||
// sbs_list: source sub-bitmaps
|
||||
// act_rcs: caller allocated list of non-transparent rectangles
|
||||
// max_act_rcs: number of allocated items in act_rcs
|
||||
// num_act_rcs: set to the number of valid items in act_rcs
|
||||
// mod_rcs, max_mod_rcs, num_mod_rcs: modified rectangles
|
||||
// returns: internal OSD overlay owned by cache, NULL on error
|
||||
// read only, valid until the next call on cache
|
||||
struct mp_image *mp_draw_sub_overlay(struct mp_draw_sub_cache *cache,
|
||||
struct sub_bitmap_list *sbs_list,
|
||||
struct mp_rect *act_rcs,
|
||||
int max_act_rcs,
|
||||
int *num_act_rcs,
|
||||
struct mp_rect *mod_rcs,
|
||||
int max_mod_rcs,
|
||||
int *num_mod_rcs);
|
||||
|
||||
extern const bool mp_draw_sub_formats[SUBBITMAP_COUNT];
|
||||
|
||||
#endif /* MPLAYER_DRAW_BMP_H */
|
||||
|
@ -425,9 +425,12 @@ void osd_draw_on_image_p(struct osd_state *osd, struct mp_osd_res res,
|
||||
// Need to lock for the dumb osd->draw_cache thing.
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
|
||||
if (!osd->draw_cache)
|
||||
osd->draw_cache = mp_draw_sub_alloc(osd);
|
||||
|
||||
stats_time_start(osd->stats, "draw-bmp");
|
||||
|
||||
if (!mp_draw_sub_bitmaps(&osd->draw_cache, dest, list))
|
||||
if (!mp_draw_sub_bitmaps(osd->draw_cache, dest, list))
|
||||
MP_WARN(osd, "Failed rendering OSD.\n");
|
||||
talloc_steal(osd, osd->draw_cache);
|
||||
|
||||
|
@ -365,8 +365,8 @@ static bool try_draw_bmp(FILE *f, int imgfmt)
|
||||
.num_items = 1,
|
||||
};
|
||||
|
||||
struct mp_draw_sub_cache *c = NULL;
|
||||
if (mp_draw_sub_bitmaps(&c, dst, &sbs_list)) {
|
||||
struct mp_draw_sub_cache *c = mp_draw_sub_alloc(NULL);
|
||||
if (mp_draw_sub_bitmaps(c, dst, &sbs_list)) {
|
||||
char *info = mp_draw_sub_get_dbg_info(c);
|
||||
fprintf(f, "%s\n", info);
|
||||
talloc_free(info);
|
||||
|
Loading…
Reference in New Issue
Block a user