swscale: add two spatially stable dithering methods

Both of these dithering methods are from http://pippin.gimp.org/a_dither/ for
GIF they can be considered better than bayer (provides more gray-levels), and
spatial stability - often more than twice as good compression and less visual
flicker than error diffusion methods (the methods also avoids error-shadow
artifacts of diffusion dithers).

These methods are similar to blue/green noise type dither masks; but are
simple enough to generate their mask on the fly. They are still research work
in progress; though more expensive to generate masks (which can be used in a
LUT) like 'void and cluster' and similar methods will yield superior results
This commit is contained in:
Øyvind Kolås 2014-03-23 02:13:26 +01:00 committed by Michael Niedermayer
parent a490970af2
commit 3e6016622e
5 changed files with 82 additions and 23 deletions

View File

@ -112,6 +112,14 @@ bayer dither
@item ed
error diffusion dither
@item a_dither
arithmetic dither, based using addition
@item x_dither
arithmetic dither, based using xor (more random/less apparent patterning that
a_dither).
@end table
@end table

View File

@ -69,10 +69,12 @@ static const AVOption swscale_options[] = {
{ "dst_v_chr_pos", "destination vertical chroma position in luma grid/256" , OFFSET(dst_v_chr_pos), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 512, VE },
{ "dst_h_chr_pos", "destination horizontal chroma position in luma grid/256", OFFSET(dst_h_chr_pos), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 512, VE },
{ "sws_dither", "set dithering algorithm", OFFSET(dither), AV_OPT_TYPE_INT, { .i64 = SWS_DITHER_AUTO }, 0, NB_SWS_DITHER, VE, "sws_dither" },
{ "auto", "leave choice to sws", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_AUTO }, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "bayer", "bayer dither", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_BAYER }, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "ed", "error diffusion", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_ED }, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "sws_dither", "set dithering algorithm", OFFSET(dither), AV_OPT_TYPE_INT, { .i64 = SWS_DITHER_AUTO }, 0, NB_SWS_DITHER, VE, "sws_dither" },
{ "auto", "leave choice to sws", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_AUTO }, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "bayer", "bayer dither", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_BAYER }, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "ed", "error diffusion", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_ED }, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "a_dither", "arithmetic addition dither", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_A_DITHER}, INT_MIN, INT_MAX, VE, "sws_dither" },
{ "x_dither", "arithmetic xor dither", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_DITHER_X_DITHER}, INT_MIN, INT_MAX, VE, "sws_dither" },
{ NULL }
};

View File

