Coverage Report

Created: 2025-11-16 07:22

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
287k
                    bool *gradient_only) {
46
287k
  *num_props = 0;
47
287k
  bool has_wp = false;
48
287k
  bool has_non_wp = false;
49
287k
  *gradient_only = true;
50
694k
  const auto mark_property = [&](int32_t p) {
51
694k
    if (p == kWPProp) {
52
136k
      has_wp = true;
53
558k
    } else if (p >= kNumStaticProperties) {
54
280k
      has_non_wp = true;
55
280k
    }
56
694k
    if (p >= kNumStaticProperties && p != kGradientProp) {
57
344k
      *gradient_only = false;
58
344k
    }
59
694k
  };
60
287k
  FlatTree output;
61
287k
  std::queue<size_t> nodes;
62
287k
  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
1.50M
  while (!nodes.empty()) {
70
1.21M
    size_t cur = nodes.front();
71
1.21M
    nodes.pop();
72
    // Skip nodes that we can decide now, by jumping directly to their children.
73
1.30M
    while (global_tree[cur].property < kNumStaticProperties &&
74
1.07M
           global_tree[cur].property != -1) {
75
88.9k
      if (static_props[global_tree[cur].property] > global_tree[cur].splitval) {
76
48.8k
        cur = global_tree[cur].lchild;
77
48.8k
      } else {
78
40.1k
        cur = global_tree[cur].rchild;
79
40.1k
      }
80
88.9k
    }
81
1.21M
    FlatDecisionNode flat;
82
1.21M
    if (global_tree[cur].property == -1) {
83
981k
      flat.property0 = -1;
84
981k
      flat.childID = global_tree[cur].lchild;
85
981k
      flat.predictor = global_tree[cur].predictor;
86
981k
      flat.predictor_offset = global_tree[cur].predictor_offset;
87
981k
      flat.multiplier = global_tree[cur].multiplier;
88
981k
      *gradient_only &= flat.predictor == Predictor::Gradient;
89
981k
      has_wp |= flat.predictor == Predictor::Weighted;
90
981k
      has_non_wp |= flat.predictor != Predictor::Weighted;
91
981k
      output.push_back(flat);
92
981k
      continue;
93
981k
    }
94
231k
    flat.childID = output.size() + nodes.size() + 1;
95
96
231k
    flat.property0 = global_tree[cur].property;
97
231k
    *num_props = std::max<size_t>(flat.property0 + 1, *num_props);
98
231k
    flat.splitval0 = global_tree[cur].splitval;
99
100
694k
    for (size_t i = 0; i < 2; i++) {
101
462k
      size_t cur_child =
102
462k
          i == 0 ? global_tree[cur].lchild : global_tree[cur].rchild;
103
      // Skip nodes that we can decide now.
104
493k
      while (global_tree[cur_child].property < kNumStaticProperties &&
105
307k
             global_tree[cur_child].property != -1) {
106
30.1k
        if (static_props[global_tree[cur_child].property] >
107
30.1k
            global_tree[cur_child].splitval) {
108
16.4k
          cur_child = global_tree[cur_child].lchild;
109
16.4k
        } else {
110
13.6k
          cur_child = global_tree[cur_child].rchild;
111
13.6k
        }
112
30.1k
      }
113
      // We ended up in a leaf, add a placeholder decision and two copies of the
114
      // leaf.
115
462k
      if (global_tree[cur_child].property == -1) {
116
277k
        flat.properties[i] = 0;
117
277k
        flat.splitvals[i] = 0;
118
277k
        nodes.push(cur_child);
119
277k
        nodes.push(cur_child);
120
277k
      } else {
121
185k
        flat.properties[i] = global_tree[cur_child].property;
122
185k
        flat.splitvals[i] = global_tree[cur_child].splitval;
123
185k
        nodes.push(global_tree[cur_child].lchild);
124
185k
        nodes.push(global_tree[cur_child].rchild);
125
185k
        *num_props = std::max<size_t>(flat.properties[i] + 1, *num_props);
126
185k
      }
127
462k
    }
128
129
462k
    for (int16_t property : flat.properties) mark_property(property);
130
231k
    mark_property(flat.property0);
131
231k
    output.push_back(flat);
132
231k
  }
133
287k
  if (*num_props > kNumNonrefProperties) {
134
3.73k
    *num_props =
135
3.73k
        DivCeil(*num_props - kNumNonrefProperties, kExtraPropsPerChannel) *
136
3.73k
            kExtraPropsPerChannel +
137
3.73k
        kNumNonrefProperties;
138
283k
  } else {
139
283k
    *num_props = kNumNonrefProperties;
140
283k
  }
141
287k
  *use_wp = has_wp;
142
287k
  *wp_only = has_wp && !has_non_wp;
143
144
287k
  return output;
145
287k
}
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
258k
                                 uint32_t &fl_v) {
157
258k
  JxlMemoryManager *memory_manager = image->memory_manager();
158
258k
  Channel &channel = image->channel[chan];
159
160
258k
  std::array<pixel_type, kNumStaticProperties> static_props = {
161
258k
      {chan, static_cast<int>(group_id)}};
162
  // TODO(veluca): filter the tree according to static_props.
163
164
  // zero pixel channel? could happen
165
258k
  if (channel.w == 0 || channel.h == 0) return true;
166
167
258k
  bool tree_has_wp_prop_or_pred = false;
168
258k
  bool is_wp_only = false;
169
258k
  bool is_gradient_only = false;
170
258k
  size_t num_props;
171
258k
  FlatTree tree =
172
258k
      FilterTree(global_tree, static_props, &num_props,
173
258k
                 &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
760k
  for (auto &node : tree) {
178
760k
    if (node.property0 == -1) {
179
635k
      node.childID = context_map[node.childID];
180
635k
    }
181
760k
  }
182
183
258k
  JXL_DEBUG_V(3, "Decoded MA tree with %" PRIuS " nodes", tree.size());
184
185
  // MAANS decode
186
258k
  const auto make_pixel = [](uint64_t v, pixel_type multiplier,
187
219M
                             pixel_type_w offset) -> pixel_type {
188
219M
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
189
219M
    pixel_type_w val = static_cast<pixel_type_w>(UnpackSigned(v));
190
    // if it overflows, it overflows, and we have a problem anyway
191
219M
    return val * multiplier + offset;
192
219M
  };
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
42.0M
                             pixel_type_w offset) -> pixel_type {
188
42.0M
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
189
42.0M
    pixel_type_w val = static_cast<pixel_type_w>(UnpackSigned(v));
190
    // if it overflows, it overflows, and we have a problem anyway
191
42.0M
    return val * multiplier + offset;
192
42.0M
  };
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
177M
                             pixel_type_w offset) -> pixel_type {
188
177M
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
189
177M
    pixel_type_w val = static_cast<pixel_type_w>(UnpackSigned(v));
190
    // if it overflows, it overflows, and we have a problem anyway
191
177M
    return val * multiplier + offset;
192
177M
  };
193
194
258k
  if (tree.size() == 1) {
195
    // special optimized case: no meta-adaptation, so no need
196
    // to compute properties.
197
223k
    Predictor predictor = tree[0].predictor;
198
223k
    int64_t offset = tree[0].predictor_offset;
199
223k
    int32_t multiplier = tree[0].multiplier;
200
223k
    size_t ctx_id = tree[0].childID;
201
223k
    if (predictor == Predictor::Zero) {
202
149k
      uint32_t value;
203
149k
      if (reader->IsSingleValueAndAdvance(ctx_id, &value,
204
149k
                                          channel.w * channel.h)) {
205
        // Special-case: histogram has a single symbol, with no extra bits, and
206
        // we use ANS mode.
207
50.6k
        JXL_DEBUG_V(8, "Fastest track.");
208
50.6k
        pixel_type v = make_pixel(value, multiplier, offset);
209
1.23M
        for (size_t y = 0; y < channel.h; y++) {
210
1.18M
          pixel_type *JXL_RESTRICT r = channel.Row(y);
211
1.18M
          std::fill(r, r + channel.w, v);
212
1.18M
        }
213
98.6k
      } else {
214
98.6k
        JXL_DEBUG_V(8, "Fast track.");
215
98.6k
        if (multiplier == 1 && offset == 0) {
216
1.12M
          for (size_t y = 0; y < channel.h; y++) {
217
1.05M
            pixel_type *JXL_RESTRICT r = channel.Row(y);
218
58.0M
            for (size_t x = 0; x < channel.w; x++) {
219
56.9M
              uint32_t v =
220
56.9M
                  reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
221
56.9M
              r[x] = UnpackSigned(v);
222
56.9M
            }
223
1.05M
          }
224
70.7k
        } else {
225
614k
          for (size_t y = 0; y < channel.h; y++) {
226
586k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
227
30.1M
            for (size_t x = 0; x < channel.w; x++) {
228
29.5M
              uint32_t v =
229
29.5M
                  reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
230
29.5M
                                                                         br);
231
29.5M
              r[x] = make_pixel(v, multiplier, offset);
232
29.5M
            }
233
586k
          }
234
27.8k
        }
235
98.6k
      }
236
149k
      return true;
237
149k
    } else if (uses_lz77 && predictor == Predictor::Gradient && offset == 0 &&
238
1.14k
               multiplier == 1 && reader->IsHuffRleOnly()) {
239
78
      JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
240
78
      pixel_type_w sv = UnpackSigned(fl_v);
241
1.11k
      for (size_t y = 0; y < channel.h; y++) {
242
1.03k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
243
1.03k
        const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
244
1.03k
        const pixel_type *JXL_RESTRICT rtopleft =
245
1.03k
            (y ? channel.Row(y - 1) - 1 : r - 1);
246
1.03k
        pixel_type_w guess_0 = (y ? rtop[0] : 0);
247
1.03k
        if (fl_run == 0) {
248
1.03k
          reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
249
1.03k
                                                     &fl_run);
250
1.03k
          sv = UnpackSigned(fl_v);
251
1.03k
        } else {
252
0
          fl_run--;
253
0
        }
254
1.03k
        r[0] = sv + guess_0;
255
40.2k
        for (size_t x = 1; x < channel.w; x++) {
256
39.2k
          pixel_type left = r[x - 1];
257
39.2k
          pixel_type top = rtop[x];
258
39.2k
          pixel_type topleft = rtopleft[x];
259
39.2k
          pixel_type_w guess = ClampedGradient(top, left, topleft);
260
39.2k
          if (!fl_run) {
261
39.2k
            reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
262
39.2k
                                                       &fl_run);
263
39.2k
            sv = UnpackSigned(fl_v);
264
39.2k
          } else {
265
0
            fl_run--;
266
0
          }
267
39.2k
          r[x] = sv + guess;
268
39.2k
        }
269
1.03k
      }
270
78
      return true;
271
73.6k
    } else if (predictor == Predictor::Gradient && offset == 0 &&
272
5.43k
               multiplier == 1) {
273
4.50k
      JXL_DEBUG_V(8, "Gradient very fast track.");
274
4.50k
      const ptrdiff_t onerow = channel.plane.PixelsPerRow();
275
147k
      for (size_t y = 0; y < channel.h; y++) {
276
142k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
277
3.19M
        for (size_t x = 0; x < channel.w; x++) {
278
3.05M
          pixel_type left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
279
3.05M
          pixel_type top = (y ? *(r + x - onerow) : left);
280
3.05M
          pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
281
3.05M
          pixel_type guess = ClampedGradient(top, left, topleft);
282
3.05M
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
283
3.05M
              ctx_id, br);
284
3.05M
          r[x] = make_pixel(v, 1, guess);
285
3.05M
        }
286
142k
      }
287
4.50k
      return true;
288
4.50k
    }
289
223k
  }
290
291
  // Check if this tree is a WP-only tree with a small enough property value
292
  // range.
293
104k
  if (is_wp_only) {
294
15.1k
    is_wp_only = TreeToLookupTable(tree, tree_lut);
295
15.1k
  }
296
104k
  if (is_gradient_only) {
297
4.88k
    is_gradient_only = TreeToLookupTable(tree, tree_lut);
298
4.88k
  }
299
300
104k
  if (is_gradient_only) {
301
2.17k
    JXL_DEBUG_V(8, "Gradient fast track.");
302
2.17k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
303
102k
    for (size_t y = 0; y < channel.h; y++) {
304
100k
      pixel_type *JXL_RESTRICT r = channel.Row(y);
305
2.38M
      for (size_t x = 0; x < channel.w; x++) {
306
2.28M
        pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
307
2.28M
        pixel_type_w top = (y ? *(r + x - onerow) : left);
308
2.28M
        pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
309
2.28M
        int32_t guess = ClampedGradient(top, left, topleft);
310
2.28M
        uint32_t pos =
311
2.28M
            kPropRangeFast +
312
2.28M
            std::min<pixel_type_w>(
313
2.28M
                std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
314
2.28M
                kPropRangeFast - 1);
315
2.28M
        uint32_t ctx_id = tree_lut.context_lookup[pos];
316
2.28M
        uint64_t v =
317
2.28M
            reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
318
2.28M
        r[x] = make_pixel(v, 1, guess);
319
2.28M
      }
320
100k
    }
321
102k
  } else if (!uses_lz77 && is_wp_only && channel.w > 8) {
322
6.37k
    JXL_DEBUG_V(8, "WP fast track.");
323
6.37k
    weighted::State wp_state(wp_header, channel.w, channel.h);
324
6.37k
    Properties properties(1);
325
144k
    for (size_t y = 0; y < channel.h; y++) {
326
137k
      pixel_type *JXL_RESTRICT r = channel.Row(y);
327
137k
      const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
328
137k
      const pixel_type *JXL_RESTRICT rtoptop =
329
137k
          (y > 1 ? channel.Row(y - 2) : rtop);
330
137k
      const pixel_type *JXL_RESTRICT rtopleft =
331
137k
          (y ? channel.Row(y - 1) - 1 : r - 1);
332
137k
      const pixel_type *JXL_RESTRICT rtopright =
333
137k
          (y ? channel.Row(y - 1) + 1 : r - 1);
334
137k
      size_t x = 0;
335
137k
      {
336
137k
        size_t offset = 0;
337
137k
        pixel_type_w left = y ? rtop[x] : 0;
338
137k
        pixel_type_w toptop = y ? rtoptop[x] : 0;
339
137k
        pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
340
137k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
341
137k
            x, y, channel.w, left, left, topright, left, toptop, &properties,
342
137k
            offset);
343
137k
        uint32_t pos =
344
137k
            kPropRangeFast +
345
137k
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
346
137k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
347
137k
        uint64_t v =
348
137k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
349
137k
        r[x] = make_pixel(v, 1, guess);
350
137k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
351
137k
      }
352
8.25M
      for (x = 1; x + 1 < channel.w; x++) {
353
8.12M
        size_t offset = 0;
354
8.12M
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
355
8.12M
            x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
356
8.12M
            rtoptop[x], &properties, offset);
357
8.12M
        uint32_t pos =
358
8.12M
            kPropRangeFast +
359
8.12M
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
360
8.12M
        uint32_t ctx_id = tree_lut.context_lookup[pos];
361
8.12M
        uint64_t v =
362
8.12M
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
363
8.12M
        r[x] = make_pixel(v, 1, guess);
364
8.12M
        wp_state.UpdateErrors(r[x], x, y, channel.w);
365
8.12M
      }
366
137k
      {
367
137k
        size_t offset = 0;
368
137k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
369
137k
            x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
370
137k
            rtoptop[x], &properties, offset);
371
137k
        uint32_t pos =
372
137k
            kPropRangeFast +
373
137k
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
374
137k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
375
137k
        uint64_t v =
376
137k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
377
137k
        r[x] = make_pixel(v, 1, guess);
378
137k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
379
137k
      }
380
137k
    }
381
96.0k
  } else if (!tree_has_wp_prop_or_pred) {
382
    // special optimized case: the weighted predictor and its properties are not
383
    // used, so no need to compute weights and properties.
384
73.7k
    JXL_DEBUG_V(8, "Slow track.");
385
73.7k
    MATreeLookup tree_lookup(tree);
386
73.7k
    Properties properties = Properties(num_props);
387
73.7k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
388
73.7k
    JXL_ASSIGN_OR_RETURN(
389
73.7k
        Channel references,
390
73.7k
        Channel::Create(memory_manager,
391
73.7k
                        properties.size() - kNumNonrefProperties, channel.w));
392
1.75M
    for (size_t y = 0; y < channel.h; y++) {
393
1.67M
      pixel_type *JXL_RESTRICT p = channel.Row(y);
394
1.67M
      PrecomputeReferences(channel, y, *image, chan, &references);
395
1.67M
      InitPropsRow(&properties, static_props, y);
396
1.67M
      if (y > 1 && channel.w > 8 && references.w == 0) {
397
3.80M
        for (size_t x = 0; x < 2; x++) {
398
2.53M
          PredictionResult res =
399
2.53M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
400
2.53M
                              tree_lookup, references);
401
2.53M
          uint64_t v =
402
2.53M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
403
2.53M
          p[x] = make_pixel(v, res.multiplier, res.guess);
404
2.53M
        }
405
102M
        for (size_t x = 2; x < channel.w - 2; x++) {
406
101M
          PredictionResult res =
407
101M
              PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
408
101M
                                 tree_lookup, references);
409
101M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
410
101M
              res.context, br);
411
101M
          p[x] = make_pixel(v, res.multiplier, res.guess);
412
101M
        }
413
3.80M
        for (size_t x = channel.w - 2; x < channel.w; x++) {
414
2.53M
          PredictionResult res =
415
2.53M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
416
2.53M
                              tree_lookup, references);
417
2.53M
          uint64_t v =
418
2.53M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
419
2.53M
          p[x] = make_pixel(v, res.multiplier, res.guess);
420
2.53M
        }
421
1.26M
      } else {
422
12.8M
        for (size_t x = 0; x < channel.w; x++) {
423
12.4M
          PredictionResult res =
424
12.4M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
425
12.4M
                              tree_lookup, references);
426
12.4M
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
427
12.4M
              res.context, br);
428
12.4M
          p[x] = make_pixel(v, res.multiplier, res.guess);
429
12.4M
        }
430
408k
      }
431
1.67M
    }
432
73.7k
  } else {
433
22.2k
    JXL_DEBUG_V(8, "Slowest track.");
434
22.2k
    MATreeLookup tree_lookup(tree);
435
22.2k
    Properties properties = Properties(num_props);
436
22.2k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
437
22.2k
    JXL_ASSIGN_OR_RETURN(
438
22.2k
        Channel references,
439
22.2k
        Channel::Create(memory_manager,
440
22.2k
                        properties.size() - kNumNonrefProperties, channel.w));
441
22.2k
    weighted::State wp_state(wp_header, channel.w, channel.h);
442
1.45M
    for (size_t y = 0; y < channel.h; y++) {
443
1.43M
      pixel_type *JXL_RESTRICT p = channel.Row(y);
444
1.43M
      InitPropsRow(&properties, static_props, y);
445
1.43M
      PrecomputeReferences(channel, y, *image, chan, &references);
446
1.43M
      if (!uses_lz77 && y > 1 && channel.w > 8 && references.w == 0) {
447
1.24M
        for (size_t x = 0; x < 2; x++) {
448
832k
          PredictionResult res =
449
832k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
450
832k
                            tree_lookup, references, &wp_state);
451
832k
          uint64_t v =
452
832k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
453
832k
          p[x] = make_pixel(v, res.multiplier, res.guess);
454
832k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
455
832k
        }
456
45.9M
        for (size_t x = 2; x < channel.w - 2; x++) {
457
45.5M
          PredictionResult res =
458
45.5M
              PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
459
45.5M
                               tree_lookup, references, &wp_state);
460
45.5M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
461
45.5M
              res.context, br);
462
45.5M
          p[x] = make_pixel(v, res.multiplier, res.guess);
463
45.5M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
464
45.5M
        }
465
1.24M
        for (size_t x = channel.w - 2; x < channel.w; x++) {
466
832k
          PredictionResult res =
467
832k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
468
832k
                            tree_lookup, references, &wp_state);
469
832k
          uint64_t v =
470
832k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
471
832k
          p[x] = make_pixel(v, res.multiplier, res.guess);
472
832k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
473
832k
        }
474
1.01M
      } else {
475
10.9M
        for (size_t x = 0; x < channel.w; x++) {
476
9.94M
          PredictionResult res =
477
9.94M
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
478
9.94M
                            tree_lookup, references, &wp_state);
479
9.94M
          uint64_t v =
480
9.94M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
481
9.94M
          p[x] = make_pixel(v, res.multiplier, res.guess);
482
9.94M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
483
9.94M
        }
484
1.01M
      }
485
1.43M
    }
486
22.2k
  }
487
104k
  return true;
