video-dither: add video dither helper object

Add a new object that implements various dithering methods.
This commit is contained in:
Wim Taymans 2014-12-02 11:32:28 +01:00
parent 921aa7c52b
commit 0f2be22e76
7 changed files with 1762 additions and 7 deletions

View file

@ -2,7 +2,7 @@
ORC_SOURCE=video-orc
include $(top_srcdir)/common/orc.mak
glib_enum_headers = video.h video-format.h video-color.h video-info.h \
glib_enum_headers = video.h video-format.h video-color.h video-info.h video-dither.h \
colorbalance.h navigation.h video-chroma.h video-tile.h video-converter.h \
video-resampler.h
glib_enum_define = GST_VIDEO
@ -27,6 +27,7 @@ libgstvideo_@GST_API_VERSION@_la_SOURCES = \
video-chroma.c \
video-color.c \
video-converter.c \
video-dither.c \
video-info.c \
video-frame.c \
video-scaler.c \
@ -58,6 +59,7 @@ libgstvideo_@GST_API_VERSION@include_HEADERS = \
video-chroma.h \
video-color.h \
video-converter.h \
video-dither.h \
video-info.h \
video-frame.h \
video-scaler.h \

View file

@ -0,0 +1,484 @@
/* GStreamer
* Copyright (C) <2014> Wim Taymans <wim.taymans@gmail.com>
*
* 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.
*/
#include <string.h>
#include "video-dither.h"
#include "video-orc.h"
struct _GstVideoDither
{
GstVideoDitherMethod method;
GstVideoDitherFlags flags;
GstVideoFormat format;
guint width;
guint depth;
guint n_comp;
void (*func) (GstVideoDither * dither, gpointer pixels, guint x, guint y,
guint width);
guint8 shift[4];
guint16 mask[4];
guint64 orc_mask64;
guint32 orc_mask32;
gpointer errors;
};
static void
dither_none_u8_mask (GstVideoDither * dither, gpointer pixels, guint x, guint y,
guint width)
{
guint8 *p = pixels;
video_orc_dither_none_4u8_mask (p + (x * 4), dither->orc_mask32, width);
}
static void
dither_none_u16_mask (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint16 *p = pixels;
video_orc_dither_none_4u16_mask (p + (x * 4), dither->orc_mask64, width);
}
static void
dither_verterr_u8 (GstVideoDither * dither, gpointer pixels, guint x, guint y,
guint width)
{
guint8 *p = pixels;
guint16 *e = dither->errors;
if (y == 0)
memset (e + (x * 4), 0, width * 8);
video_orc_dither_verterr_4u8_mask (p + (x * 4), e + (x * 4),
dither->orc_mask64, width);
}
static void
dither_verterr_u16 (GstVideoDither * dither, gpointer pixels, guint x, guint y,
guint width)
{
guint16 *p = pixels;
guint16 *e = dither->errors;
if (y == 0)
memset (e + (x * 4), 0, width * 8);
{
gint i, end;
guint16 *m = dither->mask;
guint32 v, mp;
end = (width + x) * 4;
for (i = x * 4; i < end; i++) {
mp = m[i & 3];
v = p[i] + e[i];
/* take new error and store */
e[i] = v & mp;
/* quantize and store */
v &= ~mp;
p[i] = CLAMP (v, 0, 65535);
}
}
}
static void
dither_floyd_steinberg_u8 (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint8 *p = pixels;
guint16 *e = dither->errors;
if (y == 0)
memset (e + (x * 4), 0, (width + 1) * 8);
/* add and multiply errors from previous line */
video_orc_dither_fs_muladd_u8 (e + x * 4, width * 4);
#if 1
{
gint i, end;
guint16 *m = dither->mask, mp;
guint16 v;
end = (width + x) * 4;
for (i = x * 4; i < end; i++) {
mp = m[i & 3];
v = p[i] + ((7 * e[i] + e[i + 4]) >> 4);
/* take new error and store */
e[i + 4] = v & mp;
/* quantize and store */
v &= ~mp;
p[i] = CLAMP (v, 0, 255);
}
}
#else
video_orc_dither_fs_add_4u8 (p, e + x * 4, e + (x + 1) * 4,
dither->orc_mask64, width);
#endif
}
static void
dither_floyd_steinberg_u16 (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint16 *p = pixels;
guint16 *e = dither->errors;
if (y == 0)
memset (e + (x * 4), 0, (width + 1) * 8);
{
gint i, end;
guint16 *m = dither->mask, mp;
guint32 v;
end = (width + x) * 4;
for (i = x * 4; i < end; i++) {
mp = m[i & 3];
/* apply previous errors to pixel */
v = p[i] + ((7 * e[i] + e[i + 4] + 5 * e[i + 8] + 3 * e[i + 12]) >> 4);
/* take new error and store */
e[i + 4] = v & mp;
/* quantize and store */
v &= ~mp;
p[i] = CLAMP (v, 0, 65535);
}
}
}
static void
dither_sierra_lite_u8 (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint8 *p = pixels;
guint16 *e = dither->errors;
gint i, end;
guint16 *m = dither->mask, mp;
guint16 v;
if (y == 0)
memset (e + (x * 4), 0, (width + 4) * 8);
end = (width + x) * 4;
for (i = x; i < end; i++) {
mp = m[i & 3];
/* apply previous errors to pixel */
v = p[i] + ((2 * e[i] + e[i + 8] + e[i + 12]) >> 2);
/* store new error */
e[i + 4] = v & mp;
/* quantize and store */
v &= ~mp;
p[i] = CLAMP (v, 0, 255);
}
}
static void
dither_sierra_lite_u16 (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint16 *p = pixels;
guint16 *e = dither->errors;
gint i, end;
guint16 *m = dither->mask, mp;
guint32 v;
if (y == 0)
memset (e + (x * 4), 0, (width + 4) * 8);
end = (width + x) * 4;
for (i = x; i < end; i++) {
mp = m[i & 3];
/* apply previous errors to pixel */
v = p[i] + ((2 * e[i] + e[i + 8] + e[i + 12]) >> 2);
/* store new error */
e[i + 4] = v & mp;
/* quantize and store */
v &= ~mp;
p[i] = CLAMP (v & ~mp, 0, 65535);
}
}
static const guint16 bayer_map[16][16] = {
{0, 128, 32, 160, 8, 136, 40, 168, 2, 130, 34, 162, 10, 138, 42, 170},
{192, 64, 224, 96, 200, 72, 232, 104, 194, 66, 226, 98, 202, 74, 234, 106},
{48, 176, 16, 144, 56, 184, 24, 152, 50, 178, 18, 146, 58, 186, 26, 154},
{240, 112, 208, 80, 248, 120, 216, 88, 242, 114, 210, 82, 250, 122, 218, 90},
{12, 240, 44, 172, 4, 132, 36, 164, 14, 242, 46, 174, 6, 134, 38, 166},
{204, 76, 236, 108, 196, 68, 228, 100, 206, 78, 238, 110, 198, 70, 230, 102},
{60, 188, 28, 156, 52, 180, 20, 148, 62, 190, 30, 158, 54, 182, 22, 150},
{252, 142, 220, 92, 244, 116, 212, 84, 254, 144, 222, 94, 246, 118, 214, 86},
{3, 131, 35, 163, 11, 139, 43, 171, 1, 129, 33, 161, 9, 137, 41, 169},
{195, 67, 227, 99, 203, 75, 235, 107, 193, 65, 225, 97, 201, 73, 233, 105},
{51, 179, 19, 147, 59, 187, 27, 155, 49, 177, 17, 145, 57, 185, 25, 153},
{243, 115, 211, 83, 251, 123, 219, 91, 241, 113, 209, 81, 249, 121, 217, 89},
{15, 243, 47, 175, 7, 135, 39, 167, 13, 241, 45, 173, 5, 133, 37, 165},
{207, 79, 239, 111, 199, 71, 231, 103, 205, 77, 237, 109, 197, 69, 229, 101},
{63, 191, 31, 159, 55, 183, 23, 151, 61, 189, 29, 157, 53, 181, 21, 149},
{255, 145, 223, 95, 247, 119, 215, 87, 253, 143, 221, 93, 245, 117, 213, 85}
};
static void
dither_ordered_u8 (GstVideoDither * dither, gpointer pixels, guint x, guint y,
guint width)
{
guint8 *p = pixels;
guint8 *c = (guint8 *) dither->errors + ((y & 15) * width + (x & 15)) * 4;
video_orc_dither_ordered_u8 (p, c, width * 4);
}
static void
dither_ordered_u8_mask (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint8 *p = pixels;
guint16 *c = (guint16 *) dither->errors + ((y & 15) * width + (x & 15)) * 4;
video_orc_dither_ordered_4u8_mask (p, c, dither->orc_mask64, width);
}
static void
dither_ordered_u16_mask (GstVideoDither * dither, gpointer pixels, guint x,
guint y, guint width)
{
guint16 *p = pixels;
guint16 *c = (guint16 *) dither->errors + ((y & 15) * width + (x & 15)) * 4;
video_orc_dither_ordered_4u16_mask (p, c, dither->orc_mask64, width);
}
static void
alloc_errors (GstVideoDither * dither, guint lines)
{
guint width, n_comp;
width = dither->width;
n_comp = dither->n_comp;
dither->errors = g_malloc0 (sizeof (guint16) * (width + 8) * n_comp * lines);
}
static void
setup_bayer (GstVideoDither * dither)
{
guint i, j, k, width, n_comp, errdepth;
guint8 *shift;
width = dither->width;
shift = dither->shift;
n_comp = dither->n_comp;
if (dither->depth == 8) {
if (dither->flags & GST_VIDEO_DITHER_FLAG_QUANTIZE) {
dither->func = dither_ordered_u8_mask;
errdepth = 16;
} else {
dither->func = dither_ordered_u8;
errdepth = 8;
}
} else {
dither->func = dither_ordered_u16_mask;
errdepth = 16;
}
alloc_errors (dither, 16);
if (errdepth == 8) {
for (i = 0; i < 16; i++) {
guint8 *p = (guint8 *) dither->errors + (n_comp * width * i), v;
for (j = 0; j < width; j++) {
for (k = 0; k < n_comp; k++) {
v = bayer_map[i & 15][j & 15];
if (shift[k] < 8)
v = v >> (8 - shift[k]);
p[n_comp * j + k] = v;
}
}
}
} else {
for (i = 0; i < 16; i++) {
guint16 *p = (guint16 *) dither->errors + (n_comp * width * i), v;
for (j = 0; j < width; j++) {
for (k = 0; k < n_comp; k++) {
v = bayer_map[i & 15][j & 15];
if (shift[k] < 8)
v = v >> (8 - shift[k]);
p[n_comp * j + k] = v;
}
}
}
}
}
static gint
count_power (guint v)
{
gint res = 0;
while (v > 1) {
res++;
v >>= 1;
}
return res;
}
/**
* gst_video_dither_new:
* @method: a #GstVideoDitherMethod
* @flags: a #GstVideoDitherFlags
* @format: a #GstVideoFormat
* @quantizer: quantizer
* @width: the width of the lines
*
* Make a new dither object for dithering lines of @format using the
* algorithm described by @method.
*
* Each component will be quantized to a multiple of @quantizer. Better
* performance is achived when @quantizer is a power of 2.
*
* @width is the width of the lines that this ditherer will handle.
*
* Returns: a new #GstVideoDither
*/
GstVideoDither *
gst_video_dither_new (GstVideoDitherMethod method, GstVideoDitherFlags flags,
GstVideoFormat format, guint quantizer[GST_VIDEO_MAX_COMPONENTS],
guint width)
{
GstVideoDither *dither;
gint i;
dither = g_slice_new0 (GstVideoDither);
dither->method = method;
dither->flags = flags;
dither->format = format;
dither->width = width;
dither->n_comp = 4;
switch (format) {
case GST_VIDEO_FORMAT_AYUV:
case GST_VIDEO_FORMAT_ARGB:
dither->depth = 8;
break;
case GST_VIDEO_FORMAT_AYUV64:
case GST_VIDEO_FORMAT_ARGB64:
dither->depth = 16;
break;
default:
g_return_val_if_reached (NULL);
break;
}
for (i = 0; i < 4; i++) {
/* FIXME, only power of 2 quantizers */
guint q = quantizer[(i + 3) & 3];
dither->shift[i] = count_power (q);
dither->mask[i] = (1 << dither->shift[i]) - 1;
GST_DEBUG ("%d: quant %d shift %d mask %08x", i, q, dither->shift[i],
dither->mask[i]);
dither->orc_mask64 =
(dither->orc_mask64 << 16) | GUINT16_FROM_BE (dither->mask[i]);
dither->orc_mask32 = (dither->orc_mask32 << 8) | (guint8) dither->mask[i];
}
dither->orc_mask64 = GUINT64_FROM_BE (dither->orc_mask64);
dither->orc_mask32 = GUINT32_FROM_BE (dither->orc_mask32);
GST_DEBUG ("mask64 %08llx", (unsigned long long int) dither->orc_mask64);
GST_DEBUG ("mask32 %08x", dither->orc_mask32);
switch (method) {
case GST_VIDEO_DITHER_NONE:
if (dither->flags & GST_VIDEO_DITHER_FLAG_QUANTIZE)
if (dither->depth == 8)
dither->func = dither_none_u8_mask;
else
dither->func = dither_none_u16_mask;
else
dither->func = NULL;
break;
case GST_VIDEO_DITHER_VERTERR:
alloc_errors (dither, 1);
if (dither->depth == 8) {
dither->func = dither_verterr_u8;
} else
dither->func = dither_verterr_u16;
break;
case GST_VIDEO_DITHER_FLOYD_STEINBERG:
alloc_errors (dither, 1);
if (dither->depth == 8) {
dither->func = dither_floyd_steinberg_u8;
} else
dither->func = dither_floyd_steinberg_u16;
break;
case GST_VIDEO_DITHER_SIERRA_LITE:
alloc_errors (dither, 1);
if (dither->depth == 8) {
dither->func = dither_sierra_lite_u8;
} else
dither->func = dither_sierra_lite_u16;
break;
case GST_VIDEO_DITHER_BAYER:
setup_bayer (dither);
break;
}
return dither;
}
/**
* gst_video_dither_free:
* @dither: a #GstVideoDither
*
* Free @dither
*/
void
gst_video_dither_free (GstVideoDither * dither)
{
g_return_if_fail (dither != NULL);
g_free (dither->errors);
g_slice_free (GstVideoDither, dither);
}
/**
* gst_video_dither_line:
* @dither: a #GstVideoDither
* @line: pointer to the pixels of the line
* @x: x coordinate
* @y: y coordinate
* @width: the width
*
* Dither @width pixels starting from offset @x in @line using @dither.
*
* @y is the line number of @line in the output image.
*/
void
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);
if (dither->func)
dither->func (dither, line, x, y, width);
}

