/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 | | |
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 | | |
29 | 0 | static int PaletteCompareColorsForQsort(const void* p1, const void* p2) { |
30 | 0 | const uint32_t a = WebPMemToUint32((uint8_t*)p1); |
31 | 0 | const uint32_t b = WebPMemToUint32((uint8_t*)p2); |
32 | 0 | assert(a != b); |
33 | 0 | return (a < b) ? -1 : 1; |
34 | 0 | } |
35 | | |
36 | 0 | static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) { |
37 | 0 | return (v <= 128) ? v : (256 - v); |
38 | 0 | } |
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. |
45 | 0 | static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) { |
46 | 0 | const uint32_t diff = VP8LSubPixels(col1, col2); |
47 | 0 | const int kMoreWeightForRGBThanForAlpha = 9; |
48 | 0 | uint32_t score; |
49 | 0 | score = PaletteComponentDistance((diff >> 0) & 0xff); |
50 | 0 | score += PaletteComponentDistance((diff >> 8) & 0xff); |
51 | 0 | score += PaletteComponentDistance((diff >> 16) & 0xff); |
52 | 0 | score *= kMoreWeightForRGBThanForAlpha; |
53 | 0 | score += PaletteComponentDistance((diff >> 24) & 0xff); |
54 | 0 | return score; |
55 | 0 | } |
56 | | |
57 | 0 | static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) { |
58 | 0 | const uint32_t tmp = *col1; |
59 | 0 | *col1 = *col2; |
60 | 0 | *col2 = tmp; |
61 | 0 | } |
62 | | |
63 | 0 | int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors) { |
64 | 0 | int low = 0, hi = num_colors; |
65 | 0 | if (sorted[low] == color) return low; // loop invariant: sorted[low] != color |
66 | 0 | while (1) { |
67 | 0 | const int mid = (low + hi) >> 1; |
68 | 0 | if (sorted[mid] == color) { |
69 | 0 | return mid; |
70 | 0 | } else if (sorted[mid] < color) { |
71 | 0 | low = mid; |
72 | 0 | } else { |
73 | 0 | hi = mid; |
74 | 0 | } |
75 | 0 | } |
76 | 0 | assert(0); |
77 | 0 | return 0; |
78 | 0 | } |
79 | | |
80 | | void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors, |
81 | 0 | uint32_t sorted[], uint32_t idx_map[]) { |
82 | 0 | uint32_t i; |
83 | 0 | memcpy(sorted, palette, num_colors * sizeof(*sorted)); |
84 | 0 | qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort); |
85 | 0 | for (i = 0; i < num_colors; ++i) { |
86 | 0 | idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i; |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | //------------------------------------------------------------------------------ |
91 | | |
92 | 0 | #define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4) |
93 | 0 | #define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE). |
94 | | |
95 | 0 | int GetColorPalette(const WebPPicture* const pic, uint32_t* const palette) { |
96 | 0 | int i; |
97 | 0 | int x, y; |
98 | 0 | int num_colors = 0; |
99 | 0 | uint8_t in_use[COLOR_HASH_SIZE] = {0}; |
100 | 0 | uint32_t colors[COLOR_HASH_SIZE] = {0}; |
101 | 0 | const uint32_t* argb = pic->argb; |
102 | 0 | const int width = pic->width; |
103 | 0 | const int height = pic->height; |
104 | 0 | uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] |
105 | 0 | assert(pic != NULL); |
106 | 0 | assert(pic->use_argb); |
107 | | |
108 | 0 | for (y = 0; y < height; ++y) { |
109 | 0 | for (x = 0; x < width; ++x) { |
110 | 0 | int key; |
111 | 0 | if (argb[x] == last_pix) { |
112 | 0 | continue; |
113 | 0 | } |
114 | 0 | last_pix = argb[x]; |
115 | 0 | key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT); |
116 | 0 | while (1) { |
117 | 0 | if (!in_use[key]) { |
118 | 0 | colors[key] = last_pix; |
119 | 0 | in_use[key] = 1; |
120 | 0 | ++num_colors; |
121 | 0 | if (num_colors > MAX_PALETTE_SIZE) { |
122 | 0 | return MAX_PALETTE_SIZE + 1; // Exact count not needed. |
123 | 0 | } |
124 | 0 | break; |
125 | 0 | } else if (colors[key] == last_pix) { |
126 | 0 | break; // The color is already there. |
127 | 0 | } else { |
128 | | // Some other color sits here, so do linear conflict resolution. |
129 | 0 | ++key; |
130 | 0 | key &= (COLOR_HASH_SIZE - 1); // Key mask. |
131 | 0 | } |
132 | 0 | } |
133 | 0 | } |
134 | 0 | argb += pic->argb_stride; |
135 | 0 | } |
136 | | |
137 | 0 | if (palette != NULL) { // Fill the colors into palette. |
138 | 0 | num_colors = 0; |
139 | 0 | for (i = 0; i < COLOR_HASH_SIZE; ++i) { |
140 | 0 | if (in_use[i]) { |
141 | 0 | palette[num_colors] = colors[i]; |
142 | 0 | ++num_colors; |
143 | 0 | } |
144 | 0 | } |
145 | 0 | qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort); |
146 | 0 | } |
147 | 0 | return num_colors; |
148 | 0 | } |
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. |
161 | | static int PaletteHasNonMonotonousDeltas(const uint32_t* const palette, |
162 | 0 | int num_colors) { |
163 | 0 | uint32_t predict = 0x000000; |
164 | 0 | int i; |
165 | 0 | uint8_t sign_found = 0x00; |
166 | 0 | for (i = 0; i < num_colors; ++i) { |
167 | 0 | const uint32_t diff = VP8LSubPixels(palette[i], predict); |
168 | 0 | const uint8_t rd = (diff >> 16) & 0xff; |
169 | 0 | const uint8_t gd = (diff >> 8) & 0xff; |
170 | 0 | const uint8_t bd = (diff >> 0) & 0xff; |
171 | 0 | if (rd != 0x00) { |
172 | 0 | sign_found |= (rd < 0x80) ? 1 : 2; |
173 | 0 | } |
174 | 0 | if (gd != 0x00) { |
175 | 0 | sign_found |= (gd < 0x80) ? 8 : 16; |
176 | 0 | } |
177 | 0 | if (bd != 0x00) { |
178 | 0 | sign_found |= (bd < 0x80) ? 64 : 128; |
179 | 0 | } |
180 | 0 | predict = palette[i]; |
181 | 0 | } |
182 | 0 | return (sign_found & (sign_found << 1)) != 0; // two consequent signs. |
183 | 0 | } |
184 | | |
185 | | static void PaletteSortMinimizeDeltas(const uint32_t* const palette_sorted, |
186 | 0 | int num_colors, uint32_t* const palette) { |
187 | 0 | uint32_t predict = 0x00000000; |
188 | 0 | int i, k; |
189 | 0 | memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); |
190 | 0 | 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 | 0 | if (num_colors > 17) { |
195 | 0 | if (palette[0] == 0) { |
196 | 0 | --num_colors; |
197 | 0 | SwapColor(&palette[num_colors], &palette[0]); |
198 | 0 | } |
199 | 0 | } |
200 | 0 | for (i = 0; i < num_colors; ++i) { |
201 | 0 | int best_ix = i; |
202 | 0 | uint32_t best_score = ~0U; |
203 | 0 | for (k = i; k < num_colors; ++k) { |
204 | 0 | const uint32_t cur_score = PaletteColorDistance(palette[k], predict); |
205 | 0 | if (best_score > cur_score) { |
206 | 0 | best_score = cur_score; |
207 | 0 | best_ix = k; |
208 | 0 | } |
209 | 0 | } |
210 | 0 | SwapColor(&palette[best_ix], &palette[i]); |
211 | 0 | predict = palette[i]; |
212 | 0 | } |
213 | 0 | } |
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. |
221 | | static void CoOccurrenceFindMax(const uint32_t* const cooccurrence, |
222 | | uint32_t num_colors, uint8_t* const c1, |
223 | 0 | uint8_t* const c2) { |
224 | | // Find the index that is most frequently located adjacent to other |
225 | | // (different) indexes. |
226 | 0 | uint32_t best_sum = 0u; |
227 | 0 | uint32_t i, j, best_cooccurrence; |
228 | 0 | *c1 = 0u; |
229 | 0 | for (i = 0; i < num_colors; ++i) { |
230 | 0 | uint32_t sum = 0; |
231 | 0 | for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j]; |
232 | 0 | if (sum > best_sum) { |
233 | 0 | best_sum = sum; |
234 | 0 | *c1 = i; |
235 | 0 | } |
236 | 0 | } |
237 | | // Find the index that is most frequently found adjacent to *c1. |
238 | 0 | *c2 = 0u; |
239 | 0 | best_cooccurrence = 0u; |
240 | 0 | for (i = 0; i < num_colors; ++i) { |
241 | 0 | if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) { |
242 | 0 | best_cooccurrence = cooccurrence[*c1 * num_colors + i]; |
243 | 0 | *c2 = i; |
244 | 0 | } |
245 | 0 | } |
246 | 0 | assert(*c1 != *c2); |
247 | 0 | } |
248 | | |
249 | | // Builds the cooccurrence matrix |
250 | | static int CoOccurrenceBuild(const WebPPicture* const pic, |
251 | | const uint32_t* const palette, uint32_t num_colors, |
252 | 0 | uint32_t* cooccurrence) { |
253 | 0 | uint32_t *lines, *line_top, *line_current, *line_tmp; |
254 | 0 | int x, y; |
255 | 0 | const uint32_t* src = pic->argb; |
256 | 0 | uint32_t prev_pix = ~src[0]; |
257 | 0 | uint32_t prev_idx = 0u; |
258 | 0 | uint32_t idx_map[MAX_PALETTE_SIZE] = {0}; |
259 | 0 | uint32_t palette_sorted[MAX_PALETTE_SIZE]; |
260 | 0 | lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines)); |
261 | 0 | if (lines == NULL) { |
262 | 0 | return 0; |
263 | 0 | } |
264 | 0 | line_top = &lines[0]; |
265 | 0 | line_current = &lines[pic->width]; |
266 | 0 | PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map); |
267 | 0 | for (y = 0; y < pic->height; ++y) { |
268 | 0 | for (x = 0; x < pic->width; ++x) { |
269 | 0 | const uint32_t pix = src[x]; |
270 | 0 | if (pix != prev_pix) { |
271 | 0 | prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)]; |
272 | 0 | prev_pix = pix; |
273 | 0 | } |
274 | 0 | 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 | 0 | if (x > 0 && prev_idx != line_current[x - 1]) { |
278 | 0 | const uint32_t left_idx = line_current[x - 1]; |
279 | 0 | ++cooccurrence[prev_idx * num_colors + left_idx]; |
280 | 0 | ++cooccurrence[left_idx * num_colors + prev_idx]; |
281 | 0 | } |
282 | 0 | if (y > 0 && prev_idx != line_top[x]) { |
283 | 0 | const uint32_t top_idx = line_top[x]; |
284 | 0 | ++cooccurrence[prev_idx * num_colors + top_idx]; |
285 | 0 | ++cooccurrence[top_idx * num_colors + prev_idx]; |
286 | 0 | } |
287 | 0 | } |
288 | 0 | line_tmp = line_top; |
289 | 0 | line_top = line_current; |
290 | 0 | line_current = line_tmp; |
291 | 0 | src += pic->argb_stride; |
292 | 0 | } |
293 | 0 | WebPSafeFree(lines); |
294 | 0 | return 1; |
295 | 0 | } |
296 | | |
297 | | struct Sum { |
298 | | uint8_t index; |
299 | | uint32_t sum; |
300 | | }; |
301 | | |
302 | | static int PaletteSortModifiedZeng(const WebPPicture* const pic, |
303 | | const uint32_t* const palette_in, |
304 | | uint32_t num_colors, |
305 | 0 | uint32_t* const palette) { |
306 | 0 | uint32_t i, j, ind; |
307 | 0 | uint8_t remapping[MAX_PALETTE_SIZE]; |
308 | 0 | uint32_t* cooccurrence; |
309 | 0 | struct Sum sums[MAX_PALETTE_SIZE]; |
310 | 0 | uint32_t first, last; |
311 | 0 | uint32_t num_sums; |
312 | | // TODO(vrabaud) check whether one color images should use palette or not. |
313 | 0 | if (num_colors <= 1) return 1; |
314 | | // Build the co-occurrence matrix. |
315 | 0 | cooccurrence = |
316 | 0 | (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence)); |
317 | 0 | if (cooccurrence == NULL) { |
318 | 0 | return 0; |
319 | 0 | } |
320 | 0 | if (!CoOccurrenceBuild(pic, palette_in, num_colors, cooccurrence)) { |
321 | 0 | WebPSafeFree(cooccurrence); |
322 | 0 | return 0; |
323 | 0 | } |
324 | | |
325 | | // Initialize the mapping list with the two best indices. |
326 | 0 | 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 | 0 | first = 0; |
332 | 0 | last = 1; |
333 | 0 | num_sums = num_colors - 2; // -2 because we know the first two values |
334 | 0 | if (num_sums > 0) { |
335 | | // Initialize the sums with the first two remappings and find the best one |
336 | 0 | struct Sum* best_sum = &sums[0]; |
337 | 0 | best_sum->index = 0u; |
338 | 0 | best_sum->sum = 0u; |
339 | 0 | for (i = 0, j = 0; i < num_colors; ++i) { |
340 | 0 | if (i == remapping[0] || i == remapping[1]) continue; |
341 | 0 | sums[j].index = i; |
342 | 0 | sums[j].sum = cooccurrence[i * num_colors + remapping[0]] + |
343 | 0 | cooccurrence[i * num_colors + remapping[1]]; |
344 | 0 | if (sums[j].sum > best_sum->sum) best_sum = &sums[j]; |
345 | 0 | ++j; |
346 | 0 | } |
347 | |
|
348 | 0 | while (num_sums > 0) { |
349 | 0 | const uint8_t best_index = best_sum->index; |
350 | | // Compute delta to know if we need to prepend or append the best index. |
351 | 0 | int32_t delta = 0; |
352 | 0 | const int32_t n = num_colors - num_sums; |
353 | 0 | for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) { |
354 | 0 | const uint16_t l_j = remapping[(ind + j) % num_colors]; |
355 | 0 | delta += (n - 1 - 2 * (int32_t)j) * |
356 | 0 | (int32_t)cooccurrence[best_index * num_colors + l_j]; |
357 | 0 | } |
358 | 0 | if (delta > 0) { |
359 | 0 | first = (first == 0) ? num_colors - 1 : first - 1; |
360 | 0 | remapping[first] = best_index; |
361 | 0 | } else { |
362 | 0 | ++last; |
363 | 0 | remapping[last] = best_index; |
364 | 0 | } |
365 | | // Remove best_sum from sums. |
366 | 0 | *best_sum = sums[num_sums - 1]; |
367 | 0 | --num_sums; |
368 | | // Update all the sums and find the best one. |
369 | 0 | best_sum = &sums[0]; |
370 | 0 | for (i = 0; i < num_sums; ++i) { |
371 | 0 | sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index]; |
372 | 0 | if (sums[i].sum > best_sum->sum) best_sum = &sums[i]; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | } |
376 | 0 | assert((last + 1) % num_colors == first); |
377 | 0 | WebPSafeFree(cooccurrence); |
378 | | |
379 | | // Re-map the palette. |
380 | 0 | for (i = 0; i < num_colors; ++i) { |
381 | 0 | palette[i] = palette_in[remapping[(first + i) % num_colors]]; |
382 | 0 | } |
383 | 0 | return 1; |
384 | 0 | } |
385 | | |
386 | | // ----------------------------------------------------------------------------- |
387 | | |
388 | | int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic, |
389 | | const uint32_t* const palette_sorted, uint32_t num_colors, |
390 | 0 | uint32_t* const palette) { |
391 | 0 | switch (method) { |
392 | 0 | case kSortedDefault: |
393 | 0 | if (palette_sorted[0] == 0 && num_colors > 17) { |
394 | 0 | memcpy(palette, palette_sorted + 1, |
395 | 0 | (num_colors - 1) * sizeof(*palette_sorted)); |
396 | 0 | palette[num_colors - 1] = 0; |
397 | 0 | } else { |
398 | 0 | memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); |
399 | 0 | } |
400 | 0 | return 1; |
401 | 0 | case kMinimizeDelta: |
402 | 0 | PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette); |
403 | 0 | return 1; |
404 | 0 | case kModifiedZeng: |
405 | 0 | return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette); |
406 | 0 | case kUnusedPalette: |
407 | 0 | case kPaletteSortingNum: |
408 | 0 | break; |
409 | 0 | } |
410 | | |
411 | 0 | assert(0); |
412 | 0 | return 0; |
413 | 0 | } |