/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) constLine | 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) constLine | 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()() constLine | 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()() constLine | 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 |