@ -1508,24 +1508,71 @@ static av_always_inline void yuv2rgb_write_full(SwsContext *c,
case AV_PIX_FMT_RGB8:
{
int r,g,b;
R >>= 22;
G >>= 22;
B >>= 22;
R += (7*err[0] + 1*c->dither_error[0][i] + 5*c->dither_error[0][i+1] + 3*c->dither_error[0][i+2])>>4;
G += (7*err[1] + 1*c->dither_error[1][i] + 5*c->dither_error[1][i+1] + 3*c->dither_error[1][i+2])>>4;
B += (7*err[2] + 1*c->dither_error[2][i] + 5*c->dither_error[2][i+1] + 3*c->dither_error[2][i+2])>>4;
c->dither_error[0][i] = err[0];
c->dither_error[1][i] = err[1];
c->dither_error[2][i] = err[2];
r = R >> (isrgb8 ? 5 : 7);
g = G >> (isrgb8 ? 5 : 6);
b = B >> (isrgb8 ? 6 : 7);
r = av_clip(r, 0, isrgb8 ? 7 : 1);
g = av_clip(g, 0, isrgb8 ? 7 : 3);
b = av_clip(b, 0, isrgb8 ? 3 : 1);
err[0] = R - r*(isrgb8 ? 36 : 255);
err[1] = G - g*(isrgb8 ? 36 : 85);
err[2] = B - b*(isrgb8 ? 85 : 255);
switch (c->dither) {
default:
case SWS_DITHER_AUTO:
case SWS_DITHER_ED:
R >>= 22;
G >>= 22;
B >>= 22;
R += (7*err[0] + 1*c->dither_error[0][i] + 5*c->dither_error[0][i+1] + 3*c->dither_error[0][i+2])>>4;
G += (7*err[1] + 1*c->dither_error[1][i] + 5*c->dither_error[1][i+1] + 3*c->dither_error[1][i+2])>>4;
B += (7*err[2] + 1*c->dither_error[2][i] + 5*c->dither_error[2][i+1] + 3*c->dither_error[2][i+2])>>4;
c->dither_error[0][i] = err[0];
c->dither_error[1][i] = err[1];
c->dither_error[2][i] = err[2];
r = R >> (isrgb8 ? 5 : 7);
g = G >> (isrgb8 ? 5 : 6);
b = B >> (isrgb8 ? 6 : 7);
r = av_clip(r, 0, isrgb8 ? 7 : 1);
g = av_clip(g, 0, isrgb8 ? 7 : 3);
b = av_clip(b, 0, isrgb8 ? 3 : 1);
err[0] = R - r*(isrgb8 ? 36 : 255);
err[1] = G - g*(isrgb8 ? 36 : 85);
err[2] = B - b*(isrgb8 ? 85 : 255);
break;
case SWS_DITHER_A_DITHER:
if (isrgb8) {
/* see http://pippin.gimp.org/a_dither/ for details/origin */
#define A_DITHER(u,v) (((((u)+((v)*236))*119)&0xff))
r = (((R >> 19) + A_DITHER(i,y) -96)>>8);
g = (((G >> 19) + A_DITHER(i + 17,y) - 96)>>8);
b = (((B >> 20) + A_DITHER(i + 17*2,y) -96)>>8);
r = av_clip(r, 0, 7);
g = av_clip(g, 0, 7);
b = av_clip(b, 0, 3);
} else {
r = (((R >> 21) + A_DITHER(i,y)-256)>>8);
g = (((G >> 19) + A_DITHER(i + 17,y)-256)>>8);
b = (((B >> 21) + A_DITHER(i + 17*2,y)-256)>>8);
r = av_clip(r, 0, 1);
g = av_clip(g, 0, 3);
b = av_clip(b, 0, 1);
}
break;
case SWS_DITHER_X_DITHER:
if (isrgb8) {
/* see http://pippin.gimp.org/a_dither/ for details/origin */
#define X_DITHER(u,v) (((((u)^((v)*237))*181)&0x1ff)/2)
r = (((R >> 19) + X_DITHER(i,y) - 96)>>8);
g = (((G >> 19) + X_DITHER(i + 17,y) - 96)>>8);
b = (((B >> 20) + X_DITHER(i + 17*2,y) - 96)>>8);
r = av_clip(r, 0, 7);
g = av_clip(g, 0, 7);
b = av_clip(b, 0, 3);
} else {
r = (((R >> 21) + X_DITHER(i,y)-256)>>8);
g = (((G >> 19) + X_DITHER(i + 17,y)-256)>>8);
b = (((B >> 21) + X_DITHER(i + 17*2,y)-256)>>8);
r = av_clip(r, 0, 1);
g = av_clip(g, 0, 3);
b = av_clip(b, 0, 1);
}
break;
}
if(target == AV_PIX_FMT_BGR4_BYTE) {
dest[0] = r + 2*g + 8*b;
} else if(target == AV_PIX_FMT_RGB4_BYTE) {

View File

@ -66,6 +66,8 @@ typedef enum SwsDither {
SWS_DITHER_AUTO,
SWS_DITHER_BAYER,
SWS_DITHER_ED,
SWS_DITHER_A_DITHER,
SWS_DITHER_X_DITHER,
NB_SWS_DITHER,
} SwsDither;

View File

@ -1250,7 +1250,7 @@ av_cold int sws_init_context(SwsContext *c, SwsFilter *srcFilter,
if (c->dither == SWS_DITHER_AUTO)
c->dither = (flags & SWS_FULL_CHR_H_INT) ? SWS_DITHER_ED : SWS_DITHER_BAYER;
if (!(flags & SWS_FULL_CHR_H_INT)) {
if (c->dither == SWS_DITHER_ED) {
if (c->dither == SWS_DITHER_ED || c->dither == SWS_DITHER_A_DITHER || c->dither == SWS_DITHER_X_DITHER) {
av_log(c, AV_LOG_DEBUG,
"Desired dithering only supported in full chroma interpolation for destination format '%s'\n",
av_get_pix_fmt_name(dstFormat));