488
104k
}
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
61.1k
                                 uint32_t &fl_v) {
157
61.1k
  JxlMemoryManager *memory_manager = image->memory_manager();
158
61.1k
  Channel &channel = image->channel[chan];
159
160
61.1k
  std::array<pixel_type, kNumStaticProperties> static_props = {
161
61.1k
      {chan, static_cast<int>(group_id)}};
162
  // TODO(veluca): filter the tree according to static_props.
163
164
  // zero pixel channel? could happen
165
61.1k
  if (channel.w == 0 || channel.h == 0) return true;
166
167
61.1k
  bool tree_has_wp_prop_or_pred = false;
168
61.1k
  bool is_wp_only = false;
169
61.1k
  bool is_gradient_only = false;
170
61.1k
  size_t num_props;
171
61.1k
  FlatTree tree =
172
61.1k
      FilterTree(global_tree, static_props, &num_props,
173
61.1k
                 &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
79.6k
  for (auto &node : tree) {
178
79.6k
    if (node.property0 == -1) {
179
75.0k
      node.childID = context_map[node.childID];
180
75.0k
    }
181
79.6k
  }
182
183
61.1k
  JXL_DEBUG_V(3, "Decoded MA tree with %" PRIuS " nodes", tree.size());
184
185
  // MAANS decode
186
61.1k
  const auto make_pixel = [](uint64_t v, pixel_type multiplier,
187
61.1k
                             pixel_type_w offset) -> pixel_type {
188
61.1k
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
189
61.1k
    pixel_type_w val = static_cast<pixel_type_w>(UnpackSigned(v));
190
    // if it overflows, it overflows, and we have a problem anyway
191
61.1k
    return val * multiplier + offset;
192
61.1k
  };
193
194
61.1k
  if (tree.size() == 1) {
195
    // special optimized case: no meta-adaptation, so no need
196
    // to compute properties.
197
57.2k
    Predictor predictor = tree[0].predictor;
198
57.2k
    int64_t offset = tree[0].predictor_offset;
199
57.2k
    int32_t multiplier = tree[0].multiplier;
200
57.2k
    size_t ctx_id = tree[0].childID;
201
57.2k
    if (predictor == Predictor::Zero) {
202
42.7k
      uint32_t value;
203
42.7k
      if (reader->IsSingleValueAndAdvance(ctx_id, &value,
204
42.7k
                                          channel.w * channel.h)) {
205
        // Special-case: histogram has a single symbol, with no extra bits, and
206
        // we use ANS mode.
207
24.6k
        JXL_DEBUG_V(8, "Fastest track.");
208
24.6k
        pixel_type v = make_pixel(value, multiplier, offset);
209
580k
        for (size_t y = 0; y < channel.h; y++) {
210
555k
          pixel_type *JXL_RESTRICT r = channel.Row(y);
211
555k
          std::fill(r, r + channel.w, v);
212
555k
        }
213
24.6k
      } else {
214
18.1k
        JXL_DEBUG_V(8, "Fast track.");
215
18.1k
        if (multiplier == 1 && offset == 0) {
216
89.6k
          for (size_t y = 0; y < channel.h; y++) {
217
81.3k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
218
6.25M
            for (size_t x = 0; x < channel.w; x++) {
219
6.17M
              uint32_t v =
220
6.17M
                  reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
221
6.17M
              r[x] = UnpackSigned(v);
222
6.17M
            }
223
81.3k
          }
224
9.81k
        } else {
225
224k
          for (size_t y = 0; y < channel.h; y++) {
226
214k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
227
10.3M
            for (size_t x = 0; x < channel.w; x++) {
228
10.1M
              uint32_t v =
229
10.1M
                  reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
230
10.1M
                                                                         br);
231
10.1M
              r[x] = make_pixel(v, multiplier, offset);
232
10.1M
            }
233
214k
          }
234
9.81k
        }
235
18.1k
      }
236
42.7k
      return true;
237
42.7k
    } else if (uses_lz77 && predictor == Predictor::Gradient && offset == 0 &&
238
1.14k
               multiplier == 1 && reader->IsHuffRleOnly()) {
239
78
      JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
240
78
      pixel_type_w sv = UnpackSigned(fl_v);
241
1.11k
      for (size_t y = 0; y < channel.h; y++) {
242
1.03k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
243
1.03k
        const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
244
1.03k
        const pixel_type *JXL_RESTRICT rtopleft =
245
1.03k
            (y ? channel.Row(y - 1) - 1 : r - 1);
246
1.03k
        pixel_type_w guess_0 = (y ? rtop[0] : 0);
247
1.03k
        if (fl_run == 0) {
248
1.03k
          reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
249
1.03k
                                                     &fl_run);
250
1.03k
          sv = UnpackSigned(fl_v);
251
1.03k
        } else {
252
0
          fl_run--;
253
0
        }
254
1.03k
        r[0] = sv + guess_0;
255
40.2k
        for (size_t x = 1; x < channel.w; x++) {
256
39.2k
          pixel_type left = r[x - 1];
257
39.2k
          pixel_type top = rtop[x];
258
39.2k
          pixel_type topleft = rtopleft[x];
259
39.2k
          pixel_type_w guess = ClampedGradient(top, left, topleft);
260
39.2k
          if (!fl_run) {
261
39.2k
            reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
262
39.2k
                                                       &fl_run);
263
39.2k
            sv = UnpackSigned(fl_v);
264
39.2k
          } else {
265
0
            fl_run--;
266
0
          }
267
39.2k
          r[x] = sv + guess;
268
39.2k
        }
269
1.03k
      }
270
78
      return true;
271
14.4k
    } else if (predictor == Predictor::Gradient && offset == 0 &&
272
1.07k
               multiplier == 1) {
273
624
      JXL_DEBUG_V(8, "Gradient very fast track.");
274
624
      const ptrdiff_t onerow = channel.plane.PixelsPerRow();
275
12.5k
      for (size_t y = 0; y < channel.h; y++) {
276
11.9k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
277
1.18M
        for (size_t x = 0; x < channel.w; x++) {
278
1.17M
          pixel_type left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
279
1.17M
          pixel_type top = (y ? *(r + x - onerow) : left);
280
1.17M
          pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
281
1.17M
          pixel_type guess = ClampedGradient(top, left, topleft);
282
1.17M
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
283
1.17M
              ctx_id, br);
284
1.17M
          r[x] = make_pixel(v, 1, guess);
285
1.17M
        }
286
11.9k
      }
287
624
      return true;
288
624
    }
289
57.2k
  }
290
291
  // Check if this tree is a WP-only tree with a small enough property value
292
  // range.
293
17.6k
  if (is_wp_only) {
294
1.53k
    is_wp_only = TreeToLookupTable(tree, tree_lut);
295
1.53k
  }
296
17.6k
  if (is_gradient_only) {
297
1.39k
    is_gradient_only = TreeToLookupTable(tree, tree_lut);
298
1.39k
  }
299
300
17.6k
  if (is_gradient_only) {
301
4
    JXL_DEBUG_V(8, "Gradient fast track.");
302
4
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
303
293
    for (size_t y = 0; y < channel.h; y++) {
304
289
      pixel_type *JXL_RESTRICT r = channel.Row(y);
305
54.0k
      for (size_t x = 0; x < channel.w; x++) {
306
53.8k
        pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
307
53.8k
        pixel_type_w top = (y ? *(r + x - onerow) : left);
308
53.8k
        pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
309
53.8k
        int32_t guess = ClampedGradient(top, left, topleft);
310
53.8k
        uint32_t pos =
311
53.8k
            kPropRangeFast +
312
53.8k
            std::min<pixel_type_w>(
313
53.8k
                std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
314
53.8k
                kPropRangeFast - 1);
315
53.8k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
316
53.8k
        uint64_t v =
317
53.8k
            reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
318
53.8k
        r[x] = make_pixel(v, 1, guess);
319
53.8k
      }
320
289
    }
321
17.6k
  } else if (!uses_lz77 && is_wp_only && channel.w > 8) {
322
0
    JXL_DEBUG_V(8, "WP fast track.");
323
0
    weighted::State wp_state(wp_header, channel.w, channel.h);
324
0
    Properties properties(1);
325
0
    for (size_t y = 0; y < channel.h; y++) {
326
0
      pixel_type *JXL_RESTRICT r = channel.Row(y);
327
0
      const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
328
0
      const pixel_type *JXL_RESTRICT rtoptop =
329
0
          (y > 1 ? channel.Row(y - 2) : rtop);
330
0
      const pixel_type *JXL_RESTRICT rtopleft =
331
0
          (y ? channel.Row(y - 1) - 1 : r - 1);
332
0
      const pixel_type *JXL_RESTRICT rtopright =
333
0
          (y ? channel.Row(y - 1) + 1 : r - 1);
334
0
      size_t x = 0;
335
0
      {
336
0
        size_t offset = 0;
337
0
        pixel_type_w left = y ? rtop[x] : 0;
338
0
        pixel_type_w toptop = y ? rtoptop[x] : 0;
339
0
        pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
340
0
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
341
0
            x, y, channel.w, left, left, topright, left, toptop, &properties,
342
0
            offset);
343
0
        uint32_t pos =
344
0
            kPropRangeFast +
345
0
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
346
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
347
0
        uint64_t v =
348
0
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
349
0
        r[x] = make_pixel(v, 1, guess);
350
0
        wp_state.UpdateErrors(r[x], x, y, channel.w);
351
0
      }
352
0
      for (x = 1; x + 1 < channel.w; x++) {
353
0
        size_t offset = 0;
354
0
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
355
0
            x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
356
0
            rtoptop[x], &properties, offset);
357
0
        uint32_t pos =
358
0
            kPropRangeFast +
359
0
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
360
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
361
0
        uint64_t v =
362
0
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
363
0
        r[x] = make_pixel(v, 1, guess);
364
0
        wp_state.UpdateErrors(r[x], x, y, channel.w);
365
0
      }
366
0
      {
367
0
        size_t offset = 0;
368
0
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
369
0
            x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
370
0
            rtoptop[x], &properties, offset);
371
0
        uint32_t pos =
372
0
            kPropRangeFast +
373
0
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
374
0
        uint32_t ctx_id = tree_lut.context_lookup[pos];
375
0
        uint64_t v =
376
0
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
377
0
        r[x] = make_pixel(v, 1, guess);
378
0
        wp_state.UpdateErrors(r[x], x, y, channel.w);
379
0
      }
380
0
    }
