Path: blob/master/thirdparty/libwebp/src/utils/palette.c
9913 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>1718#include "src/dsp/lossless_common.h"19#include "src/utils/color_cache_utils.h"20#include "src/utils/utils.h"21#include "src/webp/encode.h"22#include "src/webp/format_constants.h"2324// -----------------------------------------------------------------------------2526// Palette reordering for smaller sum of deltas (and for smaller storage).2728static int PaletteCompareColorsForQsort(const void* p1, const void* p2) {29const uint32_t a = WebPMemToUint32((uint8_t*)p1);30const uint32_t b = WebPMemToUint32((uint8_t*)p2);31assert(a != b);32return (a < b) ? -1 : 1;33}3435static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) {36return (v <= 128) ? v : (256 - v);37}3839// Computes a value that is related to the entropy created by the40// palette entry diff.41//42// Note that the last & 0xff is a no-operation in the next statement, but43// removed by most compilers and is here only for regularity of the code.44static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) {45const uint32_t diff = VP8LSubPixels(col1, col2);46const int kMoreWeightForRGBThanForAlpha = 9;47uint32_t score;48score = PaletteComponentDistance((diff >> 0) & 0xff);49score += PaletteComponentDistance((diff >> 8) & 0xff);50score += PaletteComponentDistance((diff >> 16) & 0xff);51score *= kMoreWeightForRGBThanForAlpha;52score += PaletteComponentDistance((diff >> 24) & 0xff);53return score;54}5556static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) {57const uint32_t tmp = *col1;58*col1 = *col2;59*col2 = tmp;60}6162int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors) {63int low = 0, hi = num_colors;64if (sorted[low] == color) return low; // loop invariant: sorted[low] != color65while (1) {66const int mid = (low + hi) >> 1;67if (sorted[mid] == color) {68return mid;69} else if (sorted[mid] < color) {70low = mid;71} else {72hi = mid;73}74}75assert(0);76return 0;77}7879void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors,80uint32_t sorted[], uint32_t idx_map[]) {81uint32_t i;82memcpy(sorted, palette, num_colors * sizeof(*sorted));83qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort);84for (i = 0; i < num_colors; ++i) {85idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i;86}87}8889//------------------------------------------------------------------------------9091#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4)92#define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE).9394int GetColorPalette(const WebPPicture* const pic, uint32_t* const palette) {95int i;96int x, y;97int num_colors = 0;98uint8_t in_use[COLOR_HASH_SIZE] = {0};99uint32_t colors[COLOR_HASH_SIZE] = {0};100const uint32_t* argb = pic->argb;101const int width = pic->width;102const int height = pic->height;103uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0]104assert(pic != NULL);105assert(pic->use_argb);106107for (y = 0; y < height; ++y) {108for (x = 0; x < width; ++x) {109int key;110if (argb[x] == last_pix) {111continue;112}113last_pix = argb[x];114key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT);115while (1) {116if (!in_use[key]) {117colors[key] = last_pix;118in_use[key] = 1;119++num_colors;120if (num_colors > MAX_PALETTE_SIZE) {121return MAX_PALETTE_SIZE + 1; // Exact count not needed.122}123break;124} else if (colors[key] == last_pix) {125break; // The color is already there.126} else {127// Some other color sits here, so do linear conflict resolution.128++key;129key &= (COLOR_HASH_SIZE - 1); // Key mask.130}131}132}133argb += pic->argb_stride;134}135136if (palette != NULL) { // Fill the colors into palette.137num_colors = 0;138for (i = 0; i < COLOR_HASH_SIZE; ++i) {139if (in_use[i]) {140palette[num_colors] = colors[i];141++num_colors;142}143}144qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort);145}146return num_colors;147}148149#undef COLOR_HASH_SIZE150#undef COLOR_HASH_RIGHT_SHIFT151152// -----------------------------------------------------------------------------153154// The palette has been sorted by alpha. This function checks if the other155// components of the palette have a monotonic development with regards to156// position in the palette. If all have monotonic development, there is157// no benefit to re-organize them greedily. A monotonic development158// would be spotted in green-only situations (like lossy alpha) or gray-scale159// images.160static int PaletteHasNonMonotonousDeltas(const uint32_t* const palette,161int num_colors) {162uint32_t predict = 0x000000;163int i;164uint8_t sign_found = 0x00;165for (i = 0; i < num_colors; ++i) {166const uint32_t diff = VP8LSubPixels(palette[i], predict);167const uint8_t rd = (diff >> 16) & 0xff;168const uint8_t gd = (diff >> 8) & 0xff;169const uint8_t bd = (diff >> 0) & 0xff;170if (rd != 0x00) {171sign_found |= (rd < 0x80) ? 1 : 2;172}173if (gd != 0x00) {174sign_found |= (gd < 0x80) ? 8 : 16;175}176if (bd != 0x00) {177sign_found |= (bd < 0x80) ? 64 : 128;178}179predict = palette[i];180}181return (sign_found & (sign_found << 1)) != 0; // two consequent signs.182}183184static void PaletteSortMinimizeDeltas(const uint32_t* const palette_sorted,185int num_colors, uint32_t* const palette) {186uint32_t predict = 0x00000000;187int i, k;188memcpy(palette, palette_sorted, num_colors * sizeof(*palette));189if (!PaletteHasNonMonotonousDeltas(palette_sorted, num_colors)) return;190// Find greedily always the closest color of the predicted color to minimize191// deltas in the palette. This reduces storage needs since the192// palette is stored with delta encoding.193if (num_colors > 17) {194if (palette[0] == 0) {195--num_colors;196SwapColor(&palette[num_colors], &palette[0]);197}198}199for (i = 0; i < num_colors; ++i) {200int best_ix = i;201uint32_t best_score = ~0U;202for (k = i; k < num_colors; ++k) {203const uint32_t cur_score = PaletteColorDistance(palette[k], predict);204if (best_score > cur_score) {205best_score = cur_score;206best_ix = k;207}208}209SwapColor(&palette[best_ix], &palette[i]);210predict = palette[i];211}212}213214// -----------------------------------------------------------------------------215// Modified Zeng method from "A Survey on Palette Reordering216// Methods for Improving the Compression of Color-Indexed Images" by Armando J.217// Pinho and Antonio J. R. Neves.218219// Finds the biggest cooccurrence in the matrix.220static void CoOccurrenceFindMax(const uint32_t* const cooccurrence,221uint32_t num_colors, uint8_t* const c1,222uint8_t* const c2) {223// Find the index that is most frequently located adjacent to other224// (different) indexes.225uint32_t best_sum = 0u;226uint32_t i, j, best_cooccurrence;227*c1 = 0u;228for (i = 0; i < num_colors; ++i) {229uint32_t sum = 0;230for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j];231if (sum > best_sum) {232best_sum = sum;233*c1 = i;234}235}236// Find the index that is most frequently found adjacent to *c1.237*c2 = 0u;238best_cooccurrence = 0u;239for (i = 0; i < num_colors; ++i) {240if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) {241best_cooccurrence = cooccurrence[*c1 * num_colors + i];242*c2 = i;243}244}245assert(*c1 != *c2);246}247248// Builds the cooccurrence matrix249static int CoOccurrenceBuild(const WebPPicture* const pic,250const uint32_t* const palette, uint32_t num_colors,251uint32_t* cooccurrence) {252uint32_t *lines, *line_top, *line_current, *line_tmp;253int x, y;254const uint32_t* src = pic->argb;255uint32_t prev_pix = ~src[0];256uint32_t prev_idx = 0u;257uint32_t idx_map[MAX_PALETTE_SIZE] = {0};258uint32_t palette_sorted[MAX_PALETTE_SIZE];259lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines));260if (lines == NULL) {261return 0;262}263line_top = &lines[0];264line_current = &lines[pic->width];265PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map);266for (y = 0; y < pic->height; ++y) {267for (x = 0; x < pic->width; ++x) {268const uint32_t pix = src[x];269if (pix != prev_pix) {270prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)];271prev_pix = pix;272}273line_current[x] = prev_idx;274// 4-connectivity is what works best as mentioned in "On the relation275// between Memon's and the modified Zeng's palette reordering methods".276if (x > 0 && prev_idx != line_current[x - 1]) {277const uint32_t left_idx = line_current[x - 1];278++cooccurrence[prev_idx * num_colors + left_idx];279++cooccurrence[left_idx * num_colors + prev_idx];280}281if (y > 0 && prev_idx != line_top[x]) {282const uint32_t top_idx = line_top[x];283++cooccurrence[prev_idx * num_colors + top_idx];284++cooccurrence[top_idx * num_colors + prev_idx];285}286}287line_tmp = line_top;288line_top = line_current;289line_current = line_tmp;290src += pic->argb_stride;291}292WebPSafeFree(lines);293return 1;294}295296struct Sum {297uint8_t index;298uint32_t sum;299};300301static int PaletteSortModifiedZeng(const WebPPicture* const pic,302const uint32_t* const palette_in,303uint32_t num_colors,304uint32_t* const palette) {305uint32_t i, j, ind;306uint8_t remapping[MAX_PALETTE_SIZE];307uint32_t* cooccurrence;308struct Sum sums[MAX_PALETTE_SIZE];309uint32_t first, last;310uint32_t num_sums;311// TODO(vrabaud) check whether one color images should use palette or not.312if (num_colors <= 1) return 1;313// Build the co-occurrence matrix.314cooccurrence =315(uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence));316if (cooccurrence == NULL) {317return 0;318}319if (!CoOccurrenceBuild(pic, palette_in, num_colors, cooccurrence)) {320WebPSafeFree(cooccurrence);321return 0;322}323324// Initialize the mapping list with the two best indices.325CoOccurrenceFindMax(cooccurrence, num_colors, &remapping[0], &remapping[1]);326327// We need to append and prepend to the list of remapping. To this end, we328// actually define the next start/end of the list as indices in a vector (with329// a wrap around when the end is reached).330first = 0;331last = 1;332num_sums = num_colors - 2; // -2 because we know the first two values333if (num_sums > 0) {334// Initialize the sums with the first two remappings and find the best one335struct Sum* best_sum = &sums[0];336best_sum->index = 0u;337best_sum->sum = 0u;338for (i = 0, j = 0; i < num_colors; ++i) {339if (i == remapping[0] || i == remapping[1]) continue;340sums[j].index = i;341sums[j].sum = cooccurrence[i * num_colors + remapping[0]] +342cooccurrence[i * num_colors + remapping[1]];343if (sums[j].sum > best_sum->sum) best_sum = &sums[j];344++j;345}346347while (num_sums > 0) {348const uint8_t best_index = best_sum->index;349// Compute delta to know if we need to prepend or append the best index.350int32_t delta = 0;351const int32_t n = num_colors - num_sums;352for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) {353const uint16_t l_j = remapping[(ind + j) % num_colors];354delta += (n - 1 - 2 * (int32_t)j) *355(int32_t)cooccurrence[best_index * num_colors + l_j];356}357if (delta > 0) {358first = (first == 0) ? num_colors - 1 : first - 1;359remapping[first] = best_index;360} else {361++last;362remapping[last] = best_index;363}364// Remove best_sum from sums.365*best_sum = sums[num_sums - 1];366--num_sums;367// Update all the sums and find the best one.368best_sum = &sums[0];369for (i = 0; i < num_sums; ++i) {370sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index];371if (sums[i].sum > best_sum->sum) best_sum = &sums[i];372}373}374}375assert((last + 1) % num_colors == first);376WebPSafeFree(cooccurrence);377378// Re-map the palette.379for (i = 0; i < num_colors; ++i) {380palette[i] = palette_in[remapping[(first + i) % num_colors]];381}382return 1;383}384385// -----------------------------------------------------------------------------386387int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic,388const uint32_t* const palette_sorted, uint32_t num_colors,389uint32_t* const palette) {390switch (method) {391case kSortedDefault:392if (palette_sorted[0] == 0 && num_colors > 17) {393memcpy(palette, palette_sorted + 1,394(num_colors - 1) * sizeof(*palette_sorted));395palette[num_colors - 1] = 0;396} else {397memcpy(palette, palette_sorted, num_colors * sizeof(*palette));398}399return 1;400case kMinimizeDelta:401PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette);402return 1;403case kModifiedZeng:404return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette);405case kUnusedPalette:406case kPaletteSortingNum:407break;408}409410assert(0);411return 0;412}413414415