// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
// Experiments show that best performance is typically achieved for a // split-exponent of 3 or 4. Trend seems to be that '4' is better // for large-ish pictures, and '3' better for rather small-ish pictures. // This is plausible - the more special symbols we have, the better // statistics we need to get a benefit out of them.
// Our hybrid-encoding scheme has dedicated tokens for the smallest // (1 << split_exponents) numbers, and for the rest // encodes (number of bits) + (msb_in_token sub-leading binary digits) + // (lsb_in_token lowest binary digits) in the token, with the remaining bits // then being encoded as data. // // Example with split_exponent = 4, msb_in_token = 2, lsb_in_token = 0. // // Numbers N in [0 .. 15]: // These get represented as (token=N, bits=''). // Numbers N >= 16: // If n is such that 2**n <= N < 2**(n+1), // and m = N - 2**n is the 'mantissa', // these get represented as: // (token=split_token + // ((n - split_exponent) * 4) + // (m >> (n - msb_in_token)), // bits=m & (1 << (n - msb_in_token)) - 1) // Specifically, we would get: // N = 0 - 15: (token=N, nbits=0, bits='') // N = 16 (10000): (token=16, nbits=2, bits='00') // N = 17 (10001): (token=16, nbits=2, bits='01') // N = 20 (10100): (token=17, nbits=2, bits='00') // N = 24 (11000): (token=18, nbits=2, bits='00') // N = 28 (11100): (token=19, nbits=2, bits='00') // N = 32 (100000): (token=20, nbits=3, bits='000') // N = 65535: (token=63, nbits=13, bits='1111111111111') struct HybridUintConfig {
uint32_t split_exponent;
uint32_t split_token;
uint32_t msb_in_token;
uint32_t lsb_in_token;
JXL_INLINE void Encode(uint32_t value, uint32_t* JXL_RESTRICT token,
uint32_t* JXL_RESTRICT nbits,
uint32_t* JXL_RESTRICT bits) const { if (value < split_token) {
*token = value;
*nbits = 0;
*bits = 0;
} else {
uint32_t n = FloorLog2Nonzero(value);
uint32_t m = value - (1 << n);
*token = split_token +
((n - split_exponent) << (msb_in_token + lsb_in_token)) +
((m >> (n - msb_in_token)) << lsb_in_token) +
(m & ((1 << lsb_in_token) - 1));
*nbits = n - msb_in_token - lsb_in_token;
*bits = (value >> lsb_in_token) & ((1UL << *nbits) - 1);
}
}
struct LZ77Params : public Fields {
LZ77Params();
JXL_FIELDS_NAME(LZ77Params)
Status VisitFields(Visitor* JXL_RESTRICT visitor) override; bool enabled;
// Symbols above min_symbol use a special hybrid uint encoding and // represent a length, to be added to min_length.
uint32_t min_symbol;
uint32_t min_length;
// Not serialized by VisitFields.
HybridUintConfig length_uint_config{0, 0, 0};
struct ANSCode {
AlignedMemory alias_tables;
std::vector<HuffmanDecodingData> huffman_data;
std::vector<HybridUintConfig> uint_config;
std::vector<int> degenerate_symbols; bool use_prefix_code;
uint8_t log_alpha_size; // for ANS.
LZ77Params lz77; // Maximum number of bits necessary to represent the result of a // ReadHybridUint call done with this ANSCode.
size_t max_num_bits = 0;
JxlMemoryManager* memory_manager; void UpdateMaxNumBits(size_t ctx, size_t symbol);
};
class ANSSymbolReader { public: // Invalid symbol reader, to be overwritten.
ANSSymbolReader() = default; static StatusOr<ANSSymbolReader> Create(const ANSCode* code,
BitReader* JXL_RESTRICT br,
size_t distance_multiplier = 0);
template <typename BitReader> static JXL_INLINE uint32_t ReadHybridUintConfig( const HybridUintConfig& config, size_t token, BitReader* br) {
size_t split_token = config.split_token;
size_t msb_in_token = config.msb_in_token;
size_t lsb_in_token = config.lsb_in_token;
size_t split_exponent = config.split_exponent; // Fast-track version of hybrid integer decoding. if (token < split_token) return token;
uint32_t nbits = split_exponent - (msb_in_token + lsb_in_token) +
((token - split_token) >> (msb_in_token + lsb_in_token)); // Max amount of bits for ReadBits is 32 and max valid left shift is 29 // bits. However, for speed no error is propagated here, instead limit the // nbits size. If nbits > 29, the code stream is invalid, but no error is // returned. // Note that in most cases we will emit an error if the histogram allows // representing numbers that would cause invalid shifts, but we need to // keep this check as when LZ77 is enabled it might make sense to have an // histogram that could in principle cause invalid shifts.
nbits &= 31u;
uint32_t low = token & ((1 << lsb_in_token) - 1);
token >>= lsb_in_token; const size_t bits = br->PeekBits(nbits);
br->Consume(nbits);
size_t ret = (((((1 << msb_in_token) | (token & ((1 << msb_in_token) - 1)))
<< nbits) |
bits)
<< lsb_in_token) |
low; // TODO(eustas): mark BitReader as unhealthy if nbits > 29 or ret does not // fit uint32_t returnstatic_cast<uint32_t>(ret);
}
// Takes a *clustered* idx. Can only use if HuffRleOnly() is true.
JXL_INLINE void ReadHybridUintClusteredHuffRleOnly(size_t ctx,
BitReader* JXL_RESTRICT br,
uint32_t* value,
uint32_t* run) {
JXL_DASSERT(IsHuffRleOnly());
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
size_t token = ReadSymbolHuffWithoutRefill(ctx, br); if (JXL_UNLIKELY(token >= lz77_threshold_)) {
*run =
ReadHybridUintConfig(lz77_length_uint_, token - lz77_threshold_, br) +
lz77_min_length_ - 1; return;
}
*value = ReadHybridUintConfig(configs[ctx], token, br);
} bool IsHuffRleOnly() const { if (lz77_window_ == nullptr) returnfalse; if (!use_prefix_code_) returnfalse; for (size_t i = 0; i < kHuffmanTableBits; i++) { if (huffman_data_[lz77_ctx_].table_[i].bits) returnfalse; if (huffman_data_[lz77_ctx_].table_[i].value != 1) returnfalse;
} if (configs[lz77_ctx_].split_token > 1) returnfalse; returntrue;
} bool UsesLZ77() { return lz77_window_ != nullptr; }
// Takes a *clustered* idx. Inlined, for use in hot paths. template <bool uses_lz77>
JXL_INLINE size_t ReadHybridUintClusteredInlined(size_t ctx,
BitReader* JXL_RESTRICT br) { if (uses_lz77) { if (JXL_UNLIKELY(num_to_copy_ > 0)) {
size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
num_to_copy_--;
lz77_window_[(num_decoded_++) & kWindowMask] = ret; return ret;
}
}
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
size_t token = ReadSymbolWithoutRefill(ctx, br); if (uses_lz77) { if (JXL_UNLIKELY(token >= lz77_threshold_)) {
num_to_copy_ = ReadHybridUintConfig(lz77_length_uint_,
token - lz77_threshold_, br) +
lz77_min_length_;
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits // Distance code.
size_t token = ReadSymbolWithoutRefill(lz77_ctx_, br);
size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], token, br); if (JXL_LIKELY(distance < num_special_distances_)) {
distance = special_distances_[distance];
} else {
distance = distance + 1 - num_special_distances_;
} if (JXL_UNLIKELY(distance > num_decoded_)) {
distance = num_decoded_;
} if (JXL_UNLIKELY(distance > kWindowSize)) {
distance = kWindowSize;
}
copy_pos_ = num_decoded_ - distance; if (JXL_UNLIKELY(distance == 0)) {
JXL_DASSERT(lz77_window_ != nullptr); // distance 0 -> num_decoded_ == copy_pos_ == 0
size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize);
memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0]));
} // TODO(eustas): overflow; mark BitReader as unhealthy if (num_to_copy_ < lz77_min_length_) return 0; // the code below is the same as doing this: // return ReadHybridUintClustered<uses_lz77>(ctx, br); // but gcc doesn't like recursive inlining
// same but not inlined template <bool uses_lz77>
size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) { return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br);
}
// inlined only in the no-lz77 case template <bool uses_lz77>
JXL_INLINE size_t
ReadHybridUintClusteredMaybeInlined(size_t ctx, BitReader* JXL_RESTRICT br) { if (uses_lz77) { return ReadHybridUintClustered<uses_lz77>(ctx, br);
} else { return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br);
}
}
// inlined, for use in hot paths template <bool uses_lz77>
JXL_INLINE size_t
ReadHybridUintInlined(size_t ctx, BitReader* JXL_RESTRICT br, const std::vector<uint8_t>& context_map) { return ReadHybridUintClustered<uses_lz77>(context_map[ctx], br);
}
// not inlined, for use in non-hot paths
size_t ReadHybridUint(size_t ctx, BitReader* JXL_RESTRICT br, const std::vector<uint8_t>& context_map) { return ReadHybridUintClustered</*uses_lz77=*/true>(context_map[ctx], br);
}
// ctx is a *clustered* context! // This function will modify the ANS state as if `count` symbols have been // decoded. bool IsSingleValueAndAdvance(size_t ctx, uint32_t* value, size_t count) { // TODO(veluca): No optimization for Huffman mode yet. if (use_prefix_code_) returnfalse; // TODO(eustas): propagate "degenerate_symbol" to simplify this method. const uint32_t res = state_ & (ANS_TAB_SIZE - 1u); const AliasTable::Entry* table = &alias_tables_[ctx << log_alpha_size_];
AliasTable::Symbol symbol =
AliasTable::Lookup(table, res, log_entry_size_, entry_size_minus_1_); if (symbol.freq != ANS_TAB_SIZE) returnfalse; if (configs[ctx].split_token <= symbol.value) returnfalse; if (symbol.value >= lz77_threshold_) returnfalse;
*value = symbol.value; if (lz77_window_) { for (size_t i = 0; i < count; i++) {
lz77_window_[(num_decoded_++) & kWindowMask] = symbol.value;
}
} returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.