diff options
Diffstat (limited to 'sub/draw_bmp.c')
-rw-r--r-- | sub/draw_bmp.c | 1035 |
1 files changed, 1035 insertions, 0 deletions
diff --git a/sub/draw_bmp.c b/sub/draw_bmp.c new file mode 100644 index 0000000..58db162 --- /dev/null +++ b/sub/draw_bmp.c @@ -0,0 +1,1035 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stddef.h> +#include <stdbool.h> +#include <assert.h> +#include <math.h> +#include <inttypes.h> + +#include "common/common.h" +#include "draw_bmp.h" +#include "img_convert.h" +#include "video/mp_image.h" +#include "video/repack.h" +#include "video/sws_utils.h" +#include "video/img_format.h" +#include "video/csputils.h" + +const bool mp_draw_sub_formats[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = true, + [SUBBITMAP_BGRA] = true, +}; + +struct part { + int change_id; + // Sub-bitmaps scaled to final sizes. + int num_imgs; + struct mp_image **imgs; +}; + +// Must be a power of 2. Height is 1, but mark_rect() effectively operates on +// multiples of chroma sized macro-pixels. (E.g. 4:2:0 -> every second line is +// the same as the previous one, and x0%2==x1%2==0.) +#define SLICE_W 256u + +// Whether to scale in tiles. Faster, but can't use correct chroma position. +// Should be a runtime option. SLICE_W is used as tile width. The tile size +// should probably be small; too small or too big will cause overhead when +// scaling. +#define SCALE_IN_TILES 1 +#define TILE_H 4u + +struct slice { + uint16_t x0, x1; +}; + +struct mp_draw_sub_cache +{ + struct mpv_global *global; + + // Possibly cached parts. Also implies what's in the video_overlay. + struct part parts[MAX_OSD_PARTS]; + int64_t change_id; + + struct mp_image_params params; // target image params + + int w, h; // like params.w/h, but rounded up to chroma + unsigned align_x, align_y; // alignment for all video pixels + + struct mp_image *rgba_overlay; // all OSD in RGBA + struct mp_image *video_overlay; // rgba_overlay converted to video colorspace + struct mp_image *alpha_overlay; // alpha plane ref. to video_overlay + struct mp_image *calpha_overlay; // alpha_overlay scaled to chroma plane size + + unsigned s_w; // number of slices per line + struct slice *slices; // slices[y * s_w + x / SLICE_W] + bool any_osd; + + struct mp_sws_context *rgba_to_overlay; // scaler for rgba -> video csp. + struct mp_sws_context *alpha_to_calpha; // scaler for overlay -> calpha + bool scale_in_tiles; + + struct mp_sws_context *sub_scale; // scaler for SUBBITMAP_BGRA + + struct mp_repack *overlay_to_f32; // convert video_overlay to float + struct mp_image *overlay_tmp; // slice in float32 + + struct mp_repack *calpha_to_f32; // convert video_overlay to float + struct mp_image *calpha_tmp; // slice in float32 + + struct mp_repack *video_to_f32; // convert video to float + struct mp_repack *video_from_f32; // convert float back to video + struct mp_image *video_tmp; // slice in float32 + + struct mp_sws_context *premul; // video -> premultiplied video + struct mp_sws_context *unpremul; // reverse + struct mp_image *premul_tmp; + + // 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) +{ + float *dst_f = dst; + float *src_f = src; + float *src_a_f = src_a; + + for (int x = 0; x < w; x++) + dst_f[x] = src_f[x] + dst_f[x] * (1.0f - src_a_f[x]); +} + +static void blend_line_u8(void *dst, void *src, void *src_a, int w) +{ + uint8_t *dst_i = dst; + uint8_t *src_i = src; + uint8_t *src_a_i = src_a; + + for (int x = 0; x < w; x++) + dst_i[x] = src_i[x] + dst_i[x] * (255u - src_a_i[x]) / 255u; +} + +static void blend_slice(struct mp_draw_sub_cache *p) +{ + struct mp_image *ov = p->overlay_tmp; + struct mp_image *ca = p->calpha_tmp; + struct mp_image *vid = p->video_tmp; + + for (int plane = 0; plane < vid->num_planes; plane++) { + int xs = vid->fmt.xs[plane]; + int ys = vid->fmt.ys[plane]; + int h = (1 << vid->fmt.chroma_ys) - (1 << ys) + 1; + int cw = mp_chroma_div_up(vid->w, xs); + for (int y = 0; y < h; y++) { + p->blend_line(mp_image_pixel_ptr_ny(vid, plane, 0, y), + mp_image_pixel_ptr_ny(ov, plane, 0, y), + xs || ys ? mp_image_pixel_ptr_ny(ca, 0, 0, y) + : mp_image_pixel_ptr_ny(ov, ov->num_planes - 1, 0, y), + cw); + } + } +} + +static bool blend_overlay_with_video(struct mp_draw_sub_cache *p, + struct mp_image *dst) +{ + if (!repack_config_buffers(p->video_to_f32, 0, p->video_tmp, 0, dst, NULL)) + return false; + if (!repack_config_buffers(p->video_from_f32, 0, dst, 0, p->video_tmp, NULL)) + return false; + + int xs = dst->fmt.chroma_xs; + int ys = dst->fmt.chroma_ys; + + for (int y = 0; y < dst->h; y += p->align_y) { + struct slice *line = &p->slices[y * p->s_w]; + + for (int sx = 0; sx < p->s_w; sx++) { + struct slice *s = &line[sx]; + + int w = s->x1 - s->x0; + if (w <= 0) + continue; + int x = sx * SLICE_W + s->x0; + + assert(MP_IS_ALIGNED(x, p->align_x)); + assert(MP_IS_ALIGNED(w, p->align_x)); + assert(x + w <= p->w); + + repack_line(p->overlay_to_f32, 0, 0, x, y, w); + repack_line(p->video_to_f32, 0, 0, x, y, w); + if (p->calpha_to_f32) + repack_line(p->calpha_to_f32, 0, 0, x >> xs, y >> ys, w >> xs); + + blend_slice(p); + + repack_line(p->video_from_f32, x, y, 0, 0, w); + } + } + + return true; +} + +static bool convert_overlay_part(struct mp_draw_sub_cache *p, + int x0, int y0, int w, int h) +{ + struct mp_image src = *p->rgba_overlay; + struct mp_image dst = *p->video_overlay; + + mp_image_crop(&src, x0, y0, x0 + w, y0 + h); + mp_image_crop(&dst, x0, y0, x0 + w, y0 + h); + + if (mp_sws_scale(p->rgba_to_overlay, &dst, &src) < 0) + return false; + + if (p->calpha_overlay) { + src = *p->alpha_overlay; + dst = *p->calpha_overlay; + + int xs = p->video_overlay->fmt.chroma_xs; + int ys = p->video_overlay->fmt.chroma_ys; + mp_image_crop(&src, x0, y0, x0 + w, y0 + h); + mp_image_crop(&dst, x0 >> xs, y0 >> ys, (x0 + w) >> xs, (y0 + h) >> ys); + + if (mp_sws_scale(p->alpha_to_calpha, &dst, &src) < 0) + return false; + } + + return true; +} + +static bool convert_to_video_overlay(struct mp_draw_sub_cache *p) +{ + if (!p->video_overlay) + return true; + + if (p->scale_in_tiles) { + int t_h = p->rgba_overlay->h / TILE_H; + for (int ty = 0; ty < t_h; ty++) { + for (int sx = 0; sx < p->s_w; sx++) { + struct slice *s = &p->slices[ty * TILE_H * p->s_w + sx]; + bool pixels_set = false; + for (int y = 0; y < TILE_H; y++) { + if (s[0].x0 < s[0].x1) { + pixels_set = true; + break; + } + s += p->s_w; + } + if (!pixels_set) + continue; + if (!convert_overlay_part(p, sx * SLICE_W, ty * TILE_H, + SLICE_W, TILE_H)) + return false; + } + } + } else { + if (!convert_overlay_part(p, 0, 0, p->rgba_overlay->w, p->rgba_overlay->h)) + return false; + } + + return true; +} + +// Mark the given rectangle of pixels as possibly non-transparent. +// The rectangle must have been pre-clipped. +static void mark_rect(struct mp_draw_sub_cache *p, int x0, int y0, int x1, int y1) +{ + x0 = MP_ALIGN_DOWN(x0, p->align_x); + y0 = MP_ALIGN_DOWN(y0, p->align_y); + x1 = MP_ALIGN_UP(x1, p->align_x); + y1 = MP_ALIGN_UP(y1, p->align_y); + + assert(x0 >= 0 && x0 <= x1 && x1 <= p->w); + assert(y0 >= 0 && y0 <= y1 && y1 <= p->h); + + const int sx0 = x0 / SLICE_W; + const int sx1 = MPMIN(x1 / SLICE_W, p->s_w - 1); + + for (int y = y0; y < y1; y++) { + struct slice *line = &p->slices[y * p->s_w]; + + struct slice *s0 = &line[sx0]; + struct slice *s1 = &line[sx1]; + + s0->x0 = MPMIN(s0->x0, x0 % SLICE_W); + s1->x1 = MPMAX(s1->x1, ((x1 - 1) % SLICE_W) + 1); + + if (s0 != s1) { + s0->x1 = SLICE_W; + s1->x0 = 0; + + for (int x = sx0 + 1; x < sx1; x++) { + struct slice *s = &line[x]; + s->x0 = 0; + s->x1 = SLICE_W; + } + } + + // Ensure the very last slice does not extend + // beyond the total width. + struct slice *last_s = &line[p->s_w - 1]; + last_s->x1 = MPMIN(p->w - ((p->s_w - 1) * SLICE_W), last_s->x1); + + p->any_osd = true; + } +} + +static void draw_ass_rgba(uint8_t *dst, ptrdiff_t dst_stride, + uint8_t *src, ptrdiff_t src_stride, + int w, int h, uint32_t color) +{ + const unsigned int r = (color >> 24) & 0xff; + const unsigned int g = (color >> 16) & 0xff; + const unsigned int b = (color >> 8) & 0xff; + const unsigned int a = 0xff - (color & 0xff); + + for (int y = 0; y < h; y++) { + uint32_t *dstrow = (uint32_t *) dst; + for (int x = 0; x < w; x++) { + const unsigned int v = src[x]; + unsigned int aa = a * v; + uint32_t dstpix = dstrow[x]; + unsigned int dstb = dstpix & 0xFF; + unsigned int dstg = (dstpix >> 8) & 0xFF; + unsigned int dstr = (dstpix >> 16) & 0xFF; + unsigned int dsta = (dstpix >> 24) & 0xFF; + dstb = (v * b * a + dstb * (255 * 255 - aa)) / (255 * 255); + dstg = (v * g * a + dstg * (255 * 255 - aa)) / (255 * 255); + dstr = (v * r * a + dstr * (255 * 255 - aa)) / (255 * 255); + dsta = (aa * 255 + dsta * (255 * 255 - aa)) / (255 * 255); + dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24); + } + dst += dst_stride; + src += src_stride; + } +} + +static void render_ass(struct mp_draw_sub_cache *p, struct sub_bitmaps *sb) +{ + assert(sb->format == SUBBITMAP_LIBASS); + + for (int i = 0; i < sb->num_parts; i++) { + struct sub_bitmap *s = &sb->parts[i]; + + draw_ass_rgba(mp_image_pixel_ptr(p->rgba_overlay, 0, s->x, s->y), + p->rgba_overlay->stride[0], s->bitmap, s->stride, + s->w, s->h, s->libass.color); + + mark_rect(p, s->x, s->y, s->x + s->w, s->y + s->h); + } +} + +static void draw_rgba(uint8_t *dst, ptrdiff_t dst_stride, + uint8_t *src, ptrdiff_t src_stride, int w, int h) +{ + for (int y = 0; y < h; y++) { + uint32_t *srcrow = (uint32_t *)src; + uint32_t *dstrow = (uint32_t *)dst; + for (int x = 0; x < w; x++) { + uint32_t srcpix = srcrow[x]; + uint32_t dstpix = dstrow[x]; + unsigned int srcb = srcpix & 0xFF; + unsigned int srcg = (srcpix >> 8) & 0xFF; + unsigned int srcr = (srcpix >> 16) & 0xFF; + unsigned int srca = (srcpix >> 24) & 0xFF; + unsigned int dstb = dstpix & 0xFF; + unsigned int dstg = (dstpix >> 8) & 0xFF; + unsigned int dstr = (dstpix >> 16) & 0xFF; + unsigned int dsta = (dstpix >> 24) & 0xFF; + dstb = srcb + dstb * (255 * 255 - srca) / (255 * 255); + dstg = srcg + dstg * (255 * 255 - srca) / (255 * 255); + dstr = srcr + dstr * (255 * 255 - srca) / (255 * 255); + dsta = srca + dsta * (255 * 255 - srca) / (255 * 255); + dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24); + } + dst += dst_stride; + src += src_stride; + } +} + +static bool render_rgba(struct mp_draw_sub_cache *p, struct part *part, + struct sub_bitmaps *sb) +{ + assert(sb->format == SUBBITMAP_BGRA); + + if (part->change_id != sb->change_id) { + for (int n = 0; n < part->num_imgs; n++) + talloc_free(part->imgs[n]); + part->num_imgs = sb->num_parts; + MP_TARRAY_GROW(p, part->imgs, part->num_imgs); + for (int n = 0; n < part->num_imgs; n++) + part->imgs[n] = NULL; + + part->change_id = sb->change_id; + } + + for (int i = 0; i < sb->num_parts; i++) { + struct sub_bitmap *s = &sb->parts[i]; + + // Clipping is rare but necessary. + int sx0 = s->x; + int sy0 = s->y; + int sx1 = s->x + s->dw; + int sy1 = s->y + s->dh; + + int x0 = MPCLAMP(sx0, 0, p->w); + int y0 = MPCLAMP(sy0, 0, p->h); + int x1 = MPCLAMP(sx1, 0, p->w); + int y1 = MPCLAMP(sy1, 0, p->h); + + int dw = x1 - x0; + int dh = y1 - y0; + if (dw <= 0 || dh <= 0) + continue; + + // We clip the source instead of the scaled image, because that might + // avoid excessive memory usage when applying a ridiculous scale factor, + // even if that stretches it to up to 1 pixel due to integer rounding. + int sx = 0; + int sy = 0; + int sw = s->w; + int sh = s->h; + if (x0 != sx0 || y0 != sy0 || x1 != sx1 || y1 != sy1) { + double fx = s->dw / (double)s->w; + double fy = s->dh / (double)s->h; + sx = MPCLAMP((x0 - sx0) / fx, 0, s->w); + sy = MPCLAMP((y0 - sy0) / fy, 0, s->h); + sw = MPCLAMP(dw / fx, 1, s->w); + sh = MPCLAMP(dh / fy, 1, s->h); + } + + assert(sx >= 0 && sw > 0 && sx + sw <= s->w); + assert(sy >= 0 && sh > 0 && sy + sh <= s->h); + + ptrdiff_t s_stride = s->stride; + void *s_ptr = (char *)s->bitmap + s_stride * sy + sx * 4; + + if (dw != sw || dh != sh) { + struct mp_image *scaled = part->imgs[i]; + + if (!scaled) { + struct mp_image src_img = {0}; + mp_image_setfmt(&src_img, IMGFMT_BGRA); + mp_image_set_size(&src_img, sw, sh); + src_img.planes[0] = s_ptr; + src_img.stride[0] = s_stride; + src_img.params.alpha = MP_ALPHA_PREMUL; + + scaled = mp_image_alloc(IMGFMT_BGRA, dw, dh); + if (!scaled) + return false; + part->imgs[i] = talloc_steal(p, scaled); + mp_image_copy_attributes(scaled, &src_img); + + if (mp_sws_scale(p->sub_scale, scaled, &src_img) < 0) + return false; + } + + assert(scaled->w == dw); + assert(scaled->h == dh); + + s_stride = scaled->stride[0]; + s_ptr = scaled->planes[0]; + } + + draw_rgba(mp_image_pixel_ptr(p->rgba_overlay, 0, x0, y0), + p->rgba_overlay->stride[0], s_ptr, s_stride, dw, dh); + + mark_rect(p, x0, y0, x1, y1); + } + + return true; +} + +static bool render_sb(struct mp_draw_sub_cache *p, struct sub_bitmaps *sb) +{ + struct part *part = &p->parts[sb->render_index]; + + switch (sb->format) { + case SUBBITMAP_LIBASS: + render_ass(p, sb); + return true; + case SUBBITMAP_BGRA: + return render_rgba(p, part, sb); + } + + return false; +} + +static void clear_rgba_overlay(struct mp_draw_sub_cache *p) +{ + assert(p->rgba_overlay->imgfmt == IMGFMT_BGRA); + + for (int y = 0; y < p->rgba_overlay->h; y++) { + uint32_t *px = mp_image_pixel_ptr(p->rgba_overlay, 0, 0, y); + struct slice *line = &p->slices[y * p->s_w]; + + for (int sx = 0; sx < p->s_w; sx++) { + struct slice *s = &line[sx]; + + // Ensure this final slice doesn't extend beyond the width of p->s_w + if (s->x1 == SLICE_W && sx == p->s_w - 1 && y == p->rgba_overlay->h - 1) + s->x1 = MPMIN(p->w - ((p->s_w - 1) * SLICE_W), s->x1); + + if (s->x0 <= s->x1) { + memset(px + s->x0, 0, (s->x1 - s->x0) * 4); + *s = (struct slice){SLICE_W, 0}; + } + + px += SLICE_W; + } + } + + p->any_osd = false; +} + +static struct mp_sws_context *alloc_scaler(struct mp_draw_sub_cache *p) +{ + struct mp_sws_context *s = mp_sws_alloc(p); + mp_sws_enable_cmdline_opts(s, p->global); + return s; +} + +static void init_general(struct mp_draw_sub_cache *p) +{ + p->sub_scale = alloc_scaler(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; + mp_image_params_guess_csp(params); + + bool need_premul = params->alpha != MP_ALPHA_PREMUL && + (mp_imgfmt_get_desc(params->imgfmt).flags & MP_IMGFLAG_ALPHA); + + // Intermediate format for video_overlay. Requirements: + // - same subsampling as video + // - uses video colorspace + // - has alpha + // - repacker support (to the format used in p->blend_line) + // - probably 8 bit per component rather than something wasteful or strange + struct mp_regular_imgfmt vfdesc = {0}; + + int rflags = REPACK_CREATE_EXPAND_8BIT; + bool use_shortcut = false; + + p->video_to_f32 = mp_repack_create_planar(params->imgfmt, false, rflags); + talloc_steal(p, p->video_to_f32); + if (!p->video_to_f32) + return false; + mp_get_regular_imgfmt(&vfdesc, mp_repack_get_format_dst(p->video_to_f32)); + assert(vfdesc.num_planes); // must have succeeded + + if (params->color.space == MP_CSP_RGB && vfdesc.num_planes >= 3) { + use_shortcut = true; + + if (vfdesc.component_type == MP_COMPONENT_TYPE_UINT && + vfdesc.component_size == 1 && vfdesc.component_pad == 0) + p->blend_line = blend_line_u8; + } + + // If no special blender is available, blend in float. + if (!p->blend_line) { + TA_FREEP(&p->video_to_f32); + + rflags |= REPACK_CREATE_PLANAR_F32; + + p->video_to_f32 = mp_repack_create_planar(params->imgfmt, false, rflags); + talloc_steal(p, p->video_to_f32); + if (!p->video_to_f32) + return false; + + mp_get_regular_imgfmt(&vfdesc, mp_repack_get_format_dst(p->video_to_f32)); + assert(vfdesc.component_type == MP_COMPONENT_TYPE_FLOAT); + + p->blend_line = blend_line_f32; + } + + p->scale_in_tiles = SCALE_IN_TILES; + + int vid_f32_fmt = mp_repack_get_format_dst(p->video_to_f32); + + p->video_from_f32 = mp_repack_create_planar(params->imgfmt, true, rflags); + talloc_steal(p, p->video_from_f32); + if (!p->video_from_f32) + return false; + + assert(mp_repack_get_format_dst(p->video_to_f32) == + mp_repack_get_format_src(p->video_from_f32)); + + int overlay_fmt = 0; + if (use_shortcut) { + // No point in doing anything fancy. + overlay_fmt = IMGFMT_BGRA; + p->scale_in_tiles = false; + } else { + struct mp_regular_imgfmt odesc = vfdesc; + // Just use 8 bit as well (should be fine, may use less memory). + odesc.component_type = MP_COMPONENT_TYPE_UINT; + odesc.component_size = 1; + odesc.component_pad = 0; + + // Ensure there's alpha. + if (odesc.planes[odesc.num_planes - 1].components[0] != 4) { + if (odesc.num_planes >= 4) + return false; // wat + odesc.planes[odesc.num_planes++] = + (struct mp_regular_imgfmt_plane){1, {4}}; + } + + overlay_fmt = mp_find_regular_imgfmt(&odesc); + p->scale_in_tiles = odesc.chroma_xs || odesc.chroma_ys; + } + if (!overlay_fmt) + return false; + + p->overlay_to_f32 = mp_repack_create_planar(overlay_fmt, false, rflags); + talloc_steal(p, p->overlay_to_f32); + if (!p->overlay_to_f32) + return false; + + int render_fmt = mp_repack_get_format_dst(p->overlay_to_f32); + + struct mp_regular_imgfmt ofdesc = {0}; + mp_get_regular_imgfmt(&ofdesc, render_fmt); + + if (ofdesc.planes[ofdesc.num_planes - 1].components[0] != 4) + return false; + + // The formats must be the same, minus possible lack of alpha in vfdesc. + if (ofdesc.num_planes != vfdesc.num_planes && + ofdesc.num_planes - 1 != vfdesc.num_planes) + return false; + for (int n = 0; n < vfdesc.num_planes; n++) { + if (vfdesc.planes[n].components[0] != ofdesc.planes[n].components[0]) + return false; + } + + p->align_x = mp_repack_get_align_x(p->video_to_f32); + p->align_y = mp_repack_get_align_y(p->video_to_f32); + + assert(p->align_x >= mp_repack_get_align_x(p->overlay_to_f32)); + assert(p->align_y >= mp_repack_get_align_y(p->overlay_to_f32)); + + if (p->align_x > SLICE_W || p->align_y > TILE_H) + return false; + + p->w = MP_ALIGN_UP(params->w, p->align_x); + int slice_h = p->align_y; + p->h = MP_ALIGN_UP(params->h, slice_h); + + // Size of the overlay. If scaling in tiles, round up to tiles, so we don't + // need to reinit the scale for right/bottom tiles. + int w = p->w; + int h = p->h; + if (p->scale_in_tiles) { + w = MP_ALIGN_UP(w, SLICE_W); + h = MP_ALIGN_UP(h, TILE_H); + } + + p->rgba_overlay = talloc_steal(p, mp_image_alloc(IMGFMT_BGRA, w, h)); + p->overlay_tmp = talloc_steal(p, mp_image_alloc(render_fmt, SLICE_W, slice_h)); + p->video_tmp = talloc_steal(p, mp_image_alloc(vid_f32_fmt, SLICE_W, slice_h)); + if (!p->rgba_overlay || !p->overlay_tmp || !p->video_tmp) + return false; + + mp_image_params_guess_csp(&p->rgba_overlay->params); + p->rgba_overlay->params.alpha = MP_ALPHA_PREMUL; + + p->overlay_tmp->params.color = params->color; + p->video_tmp->params.color = params->color; + + if (p->rgba_overlay->imgfmt == overlay_fmt) { + if (!repack_config_buffers(p->overlay_to_f32, 0, p->overlay_tmp, + 0, p->rgba_overlay, NULL)) + return false; + } else { + // Generally non-RGB. + p->video_overlay = talloc_steal(p, mp_image_alloc(overlay_fmt, w, h)); + if (!p->video_overlay) + return false; + + p->video_overlay->params.color = params->color; + p->video_overlay->params.chroma_location = params->chroma_location; + p->video_overlay->params.alpha = MP_ALPHA_PREMUL; + + if (p->scale_in_tiles) + p->video_overlay->params.chroma_location = MP_CHROMA_CENTER; + + p->rgba_to_overlay = alloc_scaler(p); + p->rgba_to_overlay->allow_zimg = true; + if (!mp_sws_supports_formats(p->rgba_to_overlay, + p->video_overlay->imgfmt, p->rgba_overlay->imgfmt)) + return false; + + if (!repack_config_buffers(p->overlay_to_f32, 0, p->overlay_tmp, + 0, p->video_overlay, NULL)) + return false; + + // Setup a scaled alpha plane if chroma-subsampling is present. + int xs = p->video_overlay->fmt.chroma_xs; + int ys = p->video_overlay->fmt.chroma_ys; + if (xs || ys) { + // Require float so format selection becomes simpler (maybe). + assert(rflags & REPACK_CREATE_PLANAR_F32); + + // For extracting the alpha plane, construct a gray format that is + // compatible with the alpha one. + struct mp_regular_imgfmt odesc = {0}; + mp_get_regular_imgfmt(&odesc, overlay_fmt); + assert(odesc.component_size); + int aplane = odesc.num_planes - 1; + assert(odesc.planes[aplane].num_components == 1); + assert(odesc.planes[aplane].components[0] == 4); + struct mp_regular_imgfmt cadesc = odesc; + cadesc.num_planes = 1; + cadesc.planes[0] = (struct mp_regular_imgfmt_plane){1, {1}}; + cadesc.chroma_xs = cadesc.chroma_ys = 0; + + int calpha_fmt = mp_find_regular_imgfmt(&cadesc); + if (!calpha_fmt) + return false; + + // Unscaled alpha plane from p->video_overlay. + p->alpha_overlay = talloc_zero(p, struct mp_image); + mp_image_setfmt(p->alpha_overlay, calpha_fmt); + mp_image_set_size(p->alpha_overlay, w, h); + p->alpha_overlay->planes[0] = p->video_overlay->planes[aplane]; + p->alpha_overlay->stride[0] = p->video_overlay->stride[aplane]; + + // Full range gray always has the same range as alpha. + p->alpha_overlay->params.color.levels = MP_CSP_LEVELS_PC; + mp_image_params_guess_csp(&p->alpha_overlay->params); + + p->calpha_overlay = + talloc_steal(p, mp_image_alloc(calpha_fmt, w >> xs, h >> ys)); + if (!p->calpha_overlay) + return false; + p->calpha_overlay->params.color = p->alpha_overlay->params.color; + + p->calpha_to_f32 = mp_repack_create_planar(calpha_fmt, false, rflags); + talloc_steal(p, p->calpha_to_f32); + if (!p->calpha_to_f32) + return false; + + int af32_fmt = mp_repack_get_format_dst(p->calpha_to_f32); + p->calpha_tmp = talloc_steal(p, mp_image_alloc(af32_fmt, SLICE_W, 1)); + if (!p->calpha_tmp) + return false; + + if (!repack_config_buffers(p->calpha_to_f32, 0, p->calpha_tmp, + 0, p->calpha_overlay, NULL)) + return false; + + p->alpha_to_calpha = alloc_scaler(p); + if (!mp_sws_supports_formats(p->alpha_to_calpha, + calpha_fmt, calpha_fmt)) + return false; + } + } + + if (need_premul) { + p->premul = alloc_scaler(p); + p->unpremul = alloc_scaler(p); + p->premul_tmp = mp_image_alloc(params->imgfmt, params->w, params->h); + talloc_steal(p, p->premul_tmp); + if (!p->premul_tmp) + return false; + mp_image_set_params(p->premul_tmp, params); + p->premul_tmp->params.alpha = MP_ALPHA_PREMUL; + + // Only zimg supports this. + p->premul->force_scaler = MP_SWS_ZIMG; + 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){.global = p->global, .params = *params}; + if (!(to_video ? reinit_to_video(p) : reinit_to_overlay(p))) { + talloc_free_children(p); + *p = (struct mp_draw_sub_cache){.global = p->global}; + return false; + } + } + return true; +} + +char *mp_draw_sub_get_dbg_info(struct mp_draw_sub_cache *p) +{ + assert(p); + + return talloc_asprintf(NULL, + "align=%d:%d ov=%-7s, ov_f=%s, v_f=%s, a=%s, ca=%s, ca_f=%s", + p->align_x, p->align_y, + mp_imgfmt_to_name(p->video_overlay ? p->video_overlay->imgfmt : 0), + mp_imgfmt_to_name(p->overlay_tmp->imgfmt), + mp_imgfmt_to_name(p->video_tmp->imgfmt), + mp_imgfmt_to_name(p->alpha_overlay ? p->alpha_overlay->imgfmt : 0), + mp_imgfmt_to_name(p->calpha_overlay ? p->calpha_overlay->imgfmt : 0), + mp_imgfmt_to_name(p->calpha_tmp ? p->calpha_tmp->imgfmt : 0)); +} + +struct mp_draw_sub_cache *mp_draw_sub_alloc(void *ta_parent, struct mpv_global *g) +{ + struct mp_draw_sub_cache *c = talloc_zero(ta_parent, struct mp_draw_sub_cache); + c->global = g; + return c; +} + +// For tests. +struct mp_draw_sub_cache *mp_draw_sub_alloc_test(struct mp_image *dst) +{ + struct mp_draw_sub_cache *c = talloc_zero(NULL, struct mp_draw_sub_cache); + reinit_to_video(c); + return c; +} + +bool mp_draw_sub_bitmaps(struct mp_draw_sub_cache *p, struct mp_image *dst, + struct sub_bitmap_list *sbs_list) +{ + bool ok = false; + + // dst must at least be as large as the bounding box, or you may get memory + // corruption. + assert(dst->w >= sbs_list->w); + assert(dst->h >= sbs_list->h); + + if (!check_reinit(p, &dst->params, true)) + return false; + + if (p->change_id != sbs_list->change_id) { + p->change_id = sbs_list->change_id; + + clear_rgba_overlay(p); + + for (int n = 0; n < sbs_list->num_items; n++) { + if (!render_sb(p, sbs_list->items[n])) + goto done; + } + + if (!convert_to_video_overlay(p)) + goto done; + } + + 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 (target != dst) { + if (mp_sws_scale(p->unpremul, dst, p->premul_tmp) < 0) + goto done; + } + } + + ok = true; + +done: + 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); + // Ensure this does not extend beyond the total width + rc->x1 = MPCLAMP(xpos + s->x1, rc->x1, p->w); + } + } + } +} + +// 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 |