/src/libjxl/lib/jxl/enc_lz77.cc
Line | Count | Source |
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/enc_lz77.h" |
7 | | |
8 | | #include <algorithm> |
9 | | #include <cmath> |
10 | | #include <cstddef> |
11 | | #include <cstdint> |
12 | | #include <limits> |
13 | | #include <unordered_map> |
14 | | #include <vector> |
15 | | |
16 | | #include "lib/jxl/ans_params.h" |
17 | | #include "lib/jxl/base/bits.h" |
18 | | #include "lib/jxl/base/fast_math-inl.h" |
19 | | #include "lib/jxl/base/status.h" |
20 | | #include "lib/jxl/dec_ans.h" |
21 | | #include "lib/jxl/enc_ans.h" |
22 | | #include "lib/jxl/enc_ans_params.h" |
23 | | |
24 | | namespace jxl { |
25 | | |
26 | | namespace { |
27 | | |
28 | | class SymbolCostEstimator { |
29 | | public: |
30 | | SymbolCostEstimator(size_t num_contexts, bool force_huffman, |
31 | | const std::vector<std::vector<Token>>& tokens, |
32 | 852 | const LZ77Params& lz77) { |
33 | 852 | std::vector<Histogram> builder(num_contexts); |
34 | | // Build histograms for estimating lz77 savings. |
35 | 852 | HybridUintConfig uint_config; |
36 | 852 | for (const auto& stream : tokens) { |
37 | 33.2k | for (const auto& token : stream) { |
38 | 33.2k | uint32_t tok, nbits, bits; |
39 | 33.2k | (token.is_lz77_length ? lz77.length_uint_config : uint_config) |
40 | 33.2k | .Encode(token.value, &tok, &nbits, &bits); |
41 | 33.2k | tok += token.is_lz77_length ? lz77.min_symbol : 0; |
42 | 33.2k | JXL_DASSERT(token.context < num_contexts); |
43 | 33.2k | builder[token.context].Add(tok); |
44 | 33.2k | } |
45 | 852 | } |
46 | 852 | max_alphabet_size_ = 0; |
47 | 1.70k | for (size_t i = 0; i < num_contexts; i++) { |
48 | 852 | max_alphabet_size_ = |
49 | 852 | std::max(max_alphabet_size_, builder[i].counts.size()); |
50 | 852 | } |
51 | 852 | bits_.resize(num_contexts * max_alphabet_size_); |
52 | | // TODO(veluca): SIMD? |
53 | 852 | add_symbol_cost_.resize(num_contexts); |
54 | 1.70k | for (size_t i = 0; i < num_contexts; i++) { |
55 | 852 | float inv_total = 1.0f / (builder[i].total_count + 1e-8f); |
56 | 852 | float total_cost = 0; |
57 | 14.4k | for (size_t j = 0; j < builder[i].counts.size(); j++) { |
58 | 13.6k | size_t cnt = builder[i].counts[j]; |
59 | 13.6k | float cost = 0; |
60 | 13.6k | if (cnt != 0 && cnt != builder[i].total_count) { |
61 | 12.7k | cost = -FastLog2f(cnt * inv_total); |
62 | 12.7k | if (force_huffman) cost = std::ceil(cost); |
63 | 12.7k | } else if (cnt == 0) { |
64 | 852 | cost = ANS_LOG_TAB_SIZE; // Highest possible cost. |
65 | 852 | } |
66 | 13.6k | bits_[i * max_alphabet_size_ + j] = cost; |
67 | 13.6k | total_cost += cost * builder[i].counts[j]; |
68 | 13.6k | } |
69 | | // Penalty for adding a lz77 symbol to this contest (only used for static |
70 | | // cost model). Higher penalty for contexts that have a very low |
71 | | // per-symbol entropy. |
72 | 852 | add_symbol_cost_[i] = std::max(0.0f, 6.0f - total_cost * inv_total); |
73 | 852 | } |
74 | 852 | } |
75 | 33.2k | float Bits(size_t ctx, size_t sym) const { |
76 | 33.2k | return bits_[ctx * max_alphabet_size_ + sym]; |
77 | 33.2k | } |
78 | 0 | float LenCost(size_t ctx, size_t len, const LZ77Params& lz77) const { |
79 | 0 | uint32_t nbits, bits, tok; |
80 | 0 | lz77.length_uint_config.Encode(len, &tok, &nbits, &bits); |
81 | 0 | tok += lz77.min_symbol; |
82 | 0 | return nbits + Bits(ctx, tok); |
83 | 0 | } |
84 | 0 | float DistCost(size_t len, const LZ77Params& lz77) const { |
85 | 0 | uint32_t nbits, bits, tok; |
86 | 0 | HybridUintConfig().Encode(len, &tok, &nbits, &bits); |
87 | 0 | return nbits + Bits(lz77.nonserialized_distance_context, tok); |
88 | 0 | } |
89 | 0 | float AddSymbolCost(size_t idx) const { return add_symbol_cost_[idx]; } |
90 | | |
91 | | private: |
92 | | size_t max_alphabet_size_; |
93 | | std::vector<float> bits_; |
94 | | std::vector<float> add_symbol_cost_; |
95 | | }; |
96 | | |
97 | | std::vector<std::vector<Token>> ApplyLZ77_RLE( |
98 | | const HistogramParams& params, size_t num_contexts, |
99 | 852 | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { |
100 | 852 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); |
101 | | // TODO(veluca): tune heuristics here. |
102 | 852 | SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77); |
103 | 852 | float bit_decrease = 0; |
104 | 852 | size_t total_symbols = 0; |
105 | 852 | std::vector<float> sym_cost; |
106 | 852 | HybridUintConfig uint_config; |
107 | 1.70k | for (size_t stream = 0; stream < tokens.size(); stream++) { |
108 | 852 | size_t distance_multiplier = |
109 | 852 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; |
110 | 852 | const auto& in = tokens[stream]; |
111 | 852 | auto& out = tokens_lz77[stream]; |
112 | 852 | total_symbols += in.size(); |
113 | | // Cumulative sum of bit costs. |
114 | 852 | sym_cost.resize(in.size() + 1); |
115 | 34.0k | for (size_t i = 0; i < in.size(); i++) { |
116 | 33.2k | uint32_t tok, nbits, unused_bits; |
117 | 33.2k | uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits); |
118 | 33.2k | sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i]; |
119 | 33.2k | } |
120 | 852 | out.reserve(in.size()); |
121 | 25.8k | for (size_t i = 0; i < in.size(); i++) { |
122 | 24.9k | size_t num_to_copy = 0; |
123 | 24.9k | size_t distance_symbol = 0; // 1 for RLE. |
124 | 24.9k | if (distance_multiplier != 0) { |
125 | 0 | distance_symbol = 1; // Special distance 1 if enabled. |
126 | 0 | JXL_DASSERT(kSpecialDistances[1][0] == 1); |
127 | 0 | JXL_DASSERT(kSpecialDistances[1][1] == 0); |
128 | 0 | } |
129 | 24.9k | if (i > 0) { |
130 | 37.2k | for (; i + num_to_copy < in.size(); num_to_copy++) { |
131 | 36.3k | if (in[i + num_to_copy].value != in[i - 1].value) { |
132 | 23.2k | break; |
133 | 23.2k | } |
134 | 36.3k | } |
135 | 24.1k | } |
136 | 24.9k | if (num_to_copy == 0) { |
137 | 20.1k | out.push_back(in[i]); |
138 | 20.1k | continue; |
139 | 20.1k | } |
140 | 4.82k | float cost = sym_cost[i + num_to_copy] - sym_cost[i]; |
141 | | // This subtraction might overflow, but that's OK. |
142 | 4.82k | size_t lz77_len = num_to_copy - lz77.min_length; |
143 | 4.82k | float lz77_cost = num_to_copy >= lz77.min_length |
144 | 4.82k | ? CeilLog2Nonzero(lz77_len + 1) + 1 |
145 | 4.82k | : 0; |
146 | 4.82k | if (num_to_copy < lz77.min_length || cost <= lz77_cost) { |
147 | 3.97k | for (size_t j = 0; j < num_to_copy; j++) { |
148 | 2.27k | out.push_back(in[i + j]); |
149 | 2.27k | } |
150 | 1.70k | i += num_to_copy - 1; |
151 | 1.70k | continue; |
152 | 1.70k | } |
153 | | // Output the LZ77 length |
154 | 3.12k | out.emplace_back(in[i].context, static_cast<uint32_t>(lz77_len)); |
155 | 3.12k | out.back().is_lz77_length = true; |
156 | 3.12k | i += num_to_copy - 1; |
157 | 3.12k | bit_decrease += cost - lz77_cost; |
158 | | // Output the LZ77 copy distance. |
159 | 3.12k | out.emplace_back( |
160 | 3.12k | static_cast<uint32_t>(lz77.nonserialized_distance_context), |
161 | 3.12k | static_cast<uint32_t>(distance_symbol)); |
162 | 3.12k | } |
163 | 852 | } |
164 | | |
165 | 852 | if (bit_decrease > total_symbols * 0.2 + 16) { |
166 | 0 | return tokens_lz77; |
167 | 0 | } |
168 | 852 | return {}; |
169 | 852 | } |
170 | | |
171 | | |
172 | | // Computes a Murmur-style mix hash over HashSize elements. |
173 | | template <int kHashSize=3> |
174 | 0 | uint32_t GetHash(size_t pos, const std::vector<uint32_t>& data_) { |
175 | 0 | uint32_t h = 0; |
176 | 0 | if (pos + kHashSize <= data_.size()) { |
177 | 0 | for (size_t i = 0; i < kHashSize; i++) { |
178 | 0 | h ^= data_[pos+i] + 0x9e3779b9 + (h << 6) + (h >> 2); |
179 | 0 | } |
180 | 0 | h ^= h >> 16; |
181 | 0 | h *= 0x85ebca6bu; |
182 | 0 | h ^= h >> 13; |
183 | 0 | h *= 0xc2b2ae35u; |
184 | 0 | h ^= h >> 16; |
185 | |
|
186 | 0 | } else { |
187 | 0 | return 0; |
188 | 0 | } |
189 | 0 | return h; |
190 | 0 | } |
191 | | |
192 | | // Hash chain for LZ77 matching |
193 | | template <uint32_t kMaxChainLength> |
194 | | struct HashChain { |
195 | | size_t size_; |
196 | | std::vector<uint32_t> data_; |
197 | | |
198 | | unsigned hash_num_values_ = 32768; |
199 | | unsigned hash_mask_ = hash_num_values_ - 1; |
200 | | unsigned hash_shift_ = 5; |
201 | | |
202 | | std::vector<int> head; |
203 | | std::vector<uint32_t> chain; |
204 | | std::vector<int> val; |
205 | | |
206 | | // Speed up repetitions of zero |
207 | | std::vector<int> headz; |
208 | | std::vector<uint32_t> chainz; |
209 | | std::vector<uint32_t> zeros; |
210 | | uint32_t numzeros = 0; |
211 | | |
212 | | size_t window_size_; |
213 | | size_t window_mask_; |
214 | | size_t min_length_; |
215 | | size_t max_length_; |
216 | | |
217 | | // Map of special distance codes. |
218 | | std::unordered_map<int, int> special_dist_table_; |
219 | | size_t num_special_distances_ = 0; |
220 | | |
221 | | uint32_t maxchainlength = kMaxChainLength; // window_size_ to allow all |
222 | | |
223 | | HashChain(const Token* data, size_t size, size_t window_size, |
224 | | size_t min_length, size_t max_length, size_t distance_multiplier) |
225 | 0 | : size_(size), |
226 | 0 | window_size_(window_size), |
227 | 0 | window_mask_(window_size - 1), |
228 | 0 | min_length_(min_length), |
229 | 0 | max_length_(max_length) { |
230 | 0 | data_.resize(size); |
231 | 0 | for (size_t i = 0; i < size; i++) { |
232 | 0 | data_[i] = data[i].value; |
233 | 0 | } |
234 | |
|
235 | 0 | head.resize(hash_num_values_, -1); |
236 | 0 | val.resize(window_size_, -1); |
237 | 0 | chain.resize(window_size_); |
238 | 0 | for (uint32_t i = 0; i < window_size_; ++i) { |
239 | 0 | chain[i] = i; // same value as index indicates uninitialized |
240 | 0 | } |
241 | |
|
242 | 0 | zeros.resize(window_size_); |
243 | 0 | headz.resize(window_size_ + 1, -1); |
244 | 0 | chainz.resize(window_size_); |
245 | 0 | for (uint32_t i = 0; i < window_size_; ++i) { |
246 | 0 | chainz[i] = i; |
247 | 0 | } |
248 | | // Translate distance to special distance code. |
249 | 0 | if (distance_multiplier) { |
250 | | // Count down, so if due to small distance multiplier multiple distances |
251 | | // map to the same code, the smallest code will be used in the end. |
252 | 0 | for (int i = kNumSpecialDistances - 1; i >= 0; --i) { |
253 | 0 | special_dist_table_[SpecialDistance(i, distance_multiplier)] = i; |
254 | 0 | } |
255 | 0 | num_special_distances_ = kNumSpecialDistances; |
256 | 0 | } |
257 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<1u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<3u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<8u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) |
258 | | |
259 | | |
260 | 0 | uint32_t CountZeros(size_t pos, uint32_t prevzeros) const { |
261 | 0 | size_t end = pos + window_size_; |
262 | 0 | if (end > size_) end = size_; |
263 | 0 | if (prevzeros > 0) { |
264 | 0 | if (prevzeros >= window_mask_ && data_[end - 1] == 0 && |
265 | 0 | end == pos + window_size_) { |
266 | 0 | return prevzeros; |
267 | 0 | } else { |
268 | 0 | return prevzeros - 1; |
269 | 0 | } |
270 | 0 | } |
271 | 0 | uint32_t num = 0; |
272 | 0 | while (pos + num < end && data_[pos + num] == 0) num++; |
273 | 0 | return num; |
274 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<1u>::CountZeros(unsigned long, unsigned int) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<3u>::CountZeros(unsigned long, unsigned int) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<8u>::CountZeros(unsigned long, unsigned int) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::CountZeros(unsigned long, unsigned int) const |
275 | | |
276 | 0 | void Update(size_t pos) { |
277 | 0 | uint32_t hashval = GetHash(pos, data_) & hash_mask_; |
278 | 0 | uint32_t wpos = pos & window_mask_; |
279 | |
|
280 | 0 | val[wpos] = static_cast<int>(hashval); |
281 | 0 | if (head[hashval] != -1) chain[wpos] = head[hashval]; |
282 | 0 | head[hashval] = wpos; |
283 | |
|
284 | 0 | if (pos > 0 && data_[pos] != data_[pos - 1]) numzeros = 0; |
285 | 0 | numzeros = CountZeros(pos, numzeros); |
286 | |
|
287 | 0 | zeros[wpos] = numzeros; |
288 | 0 | if (headz[numzeros] != -1) chainz[wpos] = headz[numzeros]; |
289 | 0 | headz[numzeros] = wpos; |
290 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<1u>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<3u>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<8u>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::Update(unsigned long) |
291 | | |
292 | | void Update(size_t pos, size_t len) { |
293 | | for (size_t i = 0; i < len; i++) { |
294 | | Update(pos + i); |
295 | | } |
296 | | } |
297 | | |
298 | | template <typename CB> |
299 | 0 | void FindMatches(size_t pos, int max_dist, const CB& found_match) const { |
300 | 0 | uint32_t wpos = pos & window_mask_; |
301 | 0 | uint32_t hashval = GetHash(pos, data_) & hash_mask_; |
302 | 0 | uint32_t hashpos = chain[wpos]; |
303 | |
|
304 | 0 | int prev_dist = 0; |
305 | 0 | int end = std::min<int>(pos + max_length_, size_); |
306 | 0 | uint32_t chainlength = 0; |
307 | 0 | uint32_t best_len = 0; |
308 | 0 | for (;;) { |
309 | 0 | int dist = (hashpos <= wpos) ? (wpos - hashpos) |
310 | 0 | : (wpos - hashpos + window_mask_ + 1); |
311 | 0 | if (dist < prev_dist) break; |
312 | 0 | prev_dist = dist; |
313 | 0 | uint32_t len = 0; |
314 | 0 | if (dist > 0) { |
315 | 0 | int i = pos; |
316 | 0 | int j = pos - dist; |
317 | 0 | if (numzeros > 3) { |
318 | 0 | int r = std::min<int>(numzeros - 1, zeros[hashpos]); |
319 | 0 | if (i + r >= end) r = end - i - 1; |
320 | 0 | i += r; |
321 | 0 | j += r; |
322 | 0 | } |
323 | 0 | while (i < end && data_[i] == data_[j]) { |
324 | 0 | i++; |
325 | 0 | j++; |
326 | 0 | } |
327 | 0 | len = i - pos; |
328 | | // This can trigger even if the new length is slightly smaller than the |
329 | | // best length, because it is possible for a slightly cheaper distance |
330 | | // symbol to occur. |
331 | 0 | if (len >= min_length_ && len + 2 >= best_len) { |
332 | 0 | auto it = special_dist_table_.find(dist); |
333 | 0 | int dist_symbol = (it == special_dist_table_.end()) |
334 | 0 | ? (num_special_distances_ + dist - 1) |
335 | 0 | : it->second; |
336 | 0 | found_match(len, dist_symbol); |
337 | 0 | if (len > best_len) best_len = len; |
338 | 0 | } |
339 | 0 | } |
340 | |
|
341 | 0 | chainlength++; |
342 | 0 | if (chainlength >= maxchainlength) break; |
343 | | |
344 | 0 | if (numzeros >= 3 && len > numzeros) { |
345 | 0 | if (hashpos == chainz[hashpos]) break; |
346 | 0 | hashpos = chainz[hashpos]; |
347 | 0 | if (zeros[hashpos] != numzeros) break; |
348 | 0 | } else { |
349 | 0 | if (hashpos == chain[hashpos]) break; |
350 | 0 | hashpos = chain[hashpos]; |
351 | 0 | if (val[hashpos] != static_cast<int>(hashval)) { |
352 | | // outdated hash value |
353 | 0 | break; |
354 | 0 | } |
355 | 0 | } |
356 | 0 | } |
357 | 0 | } Unexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<1u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constUnexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<3u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constUnexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<8u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constUnexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<256u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) const |
358 | | void FindMatch(size_t pos, int max_dist, size_t* result_dist_symbol, |
359 | | size_t* result_len) const { |
360 | | *result_dist_symbol = 0; |
361 | | *result_len = 1; |
362 | | FindMatches(pos, max_dist, [&](size_t len, size_t dist_symbol) { |
363 | | if (len > *result_len || |
364 | | (len == *result_len && *result_dist_symbol > dist_symbol)) { |
365 | | *result_len = len; |
366 | | *result_dist_symbol = dist_symbol; |
367 | | } |
368 | | }); |
369 | | } |
370 | | }; |
371 | | |
372 | 0 | float LenCost(size_t len) { |
373 | 0 | uint32_t nbits, bits, tok; |
374 | 0 | HybridUintConfig(1, 0, 0).Encode(len, &tok, &nbits, &bits); |
375 | 0 | constexpr float kCostTable[] = { |
376 | 0 | 2.797667318563126, 3.213177690381199, 2.5706009246743737, |
377 | 0 | 2.408392498667534, 2.829649191872326, 3.3923087753324577, |
378 | 0 | 4.029267451554331, 4.415576699706408, 4.509357574741465, |
379 | 0 | 9.21481543803004, 10.020590190114898, 11.858671627804766, |
380 | 0 | 12.45853300490526, 11.713105831990857, 12.561996324849314, |
381 | 0 | 13.775477692278367, 13.174027068768641, |
382 | 0 | }; |
383 | 0 | size_t table_size = sizeof kCostTable / sizeof *kCostTable; |
384 | 0 | if (tok >= table_size) tok = table_size - 1; |
385 | 0 | return kCostTable[tok] + nbits; |
386 | 0 | } |
387 | | |
388 | | // TODO(veluca): this does not take into account usage or non-usage of distance |
389 | | // multipliers. |
390 | 0 | float DistCost(size_t dist) { |
391 | 0 | uint32_t nbits, bits, tok; |
392 | 0 | HybridUintConfig(7, 0, 0).Encode(dist, &tok, &nbits, &bits); |
393 | 0 | constexpr float kCostTable[] = { |
394 | 0 | 6.368282626312716, 5.680793277090298, 8.347404197105247, |
395 | 0 | 7.641619201599141, 6.914328374119438, 7.959808291537444, |
396 | 0 | 8.70023120759855, 8.71378518934703, 9.379132523982769, |
397 | 0 | 9.110472749092708, 9.159029569270908, 9.430936766731973, |
398 | 0 | 7.278284055315169, 7.8278514904267755, 10.026641158289236, |
399 | 0 | 9.976049229827066, 9.64351607048908, 9.563403863480442, |
400 | 0 | 10.171474111762747, 10.45950155077234, 9.994813912104219, |
401 | 0 | 10.322524683741156, 8.465808729388186, 8.756254166066853, |
402 | 0 | 10.160930174662234, 10.247329273413435, 10.04090403724809, |
403 | 0 | 10.129398517544082, 9.342311691539546, 9.07608009102374, |
404 | 0 | 10.104799540677513, 10.378079384990906, 10.165828974075072, |
405 | 0 | 10.337595322341553, 7.940557464567944, 10.575665823319431, |
406 | 0 | 11.023344321751955, 10.736144698831827, 11.118277044595054, |
407 | 0 | 7.468468230648442, 10.738305230932939, 10.906980780216568, |
408 | 0 | 10.163468216353817, 10.17805759656433, 11.167283670483565, |
409 | 0 | 11.147050200274544, 10.517921919244333, 10.651764778156886, |
410 | 0 | 10.17074446448919, 11.217636876224745, 11.261630721139484, |
411 | 0 | 11.403140815247259, 10.892472096873417, 11.1859607804481, |
412 | 0 | 8.017346947551262, 7.895143720278828, 11.036577113822025, |
413 | 0 | 11.170562110315794, 10.326988722591086, 10.40872184751056, |
414 | 0 | 11.213498225466386, 11.30580635516863, 10.672272515665442, |
415 | 0 | 10.768069466228063, 11.145257364153565, 11.64668307145549, |
416 | 0 | 10.593156194627339, 11.207499484844943, 10.767517766396908, |
417 | 0 | 10.826629811407042, 10.737764794499988, 10.6200448518045, |
418 | 0 | 10.191315385198092, 8.468384171390085, 11.731295299170432, |
419 | 0 | 11.824619886654398, 10.41518844301179, 10.16310536548649, |
420 | 0 | 10.539423685097576, 10.495136599328031, 10.469112847728267, |
421 | 0 | 11.72057686174922, 10.910326337834674, 11.378921834673758, |
422 | 0 | 11.847759036098536, 11.92071647623854, 10.810628276345282, |
423 | 0 | 11.008601085273893, 11.910326337834674, 11.949212023423133, |
424 | 0 | 11.298614839104337, 11.611603659010392, 10.472930394619985, |
425 | 0 | 11.835564720850282, 11.523267392285337, 12.01055816679611, |
426 | 0 | 8.413029688994023, 11.895784139536406, 11.984679534970505, |
427 | 0 | 11.220654278717394, 11.716311684833672, 10.61036646226114, |
428 | 0 | 10.89849965960364, 10.203762898863669, 10.997560826267238, |
429 | 0 | 11.484217379438984, 11.792836176993665, 12.24310468755171, |
430 | 0 | 11.464858097919262, 12.212747017409377, 11.425595666074955, |
431 | 0 | 11.572048533398757, 12.742093965163013, 11.381874288645637, |
432 | 0 | 12.191870445817015, 11.683156920035426, 11.152442115262197, |
433 | 0 | 11.90303691580457, 11.653292787169159, 11.938615382266098, |
434 | 0 | 16.970641701570223, 16.853602280380002, 17.26240782594733, |
435 | 0 | 16.644655390108507, 17.14310889757499, 16.910935455445955, |
436 | 0 | 17.505678976959697, 17.213498225466388, 2.4162310293553024, |
437 | 0 | 3.494587244462329, 3.5258600986408344, 3.4959806589517095, |
438 | 0 | 3.098390886949687, 3.343454654302911, 3.588847442290287, |
439 | 0 | 4.14614790111827, 5.152948641990529, 7.433696808092598, |
440 | 0 | 9.716311684833672, |
441 | 0 | }; |
442 | 0 | size_t table_size = sizeof kCostTable / sizeof *kCostTable; |
443 | 0 | if (tok >= table_size) tok = table_size - 1; |
444 | 0 | return kCostTable[tok] + nbits; |
445 | 0 | } |
446 | | |
447 | | |
448 | | // Fast hash map for LZ77 compression using a fixed-size ring buffer per bucket. |
449 | | // Overwrites the oldest candidates when a bucket reaches max capacity. |
450 | | // For optimal performance kBucketSize should be a power of 2 minus 1 |
451 | | template <uint16_t kBucketSize, int kHashSize> |
452 | | class LZ77HashMap { |
453 | | public: |
454 | | // Initializes the hash map mask, table size, and distance lookup table. |
455 | | explicit LZ77HashMap(const std::vector<uint32_t>& data, |
456 | | size_t distance_multiplier, uint32_t hash_bits = 14) |
457 | 0 | : data_(data), |
458 | 0 | hash_mask_((uint32_t{1} << hash_bits) - 1), |
459 | 0 | hash_table_(static_cast<size_t>(hash_mask_) + 1) { |
460 | 0 | num_special_distances_ = 0; |
461 | 0 | if (distance_multiplier) { |
462 | 0 | num_special_distances_ = kNumSpecialDistances; |
463 | 0 | special_dist_table_.assign(distance_multiplier * 8 + 8, -1); |
464 | 0 | for (int8_t i = 0; i < static_cast<int8_t>(kNumSpecialDistances); i++) { |
465 | 0 | special_dist_table_[SpecialDistance(i, distance_multiplier)] = i; |
466 | 0 | } |
467 | 0 | } |
468 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) |
469 | | |
470 | | // Searches for the longest matching sequence in the bucket for 'pos'. |
471 | | // Updates length and dist_symbol with the best match found, then inserts 'pos'. |
472 | 0 | void FindMatch(size_t pos, size_t& len, size_t& dist_symbol, size_t min_len) { |
473 | 0 | const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_; |
474 | 0 | len = 0; |
475 | 0 | dist_symbol = 0; |
476 | | |
477 | | // Scan all existing entries in the bucket |
478 | 0 | for (uint16_t i = 0; i < hash_table_[h].size; i++) { |
479 | 0 | const uint32_t candidate = hash_table_[h].data[i]; |
480 | 0 | size_t dist = pos - candidate; |
481 | 0 | size_t cur_length = MatchLength(candidate, pos); |
482 | | |
483 | | // Skip matches shorter than current best or min_length |
484 | 0 | if (cur_length < len || cur_length < min_len) { |
485 | 0 | continue; |
486 | 0 | } |
487 | | |
488 | | // Map distance to a symbol encoding (prefers special distance codes if available) |
489 | 0 | size_t cur_dist_symbol = (num_special_distances_ + dist - 1); |
490 | 0 | if (dist < special_dist_table_.size()) { |
491 | 0 | int lookup = special_dist_table_[dist]; |
492 | 0 | if (lookup >= 0) { |
493 | 0 | cur_dist_symbol = lookup; |
494 | 0 | } |
495 | 0 | } |
496 | | |
497 | | // Reject if same length but requires a worse/longer distance symbol |
498 | | // Trying to compare the cost from cost estimation does not help with the current implementation of sce |
499 | 0 | if (cur_length == len && cur_dist_symbol >= dist_symbol) { |
500 | 0 | continue; |
501 | 0 | } |
502 | | |
503 | 0 | len = cur_length; |
504 | 0 | dist_symbol = cur_dist_symbol; |
505 | 0 | } |
506 | | |
507 | | // Insert 'pos' into the bucket ring buffer |
508 | 0 | hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize); |
509 | 0 | hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos); |
510 | 0 | hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize; |
511 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) |
512 | | |
513 | | // Inserts 'pos' into its bucket without performing a match search. |
514 | 0 | void Update(size_t pos) { |
515 | 0 | const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_; |
516 | 0 | hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize); |
517 | 0 | hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos); |
518 | 0 | hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize; |
519 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::Update(unsigned long) |
520 | | |
521 | | private: |
522 | | // Fixed-capacity bucket stored as a ring buffer. |
523 | | struct HashBucket { |
524 | | std::array<uint32_t, kBucketSize> data; |
525 | | uint16_t idx = 0; // Insertion index |
526 | | uint16_t size = 0; // Current entry count (<= kBucketSize) |
527 | | }; |
528 | | |
529 | | // Measures matching prefix length between indices 'a' and 'b'. |
530 | 0 | size_t MatchLength(size_t a, size_t b) const { |
531 | 0 | JXL_DASSERT(a < b); |
532 | 0 | size_t len = 0; |
533 | 0 | while (b + len < data_.size() && data_[a + len] == data_[b + len]) { |
534 | 0 | ++len; |
535 | 0 | } |
536 | 0 | return len; |
537 | 0 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::MatchLength(unsigned long, unsigned long) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::MatchLength(unsigned long, unsigned long) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::MatchLength(unsigned long, unsigned long) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::MatchLength(unsigned long, unsigned long) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::MatchLength(unsigned long, unsigned long) const |
538 | | |
539 | | const std::vector<uint32_t>& data_; |
540 | | uint32_t hash_mask_; |
541 | | std::vector<HashBucket> hash_table_; |
542 | | std::vector<int8_t> special_dist_table_; |
543 | | size_t num_special_distances_; |
544 | | }; |
545 | | |
546 | | |
547 | | // Fast LZ77 compression on streams of tokens using cost-based matching decision. |
548 | | // Returns the compressed token streams if savings exceed the threshold. |
549 | | template<int kBucketSize = 7, int kHashSize = 3, bool kRuntimeCostComparison = false> |
550 | | std::vector<std::vector<Token>> ApplyLZ77_LZ77( |
551 | | const HistogramParams& params, size_t num_contexts, |
552 | | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77 |
553 | 0 | ) { |
554 | 0 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); |
555 | 0 | SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77); |
556 | 0 | float bit_decrease = 0; |
557 | 0 | size_t total_symbols = 0; |
558 | 0 | HybridUintConfig uint_config; |
559 | 0 | std::vector<float> sym_cost; |
560 | |
|
561 | 0 | for (size_t stream = 0; stream < tokens.size(); stream++) { |
562 | 0 | size_t distance_multiplier = |
563 | 0 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; |
564 | 0 | const auto& in = tokens[stream]; |
565 | 0 | auto& out = tokens_lz77[stream]; |
566 | 0 | total_symbols += in.size(); |
567 | 0 | std::vector<uint32_t> data; |
568 | 0 | data.resize(in.size()); |
569 | | |
570 | | // Cumulative sum of bit costs for fast range estimation |
571 | 0 | sym_cost.resize(in.size() + 1); |
572 | 0 | for (size_t pos = 0; pos < in.size(); pos++) { |
573 | 0 | uint32_t tok, nbits, unused_bits; |
574 | 0 | uint_config.Encode(in[pos].value, &tok, &nbits, &unused_bits); |
575 | 0 | sym_cost[pos + 1] = sce.Bits(in[pos].context, tok) + nbits + sym_cost[pos]; |
576 | 0 | data[pos] = in[pos].value; |
577 | 0 | } |
578 | |
|
579 | 0 | out.reserve(in.size()); |
580 | 0 | size_t min_length = lz77.min_length; |
581 | 0 | JXL_DASSERT(min_length >= 3); |
582 | | |
583 | 0 | LZ77HashMap<kBucketSize, kHashSize> hash_map = |
584 | 0 | LZ77HashMap<kBucketSize, kHashSize>(data, distance_multiplier); |
585 | |
|
586 | 0 | for (size_t pos = 0; pos < in.size(); pos++) { |
587 | 0 | out.push_back(in[pos]); |
588 | | |
589 | | // Guard against reading past input bounds |
590 | 0 | if (pos + kHashSize >= in.size()) { |
591 | 0 | continue; |
592 | 0 | } |
593 | | |
594 | 0 | size_t len; |
595 | 0 | size_t dist_symbol; |
596 | | |
597 | | // Find longest sequence match in current bucket |
598 | 0 | hash_map.FindMatch(pos, len, dist_symbol, min_length); |
599 | 0 | if (len < min_length) continue; |
600 | | |
601 | | // Bit cost comparison: raw tokens vs LZ77 pair |
602 | 0 | float cost = sym_cost[pos + len] - sym_cost[pos]; |
603 | 0 | size_t lz77_len = len - lz77.min_length; |
604 | 0 | float lz77_cost = LenCost(lz77_len) + DistCost(dist_symbol); |
605 | |
|
606 | 0 | if (kRuntimeCostComparison && lz77_cost > cost) { |
607 | 0 | for (size_t offset = 1; offset < len; offset++) { |
608 | 0 | out.push_back(in[pos+offset]); |
609 | 0 | hash_map.Update(pos + offset); |
610 | 0 | } |
611 | 0 | pos += len - 1; |
612 | 0 | continue; |
613 | 0 | } |
614 | | |
615 | | |
616 | 0 | bit_decrease += cost - lz77_cost - sce.AddSymbolCost(out.back().context); |
617 | | |
618 | | // Emit LZ77 length and distance tokens |
619 | 0 | out.back().value = len - min_length; |
620 | 0 | out.back().is_lz77_length = true; |
621 | 0 | out.emplace_back( |
622 | 0 | static_cast<uint32_t>(lz77.nonserialized_distance_context), |
623 | 0 | static_cast<uint32_t>(dist_symbol)); |
624 | | |
625 | | // Update hash map for skipped positions inside the match |
626 | 0 | for (size_t offset = 1; offset < len; offset++) { |
627 | 0 | hash_map.Update(pos + offset); |
628 | 0 | } |
629 | 0 | pos += len - 1; |
630 | 0 | } |
631 | 0 | } |
632 | | |
633 | | // Return LZ77 streams only if net bit savings justify overhead |
634 | 0 | if (bit_decrease > total_symbols * 0.2 + 16) { |
635 | 0 | return tokens_lz77; |
636 | 0 | } |
637 | 0 | return {}; |
638 | 0 | } Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<1, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<3, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<7, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<15, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<31, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<1, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<3, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<7, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<15, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<31, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) |
639 | | |
640 | | template <uint32_t kMaxChainLength> |
641 | | std::vector<std::vector<Token>> ApplyLZ77_Optimal( |
642 | | const HistogramParams& params, size_t num_contexts, |
643 | 0 | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { |
644 | 0 | std::vector<std::vector<Token>> tokens_for_cost_estimate = |
645 | 0 | ApplyLZ77_LZ77(params, num_contexts, tokens, lz77); |
646 | | // If greedy-LZ77 does not give better compression than no-lz77, no reason to |
647 | | // run the optimal matching. |
648 | 0 | if (tokens_for_cost_estimate.empty()) return {}; |
649 | 0 | SymbolCostEstimator sce(num_contexts + 1, params.force_huffman, |
650 | 0 | tokens_for_cost_estimate, lz77); |
651 | 0 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); |
652 | 0 | HybridUintConfig uint_config; |
653 | 0 | std::vector<float> sym_cost; |
654 | 0 | std::vector<uint32_t> dist_symbols; |
655 | 0 | for (size_t stream = 0; stream < tokens.size(); stream++) { |
656 | 0 | size_t distance_multiplier = |
657 | 0 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; |
658 | 0 | const auto& in = tokens[stream]; |
659 | 0 | auto& out = tokens_lz77[stream]; |
660 | | // Cumulative sum of bit costs. |
661 | 0 | sym_cost.resize(in.size() + 1); |
662 | 0 | for (size_t i = 0; i < in.size(); i++) { |
663 | 0 | uint32_t tok, nbits, unused_bits; |
664 | 0 | uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits); |
665 | 0 | sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i]; |
666 | 0 | } |
667 | |
|
668 | 0 | out.reserve(in.size()); |
669 | 0 | size_t max_distance = in.size(); |
670 | 0 | size_t min_length = lz77.min_length; |
671 | 0 | JXL_DASSERT(min_length >= 3); |
672 | 0 | size_t max_length = in.size(); |
673 | | |
674 | | // Use next power of two as window size. |
675 | 0 | size_t window_size = 1; |
676 | 0 | while (window_size < max_distance && window_size < kWindowSize) { |
677 | 0 | window_size <<= 1; |
678 | 0 | } |
679 | |
|
680 | 0 | HashChain<kMaxChainLength> chain(in.data(), in.size(), window_size, min_length, max_length, |
681 | 0 | distance_multiplier); |
682 | |
|
683 | 0 | struct MatchInfo { |
684 | 0 | uint32_t len; |
685 | 0 | uint32_t dist_symbol; |
686 | 0 | uint32_t ctx; |
687 | 0 | float total_cost = std::numeric_limits<float>::max(); |
688 | 0 | }; |
689 | | // Total cost to encode the first N symbols. |
690 | 0 | std::vector<MatchInfo> prefix_costs(in.size() + 1); |
691 | 0 | prefix_costs[0].total_cost = 0; |
692 | |
|
693 | 0 | size_t rle_length = 0; |
694 | 0 | size_t skip_lz77 = 0; |
695 | 0 | for (size_t i = 0; i < in.size(); i++) { |
696 | 0 | chain.Update(i); |
697 | 0 | float lit_cost = |
698 | 0 | prefix_costs[i].total_cost + sym_cost[i + 1] - sym_cost[i]; |
699 | 0 | if (prefix_costs[i + 1].total_cost > lit_cost) { |
700 | 0 | prefix_costs[i + 1].dist_symbol = 0; |
701 | 0 | prefix_costs[i + 1].len = 1; |
702 | 0 | prefix_costs[i + 1].ctx = in[i].context; |
703 | 0 | prefix_costs[i + 1].total_cost = lit_cost; |
704 | 0 | } |
705 | 0 | if (skip_lz77 > 0) { |
706 | 0 | skip_lz77--; |
707 | 0 | continue; |
708 | 0 | } |
709 | 0 | dist_symbols.clear(); |
710 | 0 | chain.FindMatches(i, max_distance, |
711 | 0 | [&dist_symbols](size_t len, size_t dist_symbol) { |
712 | 0 | if (dist_symbols.size() <= len) { |
713 | 0 | dist_symbols.resize(len + 1, dist_symbol); |
714 | 0 | } |
715 | 0 | if (dist_symbol < dist_symbols[len]) { |
716 | 0 | dist_symbols[len] = dist_symbol; |
717 | 0 | } |
718 | 0 | }); Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constUnexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constUnexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constUnexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) const |
719 | 0 | if (dist_symbols.size() <= min_length) continue; |
720 | 0 | { |
721 | 0 | size_t best_cost = dist_symbols.back(); |
722 | 0 | for (size_t j = dist_symbols.size() - 1; j >= min_length; j--) { |
723 | 0 | if (dist_symbols[j] < best_cost) { |
724 | 0 | best_cost = dist_symbols[j]; |
725 | 0 | } |
726 | 0 | dist_symbols[j] = best_cost; |
727 | 0 | } |
728 | 0 | } |
729 | 0 | for (size_t j = min_length; j < dist_symbols.size(); j++) { |
730 | | // Cost model that uses results from lazy LZ77. |
731 | 0 | float lz77_cost = sce.LenCost(in[i].context, j - min_length, lz77) + |
732 | 0 | sce.DistCost(dist_symbols[j], lz77); |
733 | 0 | float cost = prefix_costs[i].total_cost + lz77_cost; |
734 | 0 | if (prefix_costs[i + j].total_cost > cost) { |
735 | 0 | prefix_costs[i + j].len = j; |
736 | 0 | prefix_costs[i + j].dist_symbol = dist_symbols[j] + 1; |
737 | 0 | prefix_costs[i + j].ctx = in[i].context; |
738 | 0 | prefix_costs[i + j].total_cost = cost; |
739 | 0 | } |
740 | 0 | } |
741 | | // We are in a RLE sequence: skip all the symbols except the first 8 and |
742 | | // the last 8. This avoid quadratic costs for sequences with long runs of |
743 | | // the same symbol. |
744 | 0 | if ((dist_symbols.back() == 0 && distance_multiplier == 0) || |
745 | 0 | (dist_symbols.back() == 1 && distance_multiplier != 0)) { |
746 | 0 | rle_length++; |
747 | 0 | } else { |
748 | 0 | rle_length = 0; |
749 | 0 | } |
750 | 0 | if (rle_length >= 8 && dist_symbols.size() > 9) { |
751 | 0 | skip_lz77 = dist_symbols.size() - 10; |
752 | 0 | rle_length = 0; |
753 | 0 | } |
754 | 0 | } |
755 | 0 | size_t pos = in.size(); |
756 | 0 | while (pos > 0) { |
757 | 0 | bool is_lz77_length = prefix_costs[pos].dist_symbol != 0; |
758 | 0 | if (is_lz77_length) { |
759 | 0 | size_t dist_symbol = prefix_costs[pos].dist_symbol - 1; |
760 | 0 | out.emplace_back( |
761 | 0 | static_cast<uint32_t>(lz77.nonserialized_distance_context), |
762 | 0 | static_cast<uint32_t>(dist_symbol)); |
763 | 0 | } |
764 | 0 | uint32_t val = |
765 | 0 | is_lz77_length |
766 | 0 | ? (prefix_costs[pos].len - static_cast<uint32_t>(min_length)) |
767 | 0 | : in[pos - 1].value; |
768 | 0 | out.emplace_back(prefix_costs[pos].ctx, val); |
769 | 0 | out.back().is_lz77_length = is_lz77_length; |
770 | 0 | pos -= prefix_costs[pos].len; |
771 | 0 | } |
772 | 0 | if (!out.empty()) std::reverse(out.begin(), out.end()); |
773 | 0 | } |
774 | 0 | return tokens_lz77; |
775 | 0 | } Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) |
776 | | |
777 | | } // namespace |
778 | | |
779 | | std::vector<std::vector<Token>> ApplyLZ77( |
780 | | const HistogramParams& params, size_t num_contexts, |
781 | 1.70k | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { |
782 | 1.70k | switch (params.lz77_method) { |
783 | 852 | case HistogramParams::LZ77Method::kRLE: |
784 | 852 | return ApplyLZ77_RLE(params, num_contexts, tokens, lz77); |
785 | 0 | case HistogramParams::LZ77Method::kLZ77b1w3f: |
786 | 0 | return ApplyLZ77_LZ77<1, 3, false>(params, num_contexts, tokens, lz77); |
787 | 0 | case HistogramParams::LZ77Method::kLZ77b3w3f: |
788 | 0 | return ApplyLZ77_LZ77<3, 3, false>(params, num_contexts, tokens, lz77); |
789 | 0 | case HistogramParams::LZ77Method::kLZ77b7w3f: |
790 | 0 | return ApplyLZ77_LZ77<7, 3, false>(params, num_contexts, tokens, lz77); |
791 | 0 | case HistogramParams::LZ77Method::kLZ77b15w3f: |
792 | 0 | return ApplyLZ77_LZ77<15, 3, false>(params, num_contexts, tokens, lz77); |
793 | 0 | case HistogramParams::LZ77Method::kLZ77b31w3f: |
794 | 0 | return ApplyLZ77_LZ77<31, 3, false>(params, num_contexts, tokens, lz77); |
795 | 0 | case HistogramParams::LZ77Method::kLZ77b1w3t: |
796 | 0 | return ApplyLZ77_LZ77<1, 3, true>(params, num_contexts, tokens, lz77); |
797 | 0 | case HistogramParams::LZ77Method::kLZ77b3w3t: |
798 | 0 | return ApplyLZ77_LZ77<3, 3, true>(params, num_contexts, tokens, lz77); |
799 | 0 | case HistogramParams::LZ77Method::kLZ77b7w3t: |
800 | 0 | return ApplyLZ77_LZ77<7, 3, true>(params, num_contexts, tokens, lz77); |
801 | 0 | case HistogramParams::LZ77Method::kLZ77b15w3t: |
802 | 0 | return ApplyLZ77_LZ77<15, 3, true>(params, num_contexts, tokens, lz77); |
803 | 0 | case HistogramParams::LZ77Method::kLZ77b31w3t: |
804 | 0 | return ApplyLZ77_LZ77<31, 3, true>(params, num_contexts, tokens, lz77); |
805 | 0 | case HistogramParams::LZ77Method::kOptc1: |
806 | 0 | return ApplyLZ77_Optimal<1>(params, num_contexts, tokens, lz77); |
807 | 0 | case HistogramParams::LZ77Method::kOptc3: |
808 | 0 | return ApplyLZ77_Optimal<3>(params, num_contexts, tokens, lz77); |
809 | 0 | case HistogramParams::LZ77Method::kOptc8: |
810 | 0 | return ApplyLZ77_Optimal<8>(params, num_contexts, tokens, lz77); |
811 | 0 | case HistogramParams::LZ77Method::kOptc256: |
812 | 0 | return ApplyLZ77_Optimal<256>(params, num_contexts, tokens, lz77); |
813 | 852 | default: |
814 | 852 | return {}; |
815 | 1.70k | } |
816 | 1.70k | } |
817 | | |
818 | | } // namespace jxl |