Coverage Report

Created: 2026-07-20 07:19

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