diff --git a/README b/README index 1ea4d249..828e0144 100644 --- a/README +++ b/README @@ -149,6 +149,8 @@ options: -map ............. print map of extra info. -d .......... dump the compressed output (PGM file). -alpha_method .... Transparency-compression method (0..1) + -alpha_filter . predictive filtering for alpha plane. + One of: none, fast (default) or best. -noalpha ............... discard any transparency information. -short ................. condense printed message diff --git a/examples/cwebp.c b/examples/cwebp.c index d1cedc8b..ebe52afe 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -700,7 +700,8 @@ static void HelpLong(void) { printf(" -map ............. print map of extra info.\n"); printf(" -d .......... dump the compressed output (PGM file).\n"); printf(" -alpha_method .... Transparency-compression method (0..1)\n"); - printf(" -alpha_filter .... predictive filtering for Alpha (0..5)\n"); + printf(" -alpha_filter . predictive filtering for alpha plane.\n"); + printf(" One of: none, fast (default) or best.\n"); printf(" -noalpha ............... discard any transparency information.\n"); printf("\n"); @@ -795,7 +796,17 @@ int main(int argc, const char *argv[]) { } else if (!strcmp(argv[c], "-alpha_method") && c < argc - 1) { config.alpha_compression = strtol(argv[++c], NULL, 0); } else if (!strcmp(argv[c], "-alpha_filter") && c < argc - 1) { - config.alpha_filtering = strtol(argv[++c], NULL, 0); + ++c; + if (!strcmp(argv[c], "none")) { + config.alpha_filtering = 0; + } else if (!strcmp(argv[c], "fast")) { + config.alpha_filtering = 1; + } else if (!strcmp(argv[c], "best")) { + config.alpha_filtering = 2; + } else { + fprintf(stderr, "Error! Unrecognized alpha filter: %s\n", argv[c]); + goto Error; + } } else if (!strcmp(argv[c], "-noalpha")) { keep_alpha = 0; } else if (!strcmp(argv[c], "-size") && c < argc - 1) { diff --git a/man/cwebp.1 b/man/cwebp.1 index 5cd7c474..3b306923 100644 --- a/man/cwebp.1 +++ b/man/cwebp.1 @@ -41,11 +41,13 @@ Specify the compression factor for alpha compression between 0 and 100. Lossless compression of alpha is achieved using a value of 100, while the lower values result in a lossy compression. The default is 100. .TP -.B \-alpha_filter int -Specify the predictive filtering method (between 0 and 5) for alpha plane. -These correspond to prediction modes none, horizontal, vertical, gradient and -paeth filters. The prediction mode 5 will try all the prediction modes (0 to 4) -and pick the best prediction mode. The default value is 0 (no prediction). +.B \-alpha_filter string +Specify the predictive filtering method for alpha plane. One of 'none', 'fast' +or 'best', in increasing complexity and slowness order. Default is 'fast'. +Internally, alpha filtering is performed using four possible predictions (none, +horizontal, vertical, gradient). The 'best' mode will try each modes in turn and +pick the one which gives the smaller size. The 'fast' mode will just try to +form an a-priori guess without testing all modes. .TP .B \-f int Specify the strength of the deblocking filter, between 0 (no filtering) diff --git a/src/enc/alpha.c b/src/enc/alpha.c index 32c64019..1172ae50 100644 --- a/src/enc/alpha.c +++ b/src/enc/alpha.c @@ -14,6 +14,7 @@ #include "./vp8enci.h" #include "../utils/alpha.h" +#include "../utils/filters.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { @@ -33,10 +34,15 @@ int VP8EncFinishAlpha(VP8Encoder* enc) { const WebPPicture* pic = enc->pic_; uint8_t* tmp_data = NULL; size_t tmp_size = 0; + const WEBP_FILTER_TYPE filter = + (config->alpha_filtering == 0) ? WEBP_FILTER_NONE : + (config->alpha_filtering == 1) ? WEBP_FILTER_FAST : + WEBP_FILTER_BEST; + assert(pic->a); if (!EncodeAlpha(pic->a, pic->width, pic->height, pic->a_stride, config->alpha_quality, config->alpha_compression, - config->alpha_filtering, &tmp_data, &tmp_size)) { + filter, &tmp_data, &tmp_size)) { return 0; } if (tmp_size != (uint32_t)tmp_size) { // Sanity check. diff --git a/src/enc/config.c b/src/enc/config.c index 9dc7e08f..d51d9995 100644 --- a/src/enc/config.c +++ b/src/enc/config.c @@ -42,7 +42,7 @@ int WebPConfigInitInternal(WebPConfig* const config, config->autofilter = 0; config->partition_limit = 0; config->alpha_compression = 1; - config->alpha_filtering = 0; + config->alpha_filtering = 1; config->alpha_quality = 100; // TODO(skal): tune. diff --git a/src/utils/alpha.c b/src/utils/alpha.c index 8bb87177..43ef33ee 100644 --- a/src/utils/alpha.c +++ b/src/utils/alpha.c @@ -203,14 +203,35 @@ static int EncodeZlibTCoder(const uint8_t* data, int width, int height, return ok && !bw->error_; } +// ----------------------------------------------------------------------------- + static int EncodeAlphaInternal(const uint8_t* data, int width, int height, - int method, VP8BitWriter* const bw) { + int method, int filter, size_t data_size, + uint8_t* tmp_alpha, VP8BitWriter* const bw) { int ok = 0; + const uint8_t* alpha_src; + WebPFilterFunc filter_func; + uint8_t header[ALPHA_HEADER_LEN]; + const size_t expected_size = (method == 0) ? + (ALPHA_HEADER_LEN + data_size) : (data_size >> 5); + header[0] = (filter << 4) | method; + header[1] = 0; // reserved byte for later use + VP8BitWriterInit(bw, expected_size); + VP8BitWriterAppend(bw, header, sizeof(header)); + + filter_func = WebPFilters[filter]; + if (filter_func) { + filter_func(data, width, height, 1, width, tmp_alpha); + alpha_src = tmp_alpha; + } else { + alpha_src = data; + } + if (method == 0) { - ok = VP8BitWriterAppend(bw, data, width * height); + ok = VP8BitWriterAppend(bw, alpha_src, width * height); ok = ok && !bw->error_; - } else if (method == 1) { - ok = EncodeZlibTCoder(data, width, height, bw); + } else { + ok = EncodeZlibTCoder(alpha_src, width, height, bw); VP8BitWriterFinish(bw); } return ok; @@ -239,7 +260,7 @@ int EncodeAlpha(const uint8_t* data, int width, int height, int stride, assert(data != NULL && output != NULL && output_size != NULL); assert(width > 0 && height > 0); assert(stride >= width); - assert(filter < WEBP_FILTER_LAST); + assert(filter >= WEBP_FILTER_NONE && filter <= WEBP_FILTER_FAST); if (quality < 0 || quality > 100) { return 0; @@ -267,62 +288,67 @@ int EncodeAlpha(const uint8_t* data, int width, int height, int stride, } if (ok) { - WEBP_FILTER_TYPE this_filter; - size_t best_size = 1 << 30; - uint8_t* tmp_out = NULL; - uint8_t* const filtered_alpha = (uint8_t*)malloc(data_size); - if (filtered_alpha == NULL) { - free(quant_alpha); - return 0; + VP8BitWriter bw; + size_t best_score; + int test_filter; + uint8_t* filtered_alpha = NULL; + + // We always test WEBP_FILTER_NONE first. + ok = EncodeAlphaInternal(quant_alpha, width, height, method, + WEBP_FILTER_NONE, data_size, NULL, &bw); + if (!ok) { + VP8BitWriterWipeOut(&bw); + goto End; } - // Filtering. - for (this_filter = WEBP_FILTER_NONE; this_filter < WEBP_FILTER_LAST; - ++this_filter) { - uint8_t header[ALPHA_HEADER_LEN]; - VP8BitWriter bw; - WebPFilterFunc filter_func = NULL; - const size_t expected_size = (method == 0) ? - (ALPHA_HEADER_LEN + data_size) : (data_size >> 5); - if (this_filter == WEBP_FILTER_BEST) { - continue; - } else if (this_filter != filter && filter != WEBP_FILTER_BEST) { + best_score = VP8BitWriterSize(&bw); + + if (filter == WEBP_FILTER_FAST) { // Quick estimate of a second candidate? + filter = EstimateBestFilter(quant_alpha, width, height, width); + } + // Stop? + if (filter == WEBP_FILTER_NONE) { + goto Ok; + } + + filtered_alpha = (uint8_t*)malloc(data_size); + ok = (filtered_alpha != NULL); + if (!ok) { + goto End; + } + + // Try the other mode(s). + for (test_filter = WEBP_FILTER_HORIZONTAL; + ok && (test_filter <= WEBP_FILTER_GRADIENT); + ++test_filter) { + VP8BitWriter tmp_bw; + if (filter != WEBP_FILTER_BEST && test_filter != filter) { continue; } - header[0] = ((this_filter & 0x0f) << 4) | (method & 0x0f); - header[1] = 0; // reserved byte for later use - VP8BitWriterInit(&bw, expected_size); - VP8BitWriterAppend(&bw, header, sizeof(header)); - - filter_func = WebPFilters[this_filter]; - if (filter_func) { - filter_func(quant_alpha, width, height, 1, width, filtered_alpha); - ok = EncodeAlphaInternal(filtered_alpha, width, height, method, &bw); - } else { - ok = EncodeAlphaInternal(quant_alpha, width, height, method, &bw); - } + ok = EncodeAlphaInternal(quant_alpha, width, height, method, test_filter, + data_size, filtered_alpha, &tmp_bw); if (ok) { - const size_t this_size = VP8BitWriterSize(&bw); - if (this_size < best_size) { - free(tmp_out); - tmp_out = VP8BitWriterBuf(&bw); - best_size = this_size; - } else { - VP8BitWriterWipeOut(&bw); + const size_t score = VP8BitWriterSize(&tmp_bw); + if (score < best_score) { + // swap bitwriter objects. + VP8BitWriter tmp = tmp_bw; + tmp_bw = bw; + bw = tmp; + best_score = score; } } else { - free(tmp_out); VP8BitWriterWipeOut(&bw); - break; } + VP8BitWriterWipeOut(&tmp_bw); } + Ok: if (ok) { - *output_size = best_size; - *output = tmp_out; + *output_size = VP8BitWriterSize(&bw); + *output = VP8BitWriterBuf(&bw); } free(filtered_alpha); } - + End: free(quant_alpha); return ok; } @@ -379,7 +405,7 @@ int DecodeAlpha(const uint8_t* data, size_t data_size, uint8_t* decoded_data = NULL; const size_t decoded_size = height * width; uint8_t* unfiltered_data = NULL; - int filter; + WEBP_FILTER_TYPE filter; int ok = 0; int method; @@ -394,7 +420,7 @@ int DecodeAlpha(const uint8_t* data, size_t data_size, filter = data[0] >> 4; ok = (data[1] == 0); if (method < 0 || method > 1 || - filter < WEBP_FILTER_NONE || filter > WEBP_FILTER_PAETH || !ok) { + filter > WEBP_FILTER_GRADIENT || !ok) { return 0; } diff --git a/src/utils/filters.c b/src/utils/filters.c index e8dc06d4..50252763 100644 --- a/src/utils/filters.c +++ b/src/utils/filters.c @@ -115,6 +115,11 @@ static void VerticalUnfilter(const uint8_t* data, int width, int height, //------------------------------------------------------------------------------ // Gradient filter. +static WEBP_INLINE uint8_t GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return (g < 0) ? 0 : (g > 255) ? 255 : (uint8_t)g; +} + static void GradientFilter(const uint8_t* data, int width, int height, int bpp, int stride, uint8_t* filtered_data) { int h; @@ -131,9 +136,9 @@ static void GradientFilter(const uint8_t* data, int width, int height, const uint8_t* const prev_line = scan_line - stride; memcpy((void*)out, (const void*)scan_line, bpp); for (w = bpp; w < width * bpp; ++w) { - const uint8_t predictor = scan_line[w - bpp] + prev_line[w] - - prev_line[w - bpp]; - out[w] = scan_line[w] - predictor; + out[w] = scan_line[w] - GradientPredictor(scan_line[w - bpp], + prev_line[w], + prev_line[w - bpp]); } } } @@ -154,110 +159,81 @@ static void GradientUnfilter(const uint8_t* data, int width, int height, const uint8_t* const out_prev_line = out - stride; memcpy((void*)out, (const void*)scan_line, bpp); for (w = bpp; w < width * bpp; ++w) { - const uint8_t predictor = out[w - bpp] + out_prev_line[w] - - out_prev_line[w - bpp]; - out[w] = scan_line[w] + predictor; - } - } -} - -//------------------------------------------------------------------------------ -// Paeth filter. - -static WEBP_INLINE int AbsDiff(int a, int b) { - return (a > b) ? a - b : b - a; -} - -static WEBP_INLINE uint8_t PaethPredictor(uint8_t a, uint8_t b, uint8_t c) { - const int p = a + b - c; // Base. - const int pa = AbsDiff(p, a); - const int pb = AbsDiff(p, b); - const int pc = AbsDiff(p, c); - - // Return nearest to base of a, b, c. - return (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; -} - -static void PaethFilter(const uint8_t* data, int width, int height, - int bpp, int stride, uint8_t* filtered_data) { - int w; - int h; - SANITY_CHECK(data, filtered_data); - - // Top scan line (special case). - memcpy((void*)filtered_data, (const void*)data, bpp); - for (w = bpp; w < width * bpp; ++w) { - // Note: PaethPredictor(scan_line[w - bpp], 0, 0) == scan_line[w - bpp]. - filtered_data[w] = data[w] - data[w - bpp]; - } - - // Filter line-by-line. - for (h = 1; h < height; ++h) { - int w; - const uint8_t* const scan_line = data + h * stride; - uint8_t* const out = filtered_data + h * stride; - const uint8_t* const prev_line = scan_line - stride; - for (w = 0; w < bpp; ++w) { - // Note: PaethPredictor(0, prev_line[w], 0) == prev_line[w]. - out[w] = scan_line[w] - prev_line[w]; - } - for (w = bpp; w < width * bpp; ++w) { - out[w] = scan_line[w] - PaethPredictor(scan_line[w - bpp], prev_line[w], - prev_line[w - bpp]); - } - } -} - -static void PaethUnfilter(const uint8_t* data, int width, int height, - int bpp, int stride, uint8_t* recon_data) { - int w; - int h; - SANITY_CHECK(data, recon_data); - - // Top scan line (special case). - memcpy((void*)recon_data, (const void*)data, bpp); - for (w = bpp; w < width * bpp; ++w) { - // Note: PaethPredictor(out[w - bpp], 0, 0) == out[w - bpp]. - recon_data[w] = data[w] + recon_data[w - bpp]; - } - - // Unfilter line-by-line. - for (h = 1; h < height; ++h) { - int w; - const uint8_t* const scan_line = data + h * stride; - uint8_t* const out = recon_data + h * stride; - const uint8_t* const out_prev = out - stride; - for (w = 0; w < bpp; ++w) { - // Note: PaethPredictor(0, out_prev[w], 0) == out_prev[w]. - out[w] = scan_line[w] + out_prev[w]; - } - for (w = bpp; w < width * bpp; ++w) { - out[w] = scan_line[w] + PaethPredictor(out[w - bpp], out_prev[w], - out_prev[w - bpp]); + out[w] = scan_line[w] + GradientPredictor(out[w - bpp], + out_prev_line[w], + out_prev_line[w - bpp]); } } } #undef SANITY_CHECK +// ----------------------------------------------------------------------------- +// Quick estimate of a potentially interesting filter mode to try, in addition +// to the default NONE. + +#define SMAX 16 +#define SDIFF(a, b) (abs((a) - (b)) >> 4) // Scoring diff, in [0..SMAX) + +WEBP_FILTER_TYPE EstimateBestFilter(const uint8_t* data, + int width, int height, int stride) { + int i, j; + int bins[WEBP_FILTER_LAST][SMAX]; + memset(bins, 0, sizeof(bins)); + // We only sample every other pixels. That's enough. + for (j = 2; j < height - 1; j += 2) { + const uint8_t* const p = data + j * stride; + int mean = p[0]; + for (i = 2; i < width - 1; i += 2) { + const int diff0 = SDIFF(p[i], mean); + const int diff1 = SDIFF(p[i], p[i - 1]); + const int diff2 = SDIFF(p[i], p[i - width]); + const int grad_pred = + GradientPredictor(p[i - 1], p[i - width], p[i - width - 1]); + const int diff3 = SDIFF(p[i], grad_pred); + bins[WEBP_FILTER_NONE][diff0] = 1; + bins[WEBP_FILTER_HORIZONTAL][diff1] = 1; + bins[WEBP_FILTER_VERTICAL][diff2] = 1; + bins[WEBP_FILTER_GRADIENT][diff3] = 1; + mean = (3 * mean + p[i] + 2) >> 2; + } + } + { + WEBP_FILTER_TYPE filter, best_filter = WEBP_FILTER_NONE; + int best_score = 0x7fffffff; + for (filter = WEBP_FILTER_NONE; filter < WEBP_FILTER_LAST; ++filter) { + int score = 0; + for (i = 0; i < SMAX; ++i) { + if (bins[filter][i] > 0) { + score += i; + } + } + if (score < best_score) { + best_score = score; + best_filter = filter; + } + } + return best_filter; + } +} + +#undef SMAX +#undef SDIFF + //------------------------------------------------------------------------------ const WebPFilterFunc WebPFilters[WEBP_FILTER_LAST] = { NULL, // WEBP_FILTER_NONE HorizontalFilter, // WEBP_FILTER_HORIZONTAL VerticalFilter, // WEBP_FILTER_VERTICAL - GradientFilter, // WEBP_FILTER_GRADIENT - PaethFilter, // WEBP_FILTER_PAETH - NULL // WEBP_FILTER_BEST + GradientFilter // WEBP_FILTER_GRADIENT }; const WebPFilterFunc WebPUnfilters[WEBP_FILTER_LAST] = { NULL, // WEBP_FILTER_NONE HorizontalUnfilter, // WEBP_FILTER_HORIZONTAL VerticalUnfilter, // WEBP_FILTER_VERTICAL - GradientUnfilter, // WEBP_FILTER_GRADIENT - PaethUnfilter, // WEBP_FILTER_PAETH - NULL // WEBP_FILTER_BEST + GradientUnfilter // WEBP_FILTER_GRADIENT }; //------------------------------------------------------------------------------ diff --git a/src/utils/filters.h b/src/utils/filters.h index b8f90f3e..c5cdbd6d 100644 --- a/src/utils/filters.h +++ b/src/utils/filters.h @@ -24,9 +24,9 @@ typedef enum { WEBP_FILTER_HORIZONTAL, WEBP_FILTER_VERTICAL, WEBP_FILTER_GRADIENT, - WEBP_FILTER_PAETH, + WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker WEBP_FILTER_BEST, - WEBP_FILTER_LAST, + WEBP_FILTER_FAST } WEBP_FILTER_TYPE; typedef void (*WebPFilterFunc)(const uint8_t* in, int width, int height, @@ -38,10 +38,14 @@ typedef void (*WebPFilterFunc)(const uint8_t* in, int width, int height, // 'bpp' is number of bytes per pixel, and // 'stride' is number of bytes per scan line (with possible padding). // 'out' should be pre-allocated. -extern const WebPFilterFunc WebPFilters[/*WEBP_FILTER_LAST*/]; +extern const WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; // Reconstruct the original data from the given filtered data. -extern const WebPFilterFunc WebPUnfilters[/*WEBP_FILTER_LAST*/]; +extern const WebPFilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +// Fast estimate of a potentially good filter. +extern WEBP_FILTER_TYPE EstimateBestFilter(const uint8_t* data, + int width, int height, int stride); #if defined(__cplusplus) || defined(c_plusplus) } // extern "C" diff --git a/src/webp/encode.h b/src/webp/encode.h index c883e283..8f6c85b6 100644 --- a/src/webp/encode.h +++ b/src/webp/encode.h @@ -75,8 +75,7 @@ typedef struct { // 1 = backward reference counts encoded with // arithmetic encoder). Default is 1. int alpha_filtering; // Predictive filtering method for alpha plane. - // (0 = none, 1 = horizontal, 2 = vertical, 3 = grad, - // 4 = Paeth and 5 = Best of (0 .. 4). + // 0: none, 1: fast, 2: best. Default if 1. int alpha_quality; // Between 0 (smallest size) and 100 (lossless). // Default is 100. } WebPConfig;