libjxl

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

dec_ans.h (20239B)


      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 #ifndef LIB_JXL_DEC_ANS_H_
      7 #define LIB_JXL_DEC_ANS_H_
      8 
      9 // Library to decode the ANS population counts from the bit-stream and build a
     10 // decoding table from them.
     11 
     12 #include <jxl/types.h>
     13 #include <stddef.h>
     14 #include <stdint.h>
     15 
     16 #include <cstring>
     17 #include <vector>
     18 
     19 #include "lib/jxl/ans_common.h"
     20 #include "lib/jxl/ans_params.h"
     21 #include "lib/jxl/base/bits.h"
     22 #include "lib/jxl/base/byte_order.h"
     23 #include "lib/jxl/base/compiler_specific.h"
     24 #include "lib/jxl/cache_aligned.h"
     25 #include "lib/jxl/dec_bit_reader.h"
     26 #include "lib/jxl/dec_huffman.h"
     27 #include "lib/jxl/field_encodings.h"
     28 
     29 namespace jxl {
     30 
     31 class ANSSymbolReader;
     32 
     33 // Experiments show that best performance is typically achieved for a
     34 // split-exponent of 3 or 4. Trend seems to be that '4' is better
     35 // for large-ish pictures, and '3' better for rather small-ish pictures.
     36 // This is plausible - the more special symbols we have, the better
     37 // statistics we need to get a benefit out of them.
     38 
     39 // Our hybrid-encoding scheme has dedicated tokens for the smallest
     40 // (1 << split_exponents) numbers, and for the rest
     41 // encodes (number of bits) + (msb_in_token sub-leading binary digits) +
     42 // (lsb_in_token lowest binary digits) in the token, with the remaining bits
     43 // then being encoded as data.
     44 //
     45 // Example with split_exponent = 4, msb_in_token = 2, lsb_in_token = 0.
     46 //
     47 // Numbers N in [0 .. 15]:
     48 //   These get represented as (token=N, bits='').
     49 // Numbers N >= 16:
     50 //   If n is such that 2**n <= N < 2**(n+1),
     51 //   and m = N - 2**n is the 'mantissa',
     52 //   these get represented as:
     53 // (token=split_token +
     54 //        ((n - split_exponent) * 4) +
     55 //        (m >> (n - msb_in_token)),
     56 //  bits=m & (1 << (n - msb_in_token)) - 1)
     57 // Specifically, we would get:
     58 // N = 0 - 15:          (token=N, nbits=0, bits='')
     59 // N = 16 (10000):      (token=16, nbits=2, bits='00')
     60 // N = 17 (10001):      (token=16, nbits=2, bits='01')
     61 // N = 20 (10100):      (token=17, nbits=2, bits='00')
     62 // N = 24 (11000):      (token=18, nbits=2, bits='00')
     63 // N = 28 (11100):      (token=19, nbits=2, bits='00')
     64 // N = 32 (100000):     (token=20, nbits=3, bits='000')
     65 // N = 65535:           (token=63, nbits=13, bits='1111111111111')
     66 struct HybridUintConfig {
     67   uint32_t split_exponent;
     68   uint32_t split_token;
     69   uint32_t msb_in_token;
     70   uint32_t lsb_in_token;
     71   JXL_INLINE void Encode(uint32_t value, uint32_t* JXL_RESTRICT token,
     72                          uint32_t* JXL_RESTRICT nbits,
     73                          uint32_t* JXL_RESTRICT bits) const {
     74     if (value < split_token) {
     75       *token = value;
     76       *nbits = 0;
     77       *bits = 0;
     78     } else {
     79       uint32_t n = FloorLog2Nonzero(value);
     80       uint32_t m = value - (1 << n);
     81       *token = split_token +
     82                ((n - split_exponent) << (msb_in_token + lsb_in_token)) +
     83                ((m >> (n - msb_in_token)) << lsb_in_token) +
     84                (m & ((1 << lsb_in_token) - 1));
     85       *nbits = n - msb_in_token - lsb_in_token;
     86       *bits = (value >> lsb_in_token) & ((1UL << *nbits) - 1);
     87     }
     88   }
     89 
     90   explicit HybridUintConfig(uint32_t split_exponent = 4,
     91                             uint32_t msb_in_token = 2,
     92                             uint32_t lsb_in_token = 0)
     93       : split_exponent(split_exponent),
     94         split_token(1 << split_exponent),
     95         msb_in_token(msb_in_token),
     96         lsb_in_token(lsb_in_token) {
     97     JXL_DASSERT(split_exponent >= msb_in_token + lsb_in_token);
     98   }
     99 };
    100 
    101 struct LZ77Params : public Fields {
    102   LZ77Params();
    103   JXL_FIELDS_NAME(LZ77Params)
    104   Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
    105   bool enabled;
    106 
    107   // Symbols above min_symbol use a special hybrid uint encoding and
    108   // represent a length, to be added to min_length.
    109   uint32_t min_symbol;
    110   uint32_t min_length;
    111 
    112   // Not serialized by VisitFields.
    113   HybridUintConfig length_uint_config{0, 0, 0};
    114 
    115   size_t nonserialized_distance_context;
    116 };
    117 
    118 static constexpr size_t kWindowSize = 1 << 20;
    119 static constexpr size_t kNumSpecialDistances = 120;
    120 // Table of special distance codes from WebP lossless.
    121 static constexpr int8_t kSpecialDistances[kNumSpecialDistances][2] = {
    122     {0, 1},  {1, 0},  {1, 1},  {-1, 1}, {0, 2},  {2, 0},  {1, 2},  {-1, 2},
    123     {2, 1},  {-2, 1}, {2, 2},  {-2, 2}, {0, 3},  {3, 0},  {1, 3},  {-1, 3},
    124     {3, 1},  {-3, 1}, {2, 3},  {-2, 3}, {3, 2},  {-3, 2}, {0, 4},  {4, 0},
    125     {1, 4},  {-1, 4}, {4, 1},  {-4, 1}, {3, 3},  {-3, 3}, {2, 4},  {-2, 4},
    126     {4, 2},  {-4, 2}, {0, 5},  {3, 4},  {-3, 4}, {4, 3},  {-4, 3}, {5, 0},
    127     {1, 5},  {-1, 5}, {5, 1},  {-5, 1}, {2, 5},  {-2, 5}, {5, 2},  {-5, 2},
    128     {4, 4},  {-4, 4}, {3, 5},  {-3, 5}, {5, 3},  {-5, 3}, {0, 6},  {6, 0},
    129     {1, 6},  {-1, 6}, {6, 1},  {-6, 1}, {2, 6},  {-2, 6}, {6, 2},  {-6, 2},
    130     {4, 5},  {-4, 5}, {5, 4},  {-5, 4}, {3, 6},  {-3, 6}, {6, 3},  {-6, 3},
    131     {0, 7},  {7, 0},  {1, 7},  {-1, 7}, {5, 5},  {-5, 5}, {7, 1},  {-7, 1},
    132     {4, 6},  {-4, 6}, {6, 4},  {-6, 4}, {2, 7},  {-2, 7}, {7, 2},  {-7, 2},
    133     {3, 7},  {-3, 7}, {7, 3},  {-7, 3}, {5, 6},  {-5, 6}, {6, 5},  {-6, 5},
    134     {8, 0},  {4, 7},  {-4, 7}, {7, 4},  {-7, 4}, {8, 1},  {8, 2},  {6, 6},
    135     {-6, 6}, {8, 3},  {5, 7},  {-5, 7}, {7, 5},  {-7, 5}, {8, 4},  {6, 7},
    136     {-6, 7}, {7, 6},  {-7, 6}, {8, 5},  {7, 7},  {-7, 7}, {8, 6},  {8, 7}};
    137 static JXL_INLINE int SpecialDistance(size_t index, int multiplier) {
    138   int dist = kSpecialDistances[index][0] +
    139              static_cast<int>(multiplier) * kSpecialDistances[index][1];
    140   return (dist > 1) ? dist : 1;
    141 }
    142 
    143 struct ANSCode {
    144   CacheAlignedUniquePtr alias_tables;
    145   std::vector<HuffmanDecodingData> huffman_data;
    146   std::vector<HybridUintConfig> uint_config;
    147   std::vector<int> degenerate_symbols;
    148   bool use_prefix_code;
    149   uint8_t log_alpha_size;  // for ANS.
    150   LZ77Params lz77;
    151   // Maximum number of bits necessary to represent the result of a
    152   // ReadHybridUint call done with this ANSCode.
    153   size_t max_num_bits = 0;
    154   void UpdateMaxNumBits(size_t ctx, size_t symbol);
    155 };
    156 
    157 class ANSSymbolReader {
    158  public:
    159   // Invalid symbol reader, to be overwritten.
    160   ANSSymbolReader() = default;
    161   ANSSymbolReader(const ANSCode* code, BitReader* JXL_RESTRICT br,
    162                   size_t distance_multiplier = 0)
    163       : alias_tables_(
    164             reinterpret_cast<AliasTable::Entry*>(code->alias_tables.get())),
    165         huffman_data_(code->huffman_data.data()),
    166         use_prefix_code_(code->use_prefix_code),
    167         configs(code->uint_config.data()) {
    168     if (!use_prefix_code_) {
    169       state_ = static_cast<uint32_t>(br->ReadFixedBits<32>());
    170       log_alpha_size_ = code->log_alpha_size;
    171       log_entry_size_ = ANS_LOG_TAB_SIZE - code->log_alpha_size;
    172       entry_size_minus_1_ = (1 << log_entry_size_) - 1;
    173     } else {
    174       state_ = (ANS_SIGNATURE << 16u);
    175     }
    176     if (!code->lz77.enabled) return;
    177     // a std::vector incurs unacceptable decoding speed loss because of
    178     // initialization.
    179     lz77_window_storage_ = AllocateArray(kWindowSize * sizeof(uint32_t));
    180     lz77_window_ = reinterpret_cast<uint32_t*>(lz77_window_storage_.get());
    181     lz77_ctx_ = code->lz77.nonserialized_distance_context;
    182     lz77_length_uint_ = code->lz77.length_uint_config;
    183     lz77_threshold_ = code->lz77.min_symbol;
    184     lz77_min_length_ = code->lz77.min_length;
    185     num_special_distances_ =
    186         distance_multiplier == 0 ? 0 : kNumSpecialDistances;
    187     for (size_t i = 0; i < num_special_distances_; i++) {
    188       special_distances_[i] = SpecialDistance(i, distance_multiplier);
    189     }
    190   }
    191 
    192   JXL_INLINE size_t ReadSymbolANSWithoutRefill(const size_t histo_idx,
    193                                                BitReader* JXL_RESTRICT br) {
    194     const uint32_t res = state_ & (ANS_TAB_SIZE - 1u);
    195 
    196     const AliasTable::Entry* table =
    197         &alias_tables_[histo_idx << log_alpha_size_];
    198     const AliasTable::Symbol symbol =
    199         AliasTable::Lookup(table, res, log_entry_size_, entry_size_minus_1_);
    200     state_ = symbol.freq * (state_ >> ANS_LOG_TAB_SIZE) + symbol.offset;
    201 
    202 #if JXL_TRUE
    203     // Branchless version is about equally fast on SKX.
    204     const uint32_t new_state =
    205         (state_ << 16u) | static_cast<uint32_t>(br->PeekFixedBits<16>());
    206     const bool normalize = state_ < (1u << 16u);
    207     state_ = normalize ? new_state : state_;
    208     br->Consume(normalize ? 16 : 0);
    209 #else
    210     if (JXL_UNLIKELY(state_ < (1u << 16u))) {
    211       state_ = (state_ << 16u) | br->PeekFixedBits<16>();
    212       br->Consume(16);
    213     }
    214 #endif
    215     const uint32_t next_res = state_ & (ANS_TAB_SIZE - 1u);
    216     AliasTable::Prefetch(table, next_res, log_entry_size_);
    217 
    218     return symbol.value;
    219   }
    220 
    221   JXL_INLINE size_t ReadSymbolHuffWithoutRefill(const size_t histo_idx,
    222                                                 BitReader* JXL_RESTRICT br) {
    223     return huffman_data_[histo_idx].ReadSymbol(br);
    224   }
    225 
    226   JXL_INLINE size_t ReadSymbolWithoutRefill(const size_t histo_idx,
    227                                             BitReader* JXL_RESTRICT br) {
    228     // TODO(veluca): hoist if in hotter loops.
    229     if (JXL_UNLIKELY(use_prefix_code_)) {
    230       return ReadSymbolHuffWithoutRefill(histo_idx, br);
    231     }
    232     return ReadSymbolANSWithoutRefill(histo_idx, br);
    233   }
    234 
    235   JXL_INLINE size_t ReadSymbol(const size_t histo_idx,
    236                                BitReader* JXL_RESTRICT br) {
    237     br->Refill();
    238     return ReadSymbolWithoutRefill(histo_idx, br);
    239   }
    240 
    241 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    242   bool CheckANSFinalState() const { return true; }
    243 #else
    244   bool CheckANSFinalState() const { return state_ == (ANS_SIGNATURE << 16u); }
    245 #endif
    246 
    247   template <typename BitReader>
    248   static JXL_INLINE uint32_t ReadHybridUintConfig(
    249       const HybridUintConfig& config, size_t token, BitReader* br) {
    250     size_t split_token = config.split_token;
    251     size_t msb_in_token = config.msb_in_token;
    252     size_t lsb_in_token = config.lsb_in_token;
    253     size_t split_exponent = config.split_exponent;
    254     // Fast-track version of hybrid integer decoding.
    255     if (token < split_token) return token;
    256     uint32_t nbits = split_exponent - (msb_in_token + lsb_in_token) +
    257                      ((token - split_token) >> (msb_in_token + lsb_in_token));
    258     // Max amount of bits for ReadBits is 32 and max valid left shift is 29
    259     // bits. However, for speed no error is propagated here, instead limit the
    260     // nbits size. If nbits > 29, the code stream is invalid, but no error is
    261     // returned.
    262     // Note that in most cases we will emit an error if the histogram allows
    263     // representing numbers that would cause invalid shifts, but we need to
    264     // keep this check as when LZ77 is enabled it might make sense to have an
    265     // histogram that could in principle cause invalid shifts.
    266     nbits &= 31u;
    267     uint32_t low = token & ((1 << lsb_in_token) - 1);
    268     token >>= lsb_in_token;
    269     const size_t bits = br->PeekBits(nbits);
    270     br->Consume(nbits);
    271     size_t ret = (((((1 << msb_in_token) | (token & ((1 << msb_in_token) - 1)))
    272                     << nbits) |
    273                    bits)
    274                   << lsb_in_token) |
    275                  low;
    276     // TODO(eustas): mark BitReader as unhealthy if nbits > 29 or ret does not
    277     //               fit uint32_t
    278     return static_cast<uint32_t>(ret);
    279   }
    280 
    281   // Takes a *clustered* idx. Can only use if HuffRleOnly() is true.
    282   JXL_INLINE void ReadHybridUintClusteredHuffRleOnly(size_t ctx,
    283                                                      BitReader* JXL_RESTRICT br,
    284                                                      uint32_t* value,
    285                                                      uint32_t* run) {
    286     JXL_DASSERT(HuffRleOnly());
    287     br->Refill();  // covers ReadSymbolWithoutRefill + PeekBits
    288     size_t token = ReadSymbolHuffWithoutRefill(ctx, br);
    289     if (JXL_UNLIKELY(token >= lz77_threshold_)) {
    290       *run =
    291           ReadHybridUintConfig(lz77_length_uint_, token - lz77_threshold_, br) +
    292           lz77_min_length_ - 1;
    293       return;
    294     }
    295     *value = ReadHybridUintConfig(configs[ctx], token, br);
    296   }
    297   bool HuffRleOnly() {
    298     if (lz77_window_ == nullptr) return false;
    299     if (!use_prefix_code_) return false;
    300     for (size_t i = 0; i < kHuffmanTableBits; i++) {
    301       if (huffman_data_[lz77_ctx_].table_[i].bits) return false;
    302       if (huffman_data_[lz77_ctx_].table_[i].value != 1) return false;
    303     }
    304     if (configs[lz77_ctx_].split_token > 1) return false;
    305     return true;
    306   }
    307   bool UsesLZ77() { return lz77_window_ != nullptr; }
    308 
    309   // Takes a *clustered* idx. Inlined, for use in hot paths.
    310   template <bool uses_lz77>
    311   JXL_INLINE size_t ReadHybridUintClusteredInlined(size_t ctx,
    312                                                    BitReader* JXL_RESTRICT br) {
    313     if (uses_lz77) {
    314       if (JXL_UNLIKELY(num_to_copy_ > 0)) {
    315         size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
    316         num_to_copy_--;
    317         lz77_window_[(num_decoded_++) & kWindowMask] = ret;
    318         return ret;
    319       }
    320     }
    321 
    322     br->Refill();  // covers ReadSymbolWithoutRefill + PeekBits
    323     size_t token = ReadSymbolWithoutRefill(ctx, br);
    324     if (uses_lz77) {
    325       if (JXL_UNLIKELY(token >= lz77_threshold_)) {
    326         num_to_copy_ = ReadHybridUintConfig(lz77_length_uint_,
    327                                             token - lz77_threshold_, br) +
    328                        lz77_min_length_;
    329         br->Refill();  // covers ReadSymbolWithoutRefill + PeekBits
    330         // Distance code.
    331         size_t token = ReadSymbolWithoutRefill(lz77_ctx_, br);
    332         size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], token, br);
    333         if (JXL_LIKELY(distance < num_special_distances_)) {
    334           distance = special_distances_[distance];
    335         } else {
    336           distance = distance + 1 - num_special_distances_;
    337         }
    338         if (JXL_UNLIKELY(distance > num_decoded_)) {
    339           distance = num_decoded_;
    340         }
    341         if (JXL_UNLIKELY(distance > kWindowSize)) {
    342           distance = kWindowSize;
    343         }
    344         copy_pos_ = num_decoded_ - distance;
    345         if (JXL_UNLIKELY(distance == 0)) {
    346           JXL_DASSERT(lz77_window_ != nullptr);
    347           // distance 0 -> num_decoded_ == copy_pos_ == 0
    348           size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize);
    349           memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0]));
    350         }
    351         // TODO(eustas): overflow; mark BitReader as unhealthy
    352         if (num_to_copy_ < lz77_min_length_) return 0;
    353         // the code below is the same as doing this:
    354         //        return ReadHybridUintClustered<uses_lz77>(ctx, br);
    355         // but gcc doesn't like recursive inlining
    356 
    357         size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
    358         num_to_copy_--;
    359         lz77_window_[(num_decoded_++) & kWindowMask] = ret;
    360         return ret;
    361       }
    362     }
    363     size_t ret = ReadHybridUintConfig(configs[ctx], token, br);
    364     if (uses_lz77 && lz77_window_)
    365       lz77_window_[(num_decoded_++) & kWindowMask] = ret;
    366     return ret;
    367   }
    368 
    369   // same but not inlined
    370   template <bool uses_lz77>
    371   size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) {
    372     return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br);
    373   }
    374 
    375   // inlined only in the no-lz77 case
    376   template <bool uses_lz77>
    377   JXL_INLINE size_t
    378   ReadHybridUintClusteredMaybeInlined(size_t ctx, BitReader* JXL_RESTRICT br) {
    379     if (uses_lz77) {
    380       return ReadHybridUintClustered<uses_lz77>(ctx, br);
    381     } else {
    382       return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br);
    383     }
    384   }
    385 
    386   // inlined, for use in hot paths
    387   template <bool uses_lz77>
    388   JXL_INLINE size_t
    389   ReadHybridUintInlined(size_t ctx, BitReader* JXL_RESTRICT br,
    390                         const std::vector<uint8_t>& context_map) {
    391     return ReadHybridUintClustered<uses_lz77>(context_map[ctx], br);
    392   }
    393 
    394   // not inlined, for use in non-hot paths
    395   size_t ReadHybridUint(size_t ctx, BitReader* JXL_RESTRICT br,
    396                         const std::vector<uint8_t>& context_map) {
    397     return ReadHybridUintClustered</*uses_lz77=*/true>(context_map[ctx], br);
    398   }
    399 
    400   // ctx is a *clustered* context!
    401   // This function will modify the ANS state as if `count` symbols have been
    402   // decoded.
    403   bool IsSingleValueAndAdvance(size_t ctx, uint32_t* value, size_t count) {
    404     // TODO(veluca): No optimization for Huffman mode yet.
    405     if (use_prefix_code_) return false;
    406     // TODO(eustas): propagate "degenerate_symbol" to simplify this method.
    407     const uint32_t res = state_ & (ANS_TAB_SIZE - 1u);
    408     const AliasTable::Entry* table = &alias_tables_[ctx << log_alpha_size_];
    409     AliasTable::Symbol symbol =
    410         AliasTable::Lookup(table, res, log_entry_size_, entry_size_minus_1_);
    411     if (symbol.freq != ANS_TAB_SIZE) return false;
    412     if (configs[ctx].split_token <= symbol.value) return false;
    413     if (symbol.value >= lz77_threshold_) return false;
    414     *value = symbol.value;
    415     if (lz77_window_) {
    416       for (size_t i = 0; i < count; i++) {
    417         lz77_window_[(num_decoded_++) & kWindowMask] = symbol.value;
    418       }
    419     }
    420     return true;
    421   }
    422 
    423   static constexpr size_t kMaxCheckpointInterval = 512;
    424   struct Checkpoint {
    425     uint32_t state;
    426     uint32_t num_to_copy;
    427     uint32_t copy_pos;
    428     uint32_t num_decoded;
    429     uint32_t lz77_window[kMaxCheckpointInterval];
    430   };
    431   void Save(Checkpoint* checkpoint) {
    432     checkpoint->state = state_;
    433     checkpoint->num_decoded = num_decoded_;
    434     checkpoint->num_to_copy = num_to_copy_;
    435     checkpoint->copy_pos = copy_pos_;
    436     if (lz77_window_) {
    437       size_t win_start = num_decoded_ & kWindowMask;
    438       size_t win_end = (num_decoded_ + kMaxCheckpointInterval) & kWindowMask;
    439       if (win_end > win_start) {
    440         memcpy(checkpoint->lz77_window, lz77_window_ + win_start,
    441                (win_end - win_start) * sizeof(*lz77_window_));
    442       } else {
    443         memcpy(checkpoint->lz77_window, lz77_window_ + win_start,
    444                (kWindowSize - win_start) * sizeof(*lz77_window_));
    445         memcpy(checkpoint->lz77_window + (kWindowSize - win_start),
    446                lz77_window_, win_end * sizeof(*lz77_window_));
    447       }
    448     }
    449   }
    450   void Restore(const Checkpoint& checkpoint) {
    451     state_ = checkpoint.state;
    452     JXL_DASSERT(num_decoded_ <=
    453                 checkpoint.num_decoded + kMaxCheckpointInterval);
    454     num_decoded_ = checkpoint.num_decoded;
    455     num_to_copy_ = checkpoint.num_to_copy;
    456     copy_pos_ = checkpoint.copy_pos;
    457     if (lz77_window_) {
    458       size_t win_start = num_decoded_ & kWindowMask;
    459       size_t win_end = (num_decoded_ + kMaxCheckpointInterval) & kWindowMask;
    460       if (win_end > win_start) {
    461         memcpy(lz77_window_ + win_start, checkpoint.lz77_window,
    462                (win_end - win_start) * sizeof(*lz77_window_));
    463       } else {
    464         memcpy(lz77_window_ + win_start, checkpoint.lz77_window,
    465                (kWindowSize - win_start) * sizeof(*lz77_window_));
    466         memcpy(lz77_window_, checkpoint.lz77_window + (kWindowSize - win_start),
    467                win_end * sizeof(*lz77_window_));
    468       }
    469     }
    470   }
    471 
    472  private:
    473   const AliasTable::Entry* JXL_RESTRICT alias_tables_;  // not owned
    474   const HuffmanDecodingData* huffman_data_;
    475   bool use_prefix_code_;
    476   uint32_t state_ = ANS_SIGNATURE << 16u;
    477   const HybridUintConfig* JXL_RESTRICT configs;
    478   uint32_t log_alpha_size_{};
    479   uint32_t log_entry_size_{};
    480   uint32_t entry_size_minus_1_{};
    481 
    482   // LZ77 structures and constants.
    483   static constexpr size_t kWindowMask = kWindowSize - 1;
    484   CacheAlignedUniquePtr lz77_window_storage_;
    485   uint32_t* lz77_window_ = nullptr;
    486   uint32_t num_decoded_ = 0;
    487   uint32_t num_to_copy_ = 0;
    488   uint32_t copy_pos_ = 0;
    489   uint32_t lz77_ctx_ = 0;
    490   uint32_t lz77_min_length_ = 0;
    491   uint32_t lz77_threshold_ = 1 << 20;  // bigger than any symbol.
    492   HybridUintConfig lz77_length_uint_;
    493   uint32_t special_distances_[kNumSpecialDistances]{};
    494   uint32_t num_special_distances_{};
    495 };
    496 
    497 Status DecodeHistograms(BitReader* br, size_t num_contexts, ANSCode* code,
    498                         std::vector<uint8_t>* context_map,
    499                         bool disallow_lz77 = false);
    500 
    501 // Exposed for tests.
    502 Status DecodeUintConfigs(size_t log_alpha_size,
    503                          std::vector<HybridUintConfig>* uint_config,
    504                          BitReader* br);
    505 
    506 }  // namespace jxl
    507 
    508 #endif  // LIB_JXL_DEC_ANS_H_