Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
29.8k
                      const LZ77Params& lz77) {
33
29.8k
    std::vector<Histogram> builder(num_contexts);
34
    // Build histograms for estimating lz77 savings.
35
29.8k
    HybridUintConfig uint_config;
36
46.5k
    for (const auto& stream : tokens) {
37
36.3M
      for (const auto& token : stream) {
38
36.3M
        uint32_t tok, nbits, bits;
39
36.3M
        (token.is_lz77_length ? lz77.length_uint_config : uint_config)
40
36.3M
            .Encode(token.value, &tok, &nbits, &bits);
41
36.3M
        tok += token.is_lz77_length ? lz77.min_symbol : 0;
42
36.3M
        JXL_DASSERT(token.context < num_contexts);
43
36.3M
        builder[token.context].Add(tok);
44
36.3M
      }
45
46.5k
    }
46
29.8k
    max_alphabet_size_ = 0;
47
149k
    for (size_t i = 0; i < num_contexts; i++) {
48
119k
      max_alphabet_size_ =
49
119k
          std::max(max_alphabet_size_, builder[i].counts.size());
50
119k
    }
51
29.8k
    bits_.resize(num_contexts * max_alphabet_size_);
52
    // TODO(veluca): SIMD?
53
29.8k
    add_symbol_cost_.resize(num_contexts);
54
149k
    for (size_t i = 0; i < num_contexts; i++) {
55
119k
      float inv_total = 1.0f / (builder[i].total_count + 1e-8f);
56
119k
      float total_cost = 0;
57
3.71M
      for (size_t j = 0; j < builder[i].counts.size(); j++) {
58
3.59M
        size_t cnt = builder[i].counts[j];
59
3.59M
        float cost = 0;
60
3.59M
        if (cnt != 0 && cnt != builder[i].total_count) {
61
1.15M
          cost = -FastLog2f(cnt * inv_total);
62
1.15M
          if (force_huffman) cost = std::ceil(cost);
63
2.44M
        } else if (cnt == 0) {
64
2.43M
          cost = ANS_LOG_TAB_SIZE;  // Highest possible cost.
65
2.43M
        }
66
3.59M
        bits_[i * max_alphabet_size_ + j] = cost;
67
3.59M
        total_cost += cost * builder[i].counts[j];
68
3.59M
      }
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
119k
      add_symbol_cost_[i] = std::max(0.0f, 6.0f - total_cost * inv_total);
73
119k
    }
74
29.8k
  }
75
38.0M
  float Bits(size_t ctx, size_t sym) const {
76
38.0M
    return bits_[ctx * max_alphabet_size_ + sym];
77
38.0M
  }
78
848k
  float LenCost(size_t ctx, size_t len, const LZ77Params& lz77) const {
79
848k
    uint32_t nbits, bits, tok;
80
848k
    lz77.length_uint_config.Encode(len, &tok, &nbits, &bits);
81
848k
    tok += lz77.min_symbol;
82
848k
    return nbits + Bits(ctx, tok);
83
848k
  }
84
848k
  float DistCost(size_t len, const LZ77Params& lz77) const {
85
848k
    uint32_t nbits, bits, tok;
86
848k
    HybridUintConfig().Encode(len, &tok, &nbits, &bits);
87
848k
    return nbits + Bits(lz77.nonserialized_distance_context, tok);
88
848k
  }
89
12.3k
  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
29.3k
    const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) {
100
29.3k
  std::vector<std::vector<Token>> tokens_lz77(tokens.size());
101
  // TODO(veluca): tune heuristics here.
102
29.3k
  SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77);
103
29.3k
  float bit_decrease = 0;
104
29.3k
  size_t total_symbols = 0;
105
29.3k
  std::vector<float> sym_cost;
106
29.3k
  HybridUintConfig uint_config;
107
75.3k
  for (size_t stream = 0; stream < tokens.size(); stream++) {
108
45.9k
    size_t distance_multiplier =
109
45.9k
        params.image_widths.size() > stream ? params.image_widths[stream] : 0;
110
45.9k
    const auto& in = tokens[stream];
111
45.9k
    auto& out = tokens_lz77[stream];
112
45.9k
    total_symbols += in.size();
113
    // Cumulative sum of bit costs.
114
45.9k
    sym_cost.resize(in.size() + 1);
115
36.1M
    for (size_t i = 0; i < in.size(); i++) {
116
36.1M
      uint32_t tok, nbits, unused_bits;
117
36.1M
      uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits);
118
36.1M
      sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i];
119
36.1M
    }
120
45.9k
    out.reserve(in.size());
121
18.7M
    for (size_t i = 0; i < in.size(); i++) {
122
18.6M
      size_t num_to_copy = 0;
123
18.6M
      size_t distance_symbol = 0;  // 1 for RLE.
124
18.6M
      if (distance_multiplier != 0) {
125
7.75M
        distance_symbol = 1;  // Special distance 1 if enabled.
126
7.75M
        JXL_DASSERT(kSpecialDistances[1][0] == 1);
127
7.75M
        JXL_DASSERT(kSpecialDistances[1][1] == 0);
128
7.75M
      }
129
18.6M
      if (i > 0) {
130
38.3M
        for (; i + num_to_copy < in.size(); num_to_copy++) {
131
38.3M
          if (in[i + num_to_copy].value != in[i - 1].value) {
132
18.6M
            break;
133
18.6M
          }
134
38.3M
        }
135
18.6M
      }
136
18.6M
      if (num_to_copy == 0) {
137
16.3M
        out.push_back(in[i]);
138
16.3M
        continue;
139
16.3M
      }
140
2.29M
      float cost = sym_cost[i + num_to_copy] - sym_cost[i];
141
      // This subtraction might overflow, but that's OK.
142
2.29M
      size_t lz77_len = num_to_copy - lz77.min_length;
143
2.29M
      float lz77_cost = num_to_copy >= lz77.min_length
144
2.29M
                            ? CeilLog2Nonzero(lz77_len + 1) + 1
145
2.29M
                            : 0;
146
2.29M
      if (num_to_copy < lz77.min_length || cost <= lz77_cost) {
147
11.6M
        for (size_t j = 0; j < num_to_copy; j++) {
148
9.66M
          out.push_back(in[i + j]);
149
9.66M
        }
150
2.02M
        i += num_to_copy - 1;
151
2.02M
        continue;
152
2.02M
      }
153
      // Output the LZ77 length
154
270k
      out.emplace_back(in[i].context, static_cast<uint32_t>(lz77_len));
155
270k
      out.back().is_lz77_length = true;
156
270k
      i += num_to_copy - 1;
157
270k
      bit_decrease += cost - lz77_cost;
158
      // Output the LZ77 copy distance.
159
270k
      out.emplace_back(
160
270k
          static_cast<uint32_t>(lz77.nonserialized_distance_context),
161
270k
          static_cast<uint32_t>(distance_symbol));
162
270k
    }
163
45.9k
  }
164
165
29.3k
  if (bit_decrease > total_symbols * 0.2 + 16) {
166
2.88k
    return tokens_lz77;
167
2.88k
  }
168
26.4k
  return {};
169
29.3k
}
170
171
172
// Computes a Murmur-style mix hash over HashSize elements.
173
template <int kHashSize=3>
174
337k
uint32_t GetHash(size_t pos, const std::vector<uint32_t>& data_) {
175
337k
  uint32_t h = 0;
176
337k
  if (pos + kHashSize <= data_.size()) {
177
1.34M
    for (size_t i = 0; i < kHashSize; i++) {
178
1.00M
      h ^= data_[pos+i] + 0x9e3779b9 + (h << 6) + (h >> 2);
179
1.00M
    }
180
336k
    h ^= h >> 16;
181
336k
    h *= 0x85ebca6bu;
182
336k
    h ^= h >> 13;
183
336k
    h *= 0xc2b2ae35u;
184
336k
    h ^= h >> 16;
185
186
336k
  } else {
187
1.15k
    return 0;
188
1.15k
  }
189
336k
  return h;
190
337k
}
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
181
      : size_(size),
226
181
        window_size_(window_size),
227
181
        window_mask_(window_size - 1),
228
181
        min_length_(min_length),
229
181
        max_length_(max_length) {
230
181
    data_.resize(size);
231
92.7k
    for (size_t i = 0; i < size; i++) {
232
92.5k
      data_[i] = data[i].value;
233
92.5k
    }
234
235
181
    head.resize(hash_num_values_, -1);
236
181
    val.resize(window_size_, -1);
237
181
    chain.resize(window_size_);
238
149k
    for (uint32_t i = 0; i < window_size_; ++i) {
239
149k
      chain[i] = i;  // same value as index indicates uninitialized
240
149k
    }
241
242
181
    zeros.resize(window_size_);
243
181
    headz.resize(window_size_ + 1, -1);
244
181
    chainz.resize(window_size_);
245
149k
    for (uint32_t i = 0; i < window_size_; ++i) {
246
149k
      chainz[i] = i;
247
149k
    }
248
    // Translate distance to special distance code.
249
181
    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
181
  }
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)
enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
225
181
      : size_(size),
226
181
        window_size_(window_size),
227
181
        window_mask_(window_size - 1),
228
181
        min_length_(min_length),
229
181
        max_length_(max_length) {
230
181
    data_.resize(size);
231
92.7k
    for (size_t i = 0; i < size; i++) {
232
92.5k
      data_[i] = data[i].value;
233
92.5k
    }
234
235
181
    head.resize(hash_num_values_, -1);
236
181
    val.resize(window_size_, -1);
237
181
    chain.resize(window_size_);
238
149k
    for (uint32_t i = 0; i < window_size_; ++i) {
239
149k
      chain[i] = i;  // same value as index indicates uninitialized
240
149k
    }
241
242
181
    zeros.resize(window_size_);
243
181
    headz.resize(window_size_ + 1, -1);
244
181
    chainz.resize(window_size_);
245
149k
    for (uint32_t i = 0; i < window_size_; ++i) {
246
149k
      chainz[i] = i;
247
149k
    }
248
    // Translate distance to special distance code.
249
181
    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
181
  }
258
259
260
92.5k
  uint32_t CountZeros(size_t pos, uint32_t prevzeros) const {
261
92.5k
    size_t end = pos + window_size_;
262
92.5k
    if (end > size_) end = size_;
263
92.5k
    if (prevzeros > 0) {
264
35.6k
      if (prevzeros >= window_mask_ && data_[end - 1] == 0 &&
265
0
          end == pos + window_size_) {
266
0
        return prevzeros;
267
35.6k
      } else {
268
35.6k
        return prevzeros - 1;
269
35.6k
      }
270
35.6k
    }
271
56.8k
    uint32_t num = 0;
272
103k
    while (pos + num < end && data_[pos + num] == 0) num++;
273
56.8k
    return num;
274
92.5k
  }
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
enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::CountZeros(unsigned long, unsigned int) const
Line
Count
Source
260
92.5k
  uint32_t CountZeros(size_t pos, uint32_t prevzeros) const {
261
92.5k
    size_t end = pos + window_size_;
262
92.5k
    if (end > size_) end = size_;
263
92.5k
    if (prevzeros > 0) {
264
35.6k
      if (prevzeros >= window_mask_ && data_[end - 1] == 0 &&
265
0
          end == pos + window_size_) {
266
0
        return prevzeros;
267
35.6k
      } else {
268
35.6k
        return prevzeros - 1;
269
35.6k
      }
270
35.6k
    }
271
56.8k
    uint32_t num = 0;
272
103k
    while (pos + num < end && data_[pos + num] == 0) num++;
273
56.8k
    return num;
274
92.5k
  }
275
276
92.5k
  void Update(size_t pos) {
277
92.5k
    uint32_t hashval = GetHash(pos, data_) & hash_mask_;
278
92.5k
    uint32_t wpos = pos & window_mask_;
279
280
92.5k
    val[wpos] = static_cast<int>(hashval);
281
92.5k
    if (head[hashval] != -1) chain[wpos] = head[hashval];
282
92.5k
    head[hashval] = wpos;
283
284
92.5k
    if (pos > 0 && data_[pos] != data_[pos - 1]) numzeros = 0;
285
92.5k
    numzeros = CountZeros(pos, numzeros);
286
287
92.5k
    zeros[wpos] = numzeros;
288
92.5k
    if (headz[numzeros] != -1) chainz[wpos] = headz[numzeros];
289
92.5k
    headz[numzeros] = wpos;
290
92.5k
  }
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)
enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::Update(unsigned long)
Line
Count
Source
276
92.5k
  void Update(size_t pos) {
277
92.5k
    uint32_t hashval = GetHash(pos, data_) & hash_mask_;
278
92.5k
    uint32_t wpos = pos & window_mask_;
279
280
92.5k
    val[wpos] = static_cast<int>(hashval);
281
92.5k
    if (head[hashval] != -1) chain[wpos] = head[hashval];
282
92.5k
    head[hashval] = wpos;
283
284
92.5k
    if (pos > 0 && data_[pos] != data_[pos - 1]) numzeros = 0;
285
92.5k
    numzeros = CountZeros(pos, numzeros);
286
287
92.5k
    zeros[wpos] = numzeros;
288
92.5k
    if (headz[numzeros] != -1) chainz[wpos] = headz[numzeros];
289
92.5k
    headz[numzeros] = wpos;
290
92.5k
  }
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
78.7k
  void FindMatches(size_t pos, int max_dist, const CB& found_match) const {
300
78.7k
    uint32_t wpos = pos & window_mask_;
301
78.7k
    uint32_t hashval = GetHash(pos, data_) & hash_mask_;
302
78.7k
    uint32_t hashpos = chain[wpos];
303
304
78.7k
    int prev_dist = 0;
305
78.7k
    int end = std::min<int>(pos + max_length_, size_);
306
78.7k
    uint32_t chainlength = 0;
307
78.7k
    uint32_t best_len = 0;
308
2.09M
    for (;;) {
309
2.09M
      int dist = (hashpos <= wpos) ? (wpos - hashpos)
310
2.09M
                                   : (wpos - hashpos + window_mask_ + 1);
311
2.09M
      if (dist < prev_dist) break;
312
2.09M
      prev_dist = dist;
313
2.09M
      uint32_t len = 0;
314
2.09M
      if (dist > 0) {
315
2.06M
        int i = pos;
316
2.06M
        int j = pos - dist;
317
2.06M
        if (numzeros > 3) {
318
1.36M
          int r = std::min<int>(numzeros - 1, zeros[hashpos]);
319
1.36M
          if (i + r >= end) r = end - i - 1;
320
1.36M
          i += r;
321
1.36M
          j += r;
322
1.36M
        }
323
8.54M
        while (i < end && data_[i] == data_[j]) {
324
6.47M
          i++;
325
6.47M
          j++;
326
6.47M
        }
327
2.06M
        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
2.06M
        if (len >= min_length_ && len + 2 >= best_len) {
332
1.21M
          auto it = special_dist_table_.find(dist);
333
1.21M
          int dist_symbol = (it == special_dist_table_.end())
334
1.21M
                                ? (num_special_distances_ + dist - 1)
335
1.21M
                                : it->second;
336
1.21M
          found_match(len, dist_symbol);
337
1.21M
          if (len > best_len) best_len = len;
338
1.21M
        }
339
2.06M
      }
340
341
2.09M
      chainlength++;
342
2.09M
      if (chainlength >= maxchainlength) break;
343
344
2.09M
      if (numzeros >= 3 && len > numzeros) {
345
29.3k
        if (hashpos == chainz[hashpos]) break;
346
28.0k
        hashpos = chainz[hashpos];
347
28.0k
        if (zeros[hashpos] != numzeros) break;
348
2.06M
      } else {
349
2.06M
        if (hashpos == chain[hashpos]) break;
350
1.98M
        hashpos = chain[hashpos];
351
1.98M
        if (val[hashpos] != static_cast<int>(hashval)) {
352
          // outdated hash value
353
0
          break;
354
0
        }
355
1.98M
      }
356
2.09M
    }
357
78.7k
  }
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&) const
Unexecuted 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&) const
Unexecuted 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&) const
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
Line
Count
Source
299
78.7k
  void FindMatches(size_t pos, int max_dist, const CB& found_match) const {
300
78.7k
    uint32_t wpos = pos & window_mask_;
301
78.7k
    uint32_t hashval = GetHash(pos, data_) & hash_mask_;
302
78.7k
    uint32_t hashpos = chain[wpos];
303
304
78.7k
    int prev_dist = 0;
305
78.7k
    int end = std::min<int>(pos + max_length_, size_);
306
78.7k
    uint32_t chainlength = 0;
307
78.7k
    uint32_t best_len = 0;
308
2.09M
    for (;;) {
309
2.09M
      int dist = (hashpos <= wpos) ? (wpos - hashpos)
310
2.09M
                                   : (wpos - hashpos + window_mask_ + 1);
311
2.09M
      if (dist < prev_dist) break;
312
2.09M
      prev_dist = dist;
313
2.09M
      uint32_t len = 0;
314
2.09M
      if (dist > 0) {
315
2.06M
        int i = pos;
316
2.06M
        int j = pos - dist;
317
2.06M
        if (numzeros > 3) {
318
1.36M
          int r = std::min<int>(numzeros - 1, zeros[hashpos]);
319
1.36M
          if (i + r >= end) r = end - i - 1;
320
1.36M
          i += r;
321
1.36M
          j += r;
322
1.36M
        }
323
8.54M
        while (i < end && data_[i] == data_[j]) {
324
6.47M
          i++;
325
6.47M
          j++;
326
6.47M
        }
327
2.06M
        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
2.06M
        if (len >= min_length_ && len + 2 >= best_len) {
332
1.21M
          auto it = special_dist_table_.find(dist);
333
1.21M
          int dist_symbol = (it == special_dist_table_.end())
334
1.21M
                                ? (num_special_distances_ + dist - 1)
335
1.21M
                                : it->second;
336
1.21M
          found_match(len, dist_symbol);
337
1.21M
          if (len > best_len) best_len = len;
338
1.21M
        }
339
2.06M
      }
340
341
2.09M
      chainlength++;
342
2.09M
      if (chainlength >= maxchainlength) break;
343
344
2.09M
      if (numzeros >= 3 && len > numzeros) {
345
29.3k
        if (hashpos == chainz[hashpos]) break;
346
28.0k
        hashpos = chainz[hashpos];
347
28.0k
        if (zeros[hashpos] != numzeros) break;
348
2.06M
      } else {
349
2.06M
        if (hashpos == chain[hashpos]) break;
350
1.98M
        hashpos = chain[hashpos];
351
1.98M
        if (val[hashpos] != static_cast<int>(hashval)) {
352
          // outdated hash value
353
0
          break;
354
0
        }
355
1.98M
      }
356
2.09M
    }
357
78.7k
  }
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
12.3k
float LenCost(size_t len) {
373
12.3k
  uint32_t nbits, bits, tok;
374
12.3k
  HybridUintConfig(1, 0, 0).Encode(len, &tok, &nbits, &bits);
375
12.3k
  constexpr float kCostTable[] = {
376
12.3k
      2.797667318563126,  3.213177690381199,  2.5706009246743737,
377
12.3k
      2.408392498667534,  2.829649191872326,  3.3923087753324577,
378
12.3k
      4.029267451554331,  4.415576699706408,  4.509357574741465,
379
12.3k
      9.21481543803004,   10.020590190114898, 11.858671627804766,
380
12.3k
      12.45853300490526,  11.713105831990857, 12.561996324849314,
381
12.3k
      13.775477692278367, 13.174027068768641,
382
12.3k
  };
383
12.3k
  size_t table_size = sizeof kCostTable / sizeof *kCostTable;
384
12.3k
  if (tok >= table_size) tok = table_size - 1;
385
12.3k
  return kCostTable[tok] + nbits;
386
12.3k
}
387
388
// TODO(veluca): this does not take into account usage or non-usage of distance
389
// multipliers.
390
12.3k
float DistCost(size_t dist) {
391
12.3k
  uint32_t nbits, bits, tok;
392
12.3k
  HybridUintConfig(7, 0, 0).Encode(dist, &tok, &nbits, &bits);
393
12.3k
  constexpr float kCostTable[] = {
394
12.3k
      6.368282626312716,  5.680793277090298,  8.347404197105247,
395
12.3k
      7.641619201599141,  6.914328374119438,  7.959808291537444,
396
12.3k
      8.70023120759855,   8.71378518934703,   9.379132523982769,
397
12.3k
      9.110472749092708,  9.159029569270908,  9.430936766731973,
398
12.3k
      7.278284055315169,  7.8278514904267755, 10.026641158289236,
399
12.3k
      9.976049229827066,  9.64351607048908,   9.563403863480442,
400
12.3k
      10.171474111762747, 10.45950155077234,  9.994813912104219,
401
12.3k
      10.322524683741156, 8.465808729388186,  8.756254166066853,
402
12.3k
      10.160930174662234, 10.247329273413435, 10.04090403724809,
403
12.3k
      10.129398517544082, 9.342311691539546,  9.07608009102374,
404
12.3k
      10.104799540677513, 10.378079384990906, 10.165828974075072,
405
12.3k
      10.337595322341553, 7.940557464567944,  10.575665823319431,
406
12.3k
      11.023344321751955, 10.736144698831827, 11.118277044595054,
407
12.3k
      7.468468230648442,  10.738305230932939, 10.906980780216568,
408
12.3k
      10.163468216353817, 10.17805759656433,  11.167283670483565,
409
12.3k
      11.147050200274544, 10.517921919244333, 10.651764778156886,
410
12.3k
      10.17074446448919,  11.217636876224745, 11.261630721139484,
411
12.3k
      11.403140815247259, 10.892472096873417, 11.1859607804481,
412
12.3k
      8.017346947551262,  7.895143720278828,  11.036577113822025,
413
12.3k
      11.170562110315794, 10.326988722591086, 10.40872184751056,
414
12.3k
      11.213498225466386, 11.30580635516863,  10.672272515665442,
415
12.3k
      10.768069466228063, 11.145257364153565, 11.64668307145549,
416
12.3k
      10.593156194627339, 11.207499484844943, 10.767517766396908,
417
12.3k
      10.826629811407042, 10.737764794499988, 10.6200448518045,
418
12.3k
      10.191315385198092, 8.468384171390085,  11.731295299170432,
419
12.3k
      11.824619886654398, 10.41518844301179,  10.16310536548649,
420
12.3k
      10.539423685097576, 10.495136599328031, 10.469112847728267,
421
12.3k
      11.72057686174922,  10.910326337834674, 11.378921834673758,
422
12.3k
      11.847759036098536, 11.92071647623854,  10.810628276345282,
423
12.3k
      11.008601085273893, 11.910326337834674, 11.949212023423133,
424
12.3k
      11.298614839104337, 11.611603659010392, 10.472930394619985,
425
12.3k
      11.835564720850282, 11.523267392285337, 12.01055816679611,
426
12.3k
      8.413029688994023,  11.895784139536406, 11.984679534970505,
427
12.3k
      11.220654278717394, 11.716311684833672, 10.61036646226114,
428
12.3k
      10.89849965960364,  10.203762898863669, 10.997560826267238,
429
12.3k
      11.484217379438984, 11.792836176993665, 12.24310468755171,
430
12.3k
      11.464858097919262, 12.212747017409377, 11.425595666074955,
431
12.3k
      11.572048533398757, 12.742093965163013, 11.381874288645637,
432
12.3k
      12.191870445817015, 11.683156920035426, 11.152442115262197,
433
12.3k
      11.90303691580457,  11.653292787169159, 11.938615382266098,
434
12.3k
      16.970641701570223, 16.853602280380002, 17.26240782594733,
435
12.3k
      16.644655390108507, 17.14310889757499,  16.910935455445955,
436
12.3k
      17.505678976959697, 17.213498225466388, 2.4162310293553024,
437
12.3k
      3.494587244462329,  3.5258600986408344, 3.4959806589517095,
438
12.3k
      3.098390886949687,  3.343454654302911,  3.588847442290287,
439
12.3k
      4.14614790111827,   5.152948641990529,  7.433696808092598,
440
12.3k
      9.716311684833672,
441
12.3k
  };
442
12.3k
  size_t table_size = sizeof kCostTable / sizeof *kCostTable;
443
12.3k
  if (tok >= table_size) tok = table_size - 1;
444
12.3k
  return kCostTable[tok] + nbits;
445
12.3k
}
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
334
        : data_(data),
458
334
          hash_mask_((uint32_t{1} << hash_bits) - 1),
459
334
          hash_table_(static_cast<size_t>(hash_mask_) + 1) {
460
334
        num_special_distances_ = 0;
461
334
        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
334
    }
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)
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)
Line
Count
Source
457
334
        : data_(data),
458
334
          hash_mask_((uint32_t{1} << hash_bits) - 1),
459
334
          hash_table_(static_cast<size_t>(hash_mask_) + 1) {
460
334
        num_special_distances_ = 0;
461
334
        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
334
    }
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
63.6k
    void FindMatch(size_t pos, size_t& len, size_t& dist_symbol, size_t min_len) {
473
63.6k
        const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_;
474
63.6k
        len = 0;
475
63.6k
        dist_symbol = 0;
476
477
        // Scan all existing entries in the bucket
478
115k
        for (uint16_t i = 0; i < hash_table_[h].size; i++) {
479
51.9k
            const uint32_t candidate = hash_table_[h].data[i];
480
51.9k
            size_t dist = pos - candidate;
481
51.9k
            size_t cur_length = MatchLength(candidate, pos);
482
483
            // Skip matches shorter than current best or min_length
484
51.9k
            if (cur_length < len || cur_length < min_len) {
485
14.6k
                continue;
486
14.6k
            }
487
488
            // Map distance to a symbol encoding (prefers special distance codes if available)
489
37.2k
            size_t cur_dist_symbol = (num_special_distances_ + dist - 1);
490
37.2k
            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
37.2k
            if (cur_length == len && cur_dist_symbol >= dist_symbol) {
500
7.99k
                continue;
501
7.99k
            }
502
503
29.2k
            len = cur_length;
504
29.2k
            dist_symbol = cur_dist_symbol;
505
29.2k
        }
506
507
        // Insert 'pos' into the bucket ring buffer
508
63.6k
        hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize);
509
63.6k
        hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos);
510
63.6k
        hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize;
511
63.6k
    }
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)
enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long)
Line
Count
Source
472
63.6k
    void FindMatch(size_t pos, size_t& len, size_t& dist_symbol, size_t min_len) {
473
63.6k
        const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_;
474
63.6k
        len = 0;
475
63.6k
        dist_symbol = 0;
476
477
        // Scan all existing entries in the bucket
478
115k
        for (uint16_t i = 0; i < hash_table_[h].size; i++) {
479
51.9k
            const uint32_t candidate = hash_table_[h].data[i];
480
51.9k
            size_t dist = pos - candidate;
481
51.9k
            size_t cur_length = MatchLength(candidate, pos);
482
483
            // Skip matches shorter than current best or min_length
484
51.9k
            if (cur_length < len || cur_length < min_len) {
485
14.6k
                continue;
486
14.6k
            }
487
488
            // Map distance to a symbol encoding (prefers special distance codes if available)
489
37.2k
            size_t cur_dist_symbol = (num_special_distances_ + dist - 1);
490
37.2k
            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
37.2k
            if (cur_length == len && cur_dist_symbol >= dist_symbol) {
500
7.99k
                continue;
501
7.99k
            }
502
503
29.2k
            len = cur_length;
504
29.2k
            dist_symbol = cur_dist_symbol;
505
29.2k
        }
506
507
        // Insert 'pos' into the bucket ring buffer
508
63.6k
        hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize);
509
63.6k
        hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos);
510
63.6k
        hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize;
511
63.6k
    }
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
102k
    void Update(size_t pos) {
515
102k
        const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_;
516
102k
        hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize);
517
102k
        hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos);
518
102k
        hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize;
519
102k
    }
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)
enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::Update(unsigned long)
Line
Count
Source
514
102k
    void Update(size_t pos) {
515
102k
        const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_;
516
102k
        hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize);
517
102k
        hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos);
518
102k
        hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize;
519
102k
    }
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
51.9k
    size_t MatchLength(size_t a, size_t b) const {
531
51.9k
        JXL_DASSERT(a < b);
532
51.9k
        size_t len = 0;
533
448k
        while (b + len < data_.size() && data_[a + len] == data_[b + len]) {
534
396k
            ++len;
535
396k
        }
536
51.9k
        return len;
537
51.9k
    }
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
enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::MatchLength(unsigned long, unsigned long) const
Line
Count
Source
530
51.9k
    size_t MatchLength(size_t a, size_t b) const {
531
51.9k
        JXL_DASSERT(a < b);
532
51.9k
        size_t len = 0;
533
448k
        while (b + len < data_.size() && data_[a + len] == data_[b + len]) {
534
396k
            ++len;
535
396k
        }
536
51.9k
        return len;
537
51.9k
    }
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
334
) {
554
334
  std::vector<std::vector<Token>> tokens_lz77(tokens.size());
555
334
  SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77);
556
334
  float bit_decrease = 0;
557
334
  size_t total_symbols = 0;
558
334
  HybridUintConfig uint_config;
559
334
  std::vector<float> sym_cost;
560
561
668
  for (size_t stream = 0; stream < tokens.size(); stream++) {
562
334
    size_t distance_multiplier =
563
334
        params.image_widths.size() > stream ? params.image_widths[stream] : 0;
564
334
    const auto& in = tokens[stream];
565
334
    auto& out = tokens_lz77[stream];
566
334
    total_symbols += in.size();
567
334
    std::vector<uint32_t> data;
568
334
    data.resize(in.size());
569
570
    // Cumulative sum of bit costs for fast range estimation
571
334
    sym_cost.resize(in.size() + 1);
572
166k
    for (size_t pos = 0; pos < in.size(); pos++) {
573
166k
      uint32_t tok, nbits, unused_bits;
574
166k
      uint_config.Encode(in[pos].value, &tok, &nbits, &unused_bits);
575
166k
      sym_cost[pos + 1] = sce.Bits(in[pos].context, tok) + nbits + sym_cost[pos];
576
166k
      data[pos] = in[pos].value;
577
166k
    }
578
579
334
    out.reserve(in.size());
580
334
    size_t min_length = lz77.min_length;
581
334
    JXL_DASSERT(min_length >= 3);
582
583
334
    LZ77HashMap<kBucketSize, kHashSize> hash_map =
584
334
        LZ77HashMap<kBucketSize, kHashSize>(data, distance_multiplier);
585
586
64.3k
    for (size_t pos = 0; pos < in.size(); pos++) {
587
64.0k
      out.push_back(in[pos]);
588
589
      // Guard against reading past input bounds
590
64.0k
      if (pos + kHashSize >= in.size()) {
591
334
        continue;
592
334
      }
593
594
63.6k
      size_t len;
595
63.6k
      size_t dist_symbol;
596
597
      // Find longest sequence match in current bucket
598
63.6k
      hash_map.FindMatch(pos, len, dist_symbol, min_length);
599
63.6k
      if (len < min_length) continue;
600
601
      // Bit cost comparison: raw tokens vs LZ77 pair
602
12.3k
      float cost = sym_cost[pos + len] - sym_cost[pos];
603
12.3k
      size_t lz77_len = len - lz77.min_length;
604
12.3k
      float lz77_cost = LenCost(lz77_len) + DistCost(dist_symbol);
605
606
12.3k
      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
12.3k
      bit_decrease += cost - lz77_cost - sce.AddSymbolCost(out.back().context);
617
618
      // Emit LZ77 length and distance tokens
619
12.3k
      out.back().value = len - min_length;
620
12.3k
      out.back().is_lz77_length = true;
621
12.3k
      out.emplace_back(
622
12.3k
          static_cast<uint32_t>(lz77.nonserialized_distance_context),
623
12.3k
          static_cast<uint32_t>(dist_symbol));
624
625
      // Update hash map for skipped positions inside the match
626
114k
      for (size_t offset = 1; offset < len; offset++) {
627
102k
        hash_map.Update(pos + offset);
628
102k
      }
629
12.3k
      pos += len - 1;
630
12.3k
    }
631
334
  }
632
633
  // Return LZ77 streams only if net bit savings justify overhead
634
334
  if (bit_decrease > total_symbols * 0.2 + 16) {
635
181
    return tokens_lz77;
636
181
  }
637
153
  return {};
638
334
}
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&)
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&)
Line
Count
Source
553
334
) {
554
334
  std::vector<std::vector<Token>> tokens_lz77(tokens.size());
555
334
  SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77);
556
334
  float bit_decrease = 0;
557
334
  size_t total_symbols = 0;
558
334
  HybridUintConfig uint_config;
559
334
  std::vector<float> sym_cost;
560
561
668
  for (size_t stream = 0; stream < tokens.size(); stream++) {
562
334
    size_t distance_multiplier =
563
334
        params.image_widths.size() > stream ? params.image_widths[stream] : 0;
564
334
    const auto& in = tokens[stream];
565
334
    auto& out = tokens_lz77[stream];
566
334
    total_symbols += in.size();
567
334
    std::vector<uint32_t> data;
568
334
    data.resize(in.size());
569
570
    // Cumulative sum of bit costs for fast range estimation
571
334
    sym_cost.resize(in.size() + 1);
572
166k
    for (size_t pos = 0; pos < in.size(); pos++) {
573
166k
      uint32_t tok, nbits, unused_bits;
574
166k
      uint_config.Encode(in[pos].value, &tok, &nbits, &unused_bits);
575
166k
      sym_cost[pos + 1] = sce.Bits(in[pos].context, tok) + nbits + sym_cost[pos];
576
166k
      data[pos] = in[pos].value;
577
166k
    }
578
579
334
    out.reserve(in.size());
580
334
    size_t min_length = lz77.min_length;
581
334
    JXL_DASSERT(min_length >= 3);
582
583
334
    LZ77HashMap<kBucketSize, kHashSize> hash_map =
584
334
        LZ77HashMap<kBucketSize, kHashSize>(data, distance_multiplier);
585
586
64.3k
    for (size_t pos = 0; pos < in.size(); pos++) {
587
64.0k
      out.push_back(in[pos]);
588
589
      // Guard against reading past input bounds
590
64.0k
      if (pos + kHashSize >= in.size()) {
591
334
        continue;
592
334
      }
593
594
63.6k
      size_t len;
595
63.6k
      size_t dist_symbol;
596
597
      // Find longest sequence match in current bucket
598
63.6k
      hash_map.FindMatch(pos, len, dist_symbol, min_length);
599
63.6k
      if (len < min_length) continue;
600
601
      // Bit cost comparison: raw tokens vs LZ77 pair
602
12.3k
      float cost = sym_cost[pos + len] - sym_cost[pos];
603
12.3k
      size_t lz77_len = len - lz77.min_length;
604
12.3k
      float lz77_cost = LenCost(lz77_len) + DistCost(dist_symbol);
605
606
12.3k
      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
12.3k
      bit_decrease += cost - lz77_cost - sce.AddSymbolCost(out.back().context);
617
618
      // Emit LZ77 length and distance tokens
619
12.3k
      out.back().value = len - min_length;
620
12.3k
      out.back().is_lz77_length = true;
621
12.3k
      out.emplace_back(
622
12.3k
          static_cast<uint32_t>(lz77.nonserialized_distance_context),
623
12.3k
          static_cast<uint32_t>(dist_symbol));
624
625
      // Update hash map for skipped positions inside the match
626
114k
      for (size_t offset = 1; offset < len; offset++) {
627
102k
        hash_map.Update(pos + offset);
628
102k
      }
629
12.3k
      pos += len - 1;
630
12.3k
    }
631
334
  }
632
633
  // Return LZ77 streams only if net bit savings justify overhead
634
334
  if (bit_decrease > total_symbols * 0.2 + 16) {
635
181
    return tokens_lz77;
636
181
  }
637
153
  return {};
638
334
}
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
334
    const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) {
644
334
  std::vector<std::vector<Token>> tokens_for_cost_estimate =
645
334
      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
334
  if (tokens_for_cost_estimate.empty()) return {};
649
181
  SymbolCostEstimator sce(num_contexts + 1, params.force_huffman,
650
181
                          tokens_for_cost_estimate, lz77);
651
181
  std::vector<std::vector<Token>> tokens_lz77(tokens.size());
652
181
  HybridUintConfig uint_config;
653
181
  std::vector<float> sym_cost;
654
181
  std::vector<uint32_t> dist_symbols;
655
362
  for (size_t stream = 0; stream < tokens.size(); stream++) {
656
181
    size_t distance_multiplier =
657
181
        params.image_widths.size() > stream ? params.image_widths[stream] : 0;
658
181
    const auto& in = tokens[stream];
659
181
    auto& out = tokens_lz77[stream];
660
    // Cumulative sum of bit costs.
661
181
    sym_cost.resize(in.size() + 1);
662
92.7k
    for (size_t i = 0; i < in.size(); i++) {
663
92.5k
      uint32_t tok, nbits, unused_bits;
664
92.5k
      uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits);
665
92.5k
      sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i];
666
92.5k
    }
667
668
181
    out.reserve(in.size());
669
181
    size_t max_distance = in.size();
670
181
    size_t min_length = lz77.min_length;
671
181
    JXL_DASSERT(min_length >= 3);
672
181
    size_t max_length = in.size();
673
674
    // Use next power of two as window size.
675
181
    size_t window_size = 1;
676
1.92k
    while (window_size < max_distance && window_size < kWindowSize) {
677
1.74k
      window_size <<= 1;
678
1.74k
    }
679
680
181
    HashChain<kMaxChainLength> chain(in.data(), in.size(), window_size, min_length, max_length,
681
181
                    distance_multiplier);
