Coverage Report

Created: 2025-09-08 07:52

/src/libjxl/lib/jxl/modular/encoding/encoding.cc
Line
Count
Source (jump to first uncovered line)
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/modular/encoding/encoding.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <algorithm>
11
#include <array>
12
#include <cstddef>
13
#include <cstdint>
14
#include <cstdlib>
15
#include <queue>
16
#include <utility>
17
#include <vector>
18
19
#include "lib/jxl/base/printf_macros.h"
20
#include "lib/jxl/base/scope_guard.h"
21
#include "lib/jxl/base/status.h"
22
#include "lib/jxl/dec_ans.h"
23
#include "lib/jxl/dec_bit_reader.h"
24
#include "lib/jxl/frame_dimensions.h"
25
#include "lib/jxl/image_ops.h"
26
#include "lib/jxl/modular/encoding/context_predict.h"
27
#include "lib/jxl/modular/options.h"
28
#include "lib/jxl/pack_signed.h"
29
30
namespace jxl {
31
32
// Removes all nodes that use a static property (i.e. channel or group ID) from
33
// the tree and collapses each node on even levels with its two children to
34
// produce a flatter tree. Also computes whether the resulting tree requires
35
// using the weighted predictor.
36
FlatTree FilterTree(const Tree &global_tree,
37
                    std::array<pixel_type, kNumStaticProperties> &static_props,
38
                    size_t *num_props, bool *use_wp, bool *wp_only,
39
148k
                    bool *gradient_only) {
40
148k
  *num_props = 0;
41
148k
  bool has_wp = false;
42
148k
  bool has_non_wp = false;
43
148k
  *gradient_only = true;
44
209k
  const auto mark_property = [&](int32_t p) {
45
209k
    if (p == kWPProp) {
46
9.45k
      has_wp = true;
47
200k
    } else if (p >= kNumStaticProperties) {
48
127k
      has_non_wp = true;
49
127k
    }
50
209k
    if (p >= kNumStaticProperties && p != kGradientProp) {
51
126k
      *gradient_only = false;
52
126k
    }
53
209k
  };
54
148k
  FlatTree output;
55
148k
  std::queue<size_t> nodes;
56
148k
  nodes.push(0);
57
  // Produces a trimmed and flattened tree by doing a BFS visit of the original
58
  // tree, ignoring branches that are known to be false and proceeding two
59
  // levels at a time to collapse nodes in a flatter tree; if an inner parent
60
  // node has a leaf as a child, the leaf is duplicated and an implicit fake
61
  // node is added. This allows to reduce the number of branches when traversing
62
  // the resulting flat tree.
63
573k
  while (!nodes.empty()) {
64
424k
    size_t cur = nodes.front();
65
424k
    nodes.pop();
66
    // Skip nodes that we can decide now, by jumping directly to their children.
67
442k
    while (global_tree[cur].property < kNumStaticProperties &&
68
442k
           global_tree[cur].property != -1) {
69
17.3k
      if (static_props[global_tree[cur].property] > global_tree[cur].splitval) {
70
9.94k
        cur = global_tree[cur].lchild;
71
9.94k
      } else {
72
7.41k
        cur = global_tree[cur].rchild;
73
7.41k
      }
74
17.3k
    }
75
424k
    FlatDecisionNode flat;
76
424k
    if (global_tree[cur].property == -1) {
77
348k
      flat.property0 = -1;
78
348k
      flat.childID = global_tree[cur].lchild;
79
348k
      flat.predictor = global_tree[cur].predictor;
80
348k
      flat.predictor_offset = global_tree[cur].predictor_offset;
81
348k
      flat.multiplier = global_tree[cur].multiplier;
82
348k
      *gradient_only &= flat.predictor == Predictor::Gradient;
83
348k
      has_wp |= flat.predictor == Predictor::Weighted;
84
348k
      has_non_wp |= flat.predictor != Predictor::Weighted;
85
348k
      output.push_back(flat);
86
348k
      continue;
87
348k
    }
88
76.6k
    flat.childID = output.size() + nodes.size() + 1;
89
90
76.6k
    flat.property0 = global_tree[cur].property;
91
76.6k
    *num_props = std::max<size_t>(flat.property0 + 1, *num_props);
92
76.6k
    flat.splitval0 = global_tree[cur].splitval;
93
94
214k
    for (size_t i = 0; i < 2; i++) {
95
138k
      size_t cur_child =
96
138k
          i == 0 ? global_tree[cur].lchild : global_tree[cur].rchild;
97
      // Skip nodes that we can decide now.
98
152k
      while (global_tree[cur_child].property < kNumStaticProperties &&
99
152k
             global_tree[cur_child].property != -1) {
100
14.7k
        if (static_props[global_tree[cur_child].property] >
101
14.7k
            global_tree[cur_child].splitval) {
102
9.39k
          cur_child = global_tree[cur_child].lchild;
103
9.39k
        } else {
104
5.35k
          cur_child = global_tree[cur_child].rchild;
105
5.35k
        }
106
14.7k
      }
107
      // We ended up in a leaf, add a placeholder decision and two copies of the
108
      // leaf.
109
138k
      if (global_tree[cur_child].property == -1) {
110
74.8k
        flat.properties[i] = 0;
111
74.8k
        flat.splitvals[i] = 0;
112
74.8k
        nodes.push(cur_child);
113
74.8k
        nodes.push(cur_child);
114
74.8k
      } else {
115
63.2k
        flat.properties[i] = global_tree[cur_child].property;
116
63.2k
        flat.splitvals[i] = global_tree[cur_child].splitval;
117
63.2k
        nodes.push(global_tree[cur_child].lchild);
118
63.2k
        nodes.push(global_tree[cur_child].rchild);
119
63.2k
        *num_props = std::max<size_t>(flat.properties[i] + 1, *num_props);
120
63.2k
      }
121
138k
    }
122
123
139k
    for (int16_t property : flat.properties) mark_property(property);
124
76.6k
    mark_property(flat.property0);
125
76.6k
    output.push_back(flat);
126
76.6k
  }
127
148k
  if (*num_props > kNumNonrefProperties) {
128
33
    *num_props =
129
33
        DivCeil(*num_props - kNumNonrefProperties, kExtraPropsPerChannel) *
130
33
            kExtraPropsPerChannel +
131
33
        kNumNonrefProperties;
132
148k
  } else {
133
148k
    *num_props = kNumNonrefProperties;
134
148k
  }
135
148k
  *use_wp = has_wp;
136
148k
  *wp_only = has_wp && !has_non_wp;
137
138
148k
  return output;
139
148k
}
140
141
namespace detail {
142
template <bool uses_lz77>
143
Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
144
                                 const std::vector<uint8_t> &context_map,
145
                                 const Tree &global_tree,
146
                                 const weighted::Header &wp_header,
147
                                 pixel_type chan, size_t group_id,
148
                                 TreeLut<uint8_t, false, false> &tree_lut,
149
                                 Image *image, uint32_t &fl_run,
150
148k
                                 uint32_t &fl_v) {
151
148k
  JxlMemoryManager *memory_manager = image->memory_manager();
152
148k
  Channel &channel = image->channel[chan];
153
154
148k
  std::array<pixel_type, kNumStaticProperties> static_props = {
155
148k
      {chan, static_cast<int>(group_id)}};
156
  // TODO(veluca): filter the tree according to static_props.
157
158
  // zero pixel channel? could happen
159
148k
  if (channel.w == 0 || channel.h == 0) return true;
160
161
148k
  bool tree_has_wp_prop_or_pred = false;
162
148k
  bool is_wp_only = false;
163
148k
  bool is_gradient_only = false;
164
148k
  size_t num_props;
165
148k
  FlatTree tree =
166
148k
      FilterTree(global_tree, static_props, &num_props,
167
148k
                 &tree_has_wp_prop_or_pred, &is_wp_only, &is_gradient_only);
168
169
  // From here on, tree lookup returns a *clustered* context ID.
170
  // This avoids an extra memory lookup after tree traversal.
171
438k
  for (auto &node : tree) {
172
438k
    if (node.property0 == -1) {
173
366k
      node.childID = context_map[node.childID];
174
366k
    }
175
438k
  }
176
177
148k
  JXL_DEBUG_V(3, "Decoded MA tree with %" PRIuS " nodes", tree.size());
178
179
  // MAANS decode
180
148k
  const auto make_pixel = [](uint64_t v, pixel_type multiplier,
181
27.8M
                             pixel_type_w offset) -> pixel_type {
182
27.8M
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
183
27.8M
    pixel_type_w val = UnpackSigned(v);
184
    // if it overflows, it overflows, and we have a problem anyway
185
27.8M
    return val * multiplier + offset;
186
27.8M
  };
jxl::detail::DecodeModularChannelMAANS<true>(jxl::BitReader*, jxl::ANSSymbolReader*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> > const&, jxl::weighted::Header const&, int, unsigned long, jxl::TreeLut<unsigned char, false, false>&, jxl::Image*, unsigned int&, unsigned int&)::{lambda(unsigned long, int, long)#1}::operator()(unsigned long, int, long) const
Line
Count
Source
181
2.62k
                             pixel_type_w offset) -> pixel_type {
182
2.62k
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
183
2.62k
    pixel_type_w val = UnpackSigned(v);
184
    // if it overflows, it overflows, and we have a problem anyway
185
2.62k
    return val * multiplier + offset;
186
2.62k
  };
jxl::detail::DecodeModularChannelMAANS<false>(jxl::BitReader*, jxl::ANSSymbolReader*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> > const&, jxl::weighted::Header const&, int, unsigned long, jxl::TreeLut<unsigned char, false, false>&, jxl::Image*, unsigned int&, unsigned int&)::{lambda(unsigned long, int, long)#1}::operator()(unsigned long, int, long) const
Line
Count
Source
181
27.8M
                             pixel_type_w offset) -> pixel_type {
182
27.8M
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
183
27.8M
    pixel_type_w val = UnpackSigned(v);
184
    // if it overflows, it overflows, and we have a problem anyway
185
27.8M
    return val * multiplier + offset;
186
27.8M
  };
187
188
148k
  if (tree.size() == 1) {
189
    // special optimized case: no meta-adaptation, so no need
190
    // to compute properties.
191
144k
    Predictor predictor = tree[0].predictor;
192
144k
    int64_t offset = tree[0].predictor_offset;
193
144k
    int32_t multiplier = tree[0].multiplier;
194
144k
    size_t ctx_id = tree[0].childID;
195
144k
    if (predictor == Predictor::Zero) {
196
143k
      uint32_t value;
197
143k
      if (reader->IsSingleValueAndAdvance(ctx_id, &value,
198
143k
                                          channel.w * channel.h)) {
199
        // Special-case: histogram has a single symbol, with no extra bits, and
200
        // we use ANS mode.
201
16.6k
        JXL_DEBUG_V(8, "Fastest track.");
202
16.6k
        pixel_type v = make_pixel(value, multiplier, offset);
203
1.35M
        for (size_t y = 0; y < channel.h; y++) {
204
1.34M
          pixel_type *JXL_RESTRICT r = channel.Row(y);
205
1.34M
          std::fill(r, r + channel.w, v);
206
1.34M
        }
207
126k
      } else {
208
126k
        JXL_DEBUG_V(8, "Fast track.");
209
126k
        if (multiplier == 1 && offset == 0) {
210
2.18M
          for (size_t y = 0; y < channel.h; y++) {
211
2.06M
            pixel_type *JXL_RESTRICT r = channel.Row(y);
212
87.6M
            for (size_t x = 0; x < channel.w; x++) {
213
85.6M
              uint32_t v =
214
85.6M
                  reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
215
85.6M
              r[x] = UnpackSigned(v);
216
85.6M
            }
217
2.06M
          }
218
18.4E
        } else {
219
18.4E
          for (size_t y = 0; y < channel.h; y++) {
220
2.04k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
221
526k
            for (size_t x = 0; x < channel.w; x++) {
222
524k
              uint32_t v =
223
524k
                  reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
224
524k
                                                                         br);
225
524k
              r[x] = make_pixel(v, multiplier, offset);
226
524k
            }
227
2.04k
          }
228
18.4E
        }
229
126k
      }
230
143k
      return true;
231
143k
    } else if (uses_lz77 && predictor == Predictor::Gradient && offset == 0 &&
232
1.37k
               multiplier == 1 && reader->IsHuffRleOnly()) {
233
0
      JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
234
0
      pixel_type_w sv = UnpackSigned(fl_v);
235
0
      for (size_t y = 0; y < channel.h; y++) {
236
0
        pixel_type *JXL_RESTRICT r = channel.Row(y);
237
0
        const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
238
0
        const pixel_type *JXL_RESTRICT rtopleft =
239
0
            (y ? channel.Row(y - 1) - 1 : r - 1);
240
0
        pixel_type_w guess = (y ? rtop[0] : 0);
241
0
        if (fl_run == 0) {
242
0
          reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
243
0
                                                     &fl_run);
244
0
          sv = UnpackSigned(fl_v);
245
0
        } else {
246
0
          fl_run--;
247
0
        }
248
0
        r[0] = sv + guess;
249
0
        for (size_t x = 1; x < channel.w; x++) {
250
0
          pixel_type left = r[x - 1];
251
0
          pixel_type top = rtop[x];
252
0
          pixel_type topleft = rtopleft[x];
253
0
          pixel_type_w guess = ClampedGradient(top, left, topleft);
254
0
          if (!fl_run) {
255
0
            reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
256
0
                                                       &fl_run);
257
0
            sv = UnpackSigned(fl_v);
258
0
          } else {
259
0
            fl_run--;
260
0
          }
261
0
          r[x] = sv + guess;
262
0
        }
263
0
      }
264
0
      return true;
265
1.37k
    } else if (predictor == Predictor::Gradient && offset == 0 &&
266
1.37k
               multiplier == 1) {
267
1.19k
      JXL_DEBUG_V(8, "Gradient very fast track.");
268
1.19k
      const intptr_t onerow = channel.plane.PixelsPerRow();
269
11.8k
      for (size_t y = 0; y < channel.h; y++) {
270
10.7k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
271
140k
        for (size_t x = 0; x < channel.w; x++) {
272
129k
          pixel_type left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
273
129k
          pixel_type top = (y ? *(r + x - onerow) : left);
274
129k
          pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
275
129k
          pixel_type guess = ClampedGradient(top, left, topleft);
276
129k
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
277
129k
              ctx_id, br);
278
129k
          r[x] = make_pixel(v, 1, guess);
279
129k
        }
280
10.7k
      }
