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