682
683
181
    struct MatchInfo {
684
181
      uint32_t len;
685
181
      uint32_t dist_symbol;
686
181
      uint32_t ctx;
687
181
      float total_cost = std::numeric_limits<float>::max();
688
181
    };
689
    // Total cost to encode the first N symbols.
690
181
    std::vector<MatchInfo> prefix_costs(in.size() + 1);
691
181
    prefix_costs[0].total_cost = 0;
692
693
181
    size_t rle_length = 0;
694
181
    size_t skip_lz77 = 0;
695
92.7k
    for (size_t i = 0; i < in.size(); i++) {
696
92.5k
      chain.Update(i);
697
92.5k
      float lit_cost =
698
92.5k
          prefix_costs[i].total_cost + sym_cost[i + 1] - sym_cost[i];
699
92.5k
      if (prefix_costs[i + 1].total_cost > lit_cost) {
700
35.9k
        prefix_costs[i + 1].dist_symbol = 0;
701
35.9k
        prefix_costs[i + 1].len = 1;
702
35.9k
        prefix_costs[i + 1].ctx = in[i].context;
703
35.9k
        prefix_costs[i + 1].total_cost = lit_cost;
704
35.9k
      }
705
92.5k
      if (skip_lz77 > 0) {
706
13.7k
        skip_lz77--;
707
13.7k
        continue;
708
13.7k
      }
709
78.7k
      dist_symbols.clear();
710
78.7k
      chain.FindMatches(i, max_distance,
711
1.21M
                        [&dist_symbols](size_t len, size_t dist_symbol) {
712
1.21M
                          if (dist_symbols.size() <= len) {
713
78.7k
                            dist_symbols.resize(len + 1, dist_symbol);
714
78.7k
                          }
715
1.21M
                          if (dist_symbol < dist_symbols[len]) {
716
0
                            dist_symbols[len] = dist_symbol;
717
0
                          }
718
1.21M
                        });
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) const
Unexecuted 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) const
Unexecuted 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) const
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
Line
Count
Source
711
1.21M
                        [&dist_symbols](size_t len, size_t dist_symbol) {
712
1.21M
                          if (dist_symbols.size() <= len) {
713
78.7k
                            dist_symbols.resize(len + 1, dist_symbol);
714
78.7k
                          }
715
1.21M
                          if (dist_symbol < dist_symbols[len]) {
716
0
                            dist_symbols[len] = dist_symbol;
717
0
                          }
718
1.21M
                        });
719
78.7k
      if (dist_symbols.size() <= min_length) continue;
720
45.6k
      {
721
45.6k
        size_t best_cost = dist_symbols.back();
722
894k
        for (size_t j = dist_symbols.size() - 1; j >= min_length; j--) {
723
848k
          if (dist_symbols[j] < best_cost) {
724
33.0k
            best_cost = dist_symbols[j];
725
33.0k
          }
726
848k
          dist_symbols[j] = best_cost;
727
848k
        }
728
45.6k
      }
729
894k
      for (size_t j = min_length; j < dist_symbols.size(); j++) {
730
        // Cost model that uses results from lazy LZ77.
731
848k
        float lz77_cost = sce.LenCost(in[i].context, j - min_length, lz77) +
732
848k
                          sce.DistCost(dist_symbols[j], lz77);
733
848k
        float cost = prefix_costs[i].total_cost + lz77_cost;
734
848k
        if (prefix_costs[i + j].total_cost > cost) {
735
88.1k
          prefix_costs[i + j].len = j;
736
88.1k
          prefix_costs[i + j].dist_symbol = dist_symbols[j] + 1;
737
88.1k
          prefix_costs[i + j].ctx = in[i].context;
738
88.1k
          prefix_costs[i + j].total_cost = cost;
739
88.1k
        }
740
848k
      }
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
45.6k
      if ((dist_symbols.back() == 0 && distance_multiplier == 0) ||
745
31.4k
          (dist_symbols.back() == 1 && distance_multiplier != 0)) {
746
14.1k
        rle_length++;
747
31.4k
      } else {
748
31.4k
        rle_length = 0;
749
31.4k
      }
750
45.6k
      if (rle_length >= 8 && dist_symbols.size() > 9) {
751
631
        skip_lz77 = dist_symbols.size() - 10;
752
631
        rle_length = 0;
753
631
      }
754
45.6k
    }
755
181
    size_t pos = in.size();
756
31.1k
    while (pos > 0) {
757
30.9k
      bool is_lz77_length = prefix_costs[pos].dist_symbol != 0;
758
30.9k
      if (is_lz77_length) {
759
6.00k
        size_t dist_symbol = prefix_costs[pos].dist_symbol - 1;
760
6.00k
        out.emplace_back(
761
6.00k
            static_cast<uint32_t>(lz77.nonserialized_distance_context),
762
6.00k
            static_cast<uint32_t>(dist_symbol));
763
6.00k
      }
764
30.9k
      uint32_t val =
765
30.9k
          is_lz77_length
766
30.9k
              ? (prefix_costs[pos].len - static_cast<uint32_t>(min_length))
767
30.9k
              : in[pos - 1].value;
768
30.9k
      out.emplace_back(prefix_costs[pos].ctx, val);
769
30.9k
      out.back().is_lz77_length = is_lz77_length;
770
30.9k
      pos -= prefix_costs[pos].len;
771
30.9k
    }
772
181
    if (!out.empty()) std::reverse(out.begin(), out.end());
773
181
  }
774
181
  return tokens_lz77;
775
334
}
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&)
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&)
Line
Count
Source
643
334
    const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) {
644
334
  std::vector<std::vector<Token>> tokens_for_cost_estimate =
645
334
      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
334
  if (tokens_for_cost_estimate.empty()) return {};
649
181
  SymbolCostEstimator sce(num_contexts + 1, params.force_huffman,
650
181
                          tokens_for_cost_estimate, lz77);
651
181
  std::vector<std::vector<Token>> tokens_lz77(tokens.size());
652
181
  HybridUintConfig uint_config;
653
181
  std::vector<float> sym_cost;
654
181
  std::vector<uint32_t> dist_symbols;
655
362
  for (size_t stream = 0; stream < tokens.size(); stream++) {
656
181
    size_t distance_multiplier =
657
181
        params.image_widths.size() > stream ? params.image_widths[stream] : 0;
658
181
    const auto& in = tokens[stream];
659
181
    auto& out = tokens_lz77[stream];
660
    // Cumulative sum of bit costs.
661
181
    sym_cost.resize(in.size() + 1);
662
92.7k
    for (size_t i = 0; i < in.size(); i++) {
663
92.5k
      uint32_t tok, nbits, unused_bits;
664
92.5k
      uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits);
665
92.5k
      sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i];
666
92.5k
    }