281
1.19k
      return true;
282
1.19k
    }
283
144k
  }
284
285
  // Check if this tree is a WP-only tree with a small enough property value
286
  // range.
287
4.39k
  if (is_wp_only) {
288
176
    is_wp_only = TreeToLookupTable(tree, tree_lut);
289
176
  }
290
4.39k
  if (is_gradient_only) {
291
8
    is_gradient_only = TreeToLookupTable(tree, tree_lut);
292
8
  }
293
294
4.39k
  if (is_gradient_only) {
295
0
    JXL_DEBUG_V(8, "Gradient fast track.");
296
0
    const intptr_t onerow = channel.plane.PixelsPerRow();
297
0
    for (size_t y = 0; y < channel.h; y++) {
298
0
      pixel_type *JXL_RESTRICT r = channel.Row(y);
299
0
      for (size_t x = 0; x < channel.w; x++) {
300
0
        pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
301
0
        pixel_type_w top = (y ? *(r + x - onerow) : left);
302
0
        pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
303
0
        int32_t guess = ClampedGradient(top, left, topleft);
304
0
        uint32_t pos =
305
0
            kPropRangeFast +
306
0
            std::min<pixel_type_w>(
307
0
                std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
308
0
                kPropRangeFast - 1);
309
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
310
0
        uint64_t v =
311
0
            reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
312
0
        r[x] = make_pixel(v, 1, guess);
313
0
      }
314
0
    }
315
4.39k
  } else if (!uses_lz77 && is_wp_only && channel.w > 8) {
316
168
    JXL_DEBUG_V(8, "WP fast track.");
317
168
    weighted::State wp_state(wp_header, channel.w, channel.h);
318
168
    Properties properties(1);
319
1.19k
    for (size_t y = 0; y < channel.h; y++) {
320
1.02k
      pixel_type *JXL_RESTRICT r = channel.Row(y);
321
1.02k
      const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
322
1.02k
      const pixel_type *JXL_RESTRICT rtoptop =
323
1.02k
          (y > 1 ? channel.Row(y - 2) : rtop);
324
1.02k
      const pixel_type *JXL_RESTRICT rtopleft =
325
1.02k
          (y ? channel.Row(y - 1) - 1 : r - 1);
326
1.02k
      const pixel_type *JXL_RESTRICT rtopright =
327
1.02k
          (y ? channel.Row(y - 1) + 1 : r - 1);
328
1.02k
      size_t x = 0;
329
1.02k
      {
330
1.02k
        size_t offset = 0;
331
1.02k
        pixel_type_w left = y ? rtop[x] : 0;
332
1.02k
        pixel_type_w toptop = y ? rtoptop[x] : 0;
333
1.02k
        pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
334
1.02k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
335
1.02k
            x, y, channel.w, left, left, topright, left, toptop, &properties,
336
1.02k
            offset);
337
1.02k
        uint32_t pos =
338
1.02k
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
339
1.02k
                                      kPropRangeFast - 1);
340
1.02k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
341
1.02k
        uint64_t v =
342
1.02k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
343
1.02k
        r[x] = make_pixel(v, 1, guess);
344
1.02k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
345
1.02k
      }
