From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- third_party/jpeg-xl/lib/jxl/enc_modular.cc | 248 ++++++++++++++--------------- 1 file changed, 121 insertions(+), 127 deletions(-) (limited to 'third_party/jpeg-xl/lib/jxl/enc_modular.cc') diff --git a/third_party/jpeg-xl/lib/jxl/enc_modular.cc b/third_party/jpeg-xl/lib/jxl/enc_modular.cc index dbd62d4a01..35fac3c827 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_modular.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_modular.cc @@ -300,13 +300,12 @@ bool do_transform(Image& image, const Transform& tr, bool maybe_do_transform(Image& image, const Transform& tr, const CompressParams& cparams, - const weighted::Header& wp_header, + const weighted::Header& wp_header, float cost_before, jxl::ThreadPool* pool = nullptr, bool force_jxlart = false) { if (force_jxlart || cparams.speed_tier >= SpeedTier::kSquirrel) { return do_transform(image, tr, wp_header, pool, force_jxlart); } - float cost_before = EstimateCost(image); bool did_it = do_transform(image, tr, wp_header, pool); if (did_it) { float cost_after = EstimateCost(image); @@ -321,6 +320,110 @@ bool maybe_do_transform(Image& image, const Transform& tr, return did_it; } +void try_palettes(Image& gi, int& max_bitdepth, int& maxval, + const CompressParams& cparams_, float channel_colors_percent, + jxl::ThreadPool* pool = nullptr) { + float cost_before = 0.f; + size_t did_palette = 0; + float nb_pixels = gi.channel[0].w * gi.channel[0].h; + int nb_chans = gi.channel.size() - gi.nb_meta_channels; + // arbitrary estimate: 4.8 bpp for 8-bit RGB + float arbitrary_bpp_estimate = 0.2f * gi.bitdepth * nb_chans; + + if (cparams_.palette_colors != 0 || cparams_.lossy_palette) { + // when not estimating, assume some arbitrary bpp + cost_before = cparams_.speed_tier <= SpeedTier::kSquirrel + ? EstimateCost(gi) + : nb_pixels * arbitrary_bpp_estimate; + // all-channel palette (e.g. RGBA) + if (nb_chans > 1) { + Transform maybe_palette(TransformId::kPalette); + maybe_palette.begin_c = gi.nb_meta_channels; + maybe_palette.num_c = nb_chans; + // Heuristic choice of max colors for a palette: + // max_colors = nb_pixels * estimated_bpp_without_palette * 0.0005 + + // + nb_pixels / 128 + 128 + // (estimated_bpp_without_palette = cost_before / nb_pixels) + // Rationale: small image with large palette is not effective; + // also if the entropy (estimated bpp) is low (e.g. mostly solid/gradient + // areas), palette is less useful and may even be counterproductive. + maybe_palette.nb_colors = std::min( + static_cast(cost_before * 0.0005f + nb_pixels / 128 + 128), + std::abs(cparams_.palette_colors)); + maybe_palette.ordered_palette = cparams_.palette_colors >= 0; + maybe_palette.lossy_palette = + (cparams_.lossy_palette && maybe_palette.num_c == 3); + if (maybe_palette.lossy_palette) { + maybe_palette.predictor = Predictor::Average4; + } + // TODO(veluca): use a custom weighted header if using the weighted + // predictor. + if (maybe_do_transform(gi, maybe_palette, cparams_, weighted::Header(), + cost_before, pool, cparams_.options.zero_tokens)) { + did_palette = 1; + }; + } + // all-minus-one-channel palette (RGB with separate alpha, or CMY with + // separate K) + if (!did_palette && nb_chans > 3) { + Transform maybe_palette_3(TransformId::kPalette); + maybe_palette_3.begin_c = gi.nb_meta_channels; + maybe_palette_3.num_c = nb_chans - 1; + maybe_palette_3.nb_colors = std::min( + static_cast(cost_before * 0.0005f + nb_pixels / 128 + 128), + std::abs(cparams_.palette_colors)); + maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0; + maybe_palette_3.lossy_palette = cparams_.lossy_palette; + if (maybe_palette_3.lossy_palette) { + maybe_palette_3.predictor = Predictor::Average4; + } + if (maybe_do_transform(gi, maybe_palette_3, cparams_, weighted::Header(), + cost_before, pool, cparams_.options.zero_tokens)) { + did_palette = 1; + } + } + } + + if (channel_colors_percent > 0) { + // single channel palette (like FLIF's ChannelCompact) + size_t nb_channels = gi.channel.size() - gi.nb_meta_channels - did_palette; + int orig_bitdepth = max_bitdepth; + max_bitdepth = 0; + if (nb_channels > 0 && (did_palette || cost_before == 0)) { + cost_before = + cparams_.speed_tier < SpeedTier::kSquirrel ? EstimateCost(gi) : 0; + } + for (size_t i = did_palette; i < nb_channels + did_palette; i++) { + int32_t min; + int32_t max; + compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max); + int64_t colors = static_cast(max) - min + 1; + JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max); + Transform maybe_palette_1(TransformId::kPalette); + maybe_palette_1.begin_c = i + gi.nb_meta_channels; + maybe_palette_1.num_c = 1; + // simple heuristic: if less than X percent of the values in the range + // actually occur, it is probably worth it to do a compaction + // (but only if the channel palette is less than 6% the size of the + // image itself) + maybe_palette_1.nb_colors = + std::min(static_cast(nb_pixels / 16), + static_cast(channel_colors_percent / 100. * colors)); + if (maybe_do_transform(gi, maybe_palette_1, cparams_, weighted::Header(), + cost_before, pool)) { + // effective bit depth is lower, adjust quantization accordingly + compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max); + if (max < maxval) maxval = max; + int ch_bitdepth = + (max > 0 ? CeilLog2Nonzero(static_cast(max)) : 0); + if (ch_bitdepth > max_bitdepth) max_bitdepth = ch_bitdepth; + } else { + max_bitdepth = orig_bitdepth; + } + } + } +} + } // namespace ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header, @@ -479,7 +582,6 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header, cparams_.options.predictor = Predictor::Gradient; } } else { - delta_pred_ = cparams_.options.predictor; if (cparams_.lossy_palette) cparams_.options.predictor = Predictor::Zero; } if (!cparams_.ModularPartIsLossless()) { @@ -624,6 +726,7 @@ Status ModularFrameEncoder::ComputeEncodingData( pixel_type* const JXL_RESTRICT row_out = gi.channel[c_out].Row(y); pixel_type* const JXL_RESTRICT row_Y = gi.channel[0].Row(y); for (size_t x = 0; x < xsize; ++x) { + // TODO(eustas): check if std::roundf is appropriate row_out[x] = row_in[x] * factor + 0.5f; row_out[x] -= row_Y[x]; // zero the lsb of B @@ -720,81 +823,16 @@ Status ModularFrameEncoder::ComputeEncodingData( cparams_.lossy_palette = false; } - // Global palette - if ((cparams_.palette_colors != 0 || cparams_.lossy_palette) && !groupwise) { - // all-channel palette (e.g. RGBA) - if (gi.channel.size() - gi.nb_meta_channels > 1) { - Transform maybe_palette(TransformId::kPalette); - maybe_palette.begin_c = gi.nb_meta_channels; - maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels; - maybe_palette.nb_colors = std::min(static_cast(xsize * ysize / 2), - std::abs(cparams_.palette_colors)); - maybe_palette.ordered_palette = cparams_.palette_colors >= 0; - maybe_palette.lossy_palette = - (cparams_.lossy_palette && maybe_palette.num_c == 3); - if (maybe_palette.lossy_palette) { - maybe_palette.predictor = delta_pred_; - } - // TODO(veluca): use a custom weighted header if using the weighted - // predictor. - maybe_do_transform(gi, maybe_palette, cparams_, weighted::Header(), pool, - cparams_.options.zero_tokens); - } - // all-minus-one-channel palette (RGB with separate alpha, or CMY with - // separate K) - if (gi.channel.size() - gi.nb_meta_channels > 3) { - Transform maybe_palette_3(TransformId::kPalette); - maybe_palette_3.begin_c = gi.nb_meta_channels; - maybe_palette_3.num_c = gi.channel.size() - gi.nb_meta_channels - 1; - maybe_palette_3.nb_colors = std::min(static_cast(xsize * ysize / 3), - std::abs(cparams_.palette_colors)); - maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0; - maybe_palette_3.lossy_palette = cparams_.lossy_palette; - if (maybe_palette_3.lossy_palette) { - maybe_palette_3.predictor = delta_pred_; - } - maybe_do_transform(gi, maybe_palette_3, cparams_, weighted::Header(), - pool, cparams_.options.zero_tokens); - } - } - - // Global channel palette - if (!groupwise && cparams_.channel_colors_pre_transform_percent > 0 && - !cparams_.lossy_palette && + // Global palette transforms + float channel_colors_percent = 0; + if (!cparams_.lossy_palette && (cparams_.speed_tier <= SpeedTier::kThunder || (do_color && metadata.bit_depth.bits_per_sample > 8))) { - // single channel palette (like FLIF's ChannelCompact) - size_t nb_channels = gi.channel.size() - gi.nb_meta_channels; - int orig_bitdepth = max_bitdepth; - max_bitdepth = 0; - for (size_t i = 0; i < nb_channels; i++) { - int32_t min; - int32_t max; - compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max); - int64_t colors = static_cast(max) - min + 1; - JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max); - Transform maybe_palette_1(TransformId::kPalette); - maybe_palette_1.begin_c = i + gi.nb_meta_channels; - maybe_palette_1.num_c = 1; - // simple heuristic: if less than X percent of the values in the range - // actually occur, it is probably worth it to do a compaction - // (but only if the channel palette is less than 6% the size of the - // image itself) - maybe_palette_1.nb_colors = std::min( - static_cast(xsize * ysize / 16), - static_cast(cparams_.channel_colors_pre_transform_percent / - 100. * colors)); - if (maybe_do_transform(gi, maybe_palette_1, cparams_, weighted::Header(), - pool)) { - // effective bit depth is lower, adjust quantization accordingly - compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max); - if (max < maxval) maxval = max; - int ch_bitdepth = - (max > 0 ? CeilLog2Nonzero(static_cast(max)) : 0); - if (ch_bitdepth > max_bitdepth) max_bitdepth = ch_bitdepth; - } else - max_bitdepth = orig_bitdepth; - } + channel_colors_percent = cparams_.channel_colors_pre_transform_percent; + } + if (!groupwise) { + try_palettes(gi, max_bitdepth, maxval, cparams_, channel_colors_percent, + pool); } // don't do an RCT if we're short on bits @@ -1318,61 +1356,17 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect, if (gi.channel.empty()) return true; // Do some per-group transforms - // Local palette + // Local palette transforms // TODO(veluca): make this work with quantize-after-prediction in lossy // mode. - if (cparams_.butteraugli_distance == 0.f && cparams_.palette_colors != 0 && + if (cparams_.butteraugli_distance == 0.f && !cparams_.lossy_palette && cparams_.speed_tier < SpeedTier::kCheetah) { - // all-channel palette (e.g. RGBA) - if (gi.channel.size() - gi.nb_meta_channels > 1) { - Transform maybe_palette(TransformId::kPalette); - maybe_palette.begin_c = gi.nb_meta_channels; - maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels; - maybe_palette.nb_colors = std::abs(cparams_.palette_colors); - maybe_palette.ordered_palette = cparams_.palette_colors >= 0; - maybe_do_transform(gi, maybe_palette, cparams_, weighted::Header()); - } - // all-minus-one-channel palette (RGB with separate alpha, or CMY with - // separate K) - if (gi.channel.size() - gi.nb_meta_channels > 3) { - Transform maybe_palette_3(TransformId::kPalette); - maybe_palette_3.begin_c = gi.nb_meta_channels; - maybe_palette_3.num_c = gi.channel.size() - gi.nb_meta_channels - 1; - maybe_palette_3.nb_colors = std::abs(cparams_.palette_colors); - maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0; - maybe_palette_3.lossy_palette = cparams_.lossy_palette; - if (maybe_palette_3.lossy_palette) { - maybe_palette_3.predictor = Predictor::Weighted; - } - maybe_do_transform(gi, maybe_palette_3, cparams_, weighted::Header()); - } - } - - // Local channel palette - if (cparams_.channel_colors_percent > 0 && - cparams_.butteraugli_distance == 0.f && !cparams_.lossy_palette && - cparams_.speed_tier < SpeedTier::kCheetah && - !(cparams_.responsive && cparams_.decoding_speed_tier >= 1)) { - // single channel palette (like FLIF's ChannelCompact) - size_t nb_channels = gi.channel.size() - gi.nb_meta_channels; - for (size_t i = 0; i < nb_channels; i++) { - int32_t min; - int32_t max; - compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max); - int64_t colors = static_cast(max) - min + 1; - JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max); - Transform maybe_palette_1(TransformId::kPalette); - maybe_palette_1.begin_c = i + gi.nb_meta_channels; - maybe_palette_1.num_c = 1; - // simple heuristic: if less than X percent of the values in the range - // actually occur, it is probably worth it to do a compaction - // (but only if the channel palette is less than 80% the size of the - // image itself) - maybe_palette_1.nb_colors = std::min( - static_cast(xsize * ysize * 0.8), - static_cast(cparams_.channel_colors_percent / 100. * colors)); - maybe_do_transform(gi, maybe_palette_1, cparams_, weighted::Header()); + int max_bitdepth = 0, maxval = 0; // don't care about that here + float channel_color_percent = 0; + if (!(cparams_.responsive && cparams_.decoding_speed_tier >= 1)) { + channel_color_percent = cparams_.channel_colors_percent; } + try_palettes(gi, max_bitdepth, maxval, cparams_, channel_color_percent); } } -- cgit v1.2.3