667
668
181
    out.reserve(in.size());
669
181
    size_t max_distance = in.size();
670
181
    size_t min_length = lz77.min_length;
671
181
    JXL_DASSERT(min_length >= 3);
672
181
    size_t max_length = in.size();
673
674
    // Use next power of two as window size.
675
181
    size_t window_size = 1;
676
1.92k
    while (window_size < max_distance && window_size < kWindowSize) {
677
1.74k
      window_size <<= 1;
678
1.74k
    }
679
680
181
    HashChain<kMaxChainLength> chain(in.data(), in.size(), window_size, min_length, max_length,
681
181
                    distance_multiplier);
682
683
181
    struct MatchInfo {
684
181
      uint32_t len;
685
181
      uint32_t dist_symbol;
686
181
      uint32_t ctx;
687
181
      float total_cost = std::numeric_limits<float>::max();
688
181
    };
689
    // Total cost to encode the first N symbols.
690
181
    std::vector<MatchInfo> prefix_costs(in.size() + 1);
691
181
    prefix_costs[0].total_cost = 0;
692
693
181
    size_t rle_length = 0;
694
181
    size_t skip_lz77 = 0;
695
92.7k
    for (size_t i = 0; i < in.size(); i++) {
696
92.5k
      chain.Update(i);
697
92.5k
      float lit_cost =
698
92.5k
          prefix_costs[i].total_cost + sym_cost[i + 1] - sym_cost[i];
699
92.5k
      if (prefix_costs[i + 1].total_cost > lit_cost) {
700
35.9k
        prefix_costs[i + 1].dist_symbol = 0;
701
35.9k
        prefix_costs[i + 1].len = 1;
702
35.9k
        prefix_costs[i + 1].ctx = in[i].context;
703
35.9k
        prefix_costs[i + 1].total_cost = lit_cost;
704
35.9k
      }
705
92.5k
      if (skip_lz77 > 0) {
706
13.7k
        skip_lz77--;
707
13.7k
        continue;
708
13.7k
      }
709
78.7k
      dist_symbols.clear();
710
78.7k
      chain.FindMatches(i, max_distance,
711
78.7k
                        [&dist_symbols](size_t len, size_t dist_symbol) {
712
78.7k
                          if (dist_symbols.size() <= len) {
713
78.7k
                            dist_symbols.resize(len + 1, dist_symbol);
714
78.7k
                          }
715
78.7k
                          if (dist_symbol < dist_symbols[len]) {
716
78.7k
                            dist_symbols[len] = dist_symbol;
717
78.7k
                          }
718
78.7k
                        });
719
78.7k
      if (dist_symbols.size() <= min_length) continue;
720
45.6k
      {
721
45.6k
        size_t best_cost = dist_symbols.back();
722
894k
        for (size_t j = dist_symbols.size() - 1; j >= min_length; j--) {
723
848k
          if (dist_symbols[j] < best_cost) {
724
33.0k
            best_cost = dist_symbols[j];
725
33.0k
          }
726
848k
          dist_symbols[j] = best_cost;
727
848k
        }
728
45.6k
      }
729
894k
      for (size_t j = min_length; j < dist_symbols.size(); j++) {
730
        // Cost model that uses results from lazy LZ77.
731
848k
        float lz77_cost = sce.LenCost(in[i].context, j - min_length, lz77) +
732
848k
                          sce.DistCost(dist_symbols[j], lz77);
733
848k
        float cost = prefix_costs[i].total_cost + lz77_cost;
734
848k
        if (prefix_costs[i + j].total_cost > cost) {
735
88.1k
          prefix_costs[i + j].len = j;
736
88.1k
          prefix_costs[i + j].dist_symbol = dist_symbols[j] + 1;
737
88.1k
          prefix_costs[i + j].ctx = in[i].context;
738
88.1k
          prefix_costs[i + j].total_cost = cost;
739
88.1k
        }
740
848k
      }
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
45.6k
      if ((dist_symbols.back() == 0 && distance_multiplier == 0) ||
745
31.4k
          (dist_symbols.back() == 1 && distance_multiplier != 0)) {
746
14.1k
        rle_length++;
747
31.4k
      } else {
748
31.4k
        rle_length = 0;
749
31.4k
      }
750
45.6k
      if (rle_length >= 8 && dist_symbols.size() > 9) {
751
631
        skip_lz77 = dist_symbols.size() - 10;
752
631
        rle_length = 0;
753
631
      }
754
45.6k
    }
755
181
    size_t pos = in.size();
756
31.1k
    while (pos > 0) {
757
30.9k
      bool is_lz77_length = prefix_costs[pos].dist_symbol != 0;
758
30.9k
      if (is_lz77_length) {
759
6.00k
        size_t dist_symbol = prefix_costs[pos].dist_symbol - 1;
760
6.00k
        out.emplace_back(
761
6.00k
            static_cast<uint32_t>(lz77.nonserialized_distance_context),
762
6.00k
            static_cast<uint32_t>(dist_symbol));
763
6.00k
      }
764
30.9k
      uint32_t val =
765
30.9k
          is_lz77_length
766
30.9k
              ? (prefix_costs[pos].len - static_cast<uint32_t>(min_length))
767
30.9k
              : in[pos - 1].value;
768
30.9k
      out.emplace_back(prefix_costs[pos].ctx, val);
769
30.9k
      out.back().is_lz77_length = is_lz77_length;
770
30.9k
      pos -= prefix_costs[pos].len;
771
30.9k
    }
772
181
    if (!out.empty()) std::reverse(out.begin(), out.end());
773
181
  }
774
181
  return tokens_lz77;
775
334
}
776
777
}  // namespace
778
779
std::vector<std::vector<Token>> ApplyLZ77(
780
    const HistogramParams& params, size_t num_contexts,
781
40.4k
    const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) {
782
40.4k
  switch (params.lz77_method) {
783
29.3k
    case HistogramParams::LZ77Method::kRLE:
784
29.3k
      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
334
    case HistogramParams::LZ77Method::kOptc256:
812
334
      return ApplyLZ77_Optimal<256>(params, num_contexts, tokens, lz77);
813
10.8k
    default:
814
10.8k
      return {};
815
40.4k
  }
816
40.4k
}
817
818
}  // namespace jxl