346
191k
      for (x = 1; x + 1 < channel.w; x++) {
347
190k
        size_t offset = 0;
348
190k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
349
190k
            x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
350
190k
            rtoptop[x], &properties, offset);
351
190k
        uint32_t pos =
352
190k
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
353
190k
                                      kPropRangeFast - 1);
354
190k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
355
190k
        uint64_t v =
356
190k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
357
190k
        r[x] = make_pixel(v, 1, guess);
358
190k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
359
190k
      }
360
1.02k
      {
361
1.02k
        size_t offset = 0;
362
1.02k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
363
1.02k
            x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
364
1.02k
            rtoptop[x], &properties, offset);
365
1.02k
        uint32_t pos =
366
1.02k
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
367
1.02k
                                      kPropRangeFast - 1);
368
1.02k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
369
1.02k
        uint64_t v =
370
1.02k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
371
1.02k
        r[x] = make_pixel(v, 1, guess);
372
1.02k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
373
1.02k
      }
374
1.02k
    }
375
4.22k
  } else if (!tree_has_wp_prop_or_pred) {
376
    // special optimized case: the weighted predictor and its properties are not
377
    // used, so no need to compute weights and properties.
378
3.08k
    JXL_DEBUG_V(8, "Slow track.");
379
3.08k
    MATreeLookup tree_lookup(tree);
380
3.08k
    Properties properties = Properties(num_props);
381
3.08k
    const intptr_t onerow = channel.plane.PixelsPerRow();
382
3.08k
    JXL_ASSIGN_OR_RETURN(
383
3.08k
        Channel references,
384
3.08k
        Channel::Create(memory_manager,
385
3.08k
                        properties.size() - kNumNonrefProperties, channel.w));
386
128k
    for (size_t y = 0; y < channel.h; y++) {
387
125k
      pixel_type *JXL_RESTRICT p = channel.Row(y);
388
125k
      PrecomputeReferences(channel, y, *image, chan, &references);
389
125k
      InitPropsRow(&properties, static_props, y);
390
125k
      if (y > 1 && channel.w > 8 && references.w == 0) {
391
347k
        for (size_t x = 0; x < 2; x++) {
392
231k
          PredictionResult res =
393
231k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
394
231k
                              tree_lookup, references);
395
231k
          uint64_t v =
396
231k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
397
231k
          p[x] = make_pixel(v, res.multiplier, res.guess);
398
231k
        }
399
14.9M
        for (size_t x = 2; x < channel.w - 2; x++) {
400
14.8M
          PredictionResult res =
401
14.8M
              PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
402
14.8M
                                 tree_lookup, references);
403
14.8M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
404
14.8M
              res.context, br);
405
14.8M
          p[x] = make_pixel(v, res.multiplier, res.guess);
406
14.8M
        }
407
347k
        for (size_t x = channel.w - 2; x < channel.w; x++) {
408
231k
          PredictionResult res =
409
231k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
410
231k
                              tree_lookup, references);
411
231k
          uint64_t v =
412
231k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
413
231k
          p[x] = make_pixel(v, res.multiplier, res.guess);
414
231k
        }
415
115k
      } else {
416
488k
        for (size_t x = 0; x < channel.w; x++) {
417
478k
          PredictionResult res =
418
478k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
419
478k
                              tree_lookup, references);
420
478k
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
421
478k
              res.context, br);
422
478k
          p[x] = make_pixel(v, res.multiplier, res.guess);
423
478k
        }
424
9.73k
      }
425
125k
    }
426
3.08k
  } else {
427
1.13k
    JXL_DEBUG_V(8, "Slowest track.");
428
1.13k
    MATreeLookup tree_lookup(tree);
429
1.13k
    Properties properties = Properties(num_props);
430
1.13k
    const intptr_t onerow = channel.plane.PixelsPerRow();
431
1.13k
    JXL_ASSIGN_OR_RETURN(
432
1.13k
        Channel references,
433
1.13k
        Channel::Create(memory_manager,
434
1.13k
                        properties.size() - kNumNonrefProperties, channel.w));
435
1.13k
    weighted::State wp_state(wp_header, channel.w, channel.h);
436
94.2k
    for (size_t y = 0; y < channel.h; y++) {
437
93.1k
      pixel_type *JXL_RESTRICT p = channel.Row(y);
438
93.1k
      InitPropsRow(&properties, static_props, y);
439
93.1k
      PrecomputeReferences(channel, y, *image, chan, &references);
440
93.1k
      if (!uses_lz77 && y > 1 && channel.w > 8 && references.w == 0) {
441
272k
        for (size_t x = 0; x < 2; x++) {
442
181k
          PredictionResult res =
443
181k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
444
181k
                            tree_lookup, references, &wp_state);
445
181k
          uint64_t v =
446
181k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
447
181k
          p[x] = make_pixel(v, res.multiplier, res.guess);
448
181k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
449
181k
        }
450
15.0M
        for (size_t x = 2; x < channel.w - 2; x++) {
451
15.0M
          PredictionResult res =
452
15.0M
              PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
453
15.0M
                               tree_lookup, references, &wp_state);
454
15.0M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
455
15.0M
              res.context, br);
456
15.0M
          p[x] = make_pixel(v, res.multiplier, res.guess);
457
15.0M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
458
15.0M
        }
459
272k
        for (size_t x = channel.w - 2; x < channel.w; x++) {
460
181k
          PredictionResult res =
461
181k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
462
181k
                            tree_lookup, references, &wp_state);
463
181k
          uint64_t v =
464
181k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
465
181k
          p[x] = make_pixel(v, res.multiplier, res.guess);
466
181k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
467
181k
        }
468
91.0k
      } else {
469
400k
        for (size_t x = 0; x < channel.w; x++) {
470
398k
          PredictionResult res =
471
398k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
472
398k
                            tree_lookup, references, &wp_state);
473
398k
          uint64_t v =
474
398k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
475
398k
          p[x] = make_pixel(v, res.multiplier, res.guess);
476
398k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
477
398k
        }
478
2.07k
      }
479
93.1k
    }
480
1.13k
  }
481
4.39k
  return true;
