mirror of https://git.ffmpeg.org/ffmpeg.git
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:
parent
a490970af2
commit
3e6016622e
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue