Coverage Report

Created: 2024-07-27 06:27

/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
19
#include "src/dsp/lossless_common.h"
20
#include "src/utils/color_cache_utils.h"
21
#include "src/utils/utils.h"
22
#include "src/webp/encode.h"
23
#include "src/webp/format_constants.h"
24
25
// -----------------------------------------------------------------------------
26
27
// Palette reordering for smaller sum of deltas (and for smaller storage).
28
29
0
static int PaletteCompareColorsForQsort(const void* p1, const void* p2) {
30
0
  const uint32_t a = WebPMemToUint32((uint8_t*)p1);
31
0
  const uint32_t b = WebPMemToUint32((uint8_t*)p2);
32
0
  assert(a != b);
33
0
  return (a < b) ? -1 : 1;
34
0
}
35
36
0
static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) {
37
0
  return (v <= 128) ? v : (256 - v);
38
0
}
39
40
// Computes a value that is related to the entropy created by the
41
// palette entry diff.
42
//
43
// Note that the last & 0xff is a no-operation in the next statement, but
44
// removed by most compilers and is here only for regularity of the code.
45
0
static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) {
46
0
  const uint32_t diff = VP8LSubPixels(col1, col2);
47
0
  const int kMoreWeightForRGBThanForAlpha = 9;
48
0
  uint32_t score;
49
0
  score = PaletteComponentDistance((diff >> 0) & 0xff);
50
0
  score += PaletteComponentDistance((diff >> 8) & 0xff);
51
0
  score += PaletteComponentDistance((diff >> 16) & 0xff);
52
0
  score *= kMoreWeightForRGBThanForAlpha;
53
0
  score += PaletteComponentDistance((diff >> 24) & 0xff);
54
0
  return score;
55
0
}
56
57
0
static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) {
58
0
  const uint32_t tmp = *col1;
59
0
  *col1 = *col2;
60
0
  *col2 = tmp;
61
0
}
62
63
0
int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors) {
64
0
  int low = 0, hi = num_colors;
65
0
  if (sorted[low] == color) return low;  // loop invariant: sorted[low] != color
66
0
  while (1) {
67
0
    const int mid = (low + hi) >> 1;
68
0
    if (sorted[mid] == color) {
69
0
      return mid;
70
0
    } else if (sorted[mid] < color) {
71
0
      low = mid;
72
0
    } else {
73
0
      hi = mid;
74
0
    }
75
0
  }
76
0
  assert(0);
77
0
  return 0;
78
0
}
79
80
void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors,
81
0
                         uint32_t sorted[], uint32_t idx_map[]) {
82
0
  uint32_t i;
83
0
  memcpy(sorted, palette, num_colors * sizeof(*sorted));
84
0
  qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort);
85
0
  for (i = 0; i < num_colors; ++i) {
86
0
    idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i;
87
0
  }
88
0
}
89
90
//------------------------------------------------------------------------------
91
92
0
#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4)
93
0
#define COLOR_HASH_RIGHT_SHIFT 22  // 32 - log2(COLOR_HASH_SIZE).
94
95
0
int GetColorPalette(const WebPPicture* const pic, uint32_t* const palette) {
96
0
  int i;
97
0
  int x, y;
98
0
  int num_colors = 0;
99
0
  uint8_t in_use[COLOR_HASH_SIZE] = {0};
100
0
  uint32_t colors[COLOR_HASH_SIZE] = {0};
101
0
  const uint32_t* argb = pic->argb;
102
0
  const int width = pic->width;
103
0
  const int height = pic->height;
104
0
  uint32_t last_pix = ~argb[0];  // so we're sure that last_pix != argb[0]
105
0
  assert(pic != NULL);
106
0
  assert(pic->use_argb);
107
108
0
  for (y = 0; y < height; ++y) {
109
0
    for (x = 0; x < width; ++x) {
110
0
      int key;
111
0
      if (argb[x] == last_pix) {
112
0
        continue;
113
0
      }
114
0
      last_pix = argb[x];
115
0
      key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT);
116
0
      while (1) {
117
0
        if (!in_use[key]) {
118
0
          colors[key] = last_pix;
119
0
          in_use[key] = 1;
120
0
          ++num_colors;
121
0
          if (num_colors > MAX_PALETTE_SIZE) {
122
0
            return MAX_PALETTE_SIZE + 1;  // Exact count not needed.
123
0
          }
124
0
          break;
125
0
        } else if (colors[key] == last_pix) {
126
0
          break;  // The color is already there.
127
0
        } else {
128
          // Some other color sits here, so do linear conflict resolution.
129
0
          ++key;
130
0
          key &= (COLOR_HASH_SIZE - 1);  // Key mask.
131
0
        }
132
0
      }
133
0
    }
134
0
    argb += pic->argb_stride;
135
0
  }
136
137
0
  if (palette != NULL) {  // Fill the colors into palette.
138
0
    num_colors = 0;
139
0
    for (i = 0; i < COLOR_HASH_SIZE; ++i) {
140
0
      if (in_use[i]) {
141
0
        palette[num_colors] = colors[i];
142
0
        ++num_colors;
143
0
      }
144
0
    }
145
0
    qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort);
146
0
  }
147
0
  return num_colors;
148
0
}
149
150
#undef COLOR_HASH_SIZE
151
#undef COLOR_HASH_RIGHT_SHIFT
152
153
// -----------------------------------------------------------------------------
154
155
// The palette has been sorted by alpha. This function checks if the other
156
// components of the palette have a monotonic development with regards to
157
// position in the palette. If all have monotonic development, there is
158
// no benefit to re-organize them greedily. A monotonic development
159
// would be spotted in green-only situations (like lossy alpha) or gray-scale
160
// images.
161
static int PaletteHasNonMonotonousDeltas(const uint32_t* const palette,
162
0
                                         int num_colors) {
163
0
  uint32_t predict = 0x000000;
164
0
  int i;
165
0
  uint8_t sign_found = 0x00;
166
0
  for (i = 0; i < num_colors; ++i) {
167
0
    const uint32_t diff = VP8LSubPixels(palette[i], predict);
168
0
    const uint8_t rd = (diff >> 16) & 0xff;
169
0
    const uint8_t gd = (diff >> 8) & 0xff;
170
0
    const uint8_t bd = (diff >> 0) & 0xff;
171
0
    if (rd != 0x00) {
172
0
      sign_found |= (rd < 0x80) ? 1 : 2;
173
0
    }
174
0
    if (gd != 0x00) {
175
0
      sign_found |= (gd < 0x80) ? 8 : 16;
176
0
    }
177
0
    if (bd != 0x00) {
178
0
      sign_found |= (bd < 0x80) ? 64 : 128;
179
0
    }
180
0
    predict = palette[i];
181
0
  }
182
0
  return (sign_found & (sign_found << 1)) != 0;  // two consequent signs.
183
0
}
184
185
static void PaletteSortMinimizeDeltas(const uint32_t* const palette_sorted,
186
0
                                      int num_colors, uint32_t* const palette) {
187
0
  uint32_t predict = 0x00000000;
188
0
  int i, k;
189
0
  memcpy(palette, palette_sorted, num_colors * sizeof(*palette));
190
0
  if (!PaletteHasNonMonotonousDeltas(palette_sorted, num_colors)) return;
191
  // Find greedily always the closest color of the predicted color to minimize
192
  // deltas in the palette. This reduces storage needs since the
193
  // palette is stored with delta encoding.
194
0
  if (num_colors > 17) {
195
0
    if (palette[0] == 0) {
196
0
      --num_colors;
197
0
      SwapColor(&palette[num_colors], &palette[0]);
198
0
    }
199
0
  }
200
0
  for (i = 0; i < num_colors; ++i) {
201
0
    int best_ix = i;
202
0
    uint32_t best_score = ~0U;
203
0
    for (k = i; k < num_colors; ++k) {
204
0
      const uint32_t cur_score = PaletteColorDistance(palette[k], predict);
205
0
      if (best_score > cur_score) {
206
0
        best_score = cur_score;
207
0
        best_ix = k;
208
0
      }
209
0
    }
210
0
    SwapColor(&palette[best_ix], &palette[i]);
211
0
    predict = palette[i];
212
0
  }
213
0
}
214
215
// -----------------------------------------------------------------------------
216
// Modified Zeng method from "A Survey on Palette Reordering
217
// Methods for Improving the Compression of Color-Indexed Images" by Armando J.
218
// Pinho and Antonio J. R. Neves.
219
220
// Finds the biggest cooccurrence in the matrix.
221
static void CoOccurrenceFindMax(const uint32_t* const cooccurrence,
222
                                uint32_t num_colors, uint8_t* const c1,
223
0
                                uint8_t* const c2) {
224
  // Find the index that is most frequently located adjacent to other
225
  // (different) indexes.
226
0
  uint32_t best_sum = 0u;
227
0
  uint32_t i, j, best_cooccurrence;
228
0
  *c1 = 0u;
229
0
  for (i = 0; i < num_colors; ++i) {
230
0
    uint32_t sum = 0;
231
0
    for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j];
232
0
    if (sum > best_sum) {
233
0
      best_sum = sum;
234
0
      *c1 = i;
235
0
    }
236
0
  }
237
  // Find the index that is most frequently found adjacent to *c1.
238
0
  *c2 = 0u;
239
0
  best_cooccurrence = 0u;
240
0
  for (i = 0; i < num_colors; ++i) {
241
0
    if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) {
242
0
      best_cooccurrence = cooccurrence[*c1 * num_colors + i];
243
0
      *c2 = i;
244
0
    }
245
0
  }
246
0
  assert(*c1 != *c2);
247
0
}
248
249
// Builds the cooccurrence matrix
250
static int CoOccurrenceBuild(const WebPPicture* const pic,
251
                             const uint32_t* const palette, uint32_t num_colors,
252
0
                             uint32_t* cooccurrence) {
253
0
  uint32_t *lines, *line_top, *line_current, *line_tmp;
254
0
  int x, y;
255
0
  const uint32_t* src = pic->argb;
256
0
  uint32_t prev_pix = ~src[0];
257
0
  uint32_t prev_idx = 0u;
258
0
  uint32_t idx_map[MAX_PALETTE_SIZE] = {0};
259
0
  uint32_t palette_sorted[MAX_PALETTE_SIZE];
260
0
  lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines));
261
0
  if (lines == NULL) {
262
0
    return 0;
263
0
  }
264
0
  line_top = &lines[0];
265
0
  line_current = &lines[pic->width];
266
0
  PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map);
267
0
  for (y = 0; y < pic->height; ++y) {
268
0
    for (x = 0; x < pic->width; ++x) {
269
0
      const uint32_t pix = src[x];
270
0
      if (pix != prev_pix) {
271
0
        prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)];
272
0
        prev_pix = pix;
273
0
      }
274
0
      line_current[x] = prev_idx;
275
      // 4-connectivity is what works best as mentioned in "On the relation
276
      // between Memon's and the modified Zeng's palette reordering methods".
277
0
      if (x > 0 && prev_idx != line_current[x - 1]) {
278
0
        const uint32_t left_idx = line_current[x - 1];
279
0
        ++cooccurrence[prev_idx * num_colors + left_idx];
280
0
        ++cooccurrence[left_idx * num_colors + prev_idx];
281
0
      }
282
0
      if (y > 0 && prev_idx != line_top[x]) {
283
0
        const uint32_t top_idx = line_top[x];
284
0
        ++cooccurrence[prev_idx * num_colors + top_idx];
285
0
        ++cooccurrence[top_idx * num_colors + prev_idx];
286
0
      }
287
0
    }
288
0
    line_tmp = line_top;
289
0
    line_top = line_current;
290
0
    line_current = line_tmp;
291
0
    src += pic->argb_stride;
292
0
  }
293
0
  WebPSafeFree(lines);
294
0
  return 1;
295
0
}
296
297
struct Sum {
298
  uint8_t index;
299
  uint32_t sum;
300
};
301
302
static int PaletteSortModifiedZeng(const WebPPicture* const pic,
303
                                   const uint32_t* const palette_in,
304
                                   uint32_t num_colors,
305
0
                                   uint32_t* const palette) {
306
0
  uint32_t i, j, ind;
307
0
  uint8_t remapping[MAX_PALETTE_SIZE];
308
0
  uint32_t* cooccurrence;
309
0
  struct Sum sums[MAX_PALETTE_SIZE];
310
0
  uint32_t first, last;
311
0
  uint32_t num_sums;
312
  // TODO(vrabaud) check whether one color images should use palette or not.
313
0
  if (num_colors <= 1) return 1;
314
  // Build the co-occurrence matrix.
315
0
  cooccurrence =
316
0
      (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence));
317
0
  if (cooccurrence == NULL) {
318
0
    return 0;
319
0
  }
320
0
  if (!CoOccurrenceBuild(pic, palette_in, num_colors, cooccurrence)) {
321
0
    WebPSafeFree(cooccurrence);
322
0
    return 0;
323
0
  }
324
325
  // Initialize the mapping list with the two best indices.
326
0
  CoOccurrenceFindMax(cooccurrence, num_colors, &remapping[0], &remapping[1]);
327
328
  // We need to append and prepend to the list of remapping. To this end, we
329
  // actually define the next start/end of the list as indices in a vector (with
330
  // a wrap around when the end is reached).
331
0
  first = 0;
332
0
  last = 1;
333
0
  num_sums = num_colors - 2;  // -2 because we know the first two values
334
0
  if (num_sums > 0) {
335
    // Initialize the sums with the first two remappings and find the best one
336
0
    struct Sum* best_sum = &sums[0];
337
0
    best_sum->index = 0u;
338
0
    best_sum->sum = 0u;
339
0
    for (i = 0, j = 0; i < num_colors; ++i) {
340
0
      if (i == remapping[0] || i == remapping[1]) continue;
341
0
      sums[j].index = i;
342
0
      sums[j].sum = cooccurrence[i * num_colors + remapping[0]] +
343
0
                    cooccurrence[i * num_colors + remapping[1]];
344
0
      if (sums[j].sum > best_sum->sum) best_sum = &sums[j];
345
0
      ++j;
346
0
    }
347
348
0
    while (num_sums > 0) {
349
0
      const uint8_t best_index = best_sum->index;
350
      // Compute delta to know if we need to prepend or append the best index.
351
0
      int32_t delta = 0;
352
0
      const int32_t n = num_colors - num_sums;
353
0
      for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) {
354
0
        const uint16_t l_j = remapping[(ind + j) % num_colors];
355
0
        delta += (n - 1 - 2 * (int32_t)j) *
356
0
                 (int32_t)cooccurrence[best_index * num_colors + l_j];
357
0
      }
358
0
      if (delta > 0) {
359
0
        first = (first == 0) ? num_colors - 1 : first - 1;
360
0
        remapping[first] = best_index;
361
0
      } else {
362
0
        ++last;
363
0
        remapping[last] = best_index;
364
0
      }
365
      // Remove best_sum from sums.
366
0
      *best_sum = sums[num_sums - 1];
367
0
      --num_sums;
368
      // Update all the sums and find the best one.
369
0
      best_sum = &sums[0];
370
0
      for (i = 0; i < num_sums; ++i) {
371
0
        sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index];
372
0
        if (sums[i].sum > best_sum->sum) best_sum = &sums[i];
373
0
      }
374
0
    }
375
0
  }
376
0
  assert((last + 1) % num_colors == first);
377
0
  WebPSafeFree(cooccurrence);
378
379
  // Re-map the palette.
380
0
  for (i = 0; i < num_colors; ++i) {
381
0
    palette[i] = palette_in[remapping[(first + i) % num_colors]];
382
0
  }
383
0
  return 1;
384
0
}
385
386
// -----------------------------------------------------------------------------
387
388
int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic,
389
                const uint32_t* const palette_sorted, uint32_t num_colors,
390
0
                uint32_t* const palette) {
391
0
  switch (method) {
392
0
    case kSortedDefault:
393
0
      if (palette_sorted[0] == 0 && num_colors > 17) {
394
0
        memcpy(palette, palette_sorted + 1,
395
0
               (num_colors - 1) * sizeof(*palette_sorted));
396
0
        palette[num_colors - 1] = 0;
397
0
      } else {
398
0
        memcpy(palette, palette_sorted, num_colors * sizeof(*palette));
399
0
      }
400
0
      return 1;
401
0
    case kMinimizeDelta:
402
0
      PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette);
403
0
      return 1;
404
0
    case kModifiedZeng:
405
0
      return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette);
406
0
    case kUnusedPalette:
407
0
    case kPaletteSortingNum:
408
0
      break;
409
0
  }
410
411
0
  assert(0);
412
0
  return 0;
413
0
}