482
4.39k
}
jxl::Status jxl::detail::DecodeModularChannelMAANS<true>(jxl::BitReader*, jxl::ANSSymbolReader*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> > const&, jxl::weighted::Header const&, int, unsigned long, jxl::TreeLut<unsigned char, false, false>&, jxl::Image*, unsigned int&, unsigned int&)
Line
Count
Source
150
9.16k
                                 uint32_t &fl_v) {
151
9.16k
  JxlMemoryManager *memory_manager = image->memory_manager();
152
9.16k
  Channel &channel = image->channel[chan];
153
154
9.16k
  std::array<pixel_type, kNumStaticProperties> static_props = {
155
9.16k
      {chan, static_cast<int>(group_id)}};
156
  // TODO(veluca): filter the tree according to static_props.
157
158
  // zero pixel channel? could happen
159
9.16k
  if (channel.w == 0 || channel.h == 0) return true;
160
161
9.16k
  bool tree_has_wp_prop_or_pred = false;
162
9.16k
  bool is_wp_only = false;
163
9.16k
  bool is_gradient_only = false;
164
9.16k
  size_t num_props;
165
9.16k
  FlatTree tree =
166
9.16k
      FilterTree(global_tree, static_props, &num_props,
167
9.16k
                 &tree_has_wp_prop_or_pred, &is_wp_only, &is_gradient_only);
168
169
  // From here on, tree lookup returns a *clustered* context ID.
170
  // This avoids an extra memory lookup after tree traversal.
171
9.16k
  for (auto &node : tree) {
172
9.14k
    if (node.property0 == -1) {
173
9.14k
      node.childID = context_map[node.childID];
174
9.14k
    }
175
9.14k
  }
176
177
9.16k
  JXL_DEBUG_V(3, "Decoded MA tree with %" PRIuS " nodes", tree.size());
178
179
  // MAANS decode
180
9.16k
  const auto make_pixel = [](uint64_t v, pixel_type multiplier,
181
9.16k
                             pixel_type_w offset) -> pixel_type {
182
9.16k
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
183
9.16k
    pixel_type_w val = UnpackSigned(v);
184
    // if it overflows, it overflows, and we have a problem anyway
185
9.16k
    return val * multiplier + offset;
186
9.16k
  };
187
188
9.16k
  if (tree.size() == 1) {
189
    // special optimized case: no meta-adaptation, so no need
190
    // to compute properties.
191
9.13k
    Predictor predictor = tree[0].predictor;
192
9.13k
    int64_t offset = tree[0].predictor_offset;
193
9.13k
    int32_t multiplier = tree[0].multiplier;
194
9.13k
    size_t ctx_id = tree[0].childID;
195
9.13k
    if (predictor == Predictor::Zero) {
196
9.13k
      uint32_t value;
197
9.13k
      if (reader->IsSingleValueAndAdvance(ctx_id, &value,
198
9.13k
                                          channel.w * channel.h)) {
199
        // Special-case: histogram has a single symbol, with no extra bits, and
200
        // we use ANS mode.
201
2.62k
        JXL_DEBUG_V(8, "Fastest track.");
202
2.62k
        pixel_type v = make_pixel(value, multiplier, offset);
203
272k
        for (size_t y = 0; y < channel.h; y++) {
204
269k
          pixel_type *JXL_RESTRICT r = channel.Row(y);
205
269k
          std::fill(r, r + channel.w, v);
206
269k
        }
207
6.50k
      } else {
208
6.50k
        JXL_DEBUG_V(8, "Fast track.");
209
6.53k
        if (multiplier == 1 && offset == 0) {
210
824k
          for (size_t y = 0; y < channel.h; y++) {
211
817k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
212
53.2M
            for (size_t x = 0; x < channel.w; x++) {
213
52.4M
              uint32_t v =
214
52.4M
                  reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
215
52.4M
              r[x] = UnpackSigned(v);
216
52.4M
            }
217
817k
          }
218
18.4E
        } else {
219
18.4E
          for (size_t y = 0; y < channel.h; y++) {
220
0
            pixel_type *JXL_RESTRICT r = channel.Row(y);
221
0
            for (size_t x = 0; x < channel.w; x++) {
222
0
              uint32_t v =
223
0
                  reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
224
0
                                                                         br);
225
0
              r[x] = make_pixel(v, multiplier, offset);
226
0
            }
227
0
          }
228
18.4E
        }
229
6.50k
      }
230
9.13k
      return true;
231
9.13k
    } else if (uses_lz77 && predictor == Predictor::Gradient && offset == 0 &&
232
2
               multiplier == 1 && reader->IsHuffRleOnly()) {
233
0
      JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
234
0
      pixel_type_w sv = UnpackSigned(fl_v);
235
0
      for (size_t y = 0; y < channel.h; y++) {
236
0
        pixel_type *JXL_RESTRICT r = channel.Row(y);
237
0
        const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
238
0
        const pixel_type *JXL_RESTRICT rtopleft =
239
0
            (y ? channel.Row(y - 1) - 1 : r - 1);
240
0
        pixel_type_w guess = (y ? rtop[0] : 0);
241
0
        if (fl_run == 0) {
242
0
          reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
243
0
                                                     &fl_run);
244
0
          sv = UnpackSigned(fl_v);
245
0
        } else {
246
0
          fl_run--;
247
0
        }
248
0
        r[0] = sv + guess;
249
0
        for (size_t x = 1; x < channel.w; x++) {
250
0
          pixel_type left = r[x - 1];
251
0
          pixel_type top = rtop[x];
252
0
          pixel_type topleft = rtopleft[x];
253
0
          pixel_type_w guess = ClampedGradient(top, left, topleft);
254
0
          if (!fl_run) {
255
0
            reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
256
0
                                                       &fl_run);
257
0
            sv = UnpackSigned(fl_v);
258
0
          } else {
259
0
            fl_run--;
260
0
          }
261
0
          r[x] = sv + guess;
262
0
        }
263
0
      }
264
0
      return true;
265
2
    } else if (predictor == Predictor::Gradient && offset == 0 &&
266
2
               multiplier == 1) {
267
0
      JXL_DEBUG_V(8, "Gradient very fast track.");
268
0
      const intptr_t onerow = channel.plane.PixelsPerRow();
269
0
      for (size_t y = 0; y < channel.h; y++) {
270
0
        pixel_type *JXL_RESTRICT r = channel.Row(y);
271
0
        for (size_t x = 0; x < channel.w; x++) {
272
0
          pixel_type left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
273
0
          pixel_type top = (y ? *(r + x - onerow) : left);
274
0
          pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
275
0
          pixel_type guess = ClampedGradient(top, left, topleft);
276
0
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
277
0
              ctx_id, br);
278
0
          r[x] = make_pixel(v, 1, guess);
279
0
        }
280
0
      }
281
0
      return true;
282
0
    }
283
9.13k
  }
284
285
  // Check if this tree is a WP-only tree with a small enough property value
286
  // range.
287
36
  if (is_wp_only) {
288
0
    is_wp_only = TreeToLookupTable(tree, tree_lut);
289
0
  }
290
36
  if (is_gradient_only) {
291
0
    is_gradient_only = TreeToLookupTable(tree, tree_lut);
292
0
  }
293
294
36
  if (is_gradient_only) {
295
0
    JXL_DEBUG_V(8, "Gradient fast track.");
296
0
    const intptr_t onerow = channel.plane.PixelsPerRow();
297
0
    for (size_t y = 0; y < channel.h; y++) {
298
0
      pixel_type *JXL_RESTRICT r = channel.Row(y);
299
0
      for (size_t x = 0; x < channel.w; x++) {
300
0
        pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
301
0
        pixel_type_w top = (y ? *(r + x - onerow) : left);
302
0
        pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
303
0
        int32_t guess = ClampedGradient(top, left, topleft);
304
0
        uint32_t pos =
305
0
            kPropRangeFast +
306
0
            std::min<pixel_type_w>(
307
0
                std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
308
0
                kPropRangeFast - 1);
309
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
310
0
        uint64_t v =
311
0
            reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
312
0
        r[x] = make_pixel(v, 1, guess);
313
0
      }
314
0
    }
315
36
  } else if (!uses_lz77 && is_wp_only && channel.w > 8) {
316
0
    JXL_DEBUG_V(8, "WP fast track.");
317
0
    weighted::State wp_state(wp_header, channel.w, channel.h);
318
0
    Properties properties(1);
319
0
    for (size_t y = 0; y < channel.h; y++) {
320
0
      pixel_type *JXL_RESTRICT r = channel.Row(y);
321
0
      const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
322
0
      const pixel_type *JXL_RESTRICT rtoptop =
323
0
          (y > 1 ? channel.Row(y - 2) : rtop);
324
0
      const pixel_type *JXL_RESTRICT rtopleft =
325
0
          (y ? channel.Row(y - 1) - 1 : r - 1);
326
0
      const pixel_type *JXL_RESTRICT rtopright =
327
0
          (y ? channel.Row(y - 1) + 1 : r - 1);
328
0
      size_t x = 0;
329
0
      {
330
0
        size_t offset = 0;
331
0
        pixel_type_w left = y ? rtop[x] : 0;
332
0
        pixel_type_w toptop = y ? rtoptop[x] : 0;
333
0
        pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
334
0
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
335
0
            x, y, channel.w, left, left, topright, left, toptop, &properties,
336
0
            offset);
337
0
        uint32_t pos =
338
0
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
339
0
                                      kPropRangeFast - 1);
340
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
341
0
        uint64_t v =
342
0
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
343
0
        r[x] = make_pixel(v, 1, guess);
344
0
        wp_state.UpdateErrors(r[x], x, y, channel.w);
345
0
      }
