Coverage Report

Created: 2026-08-14 08:18

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
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&) 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
Unexecuted 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) 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
Unexecuted 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