/src/libwebp/src/utils/palette.c
Line | Count | Source |
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 | 2.72M | static int PaletteCompareColorsForQsort(const void* p1, const void* p2) { |
35 | 2.72M | const uint32_t a = WebPMemToUint32((uint8_t*)p1); |
36 | 2.72M | const uint32_t b = WebPMemToUint32((uint8_t*)p2); |
37 | 2.72M | assert(a != b); |
38 | 2.72M | return (a < b) ? -1 : 1; |
39 | 2.72M | } |
40 | | |
41 | 491k | static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) { |
42 | 491k | return (v <= 128) ? v : (256 - v); |
43 | 491k | } |
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 | 122k | static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) { |
51 | 122k | const uint32_t diff = VP8LSubPixels(col1, col2); |
52 | 122k | const int kMoreWeightForRGBThanForAlpha = 9; |
53 | 122k | uint32_t score; |
54 | 122k | score = PaletteComponentDistance((diff >> 0) & 0xff); |
55 | 122k | score += PaletteComponentDistance((diff >> 8) & 0xff); |
56 | 122k | score += PaletteComponentDistance((diff >> 16) & 0xff); |
57 | 122k | score *= kMoreWeightForRGBThanForAlpha; |
58 | 122k | score += PaletteComponentDistance((diff >> 24) & 0xff); |
59 | 122k | return score; |
60 | 122k | } |
61 | | |
62 | 2.92k | static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) { |
63 | 2.92k | const uint32_t tmp = *col1; |
64 | 2.92k | *col1 = *col2; |
65 | 2.92k | *col2 = tmp; |
66 | 2.92k | } |
67 | | |
68 | | int SearchColorNoIdx(const uint32_t WEBP_COUNTED_BY(num_colors) sorted[], |
69 | 0 | uint32_t color, int num_colors) { |
70 | 0 | int low = 0, hi = num_colors; |
71 | 0 | if (sorted[low] == color) return low; // loop invariant: sorted[low] != color |
72 | 0 | while (1) { |
73 | 0 | const int mid = (low + hi) >> 1; |
74 | 0 | if (sorted[mid] == color) { |
75 | 0 | return mid; |
76 | 0 | } else if (sorted[mid] < color) { |
77 | 0 | low = mid; |
78 | 0 | } else { |
79 | 0 | hi = mid; |
80 | 0 | } |
81 | 0 | } |
82 | 0 | assert(0); |
83 | 0 | return 0; |
84 | 0 | } |
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 | 0 | uint32_t WEBP_COUNTED_BY(num_colors) idx_map[]) { |
90 | 0 | uint32_t i; |
91 | 0 | memcpy(sorted, palette, num_colors * sizeof(*sorted)); |
92 | 0 | qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort); |
93 | 0 | for (i = 0; i < num_colors; ++i) { |
94 | 0 | idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | //------------------------------------------------------------------------------ |
99 | | |
100 | 4.21M | #define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4) |
101 | 777M | #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 | 4.11k | palette) { |
106 | 4.11k | int i; |
107 | 4.11k | int x, y; |
108 | 4.11k | int num_colors = 0; |
109 | 4.11k | uint8_t in_use[COLOR_HASH_SIZE] = {0}; |
110 | 4.11k | uint32_t colors[COLOR_HASH_SIZE] = {0}; |
111 | 4.11k | const uint32_t* argb = pic->argb; |
112 | 4.11k | const int width = pic->width; |
113 | 4.11k | const int height = pic->height; |
114 | 4.11k | uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] |
115 | 4.11k | assert(pic != NULL); |
116 | 4.11k | assert(pic->use_argb); |
117 | | |
118 | 2.22M | for (y = 0; y < height; ++y) { |
119 | 1.88G | for (x = 0; x < width; ++x) { |
120 | 1.87G | int key; |
121 | 1.87G | if (argb[x] == last_pix) { |
122 | 1.10G | continue; |
123 | 1.10G | } |
124 | 777M | last_pix = argb[x]; |
125 | 777M | key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT); |
126 | 777M | while (1) { |
127 | 777M | if (!in_use[key]) { |
128 | 418k | colors[key] = last_pix; |
129 | 418k | in_use[key] = 1; |
130 | 418k | ++num_colors; |
131 | 418k | if (num_colors > MAX_PALETTE_SIZE) { |
132 | 0 | return MAX_PALETTE_SIZE + 1; // Exact count not needed. |
133 | 0 | } |
134 | 418k | break; |
135 | 776M | } else if (colors[key] == last_pix) { |
136 | 776M | break; // The color is already there. |
137 | 776M | } else { |
138 | | // Some other color sits here, so do linear conflict resolution. |
139 | 0 | ++key; |
140 | 0 | key &= (COLOR_HASH_SIZE - 1); // Key mask. |
141 | 0 | } |
142 | 777M | } |
143 | 777M | } |
144 | 2.21M | argb += pic->argb_stride; |
145 | 2.21M | } |
146 | | |
147 | 4.11k | if (palette != NULL) { // Fill the colors into palette. |
148 | 4.11k | num_colors = 0; |
149 | 4.21M | for (i = 0; i < COLOR_HASH_SIZE; ++i) { |
150 | 4.21M | if (in_use[i]) { |
151 | 418k | palette[num_colors] = colors[i]; |
152 | 418k | ++num_colors; |
153 | 418k | } |
154 | 4.21M | } |
155 | 4.11k | qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort); |
156 | 4.11k | } |
157 | 4.11k | return num_colors; |
158 | 4.11k | } |
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 | 2.10k | const uint32_t* const WEBP_COUNTED_BY(num_colors) palette, int num_colors) { |
173 | 2.10k | uint32_t predict = 0x000000; |
174 | 2.10k | int i; |
175 | 2.10k | uint8_t sign_found = 0x00; |
176 | 86.9k | for (i = 0; i < num_colors; ++i) { |
177 | 84.8k | const uint32_t diff = VP8LSubPixels(palette[i], predict); |
178 | 84.8k | const uint8_t rd = (diff >> 16) & 0xff; |
179 | 84.8k | const uint8_t gd = (diff >> 8) & 0xff; |
180 | 84.8k | const uint8_t bd = (diff >> 0) & 0xff; |
181 | 84.8k | if (rd != 0x00) { |
182 | 0 | sign_found |= (rd < 0x80) ? 1 : 2; |
183 | 0 | } |
184 | 84.8k | if (gd != 0x00) { |
185 | 83.2k | sign_found |= (gd < 0x80) ? 8 : 16; |
186 | 83.2k | } |
187 | 84.8k | if (bd != 0x00) { |
188 | 0 | sign_found |= (bd < 0x80) ? 64 : 128; |
189 | 0 | } |
190 | 84.8k | predict = palette[i]; |
191 | 84.8k | } |
192 | 2.10k | return (sign_found & (sign_found << 1)) != 0; // two consequent signs. |
193 | 2.10k | } |
194 | | |
195 | | static void PaletteSortMinimizeDeltas( |
196 | | const uint32_t* const WEBP_COUNTED_BY(num_colors) palette_sorted, |
197 | 2.10k | int num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
198 | 2.10k | uint32_t predict = 0x00000000; |
199 | 2.10k | int i, k; |
200 | 2.10k | memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); |
201 | 2.10k | 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 | 302 | if (num_colors > 17) { |
206 | 16 | if (palette[0] == 0) { |
207 | 15 | --num_colors; |
208 | 15 | SwapColor(&palette[num_colors], &palette[0]); |
209 | 15 | } |
210 | 16 | } |
211 | 3.21k | for (i = 0; i < num_colors; ++i) { |
212 | 2.90k | int best_ix = i; |
213 | 2.90k | uint32_t best_score = ~0U; |
214 | 125k | for (k = i; k < num_colors; ++k) { |
215 | 122k | const uint32_t cur_score = PaletteColorDistance(palette[k], predict); |
216 | 122k | if (best_score > cur_score) { |
217 | 26.1k | best_score = cur_score; |
218 | 26.1k | best_ix = k; |
219 | 26.1k | } |
220 | 122k | } |
221 | 2.90k | SwapColor(&palette[best_ix], &palette[i]); |
222 | 2.90k | predict = palette[i]; |
223 | 2.90k | } |
224 | 302 | } |
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 | 0 | 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 | 0 | uint32_t best_sum = 0u; |
238 | 0 | uint32_t i, j, best_cooccurrence; |
239 | 0 | *c1 = 0u; |
240 | 0 | for (i = 0; i < num_colors; ++i) { |
241 | 0 | uint32_t sum = 0; |
242 | 0 | for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j]; |
243 | 0 | if (sum > best_sum) { |
244 | 0 | best_sum = sum; |
245 | 0 | *c1 = i; |
246 | 0 | } |
247 | 0 | } |
248 | | // Find the index that is most frequently found adjacent to *c1. |
249 | 0 | *c2 = 0u; |
250 | 0 | best_cooccurrence = 0u; |
251 | 0 | for (i = 0; i < num_colors; ++i) { |
252 | 0 | if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) { |
253 | 0 | best_cooccurrence = cooccurrence[*c1 * num_colors + i]; |
254 | 0 | *c2 = i; |
255 | 0 | } |
256 | 0 | } |
257 | 0 | assert(*c1 != *c2); |
258 | 0 | } |
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 | 0 | cooccurrence) { |
267 | 0 | uint32_t *lines, *line_top, *line_current, *line_tmp; |
268 | 0 | int x, y; |
269 | 0 | const uint32_t* src = pic->argb; |
270 | 0 | uint32_t prev_pix = ~src[0]; |
271 | 0 | uint32_t prev_idx = 0u; |
272 | 0 | uint32_t idx_map[MAX_PALETTE_SIZE] = {0}; |
273 | 0 | uint32_t palette_sorted[MAX_PALETTE_SIZE]; |
274 | 0 | lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines)); |
275 | 0 | if (lines == NULL) { |
276 | 0 | return 0; |
277 | 0 | } |
278 | 0 | line_top = &lines[0]; |
279 | 0 | line_current = &lines[pic->width]; |
280 | 0 | PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map); |
281 | 0 | for (y = 0; y < pic->height; ++y) { |
282 | 0 | for (x = 0; x < pic->width; ++x) { |
283 | 0 | const uint32_t pix = src[x]; |
284 | 0 | if (pix != prev_pix) { |
285 | 0 | prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)]; |
286 | 0 | prev_pix = pix; |
287 | 0 | } |
288 | 0 | 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 | 0 | if (x > 0 && prev_idx != line_current[x - 1]) { |
292 | 0 | const uint32_t left_idx = line_current[x - 1]; |
293 | 0 | ++cooccurrence[prev_idx * num_colors + left_idx]; |
294 | 0 | ++cooccurrence[left_idx * num_colors + prev_idx]; |
295 | 0 | } |
296 | 0 | if (y > 0 && prev_idx != line_top[x]) { |
297 | 0 | const uint32_t top_idx = line_top[x]; |
298 | 0 | ++cooccurrence[prev_idx * num_colors + top_idx]; |
299 | 0 | ++cooccurrence[top_idx * num_colors + prev_idx]; |
300 | 0 | } |
301 | 0 | } |
302 | 0 | line_tmp = line_top; |
303 | 0 | line_top = line_current; |
304 | 0 | line_current = line_tmp; |
305 | 0 | src += pic->argb_stride; |
306 | 0 | } |
307 | 0 | WebPSafeFree(lines); |
308 | 0 | return 1; |
309 | 0 | } |
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 | 0 | uint32_t num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
320 | 0 | uint32_t i, j, ind; |
321 | 0 | uint8_t remapping[MAX_PALETTE_SIZE]; |
322 | 0 | uint32_t* cooccurrence; |
323 | 0 | struct Sum sums[MAX_PALETTE_SIZE]; |
324 | 0 | uint32_t first, last; |
325 | 0 | uint32_t num_sums; |
326 | | // TODO(vrabaud) check whether one color images should use palette or not. |
327 | 0 | if (num_colors <= 1) return 1; |
328 | | // Build the co-occurrence matrix. |
329 | 0 | cooccurrence = |
330 | 0 | (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence)); |
331 | 0 | if (cooccurrence == NULL) { |
332 | 0 | return 0; |
333 | 0 | } |
334 | 0 | if (!CoOccurrenceBuild(pic, palette_in, num_colors, |
335 | 0 | WEBP_UNSAFE_FORGE_BIDI_INDEXABLE( |
336 | 0 | uint32_t*, cooccurrence, |
337 | 0 | 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 | 0 | CoOccurrenceFindMax(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE( |
344 | 0 | const uint32_t*, cooccurrence, |
345 | 0 | num_colors* num_colors * sizeof(*cooccurrence)), |
346 | 0 | 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 | 0 | first = 0; |
352 | 0 | last = 1; |
353 | 0 | num_sums = num_colors - 2; // -2 because we know the first two values |
354 | 0 | if (num_sums > 0) { |
355 | | // Initialize the sums with the first two remappings and find the best one |
356 | 0 | struct Sum* best_sum = &sums[0]; |
357 | 0 | best_sum->index = 0u; |
358 | 0 | best_sum->sum = 0u; |
359 | 0 | for (i = 0, j = 0; i < num_colors; ++i) { |
360 | 0 | if (i == remapping[0] || i == remapping[1]) continue; |
361 | 0 | sums[j].index = i; |
362 | 0 | sums[j].sum = cooccurrence[i * num_colors + remapping[0]] + |
363 | 0 | cooccurrence[i * num_colors + remapping[1]]; |
364 | 0 | if (sums[j].sum > best_sum->sum) best_sum = &sums[j]; |
365 | 0 | ++j; |
366 | 0 | } |
367 | |
|
368 | 0 | while (num_sums > 0) { |
369 | 0 | const uint8_t best_index = best_sum->index; |
370 | | // Compute delta to know if we need to prepend or append the best index. |
371 | 0 | int32_t delta = 0; |
372 | 0 | const int32_t n = num_colors - num_sums; |
373 | 0 | for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) { |
374 | 0 | const uint16_t l_j = remapping[(ind + j) % num_colors]; |
375 | 0 | delta += (n - 1 - 2 * (int32_t)j) * |
376 | 0 | (int32_t)cooccurrence[best_index * num_colors + l_j]; |
377 | 0 | } |
378 | 0 | if (delta > 0) { |
379 | 0 | first = (first == 0) ? num_colors - 1 : first - 1; |
380 | 0 | remapping[first] = best_index; |
381 | 0 | } else { |
382 | 0 | ++last; |
383 | 0 | remapping[last] = best_index; |
384 | 0 | } |
385 | | // Remove best_sum from sums. |
386 | 0 | *best_sum = sums[num_sums - 1]; |
387 | 0 | --num_sums; |
388 | | // Update all the sums and find the best one. |
389 | 0 | best_sum = &sums[0]; |
390 | 0 | for (i = 0; i < num_sums; ++i) { |
391 | 0 | sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index]; |
392 | 0 | if (sums[i].sum > best_sum->sum) best_sum = &sums[i]; |
393 | 0 | } |
394 | 0 | } |
395 | 0 | } |
396 | 0 | assert((last + 1) % num_colors == first); |
397 | 0 | WebPSafeFree(cooccurrence); |
398 | | |
399 | | // Re-map the palette. |
400 | 0 | for (i = 0; i < num_colors; ++i) { |
401 | 0 | palette[i] = palette_in[remapping[(first + i) % num_colors]]; |
402 | 0 | } |
403 | 0 | return 1; |
404 | 0 | } |
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 | 2.10k | uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
413 | 2.10k | switch (method) { |
414 | 0 | case kSortedDefault: |
415 | 0 | if (palette_sorted[0] == 0 && num_colors > 17) { |
416 | 0 | memcpy(palette, palette_sorted + 1, |
417 | 0 | (num_colors - 1) * sizeof(*palette_sorted)); |
418 | 0 | palette[num_colors - 1] = 0; |
419 | 0 | } else { |
420 | 0 | memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); |
421 | 0 | } |
422 | 0 | return 1; |
423 | 2.10k | case kMinimizeDelta: |
424 | 2.10k | PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette); |
425 | 2.10k | return 1; |
426 | 0 | case kModifiedZeng: |
427 | 0 | return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette); |
428 | 0 | case kUnusedPalette: |
429 | 0 | case kPaletteSortingNum: |
430 | 0 | break; |
431 | 2.10k | } |
432 | | |
433 | 2.10k | assert(0); |
434 | 0 | return 0; |
435 | 2.10k | } |