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