381
17.6k
  } else if (!tree_has_wp_prop_or_pred) {
382
    // special optimized case: the weighted predictor and its properties are not
383
    // used, so no need to compute weights and properties.
384
15.3k
    JXL_DEBUG_V(8, "Slow track.");
385
15.3k
    MATreeLookup tree_lookup(tree);
386
15.3k
    Properties properties = Properties(num_props);
387
15.3k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
388
15.3k
    JXL_ASSIGN_OR_RETURN(
389
15.3k
        Channel references,
390
15.3k
        Channel::Create(memory_manager,
391
15.3k
                        properties.size() - kNumNonrefProperties, channel.w));
392
338k
    for (size_t y = 0; y < channel.h; y++) {
393
323k
      pixel_type *JXL_RESTRICT p = channel.Row(y);
394
323k
      PrecomputeReferences(channel, y, *image, chan, &references);
395
323k
      InitPropsRow(&properties, static_props, y);
396
323k
      if (y > 1 && channel.w > 8 && references.w == 0) {
397
794k
        for (size_t x = 0; x < 2; x++) {
398
529k
          PredictionResult res =
399
529k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
400
529k
                              tree_lookup, references);
401
529k
          uint64_t v =
402
529k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
403
529k
          p[x] = make_pixel(v, res.multiplier, res.guess);
404
529k
        }
405
21.1M
        for (size_t x = 2; x < channel.w - 2; x++) {
406
20.9M
          PredictionResult res =
407
20.9M
              PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
408
20.9M
                                 tree_lookup, references);
409
20.9M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
410
20.9M
              res.context, br);
411
20.9M
          p[x] = make_pixel(v, res.multiplier, res.guess);
412
20.9M
        }
413
794k
        for (size_t x = channel.w - 2; x < channel.w; x++) {
414
529k
          PredictionResult res =
415
529k
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
416
529k
                              tree_lookup, references);
417
529k
          uint64_t v =
418
529k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
419
529k
          p[x] = make_pixel(v, res.multiplier, res.guess);
420
529k
        }
421
264k
      } else {
422
4.38M
        for (size_t x = 0; x < channel.w; x++) {
423
4.32M
          PredictionResult res =
424
4.32M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
425
4.32M
                              tree_lookup, references);
426
4.32M
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
427
4.32M
              res.context, br);
428
4.32M
          p[x] = make_pixel(v, res.multiplier, res.guess);
429
4.32M
        }
430
58.6k
      }
431
323k
    }
432
15.3k
  } else {
433
2.29k
    JXL_DEBUG_V(8, "Slowest track.");
434
2.29k
    MATreeLookup tree_lookup(tree);
435
2.29k
    Properties properties = Properties(num_props);
436
2.29k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
437
2.29k
    JXL_ASSIGN_OR_RETURN(
438
2.29k
        Channel references,
439
2.29k
        Channel::Create(memory_manager,
440
2.29k
                        properties.size() - kNumNonrefProperties, channel.w));
441
2.29k
    weighted::State wp_state(wp_header, channel.w, channel.h);
442
67.8k
    for (size_t y = 0; y < channel.h; y++) {
443
65.5k
      pixel_type *JXL_RESTRICT p = channel.Row(y);
444
65.5k
      InitPropsRow(&properties, static_props, y);
445
65.5k
      PrecomputeReferences(channel, y, *image, chan, &references);
446
65.5k
      if (!uses_lz77 && y > 1 && channel.w > 8 && references.w == 0) {
447
0
        for (size_t x = 0; x < 2; x++) {
448
0
          PredictionResult res =
449
0
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
450
0
                            tree_lookup, references, &wp_state);
451
0
          uint64_t v =
452
0
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
453
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
454
0
          wp_state.UpdateErrors(p[x], x, y, channel.w);
455
0
        }
456
0
        for (size_t x = 2; x < channel.w - 2; x++) {
457
0
          PredictionResult res =
458
0
              PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
459
0
                               tree_lookup, references, &wp_state);
460
0
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
461
0
              res.context, br);
462
0
          p[x] = make_pixel(v, res.multiplier, res.guess);
463
0
          wp_state.UpdateErrors(p[x], x, y, channel.w);
464
0
        }
465
0
        for (size_t x = channel.w - 2; x < channel.w; 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
65.5k
      } else {
475
4.36M
        for (size_t x = 0; x < channel.w; x++) {
476
4.30M
          PredictionResult res =
477
4.30M
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
478
4.30M
                            tree_lookup, references, &wp_state);
479
4.30M
          uint64_t v =
480
4.30M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
481
4.30M
          p[x] = make_pixel(v, res.multiplier, res.guess);
482
4.30M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
483
4.30M
        }
484
65.5k
      }
485
65.5k
    }
486
2.29k
  }
487
17.6k
  return true;
488
17.6k
}
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
197k
                                 uint32_t &fl_v) {
157
197k
  JxlMemoryManager *memory_manager = image->memory_manager();
158
197k
  Channel &channel = image->channel[chan];
159
160
197k
  std::array<pixel_type, kNumStaticProperties> static_props = {
161
197k
      {chan, static_cast<int>(group_id)}};
162
  // TODO(veluca): filter the tree according to static_props.
163
164
  // zero pixel channel? could happen
165
197k
  if (channel.w == 0 || channel.h == 0) return true;
166
167
197k
  bool tree_has_wp_prop_or_pred = false;
168
197k
  bool is_wp_only = false;
169
197k
  bool is_gradient_only = false;
170
197k
  size_t num_props;
171
197k
  FlatTree tree =
172
197k
      FilterTree(global_tree, static_props, &num_props,
173
197k
                 &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
681k
  for (auto &node : tree) {
178
681k
    if (node.property0 == -1) {
179
560k
      node.childID = context_map[node.childID];
180
560k
    }
181
681k
  }
182
183
197k
  JXL_DEBUG_V(3, "Decoded MA tree with %" PRIuS " nodes", tree.size());
184
185
  // MAANS decode
186
197k
  const auto make_pixel = [](uint64_t v, pixel_type multiplier,
187
197k
                             pixel_type_w offset) -> pixel_type {
188
197k
    JXL_DASSERT((v & 0xFFFFFFFF) == v);
189
197k
    pixel_type_w val = static_cast<pixel_type_w>(UnpackSigned(v));
190
    // if it overflows, it overflows, and we have a problem anyway
191
197k
    return val * multiplier + offset;
192
197k
  };
193
194
197k
  if (tree.size() == 1) {
195
    // special optimized case: no meta-adaptation, so no need
196
    // to compute properties.
197
165k
    Predictor predictor = tree[0].predictor;
198
165k
    int64_t offset = tree[0].predictor_offset;
199
165k
    int32_t multiplier = tree[0].multiplier;
200
165k
    size_t ctx_id = tree[0].childID;
201
165k
    if (predictor == Predictor::Zero) {
202
106k
      uint32_t value;
203
106k
      if (reader->IsSingleValueAndAdvance(ctx_id, &value,
204
106k
                                          channel.w * channel.h)) {
205
        // Special-case: histogram has a single symbol, with no extra bits, and
206
        // we use ANS mode.
207
26.0k
        JXL_DEBUG_V(8, "Fastest track.");
208
26.0k
        pixel_type v = make_pixel(value, multiplier, offset);
209
653k
        for (size_t y = 0; y < channel.h; y++) {
210
627k
          pixel_type *JXL_RESTRICT r = channel.Row(y);
211
627k
          std::fill(r, r + channel.w, v);
212
627k
        }
213
80.5k
      } else {
214
80.5k
        JXL_DEBUG_V(8, "Fast track.");
215
80.5k
        if (multiplier == 1 && offset == 0) {
216
1.03M
          for (size_t y = 0; y < channel.h; y++) {
217
970k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
218
51.7M
            for (size_t x = 0; x < channel.w; x++) {
219
50.8M
              uint32_t v =
220
50.8M
                  reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
221
50.8M
              r[x] = UnpackSigned(v);
222
50.8M
            }
223
970k
          }
224
62.4k
        } else {
225
390k
          for (size_t y = 0; y < channel.h; y++) {
226
372k
            pixel_type *JXL_RESTRICT r = channel.Row(y);
227
19.8M
            for (size_t x = 0; x < channel.w; x++) {
228
19.4M
              uint32_t v =
229
19.4M
                  reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
230
19.4M
                                                                         br);
231
19.4M
              r[x] = make_pixel(v, multiplier, offset);
232
19.4M
            }
233
372k
          }
234
18.0k
        }
235
80.5k
      }
236
106k
      return true;
237
106k
    } else if (uses_lz77 && predictor == Predictor::Gradient && offset == 0 &&
238
0
               multiplier == 1 && reader->IsHuffRleOnly()) {
239
0
      JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
240
0
      pixel_type_w sv = UnpackSigned(fl_v);
241
0
      for (size_t y = 0; y < channel.h; y++) {
242
0
        pixel_type *JXL_RESTRICT r = channel.Row(y);
243
0
        const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
244
0
        const pixel_type *JXL_RESTRICT rtopleft =
245
0
            (y ? channel.Row(y - 1) - 1 : r - 1);
246
0
        pixel_type_w guess_0 = (y ? rtop[0] : 0);
247
0
        if (fl_run == 0) {
248
0
          reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
249
0
                                                     &fl_run);
250
0
          sv = UnpackSigned(fl_v);
251
0
        } else {
252
0
          fl_run--;
253
0
        }
254
0
        r[0] = sv + guess_0;
255
0
        for (size_t x = 1; x < channel.w; x++) {
256
0
          pixel_type left = r[x - 1];
257
0
          pixel_type top = rtop[x];
258
0
          pixel_type topleft = rtopleft[x];
259
0
          pixel_type_w guess = ClampedGradient(top, left, topleft);
260
0
          if (!fl_run) {
261
0
            reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &fl_v,
262
0
                                                       &fl_run);
263
0
            sv = UnpackSigned(fl_v);
264
0
          } else {
265
0
            fl_run--;
266
0
          }
267
0
          r[x] = sv + guess;
268
0
        }
269
0
      }
270
0
      return true;
271
59.1k
    } else if (predictor == Predictor::Gradient && offset == 0 &&
272
4.36k
               multiplier == 1) {
273
3.87k
      JXL_DEBUG_V(8, "Gradient very fast track.");
274
3.87k
      const ptrdiff_t onerow = channel.plane.PixelsPerRow();
275
134k
      for (size_t y = 0; y < channel.h; y++) {
276
130k
        pixel_type *JXL_RESTRICT r = channel.Row(y);
277
2.01M
        for (size_t x = 0; x < channel.w; x++) {
278
1.87M
          pixel_type left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
279
1.87M
          pixel_type top = (y ? *(r + x - onerow) : left);
280
1.87M
          pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
281
1.87M
          pixel_type guess = ClampedGradient(top, left, topleft);
282
1.87M
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
283
1.87M
              ctx_id, br);
284
1.87M
          r[x] = make_pixel(v, 1, guess);
285
1.87M
        }
286
130k
      }
