Path: blob/master/thirdparty/libwebp/src/utils/palette.c
21054 views
// Copyright 2023 Google Inc. All Rights Reserved.1//2// Use of this source code is governed by a BSD-style license3// that can be found in the COPYING file in the root of the source4// tree. An additional intellectual property rights grant can be found5// in the file PATENTS. All contributing project authors may6// be found in the AUTHORS file in the root of the source tree.7// -----------------------------------------------------------------------------8//9// Utilities for palette analysis.10//11// Author: Vincent Rabaud ([email protected])1213#include "src/utils/palette.h"1415#include <assert.h>16#include <stdlib.h>17#include <string.h>1819#include "src/dsp/lossless_common.h"20#include "src/utils/color_cache_utils.h"21#include "src/utils/utils.h"22#include "src/webp/encode.h"23#include "src/webp/format_constants.h"24#include "src/webp/types.h"2526// -----------------------------------------------------------------------------2728// Palette reordering for smaller sum of deltas (and for smaller storage).2930static int PaletteCompareColorsForQsort(const void* p1, const void* p2) {31const uint32_t a = WebPMemToUint32((uint8_t*)p1);32const uint32_t b = WebPMemToUint32((uint8_t*)p2);33assert(a != b);34return (a < b) ? -1 : 1;35}3637static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) {38return (v <= 128) ? v : (256 - v);39}4041// Computes a value that is related to the entropy created by the42// palette entry diff.43//44// Note that the last & 0xff is a no-operation in the next statement, but45// removed by most compilers and is here only for regularity of the code.46static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) {47const uint32_t diff = VP8LSubPixels(col1, col2);48const int kMoreWeightForRGBThanForAlpha = 9;49uint32_t score;50score = PaletteComponentDistance((diff >> 0) & 0xff);51score += PaletteComponentDistance((diff >> 8) & 0xff);52score += PaletteComponentDistance((diff >> 16) & 0xff);53score *= kMoreWeightForRGBThanForAlpha;54score += PaletteComponentDistance((diff >> 24) & 0xff);55return score;56}5758static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) {59const uint32_t tmp = *col1;60*col1 = *col2;61*col2 = tmp;62}6364int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors) {65int low = 0, hi = num_colors;66if (sorted[low] == color) return low; // loop invariant: sorted[low] != color67while (1) {68const int mid = (low + hi) >> 1;69if (sorted[mid] == color) {70return mid;71} else if (sorted[mid] < color) {72low = mid;73} else {74hi = mid;75}76}77assert(0);78return 0;79}8081void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors,82uint32_t sorted[], uint32_t idx_map[]) {83uint32_t i;84memcpy(sorted, palette, num_colors * sizeof(*sorted));85qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort);86for (i = 0; i < num_colors; ++i) {87idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i;88}89}9091//------------------------------------------------------------------------------9293#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4)94#define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE).9596int GetColorPalette(const WebPPicture* const pic, uint32_t* const palette) {97int i;98int x, y;99int num_colors = 0;100uint8_t in_use[COLOR_HASH_SIZE] = {0};101uint32_t colors[COLOR_HASH_SIZE] = {0};102const uint32_t* argb = pic->argb;103const int width = pic->width;104const int height = pic->height;105uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0]106assert(pic != NULL);107assert(pic->use_argb);108109for (y = 0; y < height; ++y) {110for (x = 0; x < width; ++x) {111int key;112if (argb[x] == last_pix) {113continue;114}115last_pix = argb[x];116key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT);117while (1) {118if (!in_use[key]) {119colors[key] = last_pix;120in_use[key] = 1;121++num_colors;122if (num_colors > MAX_PALETTE_SIZE) {123return MAX_PALETTE_SIZE + 1; // Exact count not needed.124}125break;126} else if (colors[key] == last_pix) {127break; // The color is already there.128} else {129// Some other color sits here, so do linear conflict resolution.130++key;131key &= (COLOR_HASH_SIZE - 1); // Key mask.132}133}134}135argb += pic->argb_stride;136}137138if (palette != NULL) { // Fill the colors into palette.139num_colors = 0;140for (i = 0; i < COLOR_HASH_SIZE; ++i) {141if (in_use[i]) {142palette[num_colors] = colors[i];143++num_colors;144}145}146qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort);147}148return num_colors;149}150151#undef COLOR_HASH_SIZE152#undef COLOR_HASH_RIGHT_SHIFT153154// -----------------------------------------------------------------------------155156// The palette has been sorted by alpha. This function checks if the other157// components of the palette have a monotonic development with regards to158// position in the palette. If all have monotonic development, there is159// no benefit to re-organize them greedily. A monotonic development160// would be spotted in green-only situations (like lossy alpha) or gray-scale161// images.162static int PaletteHasNonMonotonousDeltas(const uint32_t* const palette,163int num_colors) {164uint32_t predict = 0x000000;165int i;166uint8_t sign_found = 0x00;167for (i = 0; i < num_colors; ++i) {168const uint32_t diff = VP8LSubPixels(palette[i], predict);169const uint8_t rd = (diff >> 16) & 0xff;170const uint8_t gd = (diff >> 8) & 0xff;171const uint8_t bd = (diff >> 0) & 0xff;172if (rd != 0x00) {173sign_found |= (rd < 0x80) ? 1 : 2;174}175if (gd != 0x00) {176sign_found |= (gd < 0x80) ? 8 : 16;177}178if (bd != 0x00) {179sign_found |= (bd < 0x80) ? 64 : 128;180}181predict = palette[i];182}183return (sign_found & (sign_found << 1)) != 0; // two consequent signs.184}185186static void PaletteSortMinimizeDeltas(const uint32_t* const palette_sorted,187int num_colors, uint32_t* const palette) {188uint32_t predict = 0x00000000;189int i, k;190memcpy(palette, palette_sorted, num_colors * sizeof(*palette));191if (!PaletteHasNonMonotonousDeltas(palette_sorted, num_colors)) return;192// Find greedily always the closest color of the predicted color to minimize193// deltas in the palette. This reduces storage needs since the194// palette is stored with delta encoding.195if (num_colors > 17) {196if (palette[0] == 0) {197--num_colors;198SwapColor(&palette[num_colors], &palette[0]);199}200}201for (i = 0; i < num_colors; ++i) {202int best_ix = i;203uint32_t best_score = ~0U;204for (k = i; k < num_colors; ++k) {205const uint32_t cur_score = PaletteColorDistance(palette[k], predict);206if (best_score > cur_score) {207best_score = cur_score;208best_ix = k;209}210}211SwapColor(&palette[best_ix], &palette[i]);212predict = palette[i];213}214}215216// -----------------------------------------------------------------------------217// Modified Zeng method from "A Survey on Palette Reordering218// Methods for Improving the Compression of Color-Indexed Images" by Armando J.219// Pinho and Antonio J. R. Neves.220221// Finds the biggest cooccurrence in the matrix.222static void CoOccurrenceFindMax(const uint32_t* const cooccurrence,223uint32_t num_colors, uint8_t* const c1,224uint8_t* const c2) {225// Find the index that is most frequently located adjacent to other226// (different) indexes.227uint32_t best_sum = 0u;228uint32_t i, j, best_cooccurrence;229*c1 = 0u;230for (i = 0; i < num_colors; ++i) {231uint32_t sum = 0;232for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j];233if (sum > best_sum) {234best_sum = sum;235*c1 = i;236}237}238// Find the index that is most frequently found adjacent to *c1.239*c2 = 0u;240best_cooccurrence = 0u;241for (i = 0; i < num_colors; ++i) {242if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) {243best_cooccurrence = cooccurrence[*c1 * num_colors + i];244*c2 = i;245}246}247assert(*c1 != *c2);248}249250// Builds the cooccurrence matrix251static int CoOccurrenceBuild(const WebPPicture* const pic,252const uint32_t* const palette, uint32_t num_colors,253uint32_t* cooccurrence) {254uint32_t *lines, *line_top, *line_current, *line_tmp;255int x, y;256const uint32_t* src = pic->argb;257uint32_t prev_pix = ~src[0];258uint32_t prev_idx = 0u;259uint32_t idx_map[MAX_PALETTE_SIZE] = {0};260uint32_t palette_sorted[MAX_PALETTE_SIZE];261lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines));262if (lines == NULL) {263return 0;264}265line_top = &lines[0];266line_current = &lines[pic->width];267PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map);268for (y = 0; y < pic->height; ++y) {269for (x = 0; x < pic->width; ++x) {270const uint32_t pix = src[x];271if (pix != prev_pix) {272prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)];273prev_pix = pix;274}275line_current[x] = prev_idx;276// 4-connectivity is what works best as mentioned in "On the relation277// between Memon's and the modified Zeng's palette reordering methods".278if (x > 0 && prev_idx != line_current[x - 1]) {279const uint32_t left_idx = line_current[x - 1];280++cooccurrence[prev_idx * num_colors + left_idx];281++cooccurrence[left_idx * num_colors + prev_idx];282}283if (y > 0 && prev_idx != line_top[x]) {284const uint32_t top_idx = line_top[x];285++cooccurrence[prev_idx * num_colors + top_idx];286++cooccurrence[top_idx * num_colors + prev_idx];287}288}289line_tmp = line_top;290line_top = line_current;291line_current = line_tmp;292src += pic->argb_stride;293}294WebPSafeFree(lines);295return 1;296}297298struct Sum {299uint8_t index;300uint32_t sum;301};302303static int PaletteSortModifiedZeng(const WebPPicture* const pic,304const uint32_t* const palette_in,305uint32_t num_colors,306uint32_t* const palette) {307uint32_t i, j, ind;308uint8_t remapping[MAX_PALETTE_SIZE];309uint32_t* cooccurrence;310struct Sum sums[MAX_PALETTE_SIZE];311uint32_t first, last;312uint32_t num_sums;313// TODO(vrabaud) check whether one color images should use palette or not.314if (num_colors <= 1) return 1;315// Build the co-occurrence matrix.316cooccurrence =317(uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence));318if (cooccurrence == NULL) {319return 0;320}321if (!CoOccurrenceBuild(pic, palette_in, num_colors, cooccurrence)) {322WebPSafeFree(cooccurrence);323return 0;324}325326// Initialize the mapping list with the two best indices.327CoOccurrenceFindMax(cooccurrence, num_colors, &remapping[0], &remapping[1]);328329// We need to append and prepend to the list of remapping. To this end, we330// actually define the next start/end of the list as indices in a vector (with331// a wrap around when the end is reached).332first = 0;333last = 1;334num_sums = num_colors - 2; // -2 because we know the first two values335if (num_sums > 0) {336// Initialize the sums with the first two remappings and find the best one337struct Sum* best_sum = &sums[0];338best_sum->index = 0u;339best_sum->sum = 0u;340for (i = 0, j = 0; i < num_colors; ++i) {341if (i == remapping[0] || i == remapping[1]) continue;342sums[j].index = i;343sums[j].sum = cooccurrence[i * num_colors + remapping[0]] +344cooccurrence[i * num_colors + remapping[1]];345if (sums[j].sum > best_sum->sum) best_sum = &sums[j];346++j;347}348349while (num_sums > 0) {350const uint8_t best_index = best_sum->index;351// Compute delta to know if we need to prepend or append the best index.352int32_t delta = 0;353const int32_t n = num_colors - num_sums;354for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) {355const uint16_t l_j = remapping[(ind + j) % num_colors];356delta += (n - 1 - 2 * (int32_t)j) *357(int32_t)cooccurrence[best_index * num_colors + l_j];358}359if (delta > 0) {360first = (first == 0) ? num_colors - 1 : first - 1;361remapping[first] = best_index;362} else {363++last;364remapping[last] = best_index;365}366// Remove best_sum from sums.367*best_sum = sums[num_sums - 1];368--num_sums;369// Update all the sums and find the best one.370best_sum = &sums[0];371for (i = 0; i < num_sums; ++i) {372sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index];373if (sums[i].sum > best_sum->sum) best_sum = &sums[i];374}375}376}377assert((last + 1) % num_colors == first);378WebPSafeFree(cooccurrence);379380// Re-map the palette.381for (i = 0; i < num_colors; ++i) {382palette[i] = palette_in[remapping[(first + i) % num_colors]];383}384return 1;385}386387// -----------------------------------------------------------------------------388389int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic,390const uint32_t* const palette_sorted, uint32_t num_colors,391uint32_t* const palette) {392switch (method) {393case kSortedDefault:394if (palette_sorted[0] == 0 && num_colors > 17) {395memcpy(palette, palette_sorted + 1,396(num_colors - 1) * sizeof(*palette_sorted));397palette[num_colors - 1] = 0;398} else {399memcpy(palette, palette_sorted, num_colors * sizeof(*palette));400}401return 1;402case kMinimizeDelta:403PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette);404return 1;405case kModifiedZeng:406return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette);407case kUnusedPalette:408case kPaletteSortingNum:409break;410}411412assert(0);413return 0;414}415416417