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