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