From 3e6016622e6fc5967e55d41e8074558d43bc33c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Sun, 23 Mar 2014 02:13:26 +0100 Subject: [PATCH] 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 --- doc/scaler.texi | 8 ++++ libswscale/options.c | 10 +++-- libswscale/output.c | 83 +++++++++++++++++++++++++++-------- libswscale/swscale_internal.h | 2 + libswscale/utils.c | 2 +- 5 files changed, 82 insertions(+), 23 deletions(-) diff --git a/doc/scaler.texi b/doc/scaler.texi index f043ffd5f2..23d6393883 100644 --- a/doc/scaler.texi +++ b/doc/scaler.texi @@ -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 diff --git a/libswscale/options.c b/libswscale/options.c index 9e8703ff39..5433d55b9b 100644 --- a/libswscale/options.c +++ b/libswscale/options.c @@ -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 } }; diff --git a/libswscale/output.c b/libswscale/output.c index ddb0d0c19e..f41b32ac63 100644 --- a/libswscale/output.c +++ b/libswscale/output.c @@ -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) { diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h index 5ce54d0dc3..8c2dc7998d 100644 --- a/libswscale/swscale_internal.h +++ b/libswscale/swscale_internal.h @@ -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; diff --git a/libswscale/utils.c b/libswscale/utils.c index f2e41671a7..483b0b24e9 100644 --- a/libswscale/utils.c +++ b/libswscale/utils.c @@ -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));