View file

@ -0,0 +1,77 @@
/* GStreamer
* Copyright (C) <2014> Wim Taymans <wim.taymans@gmail.com>
*
* 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.
*/
#ifndef __GST_VIDEO_DITHER_H__
#define __GST_VIDEO_DITHER_H__
#include <gst/gst.h>
G_BEGIN_DECLS
/**
* GstVideoDitherMethod:
* @GST_VIDEO_DITHER_NONE: no dithering
* @GST_VIDEO_DITHER_VERTERR: propagate rounding errors downwards
* @GST_VIDEO_DITHER_FLOYD_STEINBERG: Dither with floyd-steinberg error diffusion
* @GST_VIDEO_DITHER_SIERRA_LITE: Dither with Sierra Lite error diffusion
* @GST_VIDEO_DITHER_BAYER: ordered dither using a bayer pattern
*
* Different dithering methods to use.
*/
typedef enum {
GST_VIDEO_DITHER_NONE,
GST_VIDEO_DITHER_VERTERR,
GST_VIDEO_DITHER_FLOYD_STEINBERG,
GST_VIDEO_DITHER_SIERRA_LITE,
GST_VIDEO_DITHER_BAYER,
} GstVideoDitherMethod;
/**
* GstVideoDitherFlags:
* @GST_VIDEO_DITHER_FLAG_NONE: no flags
* @GST_VIDEO_DITHER_FLAG_INTERLACED: the input is interlaced
* @GST_VIDEO_DITHER_FLAG_QUANTIZE: quantize values in addition to adding dither.
*
* Extra flags that influence the result from gst_video_chroma_resample_new().
*/
typedef enum {
GST_VIDEO_DITHER_FLAG_NONE = 0,
GST_VIDEO_DITHER_FLAG_INTERLACED = (1 << 0),
GST_VIDEO_DITHER_FLAG_QUANTIZE = (1 << 1),
} GstVideoDitherFlags;
typedef struct _GstVideoDither GstVideoDither;
/* circular dependency, need to include this after defining the enums */
#include <gst/video/video-format.h>
GstVideoDither * gst_video_dither_new (GstVideoDitherMethod method,
GstVideoDitherFlags flags,
GstVideoFormat format,
guint quantizer[GST_VIDEO_MAX_COMPONENTS],
guint width);
void gst_video_dither_free (GstVideoDither *dither);
void gst_video_dither_line (GstVideoDither *dither,
gpointer line, guint x, guint y, guint width);
G_END_DECLS
#endif /* __GST_VIDEO_DITHER_H__ */

