gstreamer/subprojects/gst-plugins-bad/gst/dvdspu/gstspu-vobsub-render.c
Arnaud Vrac b0ce390d50 dvdspu: render to AYUV overlay
Instead of only supporting writing SPU data directly to YUV frames,
render the SPU data to an intermediate AYUV overlay buffer. The overlay
data is then blended to the video frame.

For the PGS format, the overlay buffer size is set to the size of the
Composition Window, and its position in the overlay composition is set
to the window position. The objects to render are now cropped when the
cropping flag is set.

For the Vobsub format, the overlay buffer size is set to the size of the
Display Area.

Once rendered, the overlay composition rectangle is now moved and scaled
to fit the video output size, to avoid clipping.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5827>
2024-01-02 14:13:03 +00:00

471 lines
15 KiB
C

/* GStreamer DVD Sub-Picture Unit
* Copyright (C) 2007 Fluendo S.A. <info@fluendo.com>
* Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <gst/gst.h>
#include "gstdvdspu.h"
GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug);
#define GST_CAT_DEFAULT dvdspu_debug
static void
gstspu_vobsub_recalc_palette (GstDVDSpu * dvdspu,
SpuColour * dest, guint8 * idx, guint8 * alpha)
{
SpuState *state = &dvdspu->spu_state;
gint i;
if (state->vobsub.current_clut[idx[0]] != 0) {
for (i = 0; i < 4; i++, dest++) {
guint32 col = state->vobsub.current_clut[idx[i]];
/* Convert incoming 4-bit alpha to 8 bit for blending */
dest->A = (alpha[i] << 4) | alpha[i];
dest->Y = ((col >> 16) & 0xff) * dest->A / 255;
/* U/V are stored as V/U in the clut words, so switch them */
dest->V = ((col >> 8) & 0xff) * dest->A / 255;
dest->U = (col & 0xff) * dest->A / 255;
}
} else {
int y = 240;
/* The CLUT presumably hasn't been set, so we'll just guess some
* values for the non-transparent colors (white, grey, black) */
for (i = 0; i < 4; i++, dest++) {
dest->A = (alpha[i] << 4) | alpha[i];
if (alpha[i] != 0) {
dest[0].Y = y * dest[0].A / 255;
y -= 112;
if (y < 0)
y = 0;
}
dest[0].U = 128 * dest[0].A / 255;
dest[0].V = 128 * dest[0].A / 255;
}
}
}
/* Recalculate the main, HL & ChgCol palettes */
static void
gstspu_vobsub_update_palettes (GstDVDSpu * dvdspu, SpuState * state)
{
guint8 index[4]; /* Indices for the palette */
guint8 alpha[4]; /* Alpha values the palette */
if (state->vobsub.main_pal_dirty) {
gstspu_vobsub_recalc_palette (dvdspu, state->vobsub.main_pal,
state->vobsub.main_idx, state->vobsub.main_alpha);
/* Need to refresh the hl_ctrl info copies of the main palette too */
memcpy (state->vobsub.hl_ctrl_i.pix_ctrl_i[0].pal_cache,
state->vobsub.main_pal, 4 * sizeof (SpuColour));
memcpy (state->vobsub.hl_ctrl_i.pix_ctrl_i[2].pal_cache,
state->vobsub.main_pal, 4 * sizeof (SpuColour));
state->vobsub.main_pal_dirty = FALSE;
}
if (state->vobsub.hl_pal_dirty) {
gstspu_vobsub_recalc_palette (dvdspu,
state->vobsub.hl_ctrl_i.pix_ctrl_i[1].pal_cache, state->vobsub.hl_idx,
state->vobsub.hl_alpha);
state->vobsub.hl_pal_dirty = FALSE;
}
/* Update the offset positions for the highlight region */
if (state->vobsub.hl_rect.top != -1) {
state->vobsub.hl_ctrl_i.top = state->vobsub.hl_rect.top;
state->vobsub.hl_ctrl_i.bottom = state->vobsub.hl_rect.bottom;
state->vobsub.hl_ctrl_i.n_changes = 3;
state->vobsub.hl_ctrl_i.pix_ctrl_i[0].left = 0;
state->vobsub.hl_ctrl_i.pix_ctrl_i[1].left = state->vobsub.hl_rect.left;
state->vobsub.hl_ctrl_i.pix_ctrl_i[2].left =
state->vobsub.hl_rect.right + 1;
}
if (state->vobsub.line_ctrl_i_pal_dirty) {
gint16 l, c;
GST_LOG_OBJECT (dvdspu, "Updating chg-col-con palettes");
for (l = 0; l < state->vobsub.n_line_ctrl_i; l++) {
SpuVobsubLineCtrlI *cur_line_ctrl = state->vobsub.line_ctrl_i + l;
for (c = 0; c < cur_line_ctrl->n_changes; c++) {
SpuVobsubPixCtrlI *cur = cur_line_ctrl->pix_ctrl_i + c;
index[3] = (cur->palette >> 28) & 0x0f;
index[2] = (cur->palette >> 24) & 0x0f;
index[1] = (cur->palette >> 20) & 0x0f;
index[0] = (cur->palette >> 16) & 0x0f;
alpha[3] = (cur->palette >> 12) & 0x0f;
alpha[2] = (cur->palette >> 8) & 0x0f;
alpha[1] = (cur->palette >> 4) & 0x0f;
alpha[0] = (cur->palette) & 0x0f;
gstspu_vobsub_recalc_palette (dvdspu, cur->pal_cache, index, alpha);
}
}
state->vobsub.line_ctrl_i_pal_dirty = FALSE;
}
}
static inline guint8
gstspu_vobsub_get_nibble (SpuState * state, guint16 * rle_offset)
{
guint8 ret;
if (G_UNLIKELY (*rle_offset >= state->vobsub.max_offset))
return 0; /* Overran the buffer */
ret = state->vobsub.pix_buf_map.data[(*rle_offset) / 2];
/* If the offset is even, we shift the answer down 4 bits, otherwise not */
if (*rle_offset & 0x01)
ret &= 0x0f;
else
ret = ret >> 4;
(*rle_offset)++;
return ret;
}
static guint16
gstspu_vobsub_get_rle_code (SpuState * state, guint16 * rle_offset)
{
guint16 code;
code = gstspu_vobsub_get_nibble (state, rle_offset);
if (code < 0x4) { /* 4 .. f */
code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset);
if (code < 0x10) { /* 1x .. 3x */
code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset);
if (code < 0x40) { /* 04x .. 0fx */
code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset);
}
}
}
return code;
}
static inline gboolean
gstspu_vobsub_draw_rle_run (SpuState * state, GstVideoFrame * frame,
gint16 x, gint16 end, SpuColour * colour)
{
GST_TRACE ("Y: %d x: %d end %d %d %d %d %d",
state->vobsub.cur_Y, x, end, colour->Y, colour->U, colour->V, colour->A);
if (colour->A > 0) {
gint i;
guint8 *data;
guint8 inv_A = 255 - colour->A;
data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
data += GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) *
(state->vobsub.cur_Y - state->vobsub.disp_rect.top);
x -= state->vobsub.disp_rect.left;
end -= state->vobsub.disp_rect.left;
for (i = x; i < end; i++) {
SpuColour *pix = &((SpuColour *) data)[x++];
if (pix->A == 0) {
memcpy (pix, colour, sizeof (*pix));
} else {
pix->A = colour->A;
pix->Y = colour->Y + pix->Y * inv_A / 255;
pix->U = colour->U + pix->U * inv_A / 255;
pix->V = colour->V + pix->V * inv_A / 255;
}
}
return TRUE;
}
return FALSE;
}
static inline gint16
rle_end_x (guint16 rle_code, gint16 x, gint16 end)
{
/* run length = rle_code >> 2 */
if (G_UNLIKELY (((rle_code >> 2) == 0)))
return end;
else
return MIN (end, x + (rle_code >> 2));
}
static gboolean gstspu_vobsub_render_line_with_chgcol (SpuState * state,
GstVideoFrame * frame, guint16 * rle_offset);
static gboolean gstspu_vobsub_update_chgcol (SpuState * state);
static gboolean
gstspu_vobsub_render_line (SpuState * state, GstVideoFrame * frame,
guint16 * rle_offset)
{
gint16 x, next_x, end, rle_code, next_draw_x;
SpuColour *colour;
gboolean visible = FALSE;
/* Check for special case of chg_col info to use (either highlight or
* ChgCol command */
if (state->vobsub.cur_chg_col != NULL) {
if (gstspu_vobsub_update_chgcol (state)) {
/* Check the top & bottom, because we might not be within the region yet */
if (state->vobsub.cur_Y >= state->vobsub.cur_chg_col->top &&
state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) {
return gstspu_vobsub_render_line_with_chgcol (state, frame, rle_offset);
}
}
}
/* No special case. Render as normal */
/* We always need to start our RLE decoding byte_aligned */
*rle_offset = GST_ROUND_UP_2 (*rle_offset);
x = state->vobsub.disp_rect.left;
end = state->vobsub.disp_rect.right + 1;
while (x < end) {
rle_code = gstspu_vobsub_get_rle_code (state, rle_offset);
colour = &state->vobsub.main_pal[rle_code & 3];
next_x = rle_end_x (rle_code, x, end);
next_draw_x = next_x;
if (next_draw_x > state->vobsub.disp_rect.right)
next_draw_x = state->vobsub.disp_rect.right; /* ensure no overflow */
/* Now draw the run between [x,next_x) */
visible |=
gstspu_vobsub_draw_rle_run (state, frame, x, next_draw_x, colour);
x = next_x;
}
return visible;
}
static gboolean
gstspu_vobsub_update_chgcol (SpuState * state)
{
if (state->vobsub.cur_chg_col == NULL)
return FALSE;
if (state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom)
return TRUE;
while (state->vobsub.cur_chg_col < state->vobsub.cur_chg_col_end) {
if (state->vobsub.cur_Y >= state->vobsub.cur_chg_col->top &&
state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) {
#if 0
g_print ("Stopped @ entry %d with top %d bottom %d, cur_y %d",
(gint16) (state->vobsub.cur_chg_col - state->vobsub.line_ctrl_i),
state->vobsub.cur_chg_col->top, state->vobsub.cur_chg_col->bottom, y);
#endif
return TRUE;
}
state->vobsub.cur_chg_col++;
}
/* Finished all our cur_chg_col entries. Use the main palette from here on */
state->vobsub.cur_chg_col = NULL;
return FALSE;
}
static gboolean
gstspu_vobsub_render_line_with_chgcol (SpuState * state, GstVideoFrame * frame,
guint16 * rle_offset)
{
SpuVobsubLineCtrlI *chg_col = state->vobsub.cur_chg_col;
gint16 x, next_x, disp_end, rle_code, run_end, run_draw_end;
SpuColour *colour;
SpuVobsubPixCtrlI *cur_pix_ctrl;
SpuVobsubPixCtrlI *next_pix_ctrl;
SpuVobsubPixCtrlI *end_pix_ctrl;
SpuVobsubPixCtrlI dummy_pix_ctrl;
gboolean visible = FALSE;
gint16 cur_reg_end;
gint i;
/* We always need to start our RLE decoding byte_aligned */
*rle_offset = GST_ROUND_UP_2 (*rle_offset);
/* Our run will cover the display rect */
x = state->vobsub.disp_rect.left;
disp_end = state->vobsub.disp_rect.right + 1;
/* Work out the first pixel control info, which may point to the dummy entry if
* the global palette/alpha need using initially */
cur_pix_ctrl = chg_col->pix_ctrl_i;
end_pix_ctrl = chg_col->pix_ctrl_i + chg_col->n_changes;
if (cur_pix_ctrl->left != 0) {
next_pix_ctrl = cur_pix_ctrl;
cur_pix_ctrl = &dummy_pix_ctrl;
for (i = 0; i < 4; i++) /* Copy the main palette to our dummy entry */
dummy_pix_ctrl.pal_cache[i] = state->vobsub.main_pal[i];
} else {
next_pix_ctrl = cur_pix_ctrl + 1;
}
if (next_pix_ctrl < end_pix_ctrl)
cur_reg_end = next_pix_ctrl->left;
else
cur_reg_end = disp_end;
/* Render stuff */
while (x < disp_end) {
rle_code = gstspu_vobsub_get_rle_code (state, rle_offset);
next_x = rle_end_x (rle_code, x, disp_end);
/* Now draw the run between [x,next_x), crossing palette regions as needed */
while (x < next_x) {
run_end = MIN (next_x, cur_reg_end);
run_draw_end = run_end;
if (run_draw_end > state->vobsub.disp_rect.right)
run_draw_end = state->vobsub.disp_rect.right; /* ensure no overflow */
if (G_LIKELY (x < run_end)) {
colour = &cur_pix_ctrl->pal_cache[rle_code & 3];
visible |= gstspu_vobsub_draw_rle_run (state, frame, x,
run_draw_end, colour);
x = run_end;
}
if (x >= cur_reg_end) {
/* Advance to next region */
cur_pix_ctrl = next_pix_ctrl;
next_pix_ctrl++;
if (next_pix_ctrl < end_pix_ctrl)
cur_reg_end = next_pix_ctrl->left;
else
cur_reg_end = disp_end;
}
}
}
return visible;
}
static void
gstspu_vobsub_draw_highlight (SpuState * state,
GstVideoFrame * frame, SpuRect * rect)
{
SpuColour *cur;
SpuRect r;
guint8 *data;
guint stride;
gint16 pos;
r.left = rect->left - state->vobsub.disp_rect.left;
r.right = rect->right - state->vobsub.disp_rect.left;
r.top = rect->top - state->vobsub.disp_rect.top;
r.bottom = rect->bottom - state->vobsub.disp_rect.top;
rect = &r;
data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
cur = (SpuColour *) (data + stride * rect->top);
for (pos = rect->left; pos < rect->right; pos++)
cur[pos].A = 0x80;
cur = (SpuColour *) (data + stride * (rect->bottom - 1));
for (pos = rect->left; pos < rect->right; pos++)
cur[pos].A = 0x80;
for (pos = rect->top; pos < rect->bottom; pos++) {
cur = (SpuColour *) (data + stride * pos);
cur[rect->left].A = 0x80;
cur[rect->right - 1].A = 0x80;
}
}
void
gstspu_vobsub_render (GstDVDSpu * dvdspu, GstVideoFrame * frame)
{
SpuState *state = &dvdspu->spu_state;
gint y, last_y;
guint16 cur_offsets[2];
/* Set up our initial state */
if (G_UNLIKELY (state->vobsub.pix_buf == NULL))
return;
if (!gst_buffer_map (state->vobsub.pix_buf, &state->vobsub.pix_buf_map,
GST_MAP_READ))
return;
GST_DEBUG_OBJECT (dvdspu,
"Rendering SPU. disp_rect %d,%d to %d,%d. hl_rect %d,%d to %d,%d",
state->vobsub.disp_rect.left, state->vobsub.disp_rect.top,
state->vobsub.disp_rect.right, state->vobsub.disp_rect.bottom,
state->vobsub.hl_rect.left, state->vobsub.hl_rect.top,
state->vobsub.hl_rect.right, state->vobsub.hl_rect.bottom);
/* Update all the palette caches */
gstspu_vobsub_update_palettes (dvdspu, state);
/* Set up HL or Change Color & Contrast rect tracking */
if (state->vobsub.hl_rect.top != -1) {
state->vobsub.cur_chg_col = &state->vobsub.hl_ctrl_i;
state->vobsub.cur_chg_col_end = state->vobsub.cur_chg_col + 1;
} else if (state->vobsub.n_line_ctrl_i > 0) {
state->vobsub.cur_chg_col = state->vobsub.line_ctrl_i;
state->vobsub.cur_chg_col_end =
state->vobsub.cur_chg_col + state->vobsub.n_line_ctrl_i;
} else
state->vobsub.cur_chg_col = NULL;
/* We start rendering from the first line of the display rect */
y = state->vobsub.disp_rect.top;
last_y = state->vobsub.disp_rect.bottom;
/* When reading RLE data, we track the offset in nibbles... */
state->vobsub.max_offset = state->vobsub.pix_buf_map.size * 2;
if (y & 1) {
cur_offsets[1] = state->vobsub.pix_data[0] * 2;
cur_offsets[0] = state->vobsub.pix_data[1] * 2;
} else {
cur_offsets[0] = state->vobsub.pix_data[0] * 2;
cur_offsets[1] = state->vobsub.pix_data[1] * 2;
}
/* Render line by line */
for (state->vobsub.cur_Y = y; state->vobsub.cur_Y <= last_y;
state->vobsub.cur_Y++) {
gstspu_vobsub_render_line (state, frame,
&cur_offsets[state->vobsub.cur_Y & 1]);
}
/* for debugging purposes, draw a faint rectangle at the edges of the disp_rect */
if ((dvdspu_debug_flags & GST_DVD_SPU_DEBUG_RENDER_RECTANGLE) != 0) {
gstspu_vobsub_draw_highlight (state, frame, &state->vobsub.disp_rect);
}
/* For debugging purposes, draw a faint rectangle around the highlight rect */
if ((dvdspu_debug_flags & GST_DVD_SPU_DEBUG_HIGHLIGHT_RECTANGLE) != 0
&& state->vobsub.hl_rect.top != -1) {
gstspu_vobsub_draw_highlight (state, frame, &state->vobsub.hl_rect);
}
gst_buffer_unmap (state->vobsub.pix_buf, &state->vobsub.pix_buf_map);
}