mirror of
https://github.com/webmproject/libwebp.git
synced 2024-11-20 12:28:26 +01:00
optimize palette ordering
We compact the palette by weighted distance, favoring the green channel. Average gain on paletted file is ~0.5%, with gain up to 6-7% on some favorable cases. Encoding speed is unaffected. Disabled for alpha (or any single-channel input) Also: always use quality=20 for EncodePalette() since it doesn't make any real difference. Change-Id: I19fb14316a366f139a941b45aef5663a33c905e1
This commit is contained in:
parent
f545feee64
commit
f82cb06afb
142
src/enc/vp8l.c
142
src/enc/vp8l.c
@ -29,8 +29,10 @@
|
|||||||
#define MAX_HUFF_IMAGE_SIZE (16 * 1024 * 1024)
|
#define MAX_HUFF_IMAGE_SIZE (16 * 1024 * 1024)
|
||||||
#define MAX_COLORS_FOR_GRAPH 64
|
#define MAX_COLORS_FOR_GRAPH 64
|
||||||
|
|
||||||
|
#define OPTIMIZE_MIN_NUM_COLORS 8
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Palette
|
// Palette optimization
|
||||||
|
|
||||||
static int CompareColors(const void* p1, const void* p2) {
|
static int CompareColors(const void* p1, const void* p2) {
|
||||||
const uint32_t a = *(const uint32_t*)p1;
|
const uint32_t a = *(const uint32_t*)p1;
|
||||||
@ -38,6 +40,86 @@ static int CompareColors(const void* p1, const void* p2) {
|
|||||||
assert(a != b);
|
assert(a != b);
|
||||||
return (a < b) ? -1 : 1;
|
return (a < b) ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
static WEBP_INLINE int Distance(int a, int b) {
|
||||||
|
return abs(a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ColorDistance(uint32_t col1, uint32_t col2) {
|
||||||
|
int score = 0;
|
||||||
|
// we favor grouping green channel in the palette
|
||||||
|
score += Distance((col1 >> 0) & 0xff, (col2 >> 0) & 0xff) * 5;
|
||||||
|
score += Distance((col1 >> 8) & 0xff, (col2 >> 8) & 0xff) * 8;
|
||||||
|
score += Distance((col1 >> 16) & 0xff, (col2 >> 16) & 0xff) * 5;
|
||||||
|
score += Distance((col1 >> 24) & 0xff, (col2 >> 24) & 0xff) * 1;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SwapColor(uint32_t* const col1, uint32_t* const col2) {
|
||||||
|
if (col1 != col2) {
|
||||||
|
const uint32_t tmp = *col1;
|
||||||
|
*col1 = *col2;
|
||||||
|
*col2 = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ShouldRestoreSortedPalette(int score_new, int score_orig) {
|
||||||
|
if ((score_orig > 200) && (score_new + 100 > score_orig)) {
|
||||||
|
return 1; // improvement not big enough
|
||||||
|
}
|
||||||
|
// if drop is less 20%, it's not enough
|
||||||
|
if ((score_new + 100) > (score_orig + 100) * 80 / 100) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (score_orig > 500) { // if original palette was dispersed and...
|
||||||
|
// improvement is not clear?
|
||||||
|
if (score_new > 300) return 1;
|
||||||
|
}
|
||||||
|
return 0; // keep the new one
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OptimizePalette(uint32_t palette[], int num_colors) {
|
||||||
|
uint32_t palette_orig[MAX_PALETTE_SIZE];
|
||||||
|
int score_orig = 0, score_new = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// Compute original dispersion.
|
||||||
|
assert(num_colors > 1 && num_colors <= MAX_PALETTE_SIZE);
|
||||||
|
for (i = 1; i < num_colors; ++i) {
|
||||||
|
score_orig += ColorDistance(palette[i], palette[i - 1]);
|
||||||
|
}
|
||||||
|
score_orig /= (num_colors - 1);
|
||||||
|
// if score is already quite good, bail out at once.
|
||||||
|
if (score_orig < 100) return;
|
||||||
|
|
||||||
|
memcpy(palette_orig, palette, num_colors * sizeof(palette_orig[0]));
|
||||||
|
|
||||||
|
// palette[0] contains the lowest ordered color already. Keep it.
|
||||||
|
// Reorder subsequent palette colors by shortest distance to previous.
|
||||||
|
for (i = 1; i < num_colors; ++i) {
|
||||||
|
int j;
|
||||||
|
int best_col = -1;
|
||||||
|
int best_score = 0;
|
||||||
|
const uint32_t prev_color = palette[i - 1];
|
||||||
|
for (j = i; j < num_colors; ++j) {
|
||||||
|
const int score = ColorDistance(palette[j], prev_color);
|
||||||
|
if (best_col < 0 || score < best_score) {
|
||||||
|
best_col = j;
|
||||||
|
best_score = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
score_new += best_score;
|
||||||
|
SwapColor(&palette[best_col], &palette[i]);
|
||||||
|
}
|
||||||
|
// dispersion is typically in range ~[100-1000]
|
||||||
|
score_new /= (num_colors - 1);
|
||||||
|
|
||||||
|
if (ShouldRestoreSortedPalette(score_new, score_orig)) {
|
||||||
|
memcpy(palette, palette_orig, num_colors * sizeof(palette[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Palette
|
||||||
|
|
||||||
// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE,
|
// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE,
|
||||||
// creates a palette and returns true, else returns false.
|
// creates a palette and returns true, else returns false.
|
||||||
@ -52,6 +134,7 @@ static int AnalyzeAndCreatePalette(const WebPPicture* const pic,
|
|||||||
const uint32_t* argb = pic->argb;
|
const uint32_t* argb = pic->argb;
|
||||||
const int width = pic->width;
|
const int width = pic->width;
|
||||||
const int height = pic->height;
|
const int height = pic->height;
|
||||||
|
uint32_t all_color_bits;
|
||||||
uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0]
|
uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0]
|
||||||
|
|
||||||
for (y = 0; y < height; ++y) {
|
for (y = 0; y < height; ++y) {
|
||||||
@ -86,15 +169,24 @@ static int AnalyzeAndCreatePalette(const WebPPicture* const pic,
|
|||||||
|
|
||||||
// TODO(skal): could we reuse in_use[] to speed up EncodePalette()?
|
// TODO(skal): could we reuse in_use[] to speed up EncodePalette()?
|
||||||
num_colors = 0;
|
num_colors = 0;
|
||||||
|
all_color_bits = 0x00000000;
|
||||||
for (i = 0; i < (int)(sizeof(in_use) / sizeof(in_use[0])); ++i) {
|
for (i = 0; i < (int)(sizeof(in_use) / sizeof(in_use[0])); ++i) {
|
||||||
if (in_use[i]) {
|
if (in_use[i]) {
|
||||||
palette[num_colors] = colors[i];
|
palette[num_colors] = colors[i];
|
||||||
|
all_color_bits |= colors[i];
|
||||||
++num_colors;
|
++num_colors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qsort(palette, num_colors, sizeof(*palette), CompareColors);
|
|
||||||
*palette_size = num_colors;
|
*palette_size = num_colors;
|
||||||
|
qsort(palette, num_colors, sizeof(*palette), CompareColors);
|
||||||
|
// OptimizePalette() is not useful for single-channel (like alpha, e.g.).
|
||||||
|
if (num_colors > OPTIMIZE_MIN_NUM_COLORS &&
|
||||||
|
(all_color_bits & ~0x000000ffu) != 0 && // all red?
|
||||||
|
(all_color_bits & ~0x0000ff00u) != 0 && // all green/alpha?
|
||||||
|
(all_color_bits & ~0x00ff0000u) != 0) { // all blue?
|
||||||
|
OptimizePalette(palette, num_colors);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -881,6 +973,30 @@ static WebPEncodingError AllocateTransformBuffer(VP8LEncoder* const enc,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void MapToPalette(const uint32_t palette[], int num_colors,
|
||||||
|
uint32_t* const last_pix, int* const last_idx,
|
||||||
|
const uint32_t* src, uint8_t* dst, int width) {
|
||||||
|
int x;
|
||||||
|
int prev_idx = *last_idx;
|
||||||
|
uint32_t prev_pix = *last_pix;
|
||||||
|
for (x = 0; x < width; ++x) {
|
||||||
|
const uint32_t pix = src[x];
|
||||||
|
if (pix != prev_pix) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < num_colors; ++i) {
|
||||||
|
if (pix == palette[i]) {
|
||||||
|
prev_idx = i;
|
||||||
|
prev_pix = pix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst[x] = prev_idx;
|
||||||
|
}
|
||||||
|
*last_idx = prev_idx;
|
||||||
|
*last_pix = prev_pix;
|
||||||
|
}
|
||||||
|
|
||||||
static void ApplyPalette(uint32_t* src, uint32_t* dst,
|
static void ApplyPalette(uint32_t* src, uint32_t* dst,
|
||||||
uint32_t src_stride, uint32_t dst_stride,
|
uint32_t src_stride, uint32_t dst_stride,
|
||||||
const uint32_t* palette, int palette_size,
|
const uint32_t* palette, int palette_size,
|
||||||
@ -914,19 +1030,8 @@ static void ApplyPalette(uint32_t* src, uint32_t* dst,
|
|||||||
uint32_t last_pix = palette[0];
|
uint32_t last_pix = palette[0];
|
||||||
int last_idx = 0;
|
int last_idx = 0;
|
||||||
for (y = 0; y < height; ++y) {
|
for (y = 0; y < height; ++y) {
|
||||||
for (x = 0; x < width; ++x) {
|
MapToPalette(palette, palette_size, &last_pix, &last_idx,
|
||||||
const uint32_t pix = src[x];
|
src, row, width);
|
||||||
if (pix != last_pix) {
|
|
||||||
for (i = 0; i < palette_size; ++i) {
|
|
||||||
if (pix == palette[i]) {
|
|
||||||
last_idx = i;
|
|
||||||
last_pix = pix;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row[x] = last_idx;
|
|
||||||
}
|
|
||||||
VP8LBundleColorMap(row, width, xbits, dst);
|
VP8LBundleColorMap(row, width, xbits, dst);
|
||||||
src += src_stride;
|
src += src_stride;
|
||||||
dst += dst_stride;
|
dst += dst_stride;
|
||||||
@ -938,7 +1043,7 @@ static void ApplyPalette(uint32_t* src, uint32_t* dst,
|
|||||||
// Also, "enc->palette_" will be modified after this call and should not be used
|
// Also, "enc->palette_" will be modified after this call and should not be used
|
||||||
// later.
|
// later.
|
||||||
static WebPEncodingError EncodePalette(VP8LBitWriter* const bw,
|
static WebPEncodingError EncodePalette(VP8LBitWriter* const bw,
|
||||||
VP8LEncoder* const enc, int quality) {
|
VP8LEncoder* const enc) {
|
||||||
WebPEncodingError err = VP8_ENC_OK;
|
WebPEncodingError err = VP8_ENC_OK;
|
||||||
int i;
|
int i;
|
||||||
const WebPPicture* const pic = enc->pic_;
|
const WebPPicture* const pic = enc->pic_;
|
||||||
@ -978,8 +1083,7 @@ static WebPEncodingError EncodePalette(VP8LBitWriter* const bw,
|
|||||||
palette[i] = VP8LSubPixels(palette[i], palette[i - 1]);
|
palette[i] = VP8LSubPixels(palette[i], palette[i - 1]);
|
||||||
}
|
}
|
||||||
err = EncodeImageNoHuffman(bw, palette, &enc->hash_chain_, enc->refs_,
|
err = EncodeImageNoHuffman(bw, palette, &enc->hash_chain_, enc->refs_,
|
||||||
palette_size, 1, quality);
|
palette_size, 1, 20 /* quality */);
|
||||||
|
|
||||||
Error:
|
Error:
|
||||||
WebPSafeFree(row);
|
WebPSafeFree(row);
|
||||||
return err;
|
return err;
|
||||||
@ -1080,7 +1184,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
|
|||||||
FinishEncParams(enc);
|
FinishEncParams(enc);
|
||||||
|
|
||||||
if (enc->use_palette_) {
|
if (enc->use_palette_) {
|
||||||
err = EncodePalette(bw, enc, quality);
|
err = EncodePalette(bw, enc);
|
||||||
if (err != VP8_ENC_OK) goto Error;
|
if (err != VP8_ENC_OK) goto Error;
|
||||||
// Color cache is disabled for palette.
|
// Color cache is disabled for palette.
|
||||||
enc->cache_bits_ = 0;
|
enc->cache_bits_ = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user