palette.c (14146B)
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/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 }