File diff suppressed because it is too large Load diff

View file

@ -190,6 +190,13 @@ void video_orc_chroma_up_v2_u16 (guint16 * ORC_RESTRICT d1, guint16 * ORC_RESTRI
void video_orc_chroma_down_v2_u16 (guint16 * ORC_RESTRICT d1, const guint16 * ORC_RESTRICT s1, const guint16 * ORC_RESTRICT s2, int n);
void video_orc_chroma_down_v4_u8 (guint8 * ORC_RESTRICT d1, const guint8 * ORC_RESTRICT s1, const guint8 * ORC_RESTRICT s2, const guint8 * ORC_RESTRICT s3, const guint8 * ORC_RESTRICT s4, int n);
void video_orc_chroma_down_v4_u16 (guint16 * ORC_RESTRICT d1, const guint16 * ORC_RESTRICT s1, const guint16 * ORC_RESTRICT s2, const guint16 * ORC_RESTRICT s3, const guint16 * ORC_RESTRICT s4, int n);
void video_orc_dither_none_4u8_mask (guint8 * ORC_RESTRICT d1, int p1, int n);
void video_orc_dither_none_4u16_mask (guint16 * ORC_RESTRICT d1, orc_int64 p1, int n);
void video_orc_dither_verterr_4u8_mask (guint8 * ORC_RESTRICT d1, guint16 * ORC_RESTRICT d2, orc_int64 p1, int n);
void video_orc_dither_fs_muladd_u8 (guint16 * ORC_RESTRICT d1, int n);
void video_orc_dither_ordered_u8 (guint8 * ORC_RESTRICT d1, const guint8 * ORC_RESTRICT s1, int n);
void video_orc_dither_ordered_4u8_mask (guint8 * ORC_RESTRICT d1, const guint16 * ORC_RESTRICT s1, orc_int64 p1, int n);
void video_orc_dither_ordered_4u16_mask (guint16 * ORC_RESTRICT d1, const guint16 * ORC_RESTRICT s1, orc_int64 p1, int n);
#ifdef __cplusplus
}

