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:
Pascal Massimino 2014-10-08 08:42:36 +02:00
parent f545feee64
commit f82cb06afb

View File

@ -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;