libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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