287
3.87k
      return true;
288
3.87k
    }
289
165k
  }
290
291
  // Check if this tree is a WP-only tree with a small enough property value
292
  // range.
293
86.9k
  if (is_wp_only) {
294
13.6k
    is_wp_only = TreeToLookupTable(tree, tree_lut);
295
13.6k
  }
296
86.9k
  if (is_gradient_only) {
297
3.49k
    is_gradient_only = TreeToLookupTable(tree, tree_lut);
298
3.49k
  }
299
300
86.9k
  if (is_gradient_only) {
301
2.17k
    JXL_DEBUG_V(8, "Gradient fast track.");
302
2.17k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
303
102k
    for (size_t y = 0; y < channel.h; y++) {
304
100k
      pixel_type *JXL_RESTRICT r = channel.Row(y);
305
2.33M
      for (size_t x = 0; x < channel.w; x++) {
306
2.23M
        pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
307
2.23M
        pixel_type_w top = (y ? *(r + x - onerow) : left);
308
2.23M
        pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
309
2.23M
        int32_t guess = ClampedGradient(top, left, topleft);
310
2.23M
        uint32_t pos =
311
2.23M
            kPropRangeFast +
312
2.23M
            std::min<pixel_type_w>(
313
2.23M
                std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
314
2.23M
                kPropRangeFast - 1);
315
2.23M
        uint32_t ctx_id = tree_lut.context_lookup[pos];
316
2.23M
        uint64_t v =
317
2.23M
            reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
318
2.23M
        r[x] = make_pixel(v, 1, guess);
319
2.23M
      }
320
100k
    }
321
84.7k
  } else if (!uses_lz77 && is_wp_only && channel.w > 8) {
322
6.37k
    JXL_DEBUG_V(8, "WP fast track.");
323
6.37k
    weighted::State wp_state(wp_header, channel.w, channel.h);
324
6.37k
    Properties properties(1);
325
144k
    for (size_t y = 0; y < channel.h; y++) {
326
137k
      pixel_type *JXL_RESTRICT r = channel.Row(y);
327
137k
      const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
328
137k
      const pixel_type *JXL_RESTRICT rtoptop =
329
137k
          (y > 1 ? channel.Row(y - 2) : rtop);
330
137k
      const pixel_type *JXL_RESTRICT rtopleft =
331
137k
          (y ? channel.Row(y - 1) - 1 : r - 1);
332
137k
      const pixel_type *JXL_RESTRICT rtopright =
333
137k
          (y ? channel.Row(y - 1) + 1 : r - 1);
334
137k
      size_t x = 0;
335
137k
      {
336
137k
        size_t offset = 0;
337
137k
        pixel_type_w left = y ? rtop[x] : 0;
338
137k
        pixel_type_w toptop = y ? rtoptop[x] : 0;
339
137k
        pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
340
137k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
341
137k
            x, y, channel.w, left, left, topright, left, toptop, &properties,
342
137k
            offset);
343
137k
        uint32_t pos =
344
137k
            kPropRangeFast +
345
137k
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
346
137k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
347
137k
        uint64_t v =
348
137k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
349
137k
        r[x] = make_pixel(v, 1, guess);
350
137k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
351
137k
      }
352
8.25M
      for (x = 1; x + 1 < channel.w; x++) {
353
8.12M
        size_t offset = 0;
354
8.12M
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
355
8.12M
            x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
356
8.12M
            rtoptop[x], &properties, offset);
357
8.12M
        uint32_t pos =
358
8.12M
            kPropRangeFast +
359
8.12M
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
360
8.12M
        uint32_t ctx_id = tree_lut.context_lookup[pos];
361
8.12M
        uint64_t v =
362
8.12M
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
363
8.12M
        r[x] = make_pixel(v, 1, guess);
364
8.12M
        wp_state.UpdateErrors(r[x], x, y, channel.w);
365
8.12M
      }
366
137k
      {
367
137k
        size_t offset = 0;
368
137k
        int32_t guess = wp_state.Predict</*compute_properties=*/true>(
369
137k
            x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
370
137k
            rtoptop[x], &properties, offset);
371
137k
        uint32_t pos =
372
137k
            kPropRangeFast +
373
137k
            jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1);
374
137k
        uint32_t ctx_id = tree_lut.context_lookup[pos];
375
137k
        uint64_t v =
376
137k
            reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
377
137k
        r[x] = make_pixel(v, 1, guess);
378
137k
        wp_state.UpdateErrors(r[x], x, y, channel.w);
379
137k
      }
380
137k
    }
381
78.3k
  } else if (!tree_has_wp_prop_or_pred) {
382
    // special optimized case: the weighted predictor and its properties are not
383
    // used, so no need to compute weights and properties.
384
58.4k
    JXL_DEBUG_V(8, "Slow track.");
385
58.4k
    MATreeLookup tree_lookup(tree);
386
58.4k
    Properties properties = Properties(num_props);
387
58.4k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
388
58.4k
    JXL_ASSIGN_OR_RETURN(
389
58.4k
        Channel references,
390
58.4k
        Channel::Create(memory_manager,
391
58.4k
                        properties.size() - kNumNonrefProperties, channel.w));
392
1.41M
    for (size_t y = 0; y < channel.h; y++) {
393
1.35M
      pixel_type *JXL_RESTRICT p = channel.Row(y);
394
1.35M
      PrecomputeReferences(channel, y, *image, chan, &references);
395
1.35M
      InitPropsRow(&properties, static_props, y);
396
1.35M
      if (y > 1 && channel.w > 8 && references.w == 0) {
397
3.01M
        for (size_t x = 0; x < 2; x++) {
398
2.00M
          PredictionResult res =
399
2.00M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
400
2.00M
                              tree_lookup, references);
401
2.00M
          uint64_t v =
402
2.00M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
403
2.00M
          p[x] = make_pixel(v, res.multiplier, res.guess);
404
2.00M
        }
405
81.8M
        for (size_t x = 2; x < channel.w - 2; x++) {
406
80.8M
          PredictionResult res =
407
80.8M
              PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
408
80.8M
                                 tree_lookup, references);
409
80.8M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
410
80.8M
              res.context, br);
411
80.8M
          p[x] = make_pixel(v, res.multiplier, res.guess);
412
80.8M
        }