346
0
      for (x = 1; x + 1 < channel.w; x++) {
347
0
        size_t offset = 0;
348
0
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
349
0
            x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
350
0
            rtoptop[x], &properties, offset);
351
0
        uint32_t pos =
352
0
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
353
0
                                      kPropRangeFast - 1);
354
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
355
0
        uint64_t v =
356
0
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
357
0
        r[x] = make_pixel(v, 1, guess);
358
0
        wp_state.UpdateErrors(r[x], x, y, channel.w);
359
0
      }
360
0
      {
361
0
        size_t offset = 0;
362
0
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
363
0
            x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
364
0
            rtoptop[x], &properties, offset);
365
0
        uint32_t pos =
366
0
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
367
0
                                      kPropRangeFast - 1);
368
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
369
0
        uint64_t v =
370
0
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
371
0
        r[x] = make_pixel(v, 1, guess);
372
0
        wp_state.UpdateErrors(r[x], x, y, channel.w);
373
0
      }
374
0
    }
375
36
  } else if (!tree_has_wp_prop_or_pred) {
376
    // special optimized case: the weighted predictor and its properties are not
377
    // used, so no need to compute weights and properties.
378
0
    JXL_DEBUG_V(8, "Slow track.");
379
0
    MATreeLookup tree_lookup(tree);
380
0
    Properties properties = Properties(num_props);
381
0
    const intptr_t onerow = channel.plane.PixelsPerRow();
382
0
    JXL_ASSIGN_OR_RETURN(
383
0
        Channel references,
384
0
        Channel::Create(memory_manager,
385
0
                        properties.size() - kNumNonrefProperties, channel.w));
386
0
    for (size_t y = 0; y < channel.h; y++) {
387
0
      pixel_type *JXL_RESTRICT p = channel.Row(y);
388
0
      PrecomputeReferences(channel, y, *image, chan, &references);
389
0
      InitPropsRow(&properties, static_props, y);
390
0
      if (y > 1 && channel.w > 8 && references.w == 0) {
391
0
        for (size_t x = 0; x < 2; x++) {
392
0
          PredictionResult res =
393
0
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
394
0
                              tree_lookup, references);
395
0
          uint64_t v =
396
0
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
397
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
398
0
        }
399
0
        for (size_t x = 2; x < channel.w - 2; x++) {
400
0
          PredictionResult res =
401
0
              PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
402
0
                                 tree_lookup, references);
403
0
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
404
0
              res.context, br);
405
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
406
0
        }
407
0
        for (size_t x = channel.w - 2; x < channel.w; x++) {
408
0
          PredictionResult res =
409
0
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
410
0
                              tree_lookup, references);
411
0
          uint64_t v =
412
0
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
413
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
414
0
        }
415
0
      } else {
416
0
        for (size_t x = 0; x < channel.w; x++) {
417
0
          PredictionResult res =
418
0
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
419
0
                              tree_lookup, references);
420
0
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
421
0
              res.context, br);
422
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
423
0
        }
424
0
      }
425
0
    }
426
36
  } else {
427
36
    JXL_DEBUG_V(8, "Slowest track.");
428
36
    MATreeLookup tree_lookup(tree);
429
36
    Properties properties = Properties(num_props);
430
36
    const intptr_t onerow = channel.plane.PixelsPerRow();
431
36
    JXL_ASSIGN_OR_RETURN(
432
36
        Channel references,
433
36
        Channel::Create(memory_manager,
434
36
                        properties.size() - kNumNonrefProperties, channel.w));
435
36
    weighted::State wp_state(wp_header, channel.w, channel.h);
436
36
    for (size_t y = 0; y < channel.h; y++) {
437
0
      pixel_type *JXL_RESTRICT p = channel.Row(y);
438
0
      InitPropsRow(&properties, static_props, y);
439
0
      PrecomputeReferences(channel, y, *image, chan, &references);
440
0
      if (!uses_lz77 && y > 1 && channel.w > 8 && references.w == 0) {
441
0
        for (size_t x = 0; x < 2; x++) {
442
0
          PredictionResult res =
443
0
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
444
0
                            tree_lookup, references, &wp_state);
445
0
          uint64_t v =
446
0
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
447
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
448
0
          wp_state.UpdateErrors(p[x], x, y, channel.w);
449
0
        }
450
0
        for (size_t x = 2; x < channel.w - 2; x++) {
451
0
          PredictionResult res =
452
0
              PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
453
0
                               tree_lookup, references, &wp_state);
454
0
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
455
0
              res.context, br);
456
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
457
0
          wp_state.UpdateErrors(p[x], x, y, channel.w);
458
0
        }
459
0
        for (size_t x = channel.w - 2; x < channel.w; x++) {
460
0
          PredictionResult res =
461
0
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
462
0
                            tree_lookup, references, &wp_state);
463
0
          uint64_t v =
464
0
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
465
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
466
0
          wp_state.UpdateErrors(p[x], x, y, channel.w);
467
0
        }
468
0
      } else {
469
0
        for (size_t x = 0; x < channel.w; x++) {
470
0
          PredictionResult res =
471
0
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
472
0
                            tree_lookup, references, &wp_state);
473
0
          uint64_t v =
474
0
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
475
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
476
0
          wp_state.UpdateErrors(p[x], x, y, channel.w);
477
0
        }
478
0
      }
479
0
    }
480
36
  }
481
36
  return true;
