video-converter: add dithering

Use the new dither object to perform dithering.
Add option to select dithering method.
Add option to quantize to a specific value
This commit is contained in:
Wim Taymans 2014-12-02 11:40:59 +01:00
parent dfb202a117
commit d03136f1ea
3 changed files with 133 additions and 115 deletions

View file

@ -149,9 +149,8 @@ struct _GstVideoConverter
gint current_bits;
GstStructure *config;
GstVideoDitherMethod dither;
guint16 *errline;
guint16 *tmpline;
gboolean fill_border;
gpointer borderline;
@ -159,7 +158,6 @@ struct _GstVideoConverter
void (*convert) (GstVideoConverter * convert, const GstVideoFrame * src,
GstVideoFrame * dest);
void (*dither16) (GstVideoConverter * convert, guint16 * pixels, int j);
/* data for unpack */
GstLineCache *unpack_lines;
@ -214,6 +212,10 @@ struct _GstVideoConverter
guint down_n_lines;
gint down_offset;
/* dither */
GstLineCache *dither_lines;
GstVideoDither *dither;
/* pack */
GstLineCache *pack_lines;
guint pack_nlines;
@ -396,6 +398,8 @@ static gboolean do_vscale_lines (GstLineCache * cache, gint out_line,
gint in_line, gpointer user_data);
static gboolean do_hscale_lines (GstLineCache * cache, gint out_line,
gint in_line, gpointer user_data);
static gboolean do_dither_lines (GstLineCache * cache, gint out_line,
gint in_line, gpointer user_data);
typedef struct
{
@ -530,6 +534,8 @@ get_opt_str (GstVideoConverter * convert, const gchar * opt, const gchar * def)
#define DEFAULT_OPT_PRIMARIES_MODE "none"
#define DEFAULT_OPT_RESAMPLER_METHOD GST_VIDEO_RESAMPLER_METHOD_CUBIC
#define DEFAULT_OPT_RESAMPLER_TAPS 0
#define DEFAULT_OPT_DITHER_METHOD GST_VIDEO_DITHER_BAYER
#define DEFAULT_OPT_DITHER_QUANTIZATION 1
#define GET_OPT_FILL_BORDER(c) get_opt_bool(c, \
GST_VIDEO_CONVERTER_OPT_FILL_BORDER, DEFAULT_OPT_FILL_BORDER)
@ -546,6 +552,11 @@ get_opt_str (GstVideoConverter * convert, const gchar * opt, const gchar * def)
DEFAULT_OPT_RESAMPLER_METHOD)
#define GET_OPT_RESAMPLER_TAPS(c) get_opt_uint(c, \
GST_VIDEO_CONVERTER_OPT_RESAMPLER_TAPS, DEFAULT_OPT_RESAMPLER_TAPS)
#define GET_OPT_DITHER_METHOD(c) get_opt_enum(c, \
GST_VIDEO_CONVERTER_OPT_DITHER_METHOD, GST_TYPE_VIDEO_DITHER_METHOD, \
DEFAULT_OPT_DITHER_METHOD)
#define GET_OPT_DITHER_QUANTIZATION(c) get_opt_uint(c, \
GST_VIDEO_CONVERTER_OPT_DITHER_QUANTIZATION, DEFAULT_OPT_DITHER_QUANTIZATION)
#define CHECK_MATRIX_FULL(c) (!g_strcmp0(GET_OPT_MATRIX_MODE(c), "full"))
#define CHECK_MATRIX_NO_YUV(c) (!g_strcmp0(GET_OPT_MATRIX_MODE(c), "no-yuv"))
@ -1419,6 +1430,65 @@ chain_downsample (GstVideoConverter * convert, GstLineCache * prev)
return prev;
}
static GstLineCache *
chain_dither (GstVideoConverter * convert, GstLineCache * prev)
{
gint i;
gboolean do_dither = FALSE;
GstVideoDitherFlags flags = 0;
GstVideoDitherMethod method;
guint quant[4], target_quant;
method = GET_OPT_DITHER_METHOD (convert);
target_quant = GET_OPT_DITHER_QUANTIZATION (convert);
GST_DEBUG ("method %d, target-quantization %d", method, target_quant);
if (convert->pack_pal) {
quant[0] = 47;
quant[1] = 47;
quant[2] = 47;
quant[3] = 1;
do_dither = TRUE;
} else {
for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) {
gint depth;
depth = convert->out_info.finfo->depth[i];
if (depth == 0) {
quant[i] = 0;
continue;
}
if (convert->current_bits >= depth) {
quant[i] = 1 << (convert->current_bits - depth);
if (target_quant > quant[i]) {
flags |= GST_VIDEO_DITHER_FLAG_QUANTIZE;
quant[i] = target_quant;
}
do_dither = TRUE;
} else {
quant[i] = 0;
}
}
}
if (do_dither) {
GST_DEBUG ("chain dither");
convert->dither = gst_video_dither_new (method,
flags, convert->pack_format, quant, convert->current_width);
prev = convert->dither_lines = gst_line_cache_new (prev);
prev->write_input = TRUE;
prev->pass_alloc = TRUE;
prev->n_lines = 1;
prev->stride = convert->current_pstride * convert->current_width;
gst_line_cache_set_need_line_func (prev, do_dither_lines, convert, NULL);
}
return prev;
}
static GstLineCache *
chain_pack (GstVideoConverter * convert, GstLineCache * prev)
{
@ -1536,9 +1606,7 @@ gst_video_converter_new (GstVideoInfo * in_info, GstVideoInfo * out_info,
convert->out_info = *out_info;
/* default config */
convert->config = gst_structure_new ("GstVideoConverter",
GST_VIDEO_CONVERTER_OPT_DITHER_METHOD, GST_TYPE_VIDEO_DITHER_METHOD,
GST_VIDEO_DITHER_NONE, NULL);
convert->config = gst_structure_new_empty ("GstVideoConverter");
if (config)
gst_video_converter_set_config (convert, config);
@ -1616,12 +1684,13 @@ gst_video_converter_new (GstVideoInfo * in_info, GstVideoInfo * out_info,
prev = chain_convert_to_YUV (convert, prev);
/* downsample chroma */
prev = chain_downsample (convert, prev);
/* dither */
prev = chain_dither (convert, prev);
/* pack into final format */
convert->pack_lines = chain_pack (convert, prev);
width = MAX (convert->in_maxwidth, convert->out_maxwidth);
width += convert->out_x;
convert->errline = g_malloc0 (sizeof (guint16) * width * 4);
if (convert->fill_border && (convert->out_height < convert->out_maxheight ||
convert->out_width < convert->out_maxwidth)) {
@ -1711,10 +1780,13 @@ gst_video_converter_free (GstVideoConverter * convert)
if (convert->downsample_lines)
gst_line_cache_free (convert->downsample_lines);
if (convert->dither)
gst_video_dither_free (convert->dither);
g_free (convert->gamma_dec.gamma_table);
g_free (convert->gamma_enc.gamma_table);
g_free (convert->errline);
g_free (convert->tmpline);
g_free (convert->borderline);
if (convert->config)
@ -1723,46 +1795,6 @@ gst_video_converter_free (GstVideoConverter * convert)
g_slice_free (GstVideoConverter, convert);
}
static void
video_dither_verterr (GstVideoConverter * convert, guint16 * pixels, int j)
{
int i;
guint16 *errline = convert->errline;
unsigned int mask = 0xff;
for (i = 0; i < 4 * convert->in_width; i++) {
int x = pixels[i] + errline[i];
if (x > 65535)
x = 65535;
pixels[i] = x;
errline[i] = x & mask;
}
}
static void
video_dither_halftone (GstVideoConverter * convert, guint16 * pixels, int j)
{
int i;
static guint16 halftone[8][8] = {
{0, 128, 32, 160, 8, 136, 40, 168},
{192, 64, 224, 96, 200, 72, 232, 104},
{48, 176, 16, 144, 56, 184, 24, 152},
{240, 112, 208, 80, 248, 120, 216, 88},
{12, 240, 44, 172, 4, 132, 36, 164},
{204, 76, 236, 108, 196, 68, 228, 100},
{60, 188, 28, 156, 52, 180, 20, 148},
{252, 142, 220, 92, 244, 116, 212, 84}
};
for (i = 0; i < convert->in_width * 4; i++) {
int x;
x = pixels[i] + halftone[(i >> 2) & 7][j & 7];
if (x > 65535)
x = 65535;
pixels[i] = x;
}
}
static gboolean
copy_config (GQuark field_id, const GValue * value, gpointer user_data)
{
@ -1795,42 +1827,13 @@ gboolean
gst_video_converter_set_config (GstVideoConverter * convert,
GstStructure * config)
{
gint dither;
gboolean res = TRUE;
g_return_val_if_fail (convert != NULL, FALSE);
g_return_val_if_fail (config != NULL, FALSE);
if (gst_structure_get_enum (config, GST_VIDEO_CONVERTER_OPT_DITHER_METHOD,
GST_TYPE_VIDEO_DITHER_METHOD, &dither)) {
gboolean update = TRUE;
switch (dither) {
case GST_VIDEO_DITHER_NONE:
convert->dither16 = NULL;
break;
case GST_VIDEO_DITHER_VERTERR:
convert->dither16 = video_dither_verterr;
break;
case GST_VIDEO_DITHER_HALFTONE:
convert->dither16 = video_dither_halftone;
break;
default:
update = FALSE;
break;
}
if (update)
gst_structure_set (convert->config, GST_VIDEO_CONVERTER_OPT_DITHER_METHOD,
GST_TYPE_VIDEO_DITHER_METHOD, dither, NULL);
else
res = FALSE;
}
if (res)
gst_structure_foreach (config, copy_config, convert);
gst_structure_foreach (config, copy_config, convert);
gst_structure_free (config);
return res;
return TRUE;
}
/**
@ -1964,7 +1967,6 @@ video_converter_compute_resample (GstVideoConverter * convert)
src, 0, frame->data, frame->info.stride, \
frame->info.chroma_site, line, width);
static gpointer
get_dest_line (GstLineCache * cache, gint idx, gpointer user_data)
{
@ -2151,9 +2153,8 @@ do_convert_lines (GstLineCache * cache, gint out_line, gint in_line,
GST_DEBUG ("matrix line %d %p", in_line, srcline);
data->matrix_func (data, srcline);
}
if (convert->dither16)
convert->dither16 (convert, srcline, in_line);
/* FIXME, dither here */
if (out_bits == 8) {
GST_DEBUG ("16->8 line %d %p->%p", in_line, srcline, destline);
video_orc_convert_u16_to_u8 (destline, srcline, width * 4);
@ -2221,6 +2222,26 @@ do_downsample_lines (GstLineCache * cache, gint out_line, gint in_line,
return TRUE;
}
static gboolean
do_dither_lines (GstLineCache * cache, gint out_line, gint in_line,
gpointer user_data)
{
GstVideoConverter *convert = user_data;
gpointer *lines, destline;
lines = gst_line_cache_get_lines (cache->prev, out_line, in_line, 1);
destline = lines[0];
if (convert->dither) {
GST_DEBUG ("Dither line %d %p", in_line, destline);
gst_video_dither_line (convert->dither, destline, 0, out_line,
convert->out_width);
}
gst_line_cache_add_line (cache, in_line, destline);
return TRUE;
}
static void
video_converter_generic (GstVideoConverter * convert, const GstVideoFrame * src,
GstVideoFrame * dest)
@ -2338,8 +2359,8 @@ convert_I420_YUY2 (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2366,8 +2387,8 @@ convert_I420_UYVY (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2393,8 +2414,8 @@ convert_I420_AYUV (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2443,8 +2464,8 @@ convert_I420_Y444 (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2470,8 +2491,8 @@ convert_YUY2_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2538,8 +2559,8 @@ convert_UYVY_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2690,8 +2711,8 @@ convert_Y42B_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -2781,8 +2802,8 @@ convert_Y444_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
/* now handle last line */
if (height & 1) {
UNPACK_FRAME (src, convert->errline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->errline, height - 1, width);
UNPACK_FRAME (src, convert->tmpline, height - 1, convert->in_x, width);
PACK_FRAME (dest, convert->tmpline, height - 1, width);
}
}
@ -3070,6 +3091,9 @@ video_converter_lookup_fastpath (GstVideoConverter * convert)
if (width != convert->out_width || height != convert->out_height)
return FALSE;
if (GET_OPT_DITHER_QUANTIZATION (convert) != 1)
return FALSE;
/* we don't do gamma conversion in fastpath */
in_transf = convert->in_info.colorimetry.transfer;
out_transf = convert->out_info.colorimetry.transfer;
@ -3113,6 +3137,7 @@ video_converter_lookup_fastpath (GstVideoConverter * convert)
if (transforms[i].needs_color_matrix)
video_converter_compute_matrix (convert);
convert->convert = transforms[i].convert;
convert->tmpline = g_malloc0 (sizeof (guint16) * (width + 8) * 4);
return TRUE;
}
}

View file

@ -24,22 +24,6 @@
G_BEGIN_DECLS
/**
* GstVideoDitherMethod:
* @GST_VIDEO_DITHER_NONE: no dithering
* @GST_VIDEO_DITHER_VERTERR: propagate rounding errors downwards
* @GST_VIDEO_DITHER_HALFTONE: Dither with halftone pattern
* @GST_VIDEO_DITHER_HORIZERR: propagate rounding errors right
*
* Different dithering methods to use.
*/
typedef enum {
GST_VIDEO_DITHER_NONE,
GST_VIDEO_DITHER_VERTERR,
GST_VIDEO_DITHER_HALFTONE,
GST_VIDEO_DITHER_HORIZERR
} GstVideoDitherMethod;
/**
* GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD:
*
@ -65,6 +49,15 @@ typedef enum {
*/
#define GST_VIDEO_CONVERTER_OPT_DITHER_METHOD "GstVideoConverter.dither-method"
/**
* GST_VIDEO_CONVERTER_OPT_DITHER_QUANTIZATION:
*
* #G_TYPE_UINT, The quantization amount to dither to. Components will be
* quantized to multiples of this value.
* Default is 1
*/
#define GST_VIDEO_CONVERTER_OPT_DITHER_QUANTIZATION "GstVideoConverter.dither-quantization"
/**
* GST_VIDEO_CONVERTER_OPT_SRC_X:
*

View file

@ -477,7 +477,7 @@ gst_video_dither_line (GstVideoDither * dither, gpointer line, guint x, guint y,
guint width)
{
g_return_if_fail (dither != NULL);
g_return_if_fail (x + width < dither->width);
g_return_if_fail (x + width <= dither->width);
if (dither->func)
dither->func (dither, line, x, y, width);