413
3.01M
        for (size_t x = channel.w - 2; x < channel.w; x++) {
414
2.00M
          PredictionResult res =
415
2.00M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
416
2.00M
                              tree_lookup, references);
417
2.00M
          uint64_t v =
418
2.00M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
419
2.00M
          p[x] = make_pixel(v, res.multiplier, res.guess);
420
2.00M
        }
421
1.00M
      } else {
422
8.43M
        for (size_t x = 0; x < channel.w; x++) {
423
8.08M
          PredictionResult res =
424
8.08M
              PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
425
8.08M
                              tree_lookup, references);
426
8.08M
          uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
427
8.08M
              res.context, br);
428
8.08M
          p[x] = make_pixel(v, res.multiplier, res.guess);
429
8.08M
        }
430
349k
      }
431
1.35M
    }
432
58.4k
  } else {
433
19.9k
    JXL_DEBUG_V(8, "Slowest track.");
434
19.9k
    MATreeLookup tree_lookup(tree);
435
19.9k
    Properties properties = Properties(num_props);
436
19.9k
    const ptrdiff_t onerow = channel.plane.PixelsPerRow();
437
19.9k
    JXL_ASSIGN_OR_RETURN(
438
19.9k
        Channel references,
439
19.9k
        Channel::Create(memory_manager,
440
19.9k
                        properties.size() - kNumNonrefProperties, channel.w));
441
19.9k
    weighted::State wp_state(wp_header, channel.w, channel.h);
442
1.38M
    for (size_t y = 0; y < channel.h; y++) {
443
1.36M
      pixel_type *JXL_RESTRICT p = channel.Row(y);
444
1.36M
      InitPropsRow(&properties, static_props, y);
445
1.36M
      PrecomputeReferences(channel, y, *image, chan, &references);
446
1.36M
      if (!uses_lz77 && y > 1 && channel.w > 8 && references.w == 0) {
447
1.24M
        for (size_t x = 0; x < 2; x++) {
448
832k
          PredictionResult res =
449
832k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
450
832k
                            tree_lookup, references, &wp_state);
451
832k
          uint64_t v =
452
832k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
453
832k
          p[x] = make_pixel(v, res.multiplier, res.guess);
454
832k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
455
832k
        }
456
45.9M
        for (size_t x = 2; x < channel.w - 2; x++) {
457
45.5M
          PredictionResult res =
458
45.5M
              PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
459
45.5M
                               tree_lookup, references, &wp_state);
460
45.5M
          uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
461
45.5M
              res.context, br);
462
45.5M
          p[x] = make_pixel(v, res.multiplier, res.guess);
463
45.5M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
464
45.5M
        }
465
1.24M
        for (size_t x = channel.w - 2; x < channel.w; x++) {
466
832k
          PredictionResult res =
467
832k
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
468
832k
                            tree_lookup, references, &wp_state);
469
832k
          uint64_t v =
470
832k
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
471
832k
          p[x] = make_pixel(v, res.multiplier, res.guess);
472
832k
          wp_state.UpdateErrors(p[x], x, y, channel.w);
473
832k
        }
474
952k
      } else {
475
6.59M
        for (size_t x = 0; x < channel.w; x++) {
476
5.64M
          PredictionResult res =
477
5.64M
              PredictTreeWP(&properties, channel.w, p + x, onerow, x, y,
478
5.64M
                            tree_lookup, references, &wp_state);
479
5.64M
          uint64_t v =
480
5.64M
              reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
481
5.64M
          p[x] = make_pixel(v, res.multiplier, res.guess);
482
5.64M
          wp_state.UpdateErrors(p[x], x, y, channel.w);
483
5.64M
        }
484
952k
      }
485
1.36M
    }
486
19.9k
  }
487
86.9k
  return true;
488
86.9k
}
489
}  // namespace detail
490
491
Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
492
                                 const std::vector<uint8_t> &context_map,
493
                                 const Tree &global_tree,
494
                                 const weighted::Header &wp_header,
495
                                 pixel_type chan, size_t group_id,
496
                                 TreeLut<uint8_t, false, false> &tree_lut,
497
                                 Image *image, uint32_t &fl_run,
498
258k
                                 uint32_t &fl_v) {
499
258k
  if (reader->UsesLZ77()) {
500
61.1k
    return detail::DecodeModularChannelMAANS</*uses_lz77=*/true>(
501
61.1k
        br, reader, context_map, global_tree, wp_header, chan, group_id,
502
61.1k
        tree_lut, image, fl_run, fl_v);
503
197k
  } else {
504
197k
    return detail::DecodeModularChannelMAANS</*uses_lz77=*/false>(
505
197k
        br, reader, context_map, global_tree, wp_header, chan, group_id,
506
197k
        tree_lut, image, fl_run, fl_v);
507
197k
  }
508
258k
}
509
510
409k
GroupHeader::GroupHeader() { Bundle::Init(this); }
511
512
Status ValidateChannelDimensions(const Image &image,
513
65.4k
                                 const ModularOptions &options) {
514
65.4k
  size_t nb_channels = image.channel.size();
515
130k
  for (bool is_dc : {true, false}) {
516
130k
    size_t group_dim = options.group_dim * (is_dc ? kBlockDim : 1);
517
130k
    size_t c = image.nb_meta_channels;
518
791k
    for (; c < nb_channels; c++) {
519
662k
      const Channel &ch = image.channel[c];
520
662k
      if (ch.w > options.group_dim || ch.h > options.group_dim) break;
521
662k
    }
522
141k
    for (; c < nb_channels; c++) {
523
10.6k
      const Channel &ch = image.channel[c];
524
10.6k
      if (ch.w == 0 || ch.h == 0) continue;  // skip empty
525
9.96k
      bool is_dc_channel = std::min(ch.hshift, ch.vshift) >= 3;
526
9.96k
      if (is_dc_channel != is_dc) continue;
527
4.99k
      size_t tile_dim = group_dim >> std::max(ch.hshift, ch.vshift);
528
4.99k
      if (tile_dim == 0) {
529
8
        return JXL_FAILURE("Inconsistent transforms");
530
8
      }
531
4.99k
    }
532
130k
  }
533
65.4k
  return true;
534
65.4k
}
535
536
Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
537
                     size_t group_id, ModularOptions *options,
538
                     const Tree *global_tree, const ANSCode *global_code,
539
                     const std::vector<uint8_t> *global_ctx_map,