482
36
}
jxl::Status jxl::detail::DecodeModularChannelMAANS<false>(jxl::BitReader*, jxl::ANSSymbolReader*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> > const&, jxl::weighted::Header const&, int, unsigned long, jxl::TreeLut<unsigned char, false, false>&, jxl::Image*, unsigned int&, unsigned int&)
Line
Count
Source
150
139k
                                 uint32_t &fl_v) {
151
139k
  JxlMemoryManager *memory_manager = image->memory_manager();
152
139k
  Channel &channel = image->channel[chan];
153
154
139k
  std::array<pixel_type, kNumStaticProperties> static_props = {
155
139k
      {chan, static_cast<int>(group_id)}};
156
  // TODO(veluca): filter the tree according to static_props.
157
158
  // zero pixel channel? could happen
159
139k
  if (channel.w == 0 || channel.h == 0) return true;
160
161
139k
  bool tree_has_wp_prop_or_pred = false;
162
139k
  bool is_wp_only = false;
163
139k
  bool is_gradient_only = false;
164
139k
  size_t num_props;
165
139k
  FlatTree tree =
166
139k
      FilterTree(global_tree, static_props, &num_props,
167
139k
                 &tree_has_wp_prop_or_pred, &is_wp_only, &is_gradient_only);
168
169
  // From here on, tree lookup returns a *clustered* context ID.
170
  // This avoids an extra memory lookup after tree traversal.
171
429k
  for (auto &node : tree) {
172
429k
    if (node.property0 == -1) {
173
357k
      node.childID = context_map[node.childID];
174
357k
    }
175
429k
  }
176
177
139k
  JXL_DEBUG_V(3, "Decoded MA tree with %" PRIuS " nodes", tree.size());
178
179
  // MAANS decode
180
139k
  const auto make_pixel = [](uint64_t v, pixel_type multiplier,
181
139k
                             pixel_type_w offset) -> pixel_type {
182
139k
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
183
139k
    pixel_type_w val = UnpackSigned(v);
184
    // if it overflows, it overflows, and we have a problem anyway
185
139k
    return val * multiplier + offset;
186
139k
  };
187
188
139k
  if (tree.size() == 1) {
189
    // special optimized case: no meta-adaptation, so no need
190
    // to compute properties.
191
135k
    Predictor predictor = tree[0].predictor;
192
135k
    int64_t offset = tree[0].predictor_offset;
193
135k
    int32_t multiplier = tree[0].multiplier;
194
135k
    size_t ctx_id = tree[0].childID;
195
135k
    if (predictor == Predictor::Zero) {
196
134k
      uint32_t value;
197
134k
      if (reader->IsSingleValueAndAdvance(ctx_id, &value,
198
134k
                                          channel.w * channel.h)) {
199
        // Special-case: histogram has a single symbol, with no extra bits, and
200
        // we use ANS mode.
201
14.0k
        JXL_DEBUG_V(8, "Fastest track.");
202
14.0k
        pixel_type v = make_pixel(value, multiplier, offset);
203
1.08M
        for (size_t y = 0; y < channel.h; y++) {
204
1.07M
          pixel_type *JXL_RESTRICT r = channel.Row(y);
205
1.07M
          std::fill(r, r + channel.w, v);
206
1.07M
        }
207
120k
      } else {
208
120k
        JXL_DEBUG_V(8, "Fast track.");
209
120k
        if (multiplier == 1 && offset == 0) {
210
1.36M
          for (size_t y = 0; y < channel.h; y++) {
211
1.24M
            pixel_type *JXL_RESTRICT r = channel.Row(y);
212
34.4M
            for (size_t x = 0; x < channel.w; x++) {
213
33.2M
              uint32_t v =
214
33.2M
                  reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
215
33.2M
              r[x] = UnpackSigned(v);
216
33.2M
            }
217
1.24M
          }
218
18.4E
        } else {
219
18.4E
          for (size_t y = 0; y < channel.h; y++) {
220
2.04k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
221
526k
            for (size_t x = 0; x < channel.w; x++) {
222
524k
              uint32_t v =
223
524k
                  reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
224
524k
                                                                         br);
225
524k
              r[x] = make_pixel(v, multiplier, offset);
226
524k
            }
227
2.04k
          }
228
18.4E
        }
229
120k
      }
230
134k
      return true;
231
134k
    } else if (uses_lz77 && predictor == Predictor::Gradient && offset == 0 &&
232
1.36k
               multiplier == 1 && reader->IsHuffRleOnly()) {
233
0
      JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
234
0
      pixel_type_w sv = UnpackSigned(fl_v);
235
0
      for (size_t y = 0; y < channel.h; y++) {
236
0
        pixel_type *JXL_RESTRICT r = channel.Row(y);
237
0
        const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
238
0
        const pixel_type *JXL_RESTRICT rtopleft =
239
0
            (y ? channel.Row(y - 1) - 1 : r - 1);
240
0
        pixel_type_w guess = (y ? rtop[0] : 0);
241
0
        if (fl_run == 0) {
242
0
          reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
243
0
                                                     &fl_run);
244
0
          sv = UnpackSigned(fl_v);
245
0
        } else {
246
0
          fl_run--;
247
0
        }
248
0
        r[0] = sv + guess;
249
0
        for (size_t x = 1; x < channel.w; x++) {
250
0
          pixel_type left = r[x - 1];
251
0
          pixel_type top = rtop[x];
252
0
          pixel_type topleft = rtopleft[x];
253
0
          pixel_type_w guess = ClampedGradient(top, left, topleft);
254
0
          if (!fl_run) {
255
0
            reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
256
0
                                                       &fl_run);
257
0
            sv = UnpackSigned(fl_v);
258
0
          } else {
259
0
            fl_run--;
260
0
          }
261
0
          r[x] = sv + guess;
262
0
        }
263
0
      }
264
0
      return true;
265
1.36k
    } else if (predictor == Predictor::Gradient && offset == 0 &&
266
1.36k
               multiplier == 1) {
267
1.19k
      JXL_DEBUG_V(8, "Gradient very fast track.");
268
1.19k
      const intptr_t onerow = channel.plane.PixelsPerRow();
269
11.8k
      for (size_t y = 0; y < channel.h; y++) {
270
10.7k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
271
140k
        for (size_t x = 0; x < channel.w; x++) {
272
129k
          pixel_type left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
273
129k
          pixel_type top = (y ? *(r + x - onerow) : left);
274
129k
          pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
275
129k
          pixel_type guess = ClampedGradient(top, left, topleft);
276
129k
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
277
129k
              ctx_id, br);
278
129k
          r[x] = make_pixel(v, 1, guess);
279
129k
        }
280
10.7k
      }
281
1.19k
      return true;
282
1.19k
    }
283
135k
  }
284
285
  // Check if this tree is a WP-only tree with a small enough property value
286
  // range.
287
4.35k
  if (is_wp_only) {
288
176
    is_wp_only = TreeToLookupTable(tree, tree_lut);
289
176
  }
290
4.35k
  if (is_gradient_only) {
291
8
    is_gradient_only = TreeToLookupTable(tree, tree_lut);
292
8
  }
293
294
4.35k
  if (is_gradient_only) {
295
0
    JXL_DEBUG_V(8, "Gradient fast track.");
296
0
    const intptr_t onerow = channel.plane.PixelsPerRow();
297
0
    for (size_t y = 0; y < channel.h; y++) {
298
0
      pixel_type *JXL_RESTRICT r = channel.Row(y);
299
0
      for (size_t x = 0; x < channel.w; x++) {
300
0
        pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
301
0
        pixel_type_w top = (y ? *(r + x - onerow) : left);
302
0
        pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
303
0
        int32_t guess = ClampedGradient(top, left, topleft);
304
0
        uint32_t pos =
305
0
            kPropRangeFast +
306
0
            std::min<pixel_type_w>(
307
0
                std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
308
0
                kPropRangeFast - 1);
309
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
310
0
        uint64_t v =
311
0
            reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
312
0
        r[x] = make_pixel(v, 1, guess);
313
0
      }
314
0
    }
315
4.35k
  } else if (!uses_lz77 && is_wp_only && channel.w > 8) {
316
168
    JXL_DEBUG_V(8, "WP fast track.");
317
168
    weighted::State wp_state(wp_header, channel.w, channel.h);
318
168
    Properties properties(1);
319
1.19k
    for (size_t y = 0; y < channel.h; y++) {
320
1.02k
      pixel_type *JXL_RESTRICT r = channel.Row(y);
321
1.02k
      const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
322
1.02k
      const pixel_type *JXL_RESTRICT rtoptop =
323
1.02k
          (y > 1 ? channel.Row(y - 2) : rtop);
324
1.02k
      const pixel_type *JXL_RESTRICT rtopleft =
325
1.02k
          (y ? channel.Row(y - 1) - 1 : r - 1);
326
1.02k
      const pixel_type *JXL_RESTRICT rtopright =
327
1.02k
          (y ? channel.Row(y - 1) + 1 : r - 1);
328
1.02k
      size_t x = 0;
329
1.02k
      {
330
1.02k
        size_t offset = 0;
331
1.02k
        pixel_type_w left = y ? rtop[x] : 0;
332
1.02k
        pixel_type_w toptop = y ? rtoptop[x] : 0;
333
1.02k
        pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
334
1.02k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
335
1.02k
            x, y, channel.w, left, left, topright, left, toptop, &properties,
336
1.02k
            offset);
337
1.02k
        uint32_t pos =
338
1.02k
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
339
1.02k
                                      kPropRangeFast - 1);
340
1.02k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
341
1.02k
        uint64_t v =
342
1.02k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
343
1.02k
        r[x] = make_pixel(v, 1, guess);
344
1.02k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
345
1.02k
      }
346
191k
      for (x = 1; x + 1 < channel.w; x++) {
347
190k
        size_t offset = 0;
348
190k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
349
190k
            x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
350
190k
            rtoptop[x], &properties, offset);
351
190k
        uint32_t pos =
352
190k
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
353
190k
                                      kPropRangeFast - 1);
354
190k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
355
190k
        uint64_t v =
356
190k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
357
190k
        r[x] = make_pixel(v, 1, guess);
358
190k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
359
190k
      }
360
1.02k
      {
361
1.02k
        size_t offset = 0;
362
1.02k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
363
1.02k
            x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
364
1.02k
            rtoptop[x], &properties, offset);
365
1.02k
        uint32_t pos =
366
1.02k
            kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
367
1.02k
                                      kPropRangeFast - 1);
368
1.02k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
369
1.02k
        uint64_t v =
370
1.02k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
371
1.02k
        r[x] = make_pixel(v, 1, guess);
372
1.02k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
373
1.02k
      }
374
1.02k
    }
