go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

palette.c (14146B)


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