540
108k
                     const bool allow_truncated_group) {
541
108k
  if (image.channel.empty()) return true;
542
77.4k
  JxlMemoryManager *memory_manager = image.memory_manager();
543
544
  // decode transforms
545
77.4k
  Status status = Bundle::Read(br, &header);
546
77.4k
  if (!allow_truncated_group) JXL_RETURN_IF_ERROR(status);
547
64.7k
  if (status.IsFatalError()) return status;
548
64.7k
  if (!br->AllReadsWithinBounds()) {
549
    // Don't do/undo transforms if header is incomplete.
550
0
    header.transforms.clear();
551
0
    image.transform = header.transforms;
552
0
    for (auto &ch : image.channel) {
553
0
      ZeroFillImage(&ch.plane);
554
0
    }
555
0
    return JXL_NOT_ENOUGH_BYTES("Read overrun before ModularDecode");
556
0
  }
557
558
64.7k
  JXL_DEBUG_V(3, "Image data underwent %" PRIuS " transformations: ",
559
64.7k
              header.transforms.size());
560
64.7k
  image.transform = header.transforms;
561
64.7k
  for (Transform &transform : image.transform) {
562
19.8k
    JXL_RETURN_IF_ERROR(transform.MetaApply(image));
563
19.8k
  }
564
63.9k
  if (image.error) {
565
0
    return JXL_FAILURE("Corrupt file. Aborting.");
566
0
  }
567
63.9k
  JXL_RETURN_IF_ERROR(ValidateChannelDimensions(image, *options));
568
569
63.9k
  size_t nb_channels = image.channel.size();
570
571
63.9k
  size_t num_chans = 0;
572
63.9k
  size_t distance_multiplier = 0;
573
393k
  for (size_t i = 0; i < nb_channels; i++) {
574
330k
    Channel &channel = image.channel[i];
575
330k
    if (i >= image.nb_meta_channels && (channel.w > options->max_chan_size ||
576
326k
                                        channel.h > options->max_chan_size)) {
577
879
      break;
578
879
    }
579
329k
    if (!channel.w || !channel.h) {
580
7.46k
      continue;  // skip empty channels
581
7.46k
    }
582
322k
    if (channel.w > distance_multiplier) {
583
82.7k
      distance_multiplier = channel.w;
584
82.7k
    }
585
322k
    num_chans++;
586
322k
  }
587
63.9k
  if (num_chans == 0) return true;
588
589
63.3k
  size_t next_channel = 0;
590
63.3k
  auto scope_guard = MakeScopeGuard([&]() {
591
87.5k
    for (size_t c = next_channel; c < image.channel.size(); c++) {
592
70.7k
      ZeroFillImage(&image.channel[c].plane);
593
70.7k
    }
594
16.8k
  });
595
  // Do not do anything if truncated groups are not allowed.
596
63.3k
  if (allow_truncated_group) scope_guard.Disarm();
597
598
  // Read tree.
599
63.3k
  Tree tree_storage;
600
63.3k
  std::vector<uint8_t> context_map_storage;
601
63.3k
  ANSCode code_storage;
602
63.3k
  const Tree *tree = &tree_storage;
603
63.3k
  const ANSCode *code = &code_storage;
604
63.3k
  const std::vector<uint8_t> *context_map = &context_map_storage;
605
63.3k
  if (!header.use_global_tree) {
606
46.9k
    uint64_t max_tree_size = 1024;
607
295k
    for (size_t i = 0; i < nb_channels; i++) {
608
248k
      Channel &channel = image.channel[i];
609
248k
      if (i >= image.nb_meta_channels && (channel.w > options->max_chan_size ||
610
245k
                                          channel.h > options->max_chan_size)) {
611
108
        break;
612
108
      }
613
248k
      uint64_t pixels = channel.w * channel.h;
614
248k
      max_tree_size += pixels;
615
248k
    }
616
46.9k
    max_tree_size = std::min(static_cast<uint64_t>(1 << 20), max_tree_size);
617
46.9k
    JXL_RETURN_IF_ERROR(
618
46.9k
        DecodeTree(memory_manager, br, &tree_storage, max_tree_size));
619
37.2k
    JXL_RETURN_IF_ERROR(DecodeHistograms(memory_manager, br,
620
37.2k
                                         (tree_storage.size() + 1) / 2,
621
37.2k
                                         &code_storage, &context_map_storage));
622
37.2k
  } else {
623
16.3k
    if (!global_tree || !global_code || !global_ctx_map ||
624
16.3k
        global_tree->empty()) {
625
1.46k
      return JXL_FAILURE("No global tree available but one was requested");
626
1.46k
    }
627
14.9k
    tree = global_tree;
628
14.9k
    code = global_code;
629
14.9k
    context_map = global_ctx_map;
630
14.9k
  }
631
632
  // Read channels
633
103k
  JXL_ASSIGN_OR_RETURN(ANSSymbolReader reader,
634
103k
                       ANSSymbolReader::Create(code, br, distance_multiplier));
635
103k
  auto tree_lut = jxl::make_unique<TreeLut<uint8_t, false, false>>();
636
103k
  uint32_t fl_run = 0;
637
103k
  uint32_t fl_v = 0;
638
311k
  for (; next_channel < nb_channels; next_channel++) {
639
265k
    Channel &channel = image.channel[next_channel];
640
265k
    if (next_channel >= image.nb_meta_channels &&
641
261k
        (channel.w > options->max_chan_size ||
642
261k
         channel.h > options->max_chan_size)) {
643
195
      break;
644
195
    }
645
265k
    if (!channel.w || !channel.h) {
646
6.67k
      continue;  // skip empty channels
647
6.67k
    }
648
258k
    JXL_RETURN_IF_ERROR(DecodeModularChannelMAANS(
649
258k
        br, &reader, *context_map, *tree, header.wp_header, next_channel,
650
258k
        group_id, *tree_lut, &image, fl_run, fl_v));
651
652
    // Truncated group.
653
258k
    if (!br->AllReadsWithinBounds()) {
654
5.37k
      if (!allow_truncated_group) return JXL_FAILURE("Truncated input");
655
0
      return JXL_NOT_ENOUGH_BYTES("Read overrun in ModularDecode");
656
5.37k
    }
657
258k
  }
658
659
  // Make sure no zero-filling happens even if next_channel < nb_channels.
660
46.4k
  scope_guard.Disarm();
661
662
46.4k
  if (!reader.CheckANSFinalState()) {
663
0
    return JXL_FAILURE("ANS decode final state failed");
664
0
  }
665
46.4k
  return true;
666
46.4k
}
667
668
Status ModularGenericDecompress(BitReader *br, Image &image,
669
                                GroupHeader *header, size_t group_id,
670
                                ModularOptions *options, bool undo_transforms,
671
                                const Tree *tree, const ANSCode *code,
672
                                const std::vector<uint8_t> *ctx_map,
673
108k
                                bool allow_truncated_group) {
674
108k
  std::vector<std::pair<size_t, size_t>> req_sizes;
675
108k
  req_sizes.reserve(image.channel.size());
676
248k
  for (const auto &c : image.channel) {
677
248k
    req_sizes.emplace_back(c.w, c.h);
678
248k
  }
679
108k
  GroupHeader local_header;
680
108k
  if (header == nullptr) header = &local_header;
681
108k
  size_t bit_pos = br->TotalBitsConsumed();
682
108k
  auto dec_status = ModularDecode(br, image, *header, group_id, options, tree,
683
108k
                                  code, ctx_map, allow_truncated_group);
684
108k
  if (!allow_truncated_group) JXL_RETURN_IF_ERROR(dec_status);
685
77.9k
  if (dec_status.IsFatalError()) return dec_status;
686
77.9k
  if (undo_transforms) image.undo_transforms(header->wp_header);
687
77.9k
  if (image.error) return JXL_FAILURE("Corrupt file. Aborting.");
688
77.9k
  JXL_DEBUG_V(4,
689
77.9k
              "Modular-decoded a %" PRIuS "x%" PRIuS " nbchans=%" PRIuS
690
77.9k
              " image from %" PRIuS " bytes",
691
77.9k
              image.w, image.h, image.channel.size(),
692
77.9k
              (br->TotalBitsConsumed() - bit_pos) / 8);
693
77.9k
  JXL_DEBUG_V(5, "Modular image: %s", image.DebugString().c_str());
694
77.9k
  (void)bit_pos;
695
  // Check that after applying all transforms we are back to the requested
696
  // image sizes, otherwise there's a programming error with the
697
  // transformations.
698
77.9k
  if (undo_transforms) {
699
18.6k
    JXL_ENSURE(image.channel.size() == req_sizes.size());
700
78.6k
    for (size_t c = 0; c < req_sizes.size(); c++) {
701
59.9k
      JXL_ENSURE(req_sizes[c].first == image.channel[c].w);
702
59.9k
      JXL_ENSURE(req_sizes[c].second == image.channel[c].h);
703
59.9k
    }
704
18.6k
  }
705
77.9k
  return dec_status;
706
77.9k
}
707
708
}  // namespace jxl