enc_palette.cc (22713B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "lib/jxl/modular/transform/enc_palette.h" 7 8 #include <array> 9 #include <map> 10 #include <set> 11 12 #include "lib/jxl/base/common.h" 13 #include "lib/jxl/base/status.h" 14 #include "lib/jxl/image_ops.h" 15 #include "lib/jxl/modular/encoding/context_predict.h" 16 #include "lib/jxl/modular/modular_image.h" 17 #include "lib/jxl/modular/transform/enc_transform.h" 18 #include "lib/jxl/modular/transform/palette.h" 19 20 namespace jxl { 21 22 namespace palette_internal { 23 24 static constexpr bool kEncodeToHighQualityImplicitPalette = true; 25 26 // Inclusive. 27 static constexpr int kMinImplicitPaletteIndex = -(2 * 72 - 1); 28 29 float ColorDistance(const std::vector<float> &JXL_RESTRICT a, 30 const std::vector<pixel_type> &JXL_RESTRICT b) { 31 JXL_ASSERT(a.size() == b.size()); 32 float distance = 0; 33 float ave3 = 0; 34 if (a.size() >= 3) { 35 ave3 = (a[0] + b[0] + a[1] + b[1] + a[2] + b[2]) * (1.21f / 3.0f); 36 } 37 float sum_a = 0; 38 float sum_b = 0; 39 for (size_t c = 0; c < a.size(); ++c) { 40 const float difference = 41 static_cast<float>(a[c]) - static_cast<float>(b[c]); 42 float weight = c == 0 ? 3 : c == 1 ? 5 : 2; 43 if (c < 3 && (a[c] + b[c] >= ave3)) { 44 const float add_w[3] = { 45 1.15, 46 1.15, 47 1.12, 48 }; 49 weight += add_w[c]; 50 if (c == 2 && ((a[2] + b[2]) < 1.22 * ave3)) { 51 weight -= 0.5; 52 } 53 } 54 distance += difference * difference * weight * weight; 55 const int sum_weight = c == 0 ? 3 : c == 1 ? 5 : 1; 56 sum_a += a[c] * sum_weight; 57 sum_b += b[c] * sum_weight; 58 } 59 distance *= 4; 60 float sum_difference = sum_a - sum_b; 61 distance += sum_difference * sum_difference; 62 return distance; 63 } 64 65 static int QuantizeColorToImplicitPaletteIndex( 66 const std::vector<pixel_type> &color, const int palette_size, 67 const int bit_depth, bool high_quality) { 68 int index = 0; 69 if (high_quality) { 70 int multiplier = 1; 71 for (size_t c = 0; c < color.size(); c++) { 72 int quantized = ((kLargeCube - 1) * color[c] + (1 << (bit_depth - 1))) / 73 ((1 << bit_depth) - 1); 74 JXL_ASSERT((quantized % kLargeCube) == quantized); 75 index += quantized * multiplier; 76 multiplier *= kLargeCube; 77 } 78 return index + palette_size + kLargeCubeOffset; 79 } else { 80 int multiplier = 1; 81 for (size_t c = 0; c < color.size(); c++) { 82 int value = color[c]; 83 value -= 1 << (std::max(0, bit_depth - 3)); 84 value = std::max(0, value); 85 int quantized = ((kLargeCube - 1) * value + (1 << (bit_depth - 1))) / 86 ((1 << bit_depth) - 1); 87 JXL_ASSERT((quantized % kLargeCube) == quantized); 88 if (quantized > kSmallCube - 1) { 89 quantized = kSmallCube - 1; 90 } 91 index += quantized * multiplier; 92 multiplier *= kSmallCube; 93 } 94 return index + palette_size; 95 } 96 } 97 98 } // namespace palette_internal 99 100 int RoundInt(int value, int div) { // symmetric rounding around 0 101 if (value < 0) return -RoundInt(-value, div); 102 return (value + div / 2) / div; 103 } 104 105 struct PaletteIterationData { 106 static constexpr int kMaxDeltas = 128; 107 bool final_run = false; 108 std::vector<pixel_type> deltas[3]; 109 std::vector<double> delta_distances; 110 std::vector<pixel_type> frequent_deltas[3]; 111 112 // Populates `frequent_deltas` with items from `deltas` based on frequencies 113 // and color distances. 114 void FindFrequentColorDeltas(int num_pixels, int bitdepth) { 115 using pixel_type_3d = std::array<pixel_type, 3>; 116 std::map<pixel_type_3d, double> delta_frequency_map; 117 pixel_type bucket_size = 3 << std::max(0, bitdepth - 8); 118 // Store frequency weighted by delta distance from quantized value. 119 for (size_t i = 0; i < deltas[0].size(); ++i) { 120 pixel_type_3d delta = { 121 {RoundInt(deltas[0][i], bucket_size), 122 RoundInt(deltas[1][i], bucket_size), 123 RoundInt(deltas[2][i], bucket_size)}}; // a basic form of clustering 124 if (delta[0] == 0 && delta[1] == 0 && delta[2] == 0) continue; 125 delta_frequency_map[delta] += sqrt(sqrt(delta_distances[i])); 126 } 127 128 const float delta_distance_multiplier = 1.0f / num_pixels; 129 130 // Weigh frequencies by magnitude and normalize. 131 for (auto &delta_frequency : delta_frequency_map) { 132 std::vector<pixel_type> current_delta = {delta_frequency.first[0], 133 delta_frequency.first[1], 134 delta_frequency.first[2]}; 135 float delta_distance = 136 std::sqrt(palette_internal::ColorDistance({0, 0, 0}, current_delta)) + 137 1; 138 delta_frequency.second *= delta_distance * delta_distance_multiplier; 139 } 140 141 // Sort by weighted frequency. 142 using pixel_type_3d_frequency = std::pair<pixel_type_3d, double>; 143 std::vector<pixel_type_3d_frequency> sorted_delta_frequency_map( 144 delta_frequency_map.begin(), delta_frequency_map.end()); 145 std::sort( 146 sorted_delta_frequency_map.begin(), sorted_delta_frequency_map.end(), 147 [](const pixel_type_3d_frequency &a, const pixel_type_3d_frequency &b) { 148 return a.second > b.second; 149 }); 150 151 // Store the top deltas. 152 for (auto &delta_frequency : sorted_delta_frequency_map) { 153 if (frequent_deltas[0].size() >= kMaxDeltas) break; 154 // Number obtained by optimizing on jyrki31 corpus: 155 if (delta_frequency.second < 17) break; 156 for (int c = 0; c < 3; ++c) { 157 frequent_deltas[c].push_back(delta_frequency.first[c] * bucket_size); 158 } 159 } 160 } 161 }; 162 163 Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c, 164 uint32_t &nb_colors, uint32_t &nb_deltas, 165 bool ordered, bool lossy, Predictor &predictor, 166 const weighted::Header &wp_header, 167 PaletteIterationData &palette_iteration_data) { 168 JXL_QUIET_RETURN_IF_ERROR(CheckEqualChannels(input, begin_c, end_c)); 169 JXL_ASSERT(begin_c >= input.nb_meta_channels); 170 uint32_t nb = end_c - begin_c + 1; 171 172 size_t w = input.channel[begin_c].w; 173 size_t h = input.channel[begin_c].h; 174 175 if (!lossy && nb == 1) { 176 // Channel palette special case 177 if (nb_colors == 0) return false; 178 std::vector<pixel_type> lookup; 179 pixel_type minval; 180 pixel_type maxval; 181 compute_minmax(input.channel[begin_c], &minval, &maxval); 182 size_t lookup_table_size = 183 static_cast<int64_t>(maxval) - static_cast<int64_t>(minval) + 1; 184 if (lookup_table_size > palette_internal::kMaxPaletteLookupTableSize) { 185 // a lookup table would use too much memory, instead use a slower approach 186 // with std::set 187 std::set<pixel_type> chpalette; 188 pixel_type idx = 0; 189 for (size_t y = 0; y < h; y++) { 190 const pixel_type *p = input.channel[begin_c].Row(y); 191 for (size_t x = 0; x < w; x++) { 192 const bool new_color = chpalette.insert(p[x]).second; 193 if (new_color) { 194 idx++; 195 if (idx > static_cast<int>(nb_colors)) return false; 196 } 197 } 198 } 199 JXL_DEBUG_V(6, "Channel %i uses only %i colors.", begin_c, idx); 200 JXL_ASSIGN_OR_RETURN(Channel pch, Channel::Create(idx, 1)); 201 pch.hshift = -1; 202 pch.vshift = -1; 203 nb_colors = idx; 204 idx = 0; 205 pixel_type *JXL_RESTRICT p_palette = pch.Row(0); 206 for (pixel_type p : chpalette) { 207 p_palette[idx++] = p; 208 } 209 for (size_t y = 0; y < h; y++) { 210 pixel_type *p = input.channel[begin_c].Row(y); 211 for (size_t x = 0; x < w; x++) { 212 for (idx = 0; 213 p[x] != p_palette[idx] && idx < static_cast<int>(nb_colors); 214 idx++) { 215 // no-op 216 } 217 JXL_DASSERT(idx < static_cast<int>(nb_colors)); 218 p[x] = idx; 219 } 220 } 221 predictor = Predictor::Zero; 222 input.nb_meta_channels++; 223 input.channel.insert(input.channel.begin(), std::move(pch)); 224 225 return true; 226 } 227 lookup.resize(lookup_table_size, 0); 228 pixel_type idx = 0; 229 for (size_t y = 0; y < h; y++) { 230 const pixel_type *p = input.channel[begin_c].Row(y); 231 for (size_t x = 0; x < w; x++) { 232 if (lookup[p[x] - minval] == 0) { 233 lookup[p[x] - minval] = 1; 234 idx++; 235 if (idx > static_cast<int>(nb_colors)) return false; 236 } 237 } 238 } 239 JXL_DEBUG_V(6, "Channel %i uses only %i colors.", begin_c, idx); 240 JXL_ASSIGN_OR_RETURN(Channel pch, Channel::Create(idx, 1)); 241 pch.hshift = -1; 242 pch.vshift = -1; 243 nb_colors = idx; 244 idx = 0; 245 pixel_type *JXL_RESTRICT p_palette = pch.Row(0); 246 for (size_t i = 0; i < lookup_table_size; i++) { 247 if (lookup[i]) { 248 p_palette[idx] = i + minval; 249 lookup[i] = idx; 250 idx++; 251 } 252 } 253 for (size_t y = 0; y < h; y++) { 254 pixel_type *p = input.channel[begin_c].Row(y); 255 for (size_t x = 0; x < w; x++) p[x] = lookup[p[x] - minval]; 256 } 257 predictor = Predictor::Zero; 258 input.nb_meta_channels++; 259 input.channel.insert(input.channel.begin(), std::move(pch)); 260 return true; 261 } 262 263 Image quantized_input; 264 if (lossy) { 265 JXL_ASSIGN_OR_RETURN(quantized_input, 266 Image::Create(w, h, input.bitdepth, nb)); 267 for (size_t c = 0; c < nb; c++) { 268 CopyImageTo(input.channel[begin_c + c].plane, 269 &quantized_input.channel[c].plane); 270 } 271 } 272 273 JXL_DEBUG_V( 274 7, "Trying to represent channels %i-%i using at most a %i-color palette.", 275 begin_c, end_c, nb_colors); 276 nb_deltas = 0; 277 bool delta_used = false; 278 std::set<std::vector<pixel_type>> candidate_palette; 279 std::vector<std::vector<pixel_type>> candidate_palette_imageorder; 280 std::vector<pixel_type> color(nb); 281 std::vector<float> color_with_error(nb); 282 std::vector<const pixel_type *> p_in(nb); 283 std::map<std::vector<pixel_type>, size_t> inv_palette; 284 285 if (lossy) { 286 palette_iteration_data.FindFrequentColorDeltas(w * h, input.bitdepth); 287 nb_deltas = palette_iteration_data.frequent_deltas[0].size(); 288 289 // Count color frequency for colors that make a cross. 290 std::map<std::vector<pixel_type>, size_t> color_freq_map; 291 for (size_t y = 1; y + 1 < h; y++) { 292 for (uint32_t c = 0; c < nb; c++) { 293 p_in[c] = input.channel[begin_c + c].Row(y); 294 } 295 for (size_t x = 1; x + 1 < w; x++) { 296 for (uint32_t c = 0; c < nb; c++) { 297 color[c] = p_in[c][x]; 298 } 299 int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; 300 bool makes_cross = true; 301 for (int i = 0; i < 4 && makes_cross; ++i) { 302 int dx = offsets[i][0]; 303 int dy = offsets[i][1]; 304 for (uint32_t c = 0; c < nb && makes_cross; c++) { 305 if (input.channel[begin_c + c].Row(y + dy)[x + dx] != color[c]) { 306 makes_cross = false; 307 } 308 } 309 } 310 if (makes_cross) color_freq_map[color] += 1; 311 } 312 } 313 // Add colors satisfying frequency condition to the palette. 314 constexpr float kImageFraction = 0.01f; 315 size_t color_frequency_lower_bound = 5 + input.h * input.w * kImageFraction; 316 for (const auto &color_freq : color_freq_map) { 317 if (color_freq.second > color_frequency_lower_bound) { 318 candidate_palette.insert(color_freq.first); 319 candidate_palette_imageorder.push_back(color_freq.first); 320 } 321 } 322 } 323 324 for (size_t y = 0; y < h; y++) { 325 for (uint32_t c = 0; c < nb; c++) { 326 p_in[c] = input.channel[begin_c + c].Row(y); 327 } 328 for (size_t x = 0; x < w; x++) { 329 if (lossy && candidate_palette.size() >= nb_colors) break; 330 for (uint32_t c = 0; c < nb; c++) { 331 color[c] = p_in[c][x]; 332 } 333 const bool new_color = candidate_palette.insert(color).second; 334 if (new_color) { 335 candidate_palette_imageorder.push_back(color); 336 } 337 if (candidate_palette.size() > nb_colors) { 338 return false; // too many colors 339 } 340 } 341 } 342 343 nb_colors = nb_deltas + candidate_palette.size(); 344 JXL_DEBUG_V(6, "Channels %i-%i can be represented using a %i-color palette.", 345 begin_c, end_c, nb_colors); 346 347 JXL_ASSIGN_OR_RETURN(Channel pch, Channel::Create(nb_colors, nb)); 348 pch.hshift = -1; 349 pch.vshift = -1; 350 pixel_type *JXL_RESTRICT p_palette = pch.Row(0); 351 intptr_t onerow = pch.plane.PixelsPerRow(); 352 intptr_t onerow_image = input.channel[begin_c].plane.PixelsPerRow(); 353 const int bit_depth = std::min(input.bitdepth, 24); 354 355 if (lossy) { 356 for (uint32_t i = 0; i < nb_deltas; i++) { 357 for (size_t c = 0; c < 3; c++) { 358 p_palette[c * onerow + i] = 359 palette_iteration_data.frequent_deltas[c][i]; 360 } 361 } 362 } 363 364 int x = 0; 365 if (ordered && nb >= 3) { 366 JXL_DEBUG_V(7, "Palette of %i colors, using luma order", nb_colors); 367 // sort on luma (multiplied by alpha if available) 368 std::sort(candidate_palette_imageorder.begin(), 369 candidate_palette_imageorder.end(), 370 [](std::vector<pixel_type> ap, std::vector<pixel_type> bp) { 371 float ay; 372 float by; 373 ay = (0.299f * ap[0] + 0.587f * ap[1] + 0.114f * ap[2] + 0.1f); 374 if (ap.size() > 3) ay *= 1.f + ap[3]; 375 by = (0.299f * bp[0] + 0.587f * bp[1] + 0.114f * bp[2] + 0.1f); 376 if (bp.size() > 3) by *= 1.f + bp[3]; 377 return ay < by; 378 }); 379 } else { 380 JXL_DEBUG_V(7, "Palette of %i colors, using image order", nb_colors); 381 } 382 for (auto pcol : candidate_palette_imageorder) { 383 JXL_DEBUG_V(9, " Color %i : ", x); 384 for (size_t i = 0; i < nb; i++) { 385 p_palette[nb_deltas + i * onerow + x] = pcol[i]; 386 JXL_DEBUG_V(9, "%i ", pcol[i]); 387 } 388 inv_palette[pcol] = x; 389 x++; 390 } 391 std::vector<weighted::State> wp_states; 392 for (size_t c = 0; c < nb; c++) { 393 wp_states.emplace_back(wp_header, w, h); 394 } 395 std::vector<pixel_type *> p_quant(nb); 396 // Three rows of error for dithering: y to y + 2. 397 // Each row has two pixels of padding in the ends, which is 398 // beneficial for both precision and encoding speed. 399 std::vector<std::vector<float>> error_row[3]; 400 if (lossy) { 401 for (int i = 0; i < 3; ++i) { 402 error_row[i].resize(nb); 403 for (size_t c = 0; c < nb; ++c) { 404 error_row[i][c].resize(w + 4); 405 } 406 } 407 } 408 for (size_t y = 0; y < h; y++) { 409 for (size_t c = 0; c < nb; c++) { 410 p_in[c] = input.channel[begin_c + c].Row(y); 411 if (lossy) p_quant[c] = quantized_input.channel[c].Row(y); 412 } 413 pixel_type *JXL_RESTRICT p = input.channel[begin_c].Row(y); 414 for (size_t x = 0; x < w; x++) { 415 int index; 416 if (!lossy) { 417 for (size_t c = 0; c < nb; c++) color[c] = p_in[c][x]; 418 index = inv_palette[color]; 419 } else { 420 int best_index = 0; 421 bool best_is_delta = false; 422 float best_distance = std::numeric_limits<float>::infinity(); 423 std::vector<pixel_type> best_val(nb, 0); 424 std::vector<pixel_type> ideal_residual(nb, 0); 425 std::vector<pixel_type> quantized_val(nb); 426 std::vector<pixel_type> predictions(nb); 427 static const double kDiffusionMultiplier[] = {0.55, 0.75}; 428 for (int diffusion_index = 0; diffusion_index < 2; ++diffusion_index) { 429 for (size_t c = 0; c < nb; c++) { 430 color_with_error[c] = 431 p_in[c][x] + (palette_iteration_data.final_run ? 1 : 0) * 432 kDiffusionMultiplier[diffusion_index] * 433 error_row[0][c][x + 2]; 434 color[c] = Clamp1(lroundf(color_with_error[c]), 0l, 435 (1l << input.bitdepth) - 1); 436 } 437 438 for (size_t c = 0; c < nb; ++c) { 439 predictions[c] = PredictNoTreeWP(w, p_quant[c] + x, onerow_image, x, 440 y, predictor, &wp_states[c]) 441 .guess; 442 } 443 const auto TryIndex = [&](const int index) { 444 for (size_t c = 0; c < nb; c++) { 445 quantized_val[c] = palette_internal::GetPaletteValue( 446 p_palette, index, /*c=*/c, 447 /*palette_size=*/nb_colors, 448 /*onerow=*/onerow, /*bit_depth=*/bit_depth); 449 if (index < static_cast<int>(nb_deltas)) { 450 quantized_val[c] += predictions[c]; 451 } 452 } 453 const float color_distance = 454 32.0 / (1LL << std::max(0, 2 * (bit_depth - 8))) * 455 palette_internal::ColorDistance(color_with_error, 456 quantized_val); 457 float index_penalty = 0; 458 if (index == -1) { 459 index_penalty = -124; 460 } else if (index < 0) { 461 index_penalty = -2 * index; 462 } else if (index < static_cast<int>(nb_deltas)) { 463 index_penalty = 250; 464 } else if (index < static_cast<int>(nb_colors)) { 465 index_penalty = 150; 466 } else if (index < static_cast<int>(nb_colors) + 467 palette_internal::kLargeCubeOffset) { 468 index_penalty = 70; 469 } else { 470 index_penalty = 256; 471 } 472 const float distance = color_distance + index_penalty; 473 if (distance < best_distance) { 474 best_distance = distance; 475 best_index = index; 476 best_is_delta = index < static_cast<int>(nb_deltas); 477 best_val.swap(quantized_val); 478 for (size_t c = 0; c < nb; ++c) { 479 ideal_residual[c] = color_with_error[c] - predictions[c]; 480 } 481 } 482 }; 483 for (index = palette_internal::kMinImplicitPaletteIndex; 484 index < static_cast<int32_t>(nb_colors); index++) { 485 TryIndex(index); 486 } 487 TryIndex(palette_internal::QuantizeColorToImplicitPaletteIndex( 488 color, nb_colors, bit_depth, 489 /*high_quality=*/false)); 490 if (palette_internal::kEncodeToHighQualityImplicitPalette) { 491 TryIndex(palette_internal::QuantizeColorToImplicitPaletteIndex( 492 color, nb_colors, bit_depth, 493 /*high_quality=*/true)); 494 } 495 } 496 index = best_index; 497 delta_used |= best_is_delta; 498 if (!palette_iteration_data.final_run) { 499 for (size_t c = 0; c < 3; ++c) { 500 palette_iteration_data.deltas[c].push_back(ideal_residual[c]); 501 } 502 palette_iteration_data.delta_distances.push_back(best_distance); 503 } 504 505 for (size_t c = 0; c < nb; ++c) { 506 wp_states[c].UpdateErrors(best_val[c], x, y, w); 507 p_quant[c][x] = best_val[c]; 508 } 509 float len_error = 0; 510 for (size_t c = 0; c < nb; ++c) { 511 float local_error = color_with_error[c] - best_val[c]; 512 len_error += local_error * local_error; 513 } 514 len_error = std::sqrt(len_error); 515 float modulate = 1.0; 516 int len_limit = 38 << std::max(0, bit_depth - 8); 517 if (len_error > len_limit) { 518 modulate *= len_limit / len_error; 519 } 520 for (size_t c = 0; c < nb; ++c) { 521 float total_error = (color_with_error[c] - best_val[c]); 522 523 // If the neighboring pixels have some error in the opposite 524 // direction of total_error, cancel some or all of it out before 525 // spreading among them. 526 constexpr int offsets[12][2] = {{1, 2}, {0, 3}, {0, 4}, {1, 1}, 527 {1, 3}, {2, 2}, {1, 0}, {1, 4}, 528 {2, 1}, {2, 3}, {2, 0}, {2, 4}}; 529 float total_available = 0; 530 for (int i = 0; i < 11; ++i) { 531 const int row = offsets[i][0]; 532 const int col = offsets[i][1]; 533 if (std::signbit(error_row[row][c][x + col]) != 534 std::signbit(total_error)) { 535 total_available += error_row[row][c][x + col]; 536 } 537 } 538 float weight = 539 std::abs(total_error) / (std::abs(total_available) + 1e-3); 540 weight = std::min(weight, 1.0f); 541 for (int i = 0; i < 11; ++i) { 542 const int row = offsets[i][0]; 543 const int col = offsets[i][1]; 544 if (std::signbit(error_row[row][c][x + col]) != 545 std::signbit(total_error)) { 546 total_error += weight * error_row[row][c][x + col]; 547 error_row[row][c][x + col] *= (1 - weight); 548 } 549 } 550 total_error *= modulate; 551 const float remaining_error = (1.0f / 14.) * total_error; 552 error_row[0][c][x + 3] += 2 * remaining_error; 553 error_row[0][c][x + 4] += remaining_error; 554 error_row[1][c][x + 0] += remaining_error; 555 for (int i = 0; i < 5; ++i) { 556 error_row[1][c][x + i] += remaining_error; 557 error_row[2][c][x + i] += remaining_error; 558 } 559 } 560 } 561 if (palette_iteration_data.final_run) p[x] = index; 562 } 563 if (lossy) { 564 for (size_t c = 0; c < nb; ++c) { 565 error_row[0][c].swap(error_row[1][c]); 566 error_row[1][c].swap(error_row[2][c]); 567 std::fill(error_row[2][c].begin(), error_row[2][c].end(), 0.f); 568 } 569 } 570 } 571 if (!delta_used) { 572 predictor = Predictor::Zero; 573 } 574 if (palette_iteration_data.final_run) { 575 input.nb_meta_channels++; 576 input.channel.erase(input.channel.begin() + begin_c + 1, 577 input.channel.begin() + end_c + 1); 578 input.channel.insert(input.channel.begin(), std::move(pch)); 579 } 580 nb_colors -= nb_deltas; 581 return true; 582 } 583 584 Status FwdPalette(Image &input, uint32_t begin_c, uint32_t end_c, 585 uint32_t &nb_colors, uint32_t &nb_deltas, bool ordered, 586 bool lossy, Predictor &predictor, 587 const weighted::Header &wp_header) { 588 PaletteIterationData palette_iteration_data; 589 uint32_t nb_colors_orig = nb_colors; 590 uint32_t nb_deltas_orig = nb_deltas; 591 // preprocessing pass in case of lossy palette 592 if (lossy && input.bitdepth >= 8) { 593 JXL_RETURN_IF_ERROR(FwdPaletteIteration( 594 input, begin_c, end_c, nb_colors_orig, nb_deltas_orig, ordered, lossy, 595 predictor, wp_header, palette_iteration_data)); 596 } 597 palette_iteration_data.final_run = true; 598 return FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas, 599 ordered, lossy, predictor, wp_header, 600 palette_iteration_data); 601 } 602 603 } // namespace jxl