Coverage Report

Created: 2025-08-29 06:40

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