/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 | 0 | static int PaletteCompareColorsForQsort(const void* p1, const void* p2) { |
35 | 0 | const uint32_t a = WebPMemToUint32((uint8_t*)p1); |
36 | 0 | const uint32_t b = WebPMemToUint32((uint8_t*)p2); |
37 | 0 | assert(a != b); |
38 | 0 | return (a < b) ? -1 : 1; |
39 | 0 | } |
40 | | |
41 | 0 | static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) { |
42 | 0 | return (v <= 128) ? v : (256 - v); |
43 | 0 | } |
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 | 0 | static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) { |
51 | 0 | const uint32_t diff = VP8LSubPixels(col1, col2); |
52 | 0 | const int kMoreWeightForRGBThanForAlpha = 9; |
53 | 0 | uint32_t score; |
54 | 0 | score = PaletteComponentDistance((diff >> 0) & 0xff); |
55 | 0 | score += PaletteComponentDistance((diff >> 8) & 0xff); |
56 | 0 | score += PaletteComponentDistance((diff >> 16) & 0xff); |
57 | 0 | score *= kMoreWeightForRGBThanForAlpha; |
58 | 0 | score += PaletteComponentDistance((diff >> 24) & 0xff); |
59 | 0 | return score; |
60 | 0 | } |
61 | | |
62 | 0 | static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) { |
63 | 0 | const uint32_t tmp = *col1; |
64 | 0 | *col1 = *col2; |
65 | 0 | *col2 = tmp; |
66 | 0 | } |
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 | 0 | #define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4) |
101 | 0 | #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 | 0 | palette) { |
106 | 0 | int i; |
107 | 0 | int x, y; |
108 | 0 | int num_colors = 0; |
109 | 0 | uint8_t in_use[COLOR_HASH_SIZE] = {0}; |
110 | 0 | uint32_t colors[COLOR_HASH_SIZE] = {0}; |
111 | 0 | const uint32_t* argb = pic->argb; |
112 | 0 | const int width = pic->width; |
113 | 0 | const int height = pic->height; |
114 | 0 | uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] |
115 | 0 | assert(pic != NULL); |
116 | 0 | assert(pic->use_argb); |
117 | |
|
118 | 0 | for (y = 0; y < height; ++y) { |
119 | 0 | for (x = 0; x < width; ++x) { |
120 | 0 | int key; |
121 | 0 | if (argb[x] == last_pix) { |
122 | 0 | continue; |
123 | 0 | } |
124 | 0 | last_pix = argb[x]; |
125 | 0 | key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT); |
126 | 0 | while (1) { |
127 | 0 | if (!in_use[key]) { |
128 | 0 | colors[key] = last_pix; |
129 | 0 | in_use[key] = 1; |
130 | 0 | ++num_colors; |
131 | 0 | if (num_colors > MAX_PALETTE_SIZE) { |
132 | 0 | return MAX_PALETTE_SIZE + 1; // Exact count not needed. |
133 | 0 | } |
134 | 0 | break; |
135 | 0 | } else if (colors[key] == last_pix) { |
136 | 0 | break; // The color is already there. |
137 | 0 | } 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 | 0 | } |
143 | 0 | } |
144 | 0 | argb += pic->argb_stride; |
145 | 0 | } |
146 | | |
147 | 0 | if (palette != NULL) { // Fill the colors into palette. |
148 | 0 | num_colors = 0; |
149 | 0 | for (i = 0; i < COLOR_HASH_SIZE; ++i) { |
150 | 0 | if (in_use[i]) { |
151 | 0 | palette[num_colors] = colors[i]; |
152 | 0 | ++num_colors; |
153 | 0 | } |
154 | 0 | } |
155 | 0 | qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort); |
156 | 0 | } |
157 | 0 | return num_colors; |
158 | 0 | } |
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 | 0 | const uint32_t* const WEBP_COUNTED_BY(num_colors) palette, int num_colors) { |
173 | 0 | uint32_t predict = 0x000000; |
174 | 0 | int i; |
175 | 0 | uint8_t sign_found = 0x00; |
176 | 0 | for (i = 0; i < num_colors; ++i) { |
177 | 0 | const uint32_t diff = VP8LSubPixels(palette[i], predict); |
178 | 0 | const uint8_t rd = (diff >> 16) & 0xff; |
179 | 0 | const uint8_t gd = (diff >> 8) & 0xff; |
180 | 0 | const uint8_t bd = (diff >> 0) & 0xff; |
181 | 0 | if (rd != 0x00) { |
182 | 0 | sign_found |= (rd < 0x80) ? 1 : 2; |
183 | 0 | } |
184 | 0 | if (gd != 0x00) { |
185 | 0 | sign_found |= (gd < 0x80) ? 8 : 16; |
186 | 0 | } |
187 | 0 | if (bd != 0x00) { |
188 | 0 | sign_found |= (bd < 0x80) ? 64 : 128; |
189 | 0 | } |
190 | 0 | predict = palette[i]; |
191 | 0 | } |
192 | 0 | return (sign_found & (sign_found << 1)) != 0; // two consequent signs. |
193 | 0 | } |
194 | | |
195 | | static void PaletteSortMinimizeDeltas( |
196 | | const uint32_t* const WEBP_COUNTED_BY(num_colors) palette_sorted, |
197 | 0 | int num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
198 | 0 | uint32_t predict = 0x00000000; |
199 | 0 | int i, k; |
200 | 0 | memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); |
201 | 0 | 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 | 0 | if (num_colors > 17) { |
206 | 0 | if (palette[0] == 0) { |
207 | 0 | --num_colors; |
208 | 0 | SwapColor(&palette[num_colors], &palette[0]); |
209 | 0 | } |
210 | 0 | } |
211 | 0 | for (i = 0; i < num_colors; ++i) { |
212 | 0 | int best_ix = i; |
213 | 0 | uint32_t best_score = ~0U; |
214 | 0 | for (k = i; k < num_colors; ++k) { |
215 | 0 | const uint32_t cur_score = PaletteColorDistance(palette[k], predict); |
216 | 0 | if (best_score > cur_score) { |
217 | 0 | best_score = cur_score; |
218 | 0 | best_ix = k; |
219 | 0 | } |
220 | 0 | } |
221 | 0 | SwapColor(&palette[best_ix], &palette[i]); |
222 | 0 | predict = palette[i]; |
223 | 0 | } |
224 | 0 | } |
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 | | // Minimizes the co-occurrence linear arrangement cost |
317 | | // sum cooccurrence(i, j) * |pos(i) - pos(j)| |
318 | | // left after a constructive sort. |
319 | | // |
320 | | // Moving a color shifts every color between its old and new slot, so an |
321 | | // edge between two *untouched* colors that straddles the insertion point |
322 | | // also changes its |pos(i) - pos(j)| length by one. Omitting that "cut" |
323 | | // term makes the search sub-optimal and even uphill. |
324 | | // |
325 | | // The local search below re-evaluates every candidate slot for a color in |
326 | | // a single O(n) sweep. We don't recompute the reinsertion cost each time: |
327 | | // the cost is split into a part that only depends on the moved color's own |
328 | | // edges (updated incrementally as the sweep advances) and a part coming from |
329 | | // the untouched colors straddling the slot (looked up from 'cut[]', held |
330 | | // fixed for the current color's sweep). Summing the two at each slot keeps |
331 | | // the full pass O(n^2) instead of O(n^3). |
332 | | |
333 | | // cut[k] = signed weight difference (to_after - to_before) between the first |
334 | | // k entries of 'order' and the rest. This function's loop is O(n^2). |
335 | | static void MinLACutProfile(const uint32_t* WEBP_RESTRICT const cooccurrence, |
336 | | uint32_t num_colors, |
337 | | const uint8_t* WEBP_RESTRICT const order, |
338 | 0 | int64_t* WEBP_RESTRICT const cut) { |
339 | 0 | uint32_t k; |
340 | 0 | int64_t running = 0; |
341 | 0 | cut[0] = 0; |
342 | 0 | for (k = 0; k < num_colors; ++k) { |
343 | 0 | const uint32_t* const row = &cooccurrence[order[k] * num_colors]; |
344 | 0 | int64_t to_before = 0, to_after = 0; |
345 | 0 | uint32_t m; |
346 | 0 | for (m = 0; m < k; ++m) to_before += row[order[m]]; |
347 | 0 | for (m = k + 1; m < num_colors; ++m) to_after += row[order[m]]; |
348 | 0 | running += to_after - to_before; |
349 | 0 | cut[k + 1] = running; |
350 | 0 | } |
351 | 0 | } |
352 | | |
353 | | // Returns order[j] as if the segment order[at + 1, m] had been shifted left |
354 | | // by one, and order[at] inserted as the new order[m]. Lets MinLABestSlot() |
355 | | // work in-place without actually shifting order[]. |
356 | | static WEBP_INLINE uint32_t |
357 | | ActualColorIdx(const uint8_t* WEBP_RESTRICT const order, uint32_t at, |
358 | 0 | uint32_t m, uint32_t j) { |
359 | 0 | return (j == m) ? order[at] : (j < at) ? order[j] : order[j + 1]; |
360 | 0 | } |
361 | | |
362 | | // Returns the slot with the lowest reinsertion cost for order[at] (may be |
363 | | // 'at' itself). 'at' wins ties against the true minimum, so a color that |
364 | | // isn't worth moving never gets shuffled sideways for no gain. |
365 | | static uint32_t MinLABestSlot(const uint32_t* WEBP_RESTRICT const cooccurrence, |
366 | | uint32_t num_colors, uint32_t at, |
367 | | const int64_t* WEBP_RESTRICT const cut, |
368 | | const uint8_t* WEBP_RESTRICT const order, |
369 | 0 | const uint64_t* WEBP_RESTRICT const row_sum) { |
370 | 0 | const uint32_t color = order[at]; |
371 | 0 | const uint32_t* const row = &cooccurrence[color * num_colors]; |
372 | 0 | const uint32_t m = num_colors - 1; |
373 | | // The self-cooccurrence term is always 0, so summing over all colors but |
374 | | // 'color' (as own(j) needs) equals summing over all of them, i.e. row_sum. |
375 | 0 | const int64_t total_w = row_sum[color]; |
376 | 0 | int64_t left_w = 0, left_wr = 0, pre_v = 0; |
377 | 0 | uint32_t j, best_j = 0; |
378 | 0 | int64_t best = WEBP_INT64_MAX, at_cost = 0; |
379 | 0 | for (j = 0; j < num_colors; ++j) { |
380 | 0 | const uint32_t r = ActualColorIdx(order, at, m, j); |
381 | | // color's own edges: sum_u w(u) * |j - (r_u + (r_u >= j))|, |
382 | | // up to a constant |
383 | 0 | const int64_t own = j * (2 * left_w - total_w) - left_w - 2 * left_wr; |
384 | | // edges between the untouched colors that straddle slot j |
385 | 0 | const int64_t straddle = |
386 | 0 | (j <= at) ? cut[j] - pre_v |
387 | 0 | : cut[j + 1] - (total_w - pre_v - row[order[j]]); |
388 | 0 | const int64_t cost = own + straddle; |
389 | 0 | if (cost < best) { |
390 | 0 | best = cost; |
391 | 0 | best_j = j; |
392 | 0 | } |
393 | 0 | if (j == at) at_cost = cost; |
394 | 0 | pre_v += row[order[j]]; |
395 | 0 | left_w += row[r]; |
396 | 0 | left_wr += (int64_t)row[r] * j; |
397 | 0 | } |
398 | 0 | return (at_cost == best) ? at : best_j; // at_cost >= best always holds |
399 | 0 | } |
400 | | |
401 | 0 | #define MINLA_MAX_SWEEPS 40 |
402 | | |
403 | | // Improves order[] in-place greedily with local search: relocates one color at |
404 | | // a time to its best slot until no relocation improves. |
405 | | static void PaletteMinLARefine(const uint32_t* WEBP_RESTRICT const cooccurrence, |
406 | | uint32_t num_colors, |
407 | 0 | uint8_t* WEBP_RESTRICT const order) { |
408 | 0 | int64_t cut[MAX_PALETTE_SIZE + 1]; |
409 | 0 | uint64_t row_sum[MAX_PALETTE_SIZE]; |
410 | 0 | uint32_t c; |
411 | 0 | int sweep; |
412 | | // Per-color full row sum, independent of order[]/at: computed once here |
413 | | // instead of re-summed on every MinLABestSlot() call. |
414 | 0 | for (c = 0; c < num_colors; ++c) { |
415 | 0 | const uint32_t* const row = &cooccurrence[c * num_colors]; |
416 | 0 | uint64_t sum = 0; |
417 | 0 | uint32_t u; |
418 | 0 | for (u = 0; u < num_colors; ++u) sum += row[u]; |
419 | 0 | row_sum[c] = sum; |
420 | 0 | } |
421 | | // Computed once for the initial order[]. Kept up-to-date afterward in the |
422 | | // loop. |
423 | 0 | MinLACutProfile(cooccurrence, num_colors, order, cut); |
424 | 0 | for (sweep = 0; sweep < MINLA_MAX_SWEEPS; ++sweep) { |
425 | 0 | int moved = 0; |
426 | 0 | uint32_t at; |
427 | 0 | for (at = 0; at < num_colors; ++at) { |
428 | 0 | const uint32_t best_j = |
429 | 0 | MinLABestSlot(cooccurrence, num_colors, at, cut, order, row_sum); |
430 | 0 | if (best_j != at) { // insert: shift only the affected sub-range |
431 | 0 | const uint32_t color = order[at]; // read before the shift clobbers it |
432 | 0 | if (best_j > at) { |
433 | 0 | WEBP_UNSAFE_MEMMOVE(order + at, order + at + 1, |
434 | 0 | (best_j - at) * sizeof(*order)); |
435 | 0 | } else { |
436 | 0 | WEBP_UNSAFE_MEMMOVE(order + best_j + 1, order + best_j, |
437 | 0 | (at - best_j) * sizeof(*order)); |
438 | 0 | } |
439 | 0 | order[best_j] = (uint8_t)color; |
440 | 0 | MinLACutProfile(cooccurrence, num_colors, order, cut); |
441 | 0 | ++moved; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | if (moved == 0) break; |
445 | 0 | } |
446 | 0 | } |
447 | | #undef MINLA_MAX_SWEEPS |
448 | | |
449 | | // Refines the color order already in 'palette' and writes it back. |
450 | | static int PaletteMinLARefineColors( |
451 | | const WebPPicture* const pic, |
452 | | const uint32_t* const WEBP_COUNTED_BY(num_colors) palette_in, |
453 | 0 | uint32_t num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
454 | 0 | uint32_t* cooccurrence; |
455 | 0 | uint8_t order[MAX_PALETTE_SIZE]; |
456 | 0 | uint32_t i; |
457 | 0 | cooccurrence = |
458 | 0 | (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence)); |
459 | 0 | if (cooccurrence == NULL) return 0; |
460 | 0 | if (!CoOccurrenceBuild(pic, palette_in, num_colors, |
461 | 0 | WEBP_UNSAFE_FORGE_BIDI_INDEXABLE( |
462 | 0 | uint32_t*, cooccurrence, |
463 | 0 | num_colors* num_colors * sizeof(*cooccurrence)))) { |
464 | 0 | WebPSafeFree(cooccurrence); |
465 | 0 | return 0; |
466 | 0 | } |
467 | 0 | for (i = 0; i < num_colors; ++i) { |
468 | 0 | order[i] = (uint8_t)SearchColorNoIdx(palette_in, palette[i], num_colors); |
469 | 0 | } |
470 | 0 | PaletteMinLARefine(cooccurrence, num_colors, order); |
471 | 0 | for (i = 0; i < num_colors; ++i) palette[i] = palette_in[order[i]]; |
472 | 0 | WebPSafeFree(cooccurrence); |
473 | 0 | return 1; |
474 | 0 | } |
475 | | |
476 | | static int PaletteSortModifiedZeng( |
477 | | const WebPPicture* const pic, |
478 | | const uint32_t* const WEBP_COUNTED_BY(num_colors) palette_in, |
479 | 0 | uint32_t num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
480 | 0 | uint32_t i, ind; |
481 | 0 | uint8_t remapping[MAX_PALETTE_SIZE]; |
482 | 0 | uint32_t* cooccurrence; |
483 | 0 | struct Sum sums[MAX_PALETTE_SIZE]; |
484 | 0 | uint32_t first, last; |
485 | 0 | uint32_t num_sums; |
486 | | // TODO(vrabaud) check whether one color images should use palette or not. |
487 | 0 | if (num_colors <= 1) return 1; |
488 | | // Build the co-occurrence matrix. |
489 | 0 | cooccurrence = |
490 | 0 | (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence)); |
491 | 0 | if (cooccurrence == NULL) { |
492 | 0 | return 0; |
493 | 0 | } |
494 | 0 | if (!CoOccurrenceBuild(pic, palette_in, num_colors, |
495 | 0 | WEBP_UNSAFE_FORGE_BIDI_INDEXABLE( |
496 | 0 | uint32_t*, cooccurrence, |
497 | 0 | num_colors* num_colors * sizeof(*cooccurrence)))) { |
498 | 0 | WebPSafeFree(cooccurrence); |
499 | 0 | return 0; |
500 | 0 | } |
501 | | |
502 | | // Initialize the mapping list with the two best indices. |
503 | 0 | CoOccurrenceFindMax(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE( |
504 | 0 | const uint32_t*, cooccurrence, |
505 | 0 | num_colors* num_colors * sizeof(*cooccurrence)), |
506 | 0 | num_colors, &remapping[0], &remapping[1]); |
507 | | |
508 | | // We need to append and prepend to the list of remapping. To this end, we |
509 | | // actually define the next start/end of the list as indices in a vector (with |
510 | | // a wrap around when the end is reached). |
511 | 0 | first = 0; |
512 | 0 | last = 1; |
513 | 0 | num_sums = num_colors - 2; // -2 because we know the first two values |
514 | 0 | if (num_sums > 0) { |
515 | | // Initialize the sums with the first two remappings and find the best one |
516 | 0 | struct Sum* best_sum = &sums[0]; |
517 | 0 | int32_t j; |
518 | 0 | best_sum->index = 0u; |
519 | 0 | best_sum->sum = 0u; |
520 | 0 | for (i = 0, j = 0; i < num_colors; ++i) { |
521 | 0 | if (i == remapping[0] || i == remapping[1]) continue; |
522 | 0 | sums[j].index = i; |
523 | 0 | sums[j].sum = cooccurrence[i * num_colors + remapping[0]] + |
524 | 0 | cooccurrence[i * num_colors + remapping[1]]; |
525 | 0 | if (sums[j].sum > best_sum->sum) best_sum = &sums[j]; |
526 | 0 | ++j; |
527 | 0 | } |
528 | |
|
529 | 0 | while (num_sums > 0) { |
530 | 0 | const uint8_t best_index = best_sum->index; |
531 | | // Compute delta to know if we need to prepend or append the best index. |
532 | 0 | int32_t delta = 0; |
533 | 0 | const int32_t n = num_colors - num_sums; |
534 | 0 | for (ind = first, j = 0; j < n; ++j) { |
535 | 0 | const uint16_t l_j = remapping[(ind + j) % num_colors]; |
536 | 0 | delta += (n - 1 - 2 * j) * |
537 | 0 | (int32_t)cooccurrence[best_index * num_colors + l_j]; |
538 | 0 | } |
539 | 0 | if (delta > 0) { |
540 | 0 | first = (first == 0) ? num_colors - 1 : first - 1; |
541 | 0 | remapping[first] = best_index; |
542 | 0 | } else { |
543 | 0 | ++last; |
544 | 0 | remapping[last] = best_index; |
545 | 0 | } |
546 | | // Remove best_sum from sums. |
547 | 0 | *best_sum = sums[num_sums - 1]; |
548 | 0 | --num_sums; |
549 | | // Update all the sums and find the best one. |
550 | 0 | best_sum = &sums[0]; |
551 | 0 | for (i = 0; i < num_sums; ++i) { |
552 | 0 | sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index]; |
553 | 0 | if (sums[i].sum > best_sum->sum) best_sum = &sums[i]; |
554 | 0 | } |
555 | 0 | } |
556 | 0 | } |
557 | 0 | assert((last + 1) % num_colors == first); |
558 | 0 | WebPSafeFree(cooccurrence); |
559 | | |
560 | | // Re-map the palette. |
561 | 0 | for (i = 0; i < num_colors; ++i) { |
562 | 0 | palette[i] = palette_in[remapping[(first + i) % num_colors]]; |
563 | 0 | } |
564 | 0 | return 1; |
565 | 0 | } |
566 | | |
567 | | // ----------------------------------------------------------------------------- |
568 | | |
569 | | int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic, |
570 | | const uint32_t* const WEBP_COUNTED_BY(num_colors) |
571 | | palette_sorted, |
572 | | uint32_t num_colors, |
573 | 0 | uint32_t* const WEBP_COUNTED_BY(num_colors) palette) { |
574 | 0 | if (num_colors <= 1) { |
575 | 0 | palette[0] = palette_sorted[0]; |
576 | 0 | return 1; |
577 | 0 | } |
578 | 0 | switch (method) { |
579 | 0 | case kSortedDefault: |
580 | 0 | if (palette_sorted[0] == 0 && num_colors > 17) { |
581 | 0 | memcpy(palette, palette_sorted + 1, |
582 | 0 | (num_colors - 1) * sizeof(*palette_sorted)); |
583 | 0 | palette[num_colors - 1] = 0; |
584 | 0 | } else { |
585 | 0 | memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); |
586 | 0 | } |
587 | 0 | return 1; |
588 | 0 | case kMinimizeDelta: |
589 | 0 | PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette); |
590 | 0 | return 1; |
591 | 0 | case kModifiedZeng: |
592 | 0 | return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette); |
593 | 0 | case kMinLAFromZeng: |
594 | 0 | if (!PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette)) { |
595 | 0 | return 0; |
596 | 0 | } |
597 | 0 | return PaletteMinLARefineColors(pic, palette_sorted, num_colors, palette); |
598 | 0 | case kMinLAFromDelta: |
599 | 0 | PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette); |
600 | 0 | return PaletteMinLARefineColors(pic, palette_sorted, num_colors, palette); |
601 | 0 | case kUnusedPalette: |
602 | 0 | case kPaletteSortingNum: |
603 | 0 | break; |
604 | 0 | } |
605 | | |
606 | 0 | assert(0); |
607 | 0 | return 0; |
608 | 0 | } |