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 resultstags/n2.3
| @@ -112,6 +112,14 @@ bayer dither | |||||
| @item ed | @item ed | ||||
| error diffusion dither | 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 | ||||
| @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_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 }, | { "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 } | { NULL } | ||||
| }; | }; | ||||
| @@ -1508,24 +1508,71 @@ static av_always_inline void yuv2rgb_write_full(SwsContext *c, | |||||
| case AV_PIX_FMT_RGB8: | case AV_PIX_FMT_RGB8: | ||||
| { | { | ||||
| int r,g,b; | 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) { | if(target == AV_PIX_FMT_BGR4_BYTE) { | ||||
| dest[0] = r + 2*g + 8*b; | dest[0] = r + 2*g + 8*b; | ||||
| } else if(target == AV_PIX_FMT_RGB4_BYTE) { | } else if(target == AV_PIX_FMT_RGB4_BYTE) { | ||||
| @@ -66,6 +66,8 @@ typedef enum SwsDither { | |||||
| SWS_DITHER_AUTO, | SWS_DITHER_AUTO, | ||||
| SWS_DITHER_BAYER, | SWS_DITHER_BAYER, | ||||
| SWS_DITHER_ED, | SWS_DITHER_ED, | ||||
| SWS_DITHER_A_DITHER, | |||||
| SWS_DITHER_X_DITHER, | |||||
| NB_SWS_DITHER, | NB_SWS_DITHER, | ||||
| } SwsDither; | } SwsDither; | ||||
| @@ -1250,7 +1250,7 @@ av_cold int sws_init_context(SwsContext *c, SwsFilter *srcFilter, | |||||
| if (c->dither == SWS_DITHER_AUTO) | if (c->dither == SWS_DITHER_AUTO) | ||||
| c->dither = (flags & SWS_FULL_CHR_H_INT) ? SWS_DITHER_ED : SWS_DITHER_BAYER; | c->dither = (flags & SWS_FULL_CHR_H_INT) ? SWS_DITHER_ED : SWS_DITHER_BAYER; | ||||
| if (!(flags & SWS_FULL_CHR_H_INT)) { | 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, | av_log(c, AV_LOG_DEBUG, | ||||
| "Desired dithering only supported in full chroma interpolation for destination format '%s'\n", | "Desired dithering only supported in full chroma interpolation for destination format '%s'\n", | ||||
| av_get_pix_fmt_name(dstFormat)); | av_get_pix_fmt_name(dstFormat)); | ||||