View file

@ -1986,3 +1986,97 @@ x2 addl uuvv3, uuvv3, 4
x2 shrul uuvv3, uuvv3, 3
x2 convsuslw uv1, uuvv3
mergelq d, ay1, uv1
.function video_orc_dither_none_4u8_mask
.dest 4 p guint8
.param 4 masks
.temp 4 m
loadpl m, masks
x4 andnb p, m, p
.function video_orc_dither_none_4u16_mask
.dest 8 p guint16
.longparam 8 masks
.temp 8 m
loadpq m, masks
x4 andnw p, m, p
.function video_orc_dither_verterr_4u8_mask
.dest 4 p guint8
.dest 8 e guint16
.longparam 8 masks
.temp 8 m
.temp 8 t1
loadpq m, masks
x4 convubw t1, p
x4 addw t1, e, t1
x4 andw e, m, t1
x4 andnw t1, m, t1
x4 convsuswb p, t1
.function video_orc_dither_fs_muladd_u8
.dest 2 e guint16
.temp 2 t1
.temp 2 t2
loadoffw t2, e, 4
mullw t2, t2, 5
addw t1, t2, e
loadoffw t2, e, 8
mullw t2, t2, 3
addw e, t1, t2
# due to error propagation we should disable
# loop_shift for this function and only work on
# 4 pixels at a time.
#.function video_orc_dither_fs_add_4u8_mask
#.flags no-unroll
#.dest 4 d guint8
#.dest 8 e1 guint16
#.dest 8 e2 guint16
#.longparam 8 masks
#.temp 8 p
#.temp 8 t1
#.temp 8 t2
#
#x4 mullw t1, e1, 7
#x4 addw t1, t1, e2
#x4 shruw t1, t1, 4
#x4 convubw p, d
#x4 addw t1, t1, p
#x4 andnw p, masks, t1
#x4 convsuswb d, p
#x4 andw e2, t1, masks
.function video_orc_dither_ordered_u8
.source 1 e guint8
.dest 1 d guint8
addusb d, d, e
.function video_orc_dither_ordered_4u8_mask
.source 8 e1 guint16
.dest 4 d guint8
.longparam 8 masks
.temp 8 p
.temp 8 m
loadpq m, masks
x4 convubw p, d
x4 addw p, p, e1
x4 andnw p, m, p
x4 convsuswb d, p
.function video_orc_dither_ordered_4u16_mask
.source 8 e1 guint16
.dest 8 d guint16
.longparam 8 masks
.temp 8 p
.temp 8 m
loadpq m, masks
x4 addw p, d, e1
x4 andnw d, m, p

View file

@ -26,6 +26,7 @@ typedef struct _GstVideoAlignment GstVideoAlignment;
#include <gst/video/video-format.h>
#include <gst/video/video-color.h>
#include <gst/video/video-dither.h>
#include <gst/video/video-info.h>
#include <gst/video/video-frame.h>
#include <gst/video/video-enumtypes.h>