Introduce a 'fast' alpha mode

.. where only 2 filtering modes are potentially
tried, instead of all of them. This is fast than the exhaustive 'best'
mode, and not much worse.

Options for cwebp are:
 -alpha_filter none
 -alpha_filter fast      (<- default)
 -alpha_filter best      (<- slow)

Change-Id: I8cb90ee11b8f981811e013ea4ad5bf72ba3ea7d4
This commit is contained in:
Pascal Massimino 2012-01-08 19:27:21 -08:00
parent ad1e163a0d
commit 8ca2076de1
9 changed files with 179 additions and 153 deletions

2
README
View File

@ -149,6 +149,8 @@ options:
-map <int> ............. print map of extra info. -map <int> ............. print map of extra info.
-d <file.pgm> .......... dump the compressed output (PGM file). -d <file.pgm> .......... dump the compressed output (PGM file).
-alpha_method <int> .... Transparency-compression method (0..1) -alpha_method <int> .... Transparency-compression method (0..1)
-alpha_filter <string> . predictive filtering for alpha plane.
One of: none, fast (default) or best.
-noalpha ............... discard any transparency information. -noalpha ............... discard any transparency information.
-short ................. condense printed message -short ................. condense printed message

View File

@ -700,7 +700,8 @@ static void HelpLong(void) {
printf(" -map <int> ............. print map of extra info.\n"); printf(" -map <int> ............. print map of extra info.\n");
printf(" -d <file.pgm> .......... dump the compressed output (PGM file).\n"); printf(" -d <file.pgm> .......... dump the compressed output (PGM file).\n");
printf(" -alpha_method <int> .... Transparency-compression method (0..1)\n"); printf(" -alpha_method <int> .... Transparency-compression method (0..1)\n");
printf(" -alpha_filter <int> .... predictive filtering for Alpha (0..5)\n"); printf(" -alpha_filter <string> . predictive filtering for alpha plane.\n");
printf(" One of: none, fast (default) or best.\n");
printf(" -noalpha ............... discard any transparency information.\n"); printf(" -noalpha ............... discard any transparency information.\n");
printf("\n"); printf("\n");
@ -795,7 +796,17 @@ int main(int argc, const char *argv[]) {
} else if (!strcmp(argv[c], "-alpha_method") && c < argc - 1) { } else if (!strcmp(argv[c], "-alpha_method") && c < argc - 1) {
config.alpha_compression = strtol(argv[++c], NULL, 0); config.alpha_compression = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-alpha_filter") && c < argc - 1) { } 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")) { } else if (!strcmp(argv[c], "-noalpha")) {
keep_alpha = 0; keep_alpha = 0;
} else if (!strcmp(argv[c], "-size") && c < argc - 1) { } else if (!strcmp(argv[c], "-size") && c < argc - 1) {

View File

@ -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 Lossless compression of alpha is achieved using a value of 100, while the lower
values result in a lossy compression. The default is 100. values result in a lossy compression. The default is 100.
.TP .TP
.B \-alpha_filter int .B \-alpha_filter string
Specify the predictive filtering method (between 0 and 5) for alpha plane. Specify the predictive filtering method for alpha plane. One of 'none', 'fast'
These correspond to prediction modes none, horizontal, vertical, gradient and or 'best', in increasing complexity and slowness order. Default is 'fast'.
paeth filters. The prediction mode 5 will try all the prediction modes (0 to 4) Internally, alpha filtering is performed using four possible predictions (none,
and pick the best prediction mode. The default value is 0 (no prediction). 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 .TP
.B \-f int .B \-f int
Specify the strength of the deblocking filter, between 0 (no filtering) Specify the strength of the deblocking filter, between 0 (no filtering)

View File

@ -14,6 +14,7 @@
#include "./vp8enci.h" #include "./vp8enci.h"
#include "../utils/alpha.h" #include "../utils/alpha.h"
#include "../utils/filters.h"
#if defined(__cplusplus) || defined(c_plusplus) #if defined(__cplusplus) || defined(c_plusplus)
extern "C" { extern "C" {
@ -33,10 +34,15 @@ int VP8EncFinishAlpha(VP8Encoder* enc) {
const WebPPicture* pic = enc->pic_; const WebPPicture* pic = enc->pic_;
uint8_t* tmp_data = NULL; uint8_t* tmp_data = NULL;
size_t tmp_size = 0; 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); assert(pic->a);
if (!EncodeAlpha(pic->a, pic->width, pic->height, pic->a_stride, if (!EncodeAlpha(pic->a, pic->width, pic->height, pic->a_stride,
config->alpha_quality, config->alpha_compression, config->alpha_quality, config->alpha_compression,
config->alpha_filtering, &tmp_data, &tmp_size)) { filter, &tmp_data, &tmp_size)) {
return 0; return 0;
} }
if (tmp_size != (uint32_t)tmp_size) { // Sanity check. if (tmp_size != (uint32_t)tmp_size) { // Sanity check.

View File

@ -42,7 +42,7 @@ int WebPConfigInitInternal(WebPConfig* const config,
config->autofilter = 0; config->autofilter = 0;
config->partition_limit = 0; config->partition_limit = 0;
config->alpha_compression = 1; config->alpha_compression = 1;
config->alpha_filtering = 0; config->alpha_filtering = 1;
config->alpha_quality = 100; config->alpha_quality = 100;
// TODO(skal): tune. // TODO(skal): tune.

View File

@ -203,14 +203,35 @@ static int EncodeZlibTCoder(const uint8_t* data, int width, int height,
return ok && !bw->error_; return ok && !bw->error_;
} }
// -----------------------------------------------------------------------------
static int EncodeAlphaInternal(const uint8_t* data, int width, int height, 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; 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) { if (method == 0) {
ok = VP8BitWriterAppend(bw, data, width * height); ok = VP8BitWriterAppend(bw, alpha_src, width * height);
ok = ok && !bw->error_; ok = ok && !bw->error_;
} else if (method == 1) { } else {
ok = EncodeZlibTCoder(data, width, height, bw); ok = EncodeZlibTCoder(alpha_src, width, height, bw);
VP8BitWriterFinish(bw); VP8BitWriterFinish(bw);
} }
return ok; 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(data != NULL && output != NULL && output_size != NULL);
assert(width > 0 && height > 0); assert(width > 0 && height > 0);
assert(stride >= width); assert(stride >= width);
assert(filter < WEBP_FILTER_LAST); assert(filter >= WEBP_FILTER_NONE && filter <= WEBP_FILTER_FAST);
if (quality < 0 || quality > 100) { if (quality < 0 || quality > 100) {
return 0; return 0;
@ -267,62 +288,67 @@ int EncodeAlpha(const uint8_t* data, int width, int height, int stride,
} }
if (ok) { 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;
}
// Filtering.
for (this_filter = WEBP_FILTER_NONE; this_filter < WEBP_FILTER_LAST;
++this_filter) {
uint8_t header[ALPHA_HEADER_LEN];
VP8BitWriter bw; VP8BitWriter bw;
WebPFilterFunc filter_func = NULL; size_t best_score;
const size_t expected_size = (method == 0) ? int test_filter;
(ALPHA_HEADER_LEN + data_size) : (data_size >> 5); uint8_t* filtered_alpha = NULL;
if (this_filter == WEBP_FILTER_BEST) {
continue; // We always test WEBP_FILTER_NONE first.
} else if (this_filter != filter && filter != WEBP_FILTER_BEST) { ok = EncodeAlphaInternal(quant_alpha, width, height, method,
WEBP_FILTER_NONE, data_size, NULL, &bw);
if (!ok) {
VP8BitWriterWipeOut(&bw);
goto End;
}
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; continue;
} }
header[0] = ((this_filter & 0x0f) << 4) | (method & 0x0f); ok = EncodeAlphaInternal(quant_alpha, width, height, method, test_filter,
header[1] = 0; // reserved byte for later use data_size, filtered_alpha, &tmp_bw);
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);
}
if (ok) { if (ok) {
const size_t this_size = VP8BitWriterSize(&bw); const size_t score = VP8BitWriterSize(&tmp_bw);
if (this_size < best_size) { if (score < best_score) {
free(tmp_out); // swap bitwriter objects.
tmp_out = VP8BitWriterBuf(&bw); VP8BitWriter tmp = tmp_bw;
best_size = this_size; tmp_bw = bw;
bw = tmp;
best_score = score;
}
} else { } else {
VP8BitWriterWipeOut(&bw); VP8BitWriterWipeOut(&bw);
} }
} else { VP8BitWriterWipeOut(&tmp_bw);
free(tmp_out);
VP8BitWriterWipeOut(&bw);
break;
}
} }
Ok:
if (ok) { if (ok) {
*output_size = best_size; *output_size = VP8BitWriterSize(&bw);
*output = tmp_out; *output = VP8BitWriterBuf(&bw);
} }
free(filtered_alpha); free(filtered_alpha);
} }
End:
free(quant_alpha); free(quant_alpha);
return ok; return ok;
} }
@ -379,7 +405,7 @@ int DecodeAlpha(const uint8_t* data, size_t data_size,
uint8_t* decoded_data = NULL; uint8_t* decoded_data = NULL;
const size_t decoded_size = height * width; const size_t decoded_size = height * width;
uint8_t* unfiltered_data = NULL; uint8_t* unfiltered_data = NULL;
int filter; WEBP_FILTER_TYPE filter;
int ok = 0; int ok = 0;
int method; int method;
@ -394,7 +420,7 @@ int DecodeAlpha(const uint8_t* data, size_t data_size,
filter = data[0] >> 4; filter = data[0] >> 4;
ok = (data[1] == 0); ok = (data[1] == 0);
if (method < 0 || method > 1 || if (method < 0 || method > 1 ||
filter < WEBP_FILTER_NONE || filter > WEBP_FILTER_PAETH || !ok) { filter > WEBP_FILTER_GRADIENT || !ok) {
return 0; return 0;
} }

View File

@ -115,6 +115,11 @@ static void VerticalUnfilter(const uint8_t* data, int width, int height,
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Gradient filter. // 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, static void GradientFilter(const uint8_t* data, int width, int height,
int bpp, int stride, uint8_t* filtered_data) { int bpp, int stride, uint8_t* filtered_data) {
int h; 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; const uint8_t* const prev_line = scan_line - stride;
memcpy((void*)out, (const void*)scan_line, bpp); memcpy((void*)out, (const void*)scan_line, bpp);
for (w = bpp; w < width * bpp; ++w) { for (w = bpp; w < width * bpp; ++w) {
const uint8_t predictor = scan_line[w - bpp] + prev_line[w] - out[w] = scan_line[w] - GradientPredictor(scan_line[w - bpp],
prev_line[w - bpp]; prev_line[w],
out[w] = scan_line[w] - predictor; 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; const uint8_t* const out_prev_line = out - stride;
memcpy((void*)out, (const void*)scan_line, bpp); memcpy((void*)out, (const void*)scan_line, bpp);
for (w = bpp; w < width * bpp; ++w) { for (w = bpp; w < width * bpp; ++w) {
const uint8_t predictor = out[w - bpp] + out_prev_line[w] - out[w] = scan_line[w] + GradientPredictor(out[w - bpp],
out_prev_line[w - bpp]; out_prev_line[w],
out[w] = scan_line[w] + predictor; out_prev_line[w - bpp]);
}
}
}
//------------------------------------------------------------------------------
// 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]);
} }
} }
} }
#undef SANITY_CHECK #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] = { const WebPFilterFunc WebPFilters[WEBP_FILTER_LAST] = {
NULL, // WEBP_FILTER_NONE NULL, // WEBP_FILTER_NONE
HorizontalFilter, // WEBP_FILTER_HORIZONTAL HorizontalFilter, // WEBP_FILTER_HORIZONTAL
VerticalFilter, // WEBP_FILTER_VERTICAL VerticalFilter, // WEBP_FILTER_VERTICAL
GradientFilter, // WEBP_FILTER_GRADIENT GradientFilter // WEBP_FILTER_GRADIENT
PaethFilter, // WEBP_FILTER_PAETH
NULL // WEBP_FILTER_BEST
}; };
const WebPFilterFunc WebPUnfilters[WEBP_FILTER_LAST] = { const WebPFilterFunc WebPUnfilters[WEBP_FILTER_LAST] = {
NULL, // WEBP_FILTER_NONE NULL, // WEBP_FILTER_NONE
HorizontalUnfilter, // WEBP_FILTER_HORIZONTAL HorizontalUnfilter, // WEBP_FILTER_HORIZONTAL
VerticalUnfilter, // WEBP_FILTER_VERTICAL VerticalUnfilter, // WEBP_FILTER_VERTICAL
GradientUnfilter, // WEBP_FILTER_GRADIENT GradientUnfilter // WEBP_FILTER_GRADIENT
PaethUnfilter, // WEBP_FILTER_PAETH
NULL // WEBP_FILTER_BEST
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -24,9 +24,9 @@ typedef enum {
WEBP_FILTER_HORIZONTAL, WEBP_FILTER_HORIZONTAL,
WEBP_FILTER_VERTICAL, WEBP_FILTER_VERTICAL,
WEBP_FILTER_GRADIENT, WEBP_FILTER_GRADIENT,
WEBP_FILTER_PAETH, WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker
WEBP_FILTER_BEST, WEBP_FILTER_BEST,
WEBP_FILTER_LAST, WEBP_FILTER_FAST
} WEBP_FILTER_TYPE; } WEBP_FILTER_TYPE;
typedef void (*WebPFilterFunc)(const uint8_t* in, int width, int height, 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 // 'bpp' is number of bytes per pixel, and
// 'stride' is number of bytes per scan line (with possible padding). // 'stride' is number of bytes per scan line (with possible padding).
// 'out' should be pre-allocated. // '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. // 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) #if defined(__cplusplus) || defined(c_plusplus)
} // extern "C" } // extern "C"

View File

@ -75,8 +75,7 @@ typedef struct {
// 1 = backward reference counts encoded with // 1 = backward reference counts encoded with
// arithmetic encoder). Default is 1. // arithmetic encoder). Default is 1.
int alpha_filtering; // Predictive filtering method for alpha plane. int alpha_filtering; // Predictive filtering method for alpha plane.
// (0 = none, 1 = horizontal, 2 = vertical, 3 = grad, // 0: none, 1: fast, 2: best. Default if 1.
// 4 = Paeth and 5 = Best of (0 .. 4).
int alpha_quality; // Between 0 (smallest size) and 100 (lossless). int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
// Default is 100. // Default is 100.
} WebPConfig; } WebPConfig;