Coverage Report

Created: 2026-09-28 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}