375
4.18k
  } else if (!tree_has_wp_prop_or_pred) {
376
    // special optimized case: the weighted predictor and its properties are not
377
    // used, so no need to compute weights and properties.
378
3.08k
    JXL_DEBUG_V(8, "Slow track.");
379
3.08k
    MATreeLookup tree_lookup(tree);
380
3.08k
    Properties properties = Properties(num_props);
381
3.08k
    const intptr_t onerow = channel.plane.PixelsPerRow();
382
3.08k
    JXL_ASSIGN_OR_RETURN(
383
3.08k
        Channel references,
384
3.08k
        Channel::Create(memory_manager,
385
3.08k
                        properties.size() - kNumNonrefProperties, channel.w));
386
128k
    for (size_t y = 0; y < channel.h; y++) {
387
125k
      pixel_type *JXL_RESTRICT p = channel.Row(y);
388
125k
      PrecomputeReferences(channel, y, *image, chan, &references);
389
125k
      InitPropsRow(&properties, static_props, y);
390
125k
      if (y > 1 && channel.w > 8 && references.w == 0) {
391
347k
        for (size_t x = 0; x < 2; x++) {
392
231k
          PredictionResult res =
393
231k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
394
231k
                              tree_lookup, references);
395
231k
          uint64_t v =
396
231k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
397
231k
          p[x] = make_pixel(v, res.multiplier, res.guess);
398
231k
        }
399
14.9M
        for (size_t x = 2; x < channel.w - 2; x++) {
400
14.8M
          PredictionResult res =
401
14.8M
              PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
402
14.8M
                                 tree_lookup, references);
403
14.8M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
404
14.8M
              res.context, br);
405
14.8M
          p[x] = make_pixel(v, res.multiplier, res.guess);
406
14.8M
        }
407
347k
        for (size_t x = channel.w - 2; x < channel.w; x++) {
408
231k
          PredictionResult res =
409
231k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
410
231k
                              tree_lookup, references);
411
231k
          uint64_t v =
412
231k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
413
231k
          p[x] = make_pixel(v, res.multiplier, res.guess);
414
231k
        }
415
115k
      } else {
416
488k
        for (size_t x = 0; x < channel.w; x++) {
417
478k
          PredictionResult res =
418
478k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
419
478k
                              tree_lookup, references);
420
478k
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
421
478k
              res.context, br);
422
478k
          p[x] = make_pixel(v, res.multiplier, res.guess);
423
478k
        }
424
9.73k
      }
425
125k
    }
426
3.08k
  } else {
427
1.09k
    JXL_DEBUG_V(8, "Slowest track.");
428
1.09k
    MATreeLookup tree_lookup(tree);
429
1.09k
    Properties properties = Properties(num_props);
430
1.09k
    const intptr_t onerow = channel.plane.PixelsPerRow();
431
1.09k
    JXL_ASSIGN_OR_RETURN(
432
1.09k
        Channel references,
433
1.09k
        Channel::Create(memory_manager,
434
1.09k
                        properties.size() - kNumNonrefProperties, channel.w));
435
1.09k
    weighted::State wp_state(wp_header, channel.w, channel.h);
436
94.2k
    for (size_t y = 0; y < channel.h; y++) {
437
93.1k
      pixel_type *JXL_RESTRICT p = channel.Row(y);
438
93.1k
      InitPropsRow(&properties, static_props, y);
439
93.1k
      PrecomputeReferences(channel, y, *image, chan, &references);
440
93.1k
      if (!uses_lz77 && y > 1 && channel.w > 8 && references.w == 0) {
441
272k
        for (size_t x = 0; x < 2; x++) {
442
181k
          PredictionResult res =
443
181k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
444
181k
                            tree_lookup, references, &wp_state);
445
181k
          uint64_t v =
446
181k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
447
181k
          p[x] = make_pixel(v, res.multiplier, res.guess);
448
181k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
449
181k
        }
450
15.0M
        for (size_t x = 2; x < channel.w - 2; x++) {
451
15.0M
          PredictionResult res =
452
15.0M
              PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
453
15.0M
                               tree_lookup, references, &wp_state);
454
15.0M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
455
15.0M
              res.context, br);
456
15.0M
          p[x] = make_pixel(v, res.multiplier, res.guess);
457
15.0M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
458
15.0M
        }
459
272k
        for (size_t x = channel.w - 2; x < channel.w; x++) {
460
181k
          PredictionResult res =
461
181k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
462
181k
                            tree_lookup, references, &wp_state);
463
181k
          uint64_t v =
464
181k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
465
181k
          p[x] = make_pixel(v, res.multiplier, res.guess);
466
181k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
467
181k
        }
468
91.0k
      } else {
469
400k
        for (size_t x = 0; x < channel.w; x++) {
470
398k
          PredictionResult res =
471
398k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
472
398k
                            tree_lookup, references, &wp_state);
473
398k
          uint64_t v =
474
398k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
475
398k
          p[x] = make_pixel(v, res.multiplier, res.guess);
476
398k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
477
398k
        }
478
2.07k
      }
479
93.1k
    }
480
1.09k
  }
481
4.35k
  return true;
482
4.35k
}
483
}  // namespace detail
484
485
Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
486
                                 const std::vector<uint8_t> &context_map,
487
                                 const Tree &global_tree,
488
                                 const weighted::Header &wp_header,
489
                                 pixel_type chan, size_t group_id,
490
                                 TreeLut<uint8_t, false, false> &tree_lut,
491
                                 Image *image, uint32_t &fl_run,
492
148k
                                 uint32_t &fl_v) {
493
148k
  if (reader->UsesLZ77()) {
494
9.16k
    return detail::DecodeModularChannelMAANS</*uses_lz77=*/true>(
495
9.16k
        br, reader, context_map, global_tree, wp_header, chan, group_id,
496
9.16k
        tree_lut, image, fl_run, fl_v);
497
139k
  } else {
498
139k
    return detail::DecodeModularChannelMAANS</*uses_lz77=*/false>(
499
139k
        br, reader, context_map, global_tree, wp_header, chan, group_id,
500
139k
        tree_lut, image, fl_run, fl_v);
501
139k
  }
502
148k
}
503
504
103k
GroupHeader::GroupHeader() { Bundle::Init(this); }
505
506
Status ValidateChannelDimensions(const Image &image,
507
22.5k
                                 const ModularOptions &options) {
508
22.5k
  size_t nb_channels = image.channel.size();
509
45.1k
  for (bool is_dc : {true, false}) {
510
45.1k
    size_t group_dim = options.group_dim * (is_dc ? kBlockDim : 1);
511
45.1k
    size_t c = image.nb_meta_channels;
512
347k
    for (; c < nb_channels; c++) {
513
303k
      const Channel &ch = image.channel[c];
514
303k
      if (ch.w > options.group_dim || ch.h > options.group_dim) break;
515
303k
    }
516
63.6k
    for (; c < nb_channels; c++) {
517
18.5k
      const Channel &ch = image.channel[c];
518
18.5k
      if (ch.w == 0 || ch.h == 0) continue;  // skip empty
519
18.5k
      bool is_dc_channel = std::min(ch.hshift, ch.vshift) >= 3;
520
18.5k
      if (is_dc_channel != is_dc) continue;
521
9.25k
      size_t tile_dim = group_dim >> std::max(ch.hshift, ch.vshift);
522
9.25k
      if (tile_dim == 0) {
523
0
        return JXL_FAILURE("Inconsistent transforms");
524
0
      }
525
9.25k
    }
526
45.1k
  }
527
22.5k
  return true;
528
22.5k
}
529
530
Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
531
                     size_t group_id, ModularOptions *options,
532
                     const Tree *global_tree, const ANSCode *global_code,
533
                     const std::vector<uint8_t> *global_ctx_map,
