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