534
28.4k
                     const bool allow_truncated_group) {
535
28.4k
  if (image.channel.empty()) return true;
536
22.5k
  JxlMemoryManager *memory_manager = image.memory_manager();
537
538
  // decode transforms
539
22.5k
  Status status = Bundle::Read(br, &header);
540
22.6k
  if (!allow_truncated_group) JXL_RETURN_IF_ERROR(status);
541
22.5k
  if (status.IsFatalError()) return status;
542
22.5k
  if (!br->AllReadsWithinBounds()) {
543
    // Don't do/undo transforms if header is incomplete.
544
0
    header.transforms.clear();
545
0
    image.transform = header.transforms;
546
0
    for (auto &ch : image.channel) {
547
0
      ZeroFillImage(&ch.plane);
548
0
    }
549
0
    return Status(StatusCode::kNotEnoughBytes);
550
0
  }
551
552
22.5k
  JXL_DEBUG_V(3, "Image data underwent %" PRIuS " transformations: ",
553
22.5k
              header.transforms.size());
554
22.5k
  image.transform = header.transforms;
555
22.5k
  for (Transform &transform : image.transform) {
556
10.7k
    JXL_RETURN_IF_ERROR(transform.MetaApply(image));
557
10.7k
  }
558
22.5k
  if (image.error) {
559
0
    return JXL_FAILURE("Corrupt file. Aborting.");
560
0
  }
561
22.5k
  JXL_RETURN_IF_ERROR(ValidateChannelDimensions(image, *options));
562
563
22.5k
  size_t nb_channels = image.channel.size();
564
565
22.5k
  size_t num_chans = 0;
566
22.5k
  size_t distance_multiplier = 0;
567
174k
  for (size_t i = 0; i < nb_channels; i++) {
568
152k
    Channel &channel = image.channel[i];
569
152k
    if (!channel.w || !channel.h) {
570
1.36k
      continue;  // skip empty channels
571
1.36k
    }
572
151k
    if (i >= image.nb_meta_channels && (channel.w > options->max_chan_size ||
573
150k
                                        channel.h > options->max_chan_size)) {
574
973
      break;
575
973
    }
576
150k
    if (channel.w > distance_multiplier) {
577
32.9k
      distance_multiplier = channel.w;
578
32.9k
    }
579
150k
    num_chans++;
580
150k
  }
581
22.5k
  if (num_chans == 0) return true;
582
583
22.4k
  size_t next_channel = 0;
584
22.4k
  auto scope_guard = MakeScopeGuard([&]() {
585
2.01k
    for (size_t c = next_channel; c < image.channel.size(); c++) {
586
1.95k
      ZeroFillImage(&image.channel[c].plane);
587
1.95k
    }
588
56
  });
589
  // Do not do anything if truncated groups are not allowed.
590
22.4k
  if (allow_truncated_group) scope_guard.Disarm();
591
592
  // Read tree.
593
22.4k
  Tree tree_storage;
594
22.4k
  std::vector<uint8_t> context_map_storage;
595
22.4k
  ANSCode code_storage;
596
22.4k
  const Tree *tree = &tree_storage;
597
22.4k
  const ANSCode *code = &code_storage;
598
22.4k
  const std::vector<uint8_t> *context_map = &context_map_storage;
599
22.4k
  if (!header.use_global_tree) {
600
3.42k
    uint64_t max_tree_size = 1024;
601
26.6k
    for (size_t i = 0; i < nb_channels; i++) {
602
23.1k
      Channel &channel = image.channel[i];
603
23.1k
      if (i >= image.nb_meta_channels && (channel.w > options->max_chan_size ||
604
22.6k
                                          channel.h > options->max_chan_size)) {
605
0
        break;
606
0
      }
607
23.1k
      uint64_t pixels = channel.w * channel.h;
608
23.1k
      max_tree_size += pixels;
609
23.1k
    }
610
3.42k
    max_tree_size = std::min(static_cast<uint64_t>(1 << 20), max_tree_size);
611
3.42k
    JXL_RETURN_IF_ERROR(
612
3.42k
        DecodeTree(memory_manager, br, &tree_storage, max_tree_size));
613
3.40k
    JXL_RETURN_IF_ERROR(DecodeHistograms(memory_manager, br,
614
3.40k
                                         (tree_storage.size() + 1) / 2,
615
3.40k
                                         &code_storage, &context_map_storage));
616
19.0k
  } else {
617
19.0k
    if (!global_tree || !global_code || !global_ctx_map ||
618
19.0k
        global_tree->empty()) {
619
2
      return JXL_FAILURE("No global tree available but one was requested");
620
2
    }
621
19.0k
    tree = global_tree;
622
19.0k
    code = global_code;
623
19.0k
    context_map = global_ctx_map;
624
19.0k
  }
625
626
  // Read channels
627
44.9k
  JXL_ASSIGN_OR_RETURN(ANSSymbolReader reader,
628
44.9k
                       ANSSymbolReader::Create(code, br, distance_multiplier));
629
44.9k
  auto tree_lut = jxl::make_unique<TreeLut<uint8_t, false, false>>();
630
44.9k
  uint32_t fl_run = 0;
631
44.9k
  uint32_t fl_v = 0;
632
172k
  for (; next_channel < nb_channels; next_channel++) {
633
151k
    Channel &channel = image.channel[next_channel];
634
151k
    if (!channel.w || !channel.h) {
635
1.36k
      continue;  // skip empty channels
636
1.36k
    }
637
149k
    if (next_channel >= image.nb_meta_channels &&
638
149k
        (channel.w > options->max_chan_size ||
639
148k
         channel.h > options->max_chan_size)) {
640
883
      break;
641
883
    }
642
148k
    JXL_RETURN_IF_ERROR(DecodeModularChannelMAANS(
643
148k
        br, &reader, *context_map, *tree, header.wp_header, next_channel,
644
148k
        group_id, *tree_lut, &image, fl_run, fl_v));
645
646
    // Truncated group.
647
148k
    if (!br->AllReadsWithinBounds()) {
648
38
      if (!allow_truncated_group) return JXL_FAILURE("Truncated input");
649
0
      return Status(StatusCode::kNotEnoughBytes);
650
38
    }
651
148k
  }
652
653
  // Make sure no zero-filling happens even if next_channel < nb_channels.
654
22.4k
  scope_guard.Disarm();
655
656
22.4k
  if (!reader.CheckANSFinalState()) {
657
0
    return JXL_FAILURE("ANS decode final state failed");
658
0
  }
659
22.4k
  return true;
660
22.4k
}
661
662
Status ModularGenericDecompress(BitReader *br, Image &image,
663
                                GroupHeader *header, size_t group_id,
664
                                ModularOptions *options, bool undo_transforms,
665
                                const Tree *tree, const ANSCode *code,
666
                                const std::vector<uint8_t> *ctx_map,
667
28.4k
                                bool allow_truncated_group) {
668
28.4k
  std::vector<std::pair<uint32_t, uint32_t>> req_sizes;
669
28.4k
  req_sizes.reserve(image.channel.size());
670
88.5k
  for (const auto &c : image.channel) {
671
88.5k
    req_sizes.emplace_back(c.w, c.h);
672
88.5k
  }
673
28.4k
  GroupHeader local_header;
674
28.4k
  if (header == nullptr) header = &local_header;
675
28.4k
  size_t bit_pos = br->TotalBitsConsumed();
676
28.4k
  auto dec_status = ModularDecode(br, image, *header, group_id, options, tree,
677
28.4k
                                  code, ctx_map, allow_truncated_group);
678
28.5k
  if (!allow_truncated_group) JXL_RETURN_IF_ERROR(dec_status);
679
28.3k
  if (dec_status.IsFatalError()) return dec_status;
680
28.3k
  if (undo_transforms) image.undo_transforms(header->wp_header);
681
28.3k
  if (image.error) return JXL_FAILURE("Corrupt file. Aborting.");
682
28.3k
  JXL_DEBUG_V(4,
683
28.3k
              "Modular-decoded a %" PRIuS "x%" PRIuS " nbchans=%" PRIuS
684
28.3k
              " image from %" PRIuS " bytes",
685
28.3k
              image.w, image.h, image.channel.size(),
686
28.3k
              (br->TotalBitsConsumed() - bit_pos) / 8);
687
28.3k
  JXL_DEBUG_V(5, "Modular image: %s", image.DebugString().c_str());
688
28.3k
  (void)bit_pos;
689
  // Check that after applying all transforms we are back to the requested
690
  // image sizes, otherwise there's a programming error with the
691
  // transformations.
692
28.3k
  if (undo_transforms) {
693
17.8k
    JXL_ENSURE(image.channel.size() == req_sizes.size());
694
87.2k
    for (size_t c = 0; c < req_sizes.size(); c++) {
695
69.4k
      JXL_ENSURE(req_sizes[c].first == image.channel[c].w);
696
69.4k
      JXL_ENSURE(req_sizes[c].second == image.channel[c].h);
697
69.4k
    }
698
17.8k
  }
699
28.3k
  return dec_status;
700
28.3k
}
701
702
}  // namespace jxl