/src/libjxl/lib/jxl/modular/encoding/enc_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 <jxl/memory_manager.h> |
7 | | |
8 | | #include <algorithm> |
9 | | #include <array> |
10 | | #include <cstddef> |
11 | | #include <cstdint> |
12 | | #include <cstdlib> |
13 | | #include <limits> |
14 | | #include <queue> |
15 | | #include <utility> |
16 | | #include <vector> |
17 | | |
18 | | #include "lib/jxl/base/bits.h" |
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/status.h" |
23 | | #include "lib/jxl/enc_ans.h" |
24 | | #include "lib/jxl/enc_ans_params.h" |
25 | | #include "lib/jxl/enc_aux_out.h" |
26 | | #include "lib/jxl/enc_bit_writer.h" |
27 | | #include "lib/jxl/enc_fields.h" |
28 | | #include "lib/jxl/fields.h" |
29 | | #include "lib/jxl/image.h" |
30 | | #include "lib/jxl/image_ops.h" |
31 | | #include "lib/jxl/modular/encoding/context_predict.h" |
32 | | #include "lib/jxl/modular/encoding/dec_ma.h" |
33 | | #include "lib/jxl/modular/encoding/enc_ma.h" |
34 | | #include "lib/jxl/modular/encoding/encoding.h" |
35 | | #include "lib/jxl/modular/encoding/ma_common.h" |
36 | | #include "lib/jxl/modular/modular_image.h" |
37 | | #include "lib/jxl/modular/options.h" |
38 | | #include "lib/jxl/pack_signed.h" |
39 | | |
40 | | namespace jxl { |
41 | | |
42 | | namespace { |
43 | | // Plot tree (if enabled) and predictor usage map. |
44 | | constexpr bool kWantDebug = true; |
45 | | // constexpr bool kPrintTree = false; |
46 | | |
47 | 86.8M | inline std::array<uint8_t, 3> PredictorColor(Predictor p) { |
48 | 86.8M | switch (p) { |
49 | 12.9M | case Predictor::Zero: |
50 | 12.9M | return {{0, 0, 0}}; |
51 | 4.67M | case Predictor::Left: |
52 | 4.67M | return {{255, 0, 0}}; |
53 | 0 | case Predictor::Top: |
54 | 0 | return {{0, 255, 0}}; |
55 | 0 | case Predictor::Average0: |
56 | 0 | return {{0, 0, 255}}; |
57 | 0 | case Predictor::Average4: |
58 | 0 | return {{192, 128, 128}}; |
59 | 0 | case Predictor::Select: |
60 | 0 | return {{255, 255, 0}}; |
61 | 69.2M | case Predictor::Gradient: |
62 | 69.2M | return {{255, 0, 255}}; |
63 | 19.1k | case Predictor::Weighted: |
64 | 19.1k | return {{0, 255, 255}}; |
65 | | // TODO(jon) |
66 | 0 | default: |
67 | 0 | return {{255, 255, 255}}; |
68 | 86.8M | }; |
69 | 0 | } |
70 | | |
71 | | // `cutoffs` must be sorted. |
72 | | Tree MakeFixedTree(int property, const std::vector<int32_t> &cutoffs, |
73 | 2.13k | Predictor pred, size_t num_pixels, int bitdepth) { |
74 | 2.13k | size_t log_px = CeilLog2Nonzero(num_pixels); |
75 | 2.13k | size_t min_gap = 0; |
76 | | // Reduce fixed tree height when encoding small images. |
77 | 2.13k | if (log_px < 14) { |
78 | 1.69k | min_gap = 8 * (14 - log_px); |
79 | 1.69k | } |
80 | 2.13k | const int shift = bitdepth > 11 ? std::min(4, bitdepth - 11) : 0; |
81 | 2.13k | const int mul = 1 << shift; |
82 | 2.13k | Tree tree; |
83 | 2.13k | struct NodeInfo { |
84 | 2.13k | size_t begin, end, pos; |
85 | 2.13k | }; |
86 | 2.13k | std::queue<NodeInfo> q; |
87 | | // Leaf IDs will be set by roundtrip decoding the tree. |
88 | 2.13k | tree.push_back(PropertyDecisionNode::Leaf(pred)); |
89 | 2.13k | q.push(NodeInfo{0, cutoffs.size(), 0}); |
90 | 36.6k | while (!q.empty()) { |
91 | 34.5k | NodeInfo info = q.front(); |
92 | 34.5k | q.pop(); |
93 | 34.5k | if (info.begin + min_gap >= info.end) continue; |
94 | 16.2k | uint32_t split = (info.begin + info.end) / 2; |
95 | 16.2k | int32_t cutoff = cutoffs[split] * mul; |
96 | 16.2k | tree[info.pos] = PropertyDecisionNode::Split(property, cutoff, tree.size()); |
97 | 16.2k | q.push(NodeInfo{split + 1, info.end, tree.size()}); |
98 | 16.2k | tree.push_back(PropertyDecisionNode::Leaf(pred)); |
99 | 16.2k | q.push(NodeInfo{info.begin, split, tree.size()}); |
100 | 16.2k | tree.push_back(PropertyDecisionNode::Leaf(pred)); |
101 | 16.2k | } |
102 | 2.13k | return tree; |
103 | 2.13k | } |
104 | | |
105 | | Status GatherTreeData(const Image &image, pixel_type chan, size_t group_id, |
106 | | const weighted::Header &wp_header, |
107 | | const ModularOptions &options, TreeSamples &tree_samples, |
108 | 3.00k | size_t *total_pixels) { |
109 | 3.00k | const Channel &channel = image.channel[chan]; |
110 | 3.00k | JxlMemoryManager *memory_manager = channel.memory_manager(); |
111 | | |
112 | 3.00k | JXL_DEBUG_V(7, "Learning %" PRIuS "x%" PRIuS " channel %d", channel.w, |
113 | 3.00k | channel.h, chan); |
114 | | |
115 | 3.00k | std::array<pixel_type, kNumStaticProperties> static_props = { |
116 | 3.00k | {chan, static_cast<int>(group_id)}}; |
117 | 3.00k | Properties properties(kNumNonrefProperties + |
118 | 3.00k | kExtraPropsPerChannel * options.max_properties); |
119 | 3.00k | double pixel_fraction = std::min(1.0f, options.nb_repeats); |
120 | | // a fraction of 0 is used to disable learning entirely. |
121 | 3.00k | if (pixel_fraction > 0) { |
122 | 3.00k | pixel_fraction = std::max(pixel_fraction, |
123 | 3.00k | std::min(1.0, 1024.0 / (channel.w * channel.h))); |
124 | 3.00k | } |
125 | 3.00k | uint64_t threshold = |
126 | 3.00k | (std::numeric_limits<uint64_t>::max() >> 32) * pixel_fraction; |
127 | 3.00k | uint64_t s[2] = {static_cast<uint64_t>(0x94D049BB133111EBull), |
128 | 3.00k | static_cast<uint64_t>(0xBF58476D1CE4E5B9ull)}; |
129 | | // Xorshift128+ adapted from xorshift128+-inl.h |
130 | 23.5M | auto use_sample = [&]() { |
131 | 23.5M | auto s1 = s[0]; |
132 | 23.5M | const auto s0 = s[1]; |
133 | 23.5M | const auto bits = s1 + s0; // b, c |
134 | 23.5M | s[0] = s0; |
135 | 23.5M | s1 ^= s1 << 23; |
136 | 23.5M | s1 ^= s0 ^ (s1 >> 18) ^ (s0 >> 5); |
137 | 23.5M | s[1] = s1; |
138 | 23.5M | return (bits >> 32) <= threshold; |
139 | 23.5M | }; |
140 | | |
141 | 3.00k | const intptr_t onerow = channel.plane.PixelsPerRow(); |
142 | 3.00k | JXL_ASSIGN_OR_RETURN( |
143 | 3.00k | Channel references, |
144 | 3.00k | Channel::Create(memory_manager, properties.size() - kNumNonrefProperties, |
145 | 3.00k | channel.w)); |
146 | 3.00k | weighted::State wp_state(wp_header, channel.w, channel.h); |
147 | 3.00k | tree_samples.PrepareForSamples(pixel_fraction * channel.h * channel.w + 64); |
148 | 3.00k | const bool multiple_predictors = tree_samples.NumPredictors() != 1; |
149 | 1.18M | auto compute_sample = [&](const pixel_type *p, size_t x, size_t y) { |
150 | 1.18M | pixel_type_w pred[kNumModularPredictors]; |
151 | 1.18M | if (multiple_predictors) { |
152 | 0 | PredictLearnAll(&properties, channel.w, p + x, onerow, x, y, references, |
153 | 0 | &wp_state, pred); |
154 | 1.18M | } else { |
155 | 1.18M | pred[static_cast<int>(tree_samples.PredictorFromIndex(0))] = |
156 | 1.18M | PredictLearn(&properties, channel.w, p + x, onerow, x, y, |
157 | 1.18M | tree_samples.PredictorFromIndex(0), references, |
158 | 1.18M | &wp_state) |
159 | 1.18M | .guess; |
160 | 1.18M | } |
161 | 1.18M | (*total_pixels)++; |
162 | 1.18M | if (use_sample()) { |
163 | 663k | tree_samples.AddSample(p[x], properties, pred); |
164 | 663k | } |
165 | 1.18M | wp_state.UpdateErrors(p[x], x, y, channel.w); |
166 | 1.18M | }; |
167 | | |
168 | 200k | for (size_t y = 0; y < channel.h; y++) { |
169 | 197k | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
170 | 197k | PrecomputeReferences(channel, y, image, chan, &references); |
171 | 197k | InitPropsRow(&properties, static_props, y); |
172 | | |
173 | | // TODO(veluca): avoid computing WP if we don't use its property or |
174 | | // predictions. |
175 | 197k | if (y > 1 && channel.w > 8 && references.w == 0) { |
176 | 571k | for (size_t x = 0; x < 2; x++) { |
177 | 381k | compute_sample(p, x, y); |
178 | 381k | } |
179 | 22.5M | for (size_t x = 2; x < channel.w - 2; x++) { |
180 | 22.3M | pixel_type_w pred[kNumModularPredictors]; |
181 | 22.3M | if (multiple_predictors) { |
182 | 0 | PredictLearnAllNEC(&properties, channel.w, p + x, onerow, x, y, |
183 | 0 | references, &wp_state, pred); |
184 | 22.3M | } else { |
185 | 22.3M | pred[static_cast<int>(tree_samples.PredictorFromIndex(0))] = |
186 | 22.3M | PredictLearnNEC(&properties, channel.w, p + x, onerow, x, y, |
187 | 22.3M | tree_samples.PredictorFromIndex(0), references, |
188 | 22.3M | &wp_state) |
189 | 22.3M | .guess; |
190 | 22.3M | } |
191 | 22.3M | (*total_pixels)++; |
192 | 22.3M | if (use_sample()) { |
193 | 11.5M | tree_samples.AddSample(p[x], properties, pred); |
194 | 11.5M | } |
195 | 22.3M | wp_state.UpdateErrors(p[x], x, y, channel.w); |
196 | 22.3M | } |
197 | 571k | for (size_t x = channel.w - 2; x < channel.w; x++) { |
198 | 381k | compute_sample(p, x, y); |
199 | 381k | } |
200 | 190k | } else { |
201 | 431k | for (size_t x = 0; x < channel.w; x++) { |
202 | 424k | compute_sample(p, x, y); |
203 | 424k | } |
204 | 6.57k | } |
205 | 197k | } |
206 | 3.00k | return true; |
207 | 3.00k | } |
208 | | |
209 | | StatusOr<Tree> LearnTree( |
210 | | TreeSamples &&tree_samples, size_t total_pixels, |
211 | | const ModularOptions &options, |
212 | | const std::vector<ModularMultiplierInfo> &multiplier_info = {}, |
213 | 1.00k | StaticPropRange static_prop_range = {}) { |
214 | 1.00k | Tree tree; |
215 | 3.00k | for (size_t i = 0; i < kNumStaticProperties; i++) { |
216 | 2.00k | if (static_prop_range[i][1] == 0) { |
217 | 0 | static_prop_range[i][1] = std::numeric_limits<uint32_t>::max(); |
218 | 0 | } |
219 | 2.00k | } |
220 | 1.00k | if (!tree_samples.HasSamples()) { |
221 | 0 | tree.emplace_back(); |
222 | 0 | tree.back().predictor = tree_samples.PredictorFromIndex(0); |
223 | 0 | tree.back().property = -1; |
224 | 0 | tree.back().predictor_offset = 0; |
225 | 0 | tree.back().multiplier = 1; |
226 | 0 | return tree; |
227 | 0 | } |
228 | 1.00k | float pixel_fraction = tree_samples.NumSamples() * 1.0f / total_pixels; |
229 | 1.00k | float required_cost = pixel_fraction * 0.9 + 0.1; |
230 | 1.00k | tree_samples.AllSamplesDone(); |
231 | 1.00k | JXL_RETURN_IF_ERROR(ComputeBestTree( |
232 | 1.00k | tree_samples, options.splitting_heuristics_node_threshold * required_cost, |
233 | 1.00k | multiplier_info, static_prop_range, options.fast_decode_multiplier, |
234 | 1.00k | &tree)); |
235 | 1.00k | return tree; |
236 | 1.00k | } |
237 | | |
238 | | Status EncodeModularChannelMAANS(const Image &image, pixel_type chan, |
239 | | const weighted::Header &wp_header, |
240 | | const Tree &global_tree, Token **tokenpp, |
241 | 17.9k | size_t group_id, bool skip_encoder_fast_path) { |
242 | 17.9k | const Channel &channel = image.channel[chan]; |
243 | 17.9k | JxlMemoryManager *memory_manager = channel.memory_manager(); |
244 | 17.9k | Token *tokenp = *tokenpp; |
245 | 17.9k | JXL_ENSURE(channel.w != 0 && channel.h != 0); |
246 | | |
247 | 17.9k | Image3F predictor_img; |
248 | 17.9k | if (kWantDebug) { |
249 | 17.9k | JXL_ASSIGN_OR_RETURN(predictor_img, |
250 | 17.9k | Image3F::Create(memory_manager, channel.w, channel.h)); |
251 | 17.9k | } |
252 | | |
253 | 17.9k | JXL_DEBUG_V(6, |
254 | 17.9k | "Encoding %" PRIuS "x%" PRIuS |
255 | 17.9k | " channel %d, " |
256 | 17.9k | "(shift=%i,%i)", |
257 | 17.9k | channel.w, channel.h, chan, channel.hshift, channel.vshift); |
258 | | |
259 | 17.9k | std::array<pixel_type, kNumStaticProperties> static_props = { |
260 | 17.9k | {chan, static_cast<int>(group_id)}}; |
261 | 17.9k | bool use_wp; |
262 | 17.9k | bool is_wp_only; |
263 | 17.9k | bool is_gradient_only; |
264 | 17.9k | size_t num_props; |
265 | 17.9k | FlatTree tree = FilterTree(global_tree, static_props, &num_props, &use_wp, |
266 | 17.9k | &is_wp_only, &is_gradient_only); |
267 | 17.9k | MATreeLookup tree_lookup(tree); |
268 | 17.9k | JXL_DEBUG_V(3, "Encoding using a MA tree with %" PRIuS " nodes", tree.size()); |
269 | | |
270 | | // Check if this tree is a WP-only tree with a small enough property value |
271 | | // range. |
272 | | // Initialized to avoid clang-tidy complaining. |
273 | 17.9k | auto tree_lut = jxl::make_unique<TreeLut<uint16_t, false, false>>(); |
274 | 17.9k | if (is_wp_only) { |
275 | 6.39k | is_wp_only = TreeToLookupTable(tree, *tree_lut); |
276 | 6.39k | } |
277 | 17.9k | if (is_gradient_only) { |
278 | 2.88k | is_gradient_only = TreeToLookupTable(tree, *tree_lut); |
279 | 2.88k | } |
280 | | |
281 | 17.9k | if (is_wp_only && !skip_encoder_fast_path) { |
282 | 25.5k | for (size_t c = 0; c < 3; c++) { |
283 | 19.1k | FillImage(static_cast<float>(PredictorColor(Predictor::Weighted)[c]), |
284 | 19.1k | &predictor_img.Plane(c)); |
285 | 19.1k | } |
286 | 6.39k | const intptr_t onerow = channel.plane.PixelsPerRow(); |
287 | 6.39k | weighted::State wp_state(wp_header, channel.w, channel.h); |
288 | 6.39k | Properties properties(1); |
289 | 197k | for (size_t y = 0; y < channel.h; y++) { |
290 | 190k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
291 | 8.88M | for (size_t x = 0; x < channel.w; x++) { |
292 | 8.69M | size_t offset = 0; |
293 | 8.69M | pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0); |
294 | 8.69M | pixel_type_w top = (y ? *(r + x - onerow) : left); |
295 | 8.69M | pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left); |
296 | 8.69M | pixel_type_w topright = |
297 | 8.69M | (x + 1 < channel.w && y ? *(r + x + 1 - onerow) : top); |
298 | 8.69M | pixel_type_w toptop = (y > 1 ? *(r + x - onerow - onerow) : top); |
299 | 8.69M | int32_t guess = wp_state.Predict</*compute_properties=*/true>( |
300 | 8.69M | x, y, channel.w, top, left, topright, topleft, toptop, &properties, |
301 | 8.69M | offset); |
302 | 8.69M | uint32_t pos = |
303 | 8.69M | kPropRangeFast + |
304 | 8.69M | jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1); |
305 | 8.69M | uint32_t ctx_id = tree_lut->context_lookup[pos]; |
306 | 8.69M | int32_t residual = r[x] - guess; |
307 | 8.69M | *tokenp++ = Token(ctx_id, PackSigned(residual)); |
308 | 8.69M | wp_state.UpdateErrors(r[x], x, y, channel.w); |
309 | 8.69M | } |
310 | 190k | } |
311 | 11.5k | } else if (tree.size() == 1 && tree[0].predictor == Predictor::Gradient && |
312 | 11.5k | tree[0].multiplier == 1 && tree[0].predictor_offset == 0 && |
313 | 11.5k | !skip_encoder_fast_path) { |
314 | 9.79k | for (size_t c = 0; c < 3; c++) { |
315 | 7.34k | FillImage(static_cast<float>(PredictorColor(Predictor::Gradient)[c]), |
316 | 7.34k | &predictor_img.Plane(c)); |
317 | 7.34k | } |
318 | 2.44k | const intptr_t onerow = channel.plane.PixelsPerRow(); |
319 | 19.6k | for (size_t y = 0; y < channel.h; y++) { |
320 | 17.2k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
321 | 231k | for (size_t x = 0; x < channel.w; x++) { |
322 | 213k | pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0); |
323 | 213k | pixel_type_w top = (y ? *(r + x - onerow) : left); |
324 | 213k | pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left); |
325 | 213k | int32_t guess = ClampedGradient(top, left, topleft); |
326 | 213k | int32_t residual = r[x] - guess; |
327 | 213k | *tokenp++ = Token(tree[0].childID, PackSigned(residual)); |
328 | 213k | } |
329 | 17.2k | } |
330 | 9.07k | } else if (is_gradient_only && !skip_encoder_fast_path) { |
331 | 1.74k | for (size_t c = 0; c < 3; c++) { |
332 | 1.30k | FillImage(static_cast<float>(PredictorColor(Predictor::Gradient)[c]), |
333 | 1.30k | &predictor_img.Plane(c)); |
334 | 1.30k | } |
335 | 436 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
336 | 11.1k | for (size_t y = 0; y < channel.h; y++) { |
337 | 10.7k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
338 | 360k | for (size_t x = 0; x < channel.w; x++) { |
339 | 349k | pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0); |
340 | 349k | pixel_type_w top = (y ? *(r + x - onerow) : left); |
341 | 349k | pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left); |
342 | 349k | int32_t guess = ClampedGradient(top, left, topleft); |
343 | 349k | uint32_t pos = |
344 | 349k | kPropRangeFast + |
345 | 349k | std::min<pixel_type_w>( |
346 | 349k | std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft), |
347 | 349k | kPropRangeFast - 1); |
348 | 349k | uint32_t ctx_id = tree_lut->context_lookup[pos]; |
349 | 349k | int32_t residual = r[x] - guess; |
350 | 349k | *tokenp++ = Token(ctx_id, PackSigned(residual)); |
351 | 349k | } |
352 | 10.7k | } |
353 | 8.64k | } else if (tree.size() == 1 && tree[0].predictor == Predictor::Zero && |
354 | 8.64k | tree[0].multiplier == 1 && tree[0].predictor_offset == 0 && |
355 | 8.64k | !skip_encoder_fast_path) { |
356 | 0 | for (size_t c = 0; c < 3; c++) { |
357 | 0 | FillImage(static_cast<float>(PredictorColor(Predictor::Zero)[c]), |
358 | 0 | &predictor_img.Plane(c)); |
359 | 0 | } |
360 | 0 | for (size_t y = 0; y < channel.h; y++) { |
361 | 0 | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
362 | 0 | for (size_t x = 0; x < channel.w; x++) { |
363 | 0 | *tokenp++ = Token(tree[0].childID, PackSigned(p[x])); |
364 | 0 | } |
365 | 0 | } |
366 | 8.64k | } else if (tree.size() == 1 && tree[0].predictor != Predictor::Weighted && |
367 | 8.64k | (tree[0].multiplier & (tree[0].multiplier - 1)) == 0 && |
368 | 8.64k | tree[0].predictor_offset == 0 && !skip_encoder_fast_path) { |
369 | | // multiplier is a power of 2. |
370 | 16.8k | for (size_t c = 0; c < 3; c++) { |
371 | 12.6k | FillImage(static_cast<float>(PredictorColor(tree[0].predictor)[c]), |
372 | 12.6k | &predictor_img.Plane(c)); |
373 | 12.6k | } |
374 | 4.22k | uint32_t mul_shift = |
375 | 4.22k | FloorLog2Nonzero(static_cast<uint32_t>(tree[0].multiplier)); |
376 | 4.22k | const intptr_t onerow = channel.plane.PixelsPerRow(); |
377 | 22.6k | for (size_t y = 0; y < channel.h; y++) { |
378 | 18.4k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
379 | 237k | for (size_t x = 0; x < channel.w; x++) { |
380 | 218k | PredictionResult pred = PredictNoTreeNoWP(channel.w, r + x, onerow, x, |
381 | 218k | y, tree[0].predictor); |
382 | 218k | pixel_type_w residual = r[x] - pred.guess; |
383 | 218k | JXL_DASSERT((residual >> mul_shift) * tree[0].multiplier == residual); |
384 | 218k | *tokenp++ = Token(tree[0].childID, PackSigned(residual >> mul_shift)); |
385 | 218k | } |
386 | 18.4k | } |
387 | | |
388 | 4.41k | } else if (!use_wp && !skip_encoder_fast_path) { |
389 | 2.90k | const intptr_t onerow = channel.plane.PixelsPerRow(); |
390 | 2.90k | Properties properties(num_props); |
391 | 2.90k | JXL_ASSIGN_OR_RETURN( |
392 | 2.90k | Channel references, |
393 | 2.90k | Channel::Create(memory_manager, |
394 | 2.90k | properties.size() - kNumNonrefProperties, channel.w)); |
395 | 100k | for (size_t y = 0; y < channel.h; y++) { |
396 | 97.7k | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
397 | 97.7k | PrecomputeReferences(channel, y, image, chan, &references); |
398 | 97.7k | float *pred_img_row[3]; |
399 | 97.7k | if (kWantDebug) { |
400 | 390k | for (size_t c = 0; c < 3; c++) { |
401 | 293k | pred_img_row[c] = predictor_img.PlaneRow(c, y); |
402 | 293k | } |
403 | 97.7k | } |
404 | 97.7k | InitPropsRow(&properties, static_props, y); |
405 | 9.78M | for (size_t x = 0; x < channel.w; x++) { |
406 | 9.68M | PredictionResult res = |
407 | 9.68M | PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, |
408 | 9.68M | tree_lookup, references); |
409 | 9.68M | if (kWantDebug) { |
410 | 38.7M | for (size_t i = 0; i < 3; i++) { |
411 | 29.0M | pred_img_row[i][x] = PredictorColor(res.predictor)[i]; |
412 | 29.0M | } |
413 | 9.68M | } |
414 | 9.68M | pixel_type_w residual = p[x] - res.guess; |
415 | 9.68M | JXL_DASSERT(residual % res.multiplier == 0); |
416 | 9.68M | *tokenp++ = Token(res.context, PackSigned(residual / res.multiplier)); |
417 | 9.68M | } |
418 | 97.7k | } |
419 | 2.90k | } else { |
420 | 1.51k | const intptr_t onerow = channel.plane.PixelsPerRow(); |
421 | 1.51k | Properties properties(num_props); |
422 | 1.51k | JXL_ASSIGN_OR_RETURN( |
423 | 1.51k | Channel references, |
424 | 1.51k | Channel::Create(memory_manager, |
425 | 1.51k | properties.size() - kNumNonrefProperties, channel.w)); |
426 | 1.51k | weighted::State wp_state(wp_header, channel.w, channel.h); |
427 | 139k | for (size_t y = 0; y < channel.h; y++) { |
428 | 138k | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
429 | 138k | PrecomputeReferences(channel, y, image, chan, &references); |
430 | 138k | float *pred_img_row[3]; |
431 | 138k | if (kWantDebug) { |
432 | 553k | for (size_t c = 0; c < 3; c++) { |
433 | 414k | pred_img_row[c] = predictor_img.PlaneRow(c, y); |
434 | 414k | } |
435 | 138k | } |
436 | 138k | InitPropsRow(&properties, static_props, y); |
437 | 19.3M | for (size_t x = 0; x < channel.w; x++) { |
438 | 19.2M | PredictionResult res = |
439 | 19.2M | PredictTreeWP(&properties, channel.w, p + x, onerow, x, y, |
440 | 19.2M | tree_lookup, references, &wp_state); |
441 | 19.2M | if (kWantDebug) { |
442 | 77.0M | for (size_t i = 0; i < 3; i++) { |
443 | 57.7M | pred_img_row[i][x] = PredictorColor(res.predictor)[i]; |
444 | 57.7M | } |
445 | 19.2M | } |
446 | 19.2M | pixel_type_w residual = p[x] - res.guess; |
447 | 19.2M | JXL_DASSERT(residual % res.multiplier == 0); |
448 | 19.2M | *tokenp++ = Token(res.context, PackSigned(residual / res.multiplier)); |
449 | 19.2M | wp_state.UpdateErrors(p[x], x, y, channel.w); |
450 | 19.2M | } |
451 | 138k | } |
452 | 1.51k | } |
453 | | /* TODO(szabadka): Add cparams to the call stack here. |
454 | | if (kWantDebug && WantDebugOutput(cparams)) { |
455 | | DumpImage( |
456 | | cparams, |
457 | | ("pred_" + ToString(group_id) + "_" + ToString(chan)).c_str(), |
458 | | predictor_img); |
459 | | } |
460 | | */ |
461 | 17.9k | *tokenpp = tokenp; |
462 | 17.9k | return true; |
463 | 17.9k | } |
464 | | |
465 | | } // namespace |
466 | | |
467 | | Tree PredefinedTree(ModularOptions::TreeKind tree_kind, size_t total_pixels, |
468 | 4.26k | int bitdepth, int prevprop) { |
469 | 4.26k | switch (tree_kind) { |
470 | 0 | case ModularOptions::TreeKind::kJpegTranscodeACMeta: |
471 | | // All the data is 0, so no need for a fancy tree. |
472 | 0 | return {PropertyDecisionNode::Leaf(Predictor::Zero)}; |
473 | 0 | case ModularOptions::TreeKind::kTrivialTreeNoPredictor: |
474 | | // All the data is 0, so no need for a fancy tree. |
475 | 0 | return {PropertyDecisionNode::Leaf(Predictor::Zero)}; |
476 | 0 | case ModularOptions::TreeKind::kFalconACMeta: |
477 | | // All the data is 0 except the quant field. TODO(veluca): make that 0 |
478 | | // too. |
479 | 0 | return {PropertyDecisionNode::Leaf(Predictor::Left)}; |
480 | 2.13k | case ModularOptions::TreeKind::kACMeta: { |
481 | | // Small image. |
482 | 2.13k | if (total_pixels < 1024) { |
483 | 1.05k | return {PropertyDecisionNode::Leaf(Predictor::Left)}; |
484 | 1.05k | } |
485 | 1.07k | Tree tree; |
486 | | // 0: c > 1 |
487 | 1.07k | tree.push_back(PropertyDecisionNode::Split(0, 1, 1)); |
488 | | // 1: c > 2 |
489 | 1.07k | tree.push_back(PropertyDecisionNode::Split(0, 2, 3)); |
490 | | // 2: c > 0 |
491 | 1.07k | tree.push_back(PropertyDecisionNode::Split(0, 0, 5)); |
492 | | // 3: EPF control field (all 0 or 4), top > 3 |
493 | 1.07k | tree.push_back(PropertyDecisionNode::Split(6, 3, 21)); |
494 | | // 4: ACS+QF, y > 0 |
495 | 1.07k | tree.push_back(PropertyDecisionNode::Split(2, 0, 7)); |
496 | | // 5: CfL x |
497 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Gradient)); |
498 | | // 6: CfL b |
499 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Gradient)); |
500 | | // 7: QF: split according to the left quant value. |
501 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 5, 9)); |
502 | | // 8: ACS: split in 4 segments (8x8 from 0 to 3, large square 4-5, large |
503 | | // rectangular 6-11, 8x8 12+), according to previous ACS value. |
504 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 5, 15)); |
505 | | // QF |
506 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 11, 11)); |
507 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 3, 13)); |
508 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
509 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
510 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
511 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
512 | | // ACS |
513 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 11, 17)); |
514 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 3, 19)); |
515 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
516 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
517 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
518 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
519 | | // EPF, left > 3 |
520 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 3, 23)); |
521 | 1.07k | tree.push_back(PropertyDecisionNode::Split(7, 3, 25)); |
522 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
523 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
524 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
525 | 1.07k | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
526 | 1.07k | return tree; |
527 | 2.13k | } |
528 | 2.13k | case ModularOptions::TreeKind::kWPFixedDC: { |
529 | 2.13k | std::vector<int32_t> cutoffs = { |
530 | 2.13k | -500, -392, -255, -191, -127, -95, -63, -47, -31, -23, -15, |
531 | 2.13k | -11, -7, -4, -3, -1, 0, 1, 3, 5, 7, 11, |
532 | 2.13k | 15, 23, 31, 47, 63, 95, 127, 191, 255, 392, 500}; |
533 | 2.13k | return MakeFixedTree(kWPProp, cutoffs, Predictor::Weighted, total_pixels, |
534 | 2.13k | bitdepth); |
535 | 2.13k | } |
536 | 0 | case ModularOptions::TreeKind::kGradientFixedDC: { |
537 | 0 | std::vector<int32_t> cutoffs = { |
538 | 0 | -500, -392, -255, -191, -127, -95, -63, -47, -31, -23, -15, |
539 | 0 | -11, -7, -4, -3, -1, 0, 1, 3, 5, 7, 11, |
540 | 0 | 15, 23, 31, 47, 63, 95, 127, 191, 255, 392, 500}; |
541 | 0 | return MakeFixedTree( |
542 | 0 | prevprop > 0 ? kNumNonrefProperties + 2 : kGradientProp, cutoffs, |
543 | 0 | Predictor::Gradient, total_pixels, bitdepth); |
544 | 2.13k | } |
545 | 0 | case ModularOptions::TreeKind::kLearn: { |
546 | 0 | JXL_DEBUG_ABORT("internal: kLearn is not predefined tree"); |
547 | 0 | return {}; |
548 | 0 | } |
549 | 4.26k | } |
550 | 0 | JXL_DEBUG_ABORT("internal: unexpected TreeKind: %d", |
551 | 0 | static_cast<int>(tree_kind)); |
552 | 0 | return {}; |
553 | 0 | } |
554 | | |
555 | | StatusOr<Tree> LearnTree( |
556 | | const Image *images, const ModularOptions *options, const uint32_t start, |
557 | | const uint32_t stop, |
558 | 1.00k | const std::vector<ModularMultiplierInfo> &multiplier_info = {}) { |
559 | 1.00k | TreeSamples tree_samples; |
560 | 1.00k | JXL_RETURN_IF_ERROR(tree_samples.SetPredictor(options[start].predictor, |
561 | 1.00k | options[start].wp_tree_mode)); |
562 | 1.00k | JXL_RETURN_IF_ERROR( |
563 | 1.00k | tree_samples.SetProperties(options[start].splitting_heuristics_properties, |
564 | 1.00k | options[start].wp_tree_mode)); |
565 | 1.00k | uint32_t max_c = 0; |
566 | 1.00k | std::vector<pixel_type> pixel_samples; |
567 | 1.00k | std::vector<pixel_type> diff_samples; |
568 | 1.00k | std::vector<uint32_t> group_pixel_count; |
569 | 1.00k | std::vector<uint32_t> channel_pixel_count; |
570 | 2.00k | for (uint32_t i = start; i < stop; i++) { |
571 | 1.00k | max_c = std::max<uint32_t>(images[i].channel.size(), max_c); |
572 | 1.00k | CollectPixelSamples(images[i], options[i], i, group_pixel_count, |
573 | 1.00k | channel_pixel_count, pixel_samples, diff_samples); |
574 | 1.00k | } |
575 | 1.00k | StaticPropRange range; |
576 | 1.00k | range[0] = {{0, max_c}}; |
577 | 1.00k | range[1] = {{start, stop}}; |
578 | | |
579 | 1.00k | tree_samples.PreQuantizeProperties( |
580 | 1.00k | range, multiplier_info, group_pixel_count, channel_pixel_count, |
581 | 1.00k | pixel_samples, diff_samples, options[start].max_property_values); |
582 | | |
583 | 1.00k | size_t total_pixels = 0; |
584 | 4.00k | for (size_t i = 0; i < images[start].channel.size(); i++) { |
585 | 3.00k | if (i >= images[start].nb_meta_channels && |
586 | 3.00k | (images[start].channel[i].w > options[start].max_chan_size || |
587 | 3.00k | images[start].channel[i].h > options[start].max_chan_size)) { |
588 | 0 | break; |
589 | 0 | } |
590 | 3.00k | total_pixels += images[start].channel[i].w * images[start].channel[i].h; |
591 | 3.00k | } |
592 | 1.00k | total_pixels = std::max<size_t>(total_pixels, 1); |
593 | | |
594 | 1.00k | weighted::Header wp_header; |
595 | | |
596 | 2.00k | for (size_t i = start; i < stop; i++) { |
597 | 1.00k | size_t nb_channels = images[i].channel.size(); |
598 | | |
599 | 1.00k | if (images[i].w == 0 || images[i].h == 0 || nb_channels < 1) |
600 | 0 | continue; // is there any use for a zero-channel image? |
601 | 1.00k | if (images[i].error) return JXL_FAILURE("Invalid image"); |
602 | 1.00k | JXL_ENSURE(options[i].tree_kind == ModularOptions::TreeKind::kLearn); |
603 | | |
604 | 1.00k | JXL_DEBUG_V( |
605 | 1.00k | 2, "Encoding %" PRIuS "-channel, %i-bit, %" PRIuS "x%" PRIuS " image.", |
606 | 1.00k | nb_channels, images[i].bitdepth, images[i].w, images[i].h); |
607 | | |
608 | | // encode transforms |
609 | 1.00k | Bundle::Init(&wp_header); |
610 | 1.00k | if (options[i].predictor == Predictor::Weighted) { |
611 | 0 | weighted::PredictorMode(options[i].wp_mode, &wp_header); |
612 | 0 | } |
613 | | |
614 | | // Gather tree data |
615 | 4.00k | for (size_t c = 0; c < nb_channels; c++) { |
616 | 3.00k | if (c >= images[i].nb_meta_channels && |
617 | 3.00k | (images[i].channel[c].w > options[i].max_chan_size || |
618 | 3.00k | images[i].channel[c].h > options[i].max_chan_size)) { |
619 | 0 | break; |
620 | 0 | } |
621 | 3.00k | if (!images[i].channel[c].w || !images[i].channel[c].h) { |
622 | 0 | continue; // skip empty channels |
623 | 0 | } |
624 | 3.00k | JXL_RETURN_IF_ERROR(GatherTreeData(images[i], c, i, wp_header, options[i], |
625 | 3.00k | tree_samples, &total_pixels)); |
626 | 3.00k | } |
627 | 1.00k | } |
628 | | |
629 | | // TODO(veluca): parallelize more. |
630 | 1.00k | JXL_ASSIGN_OR_RETURN(Tree tree, |
631 | 1.00k | LearnTree(std::move(tree_samples), total_pixels, |
632 | 1.00k | options[start], multiplier_info, range)); |
633 | 1.00k | return tree; |
634 | 1.00k | } |
635 | | |
636 | | Status ModularCompress(const Image &image, const ModularOptions &options, |
637 | | size_t group_id, const Tree &tree, GroupHeader &header, |
638 | 72.0k | std::vector<Token> &tokens, size_t *width) { |
639 | 72.0k | size_t nb_channels = image.channel.size(); |
640 | | |
641 | 72.0k | if (image.w == 0 || image.h == 0 || nb_channels < 1) |
642 | 66.7k | return true; // is there any use for a zero-channel image? |
643 | 5.26k | if (image.error) return JXL_FAILURE("Invalid image"); |
644 | | |
645 | 5.26k | JXL_DEBUG_V( |
646 | 5.26k | 2, "Encoding %" PRIuS "-channel, %i-bit, %" PRIuS "x%" PRIuS " image.", |
647 | 5.26k | nb_channels, image.bitdepth, image.w, image.h); |
648 | | |
649 | | // encode transforms |
650 | 5.26k | Bundle::Init(&header); |
651 | 5.26k | if (options.predictor == Predictor::Weighted) { |
652 | 2.13k | weighted::PredictorMode(options.wp_mode, &header.wp_header); |
653 | 2.13k | } |
654 | 5.26k | header.transforms = image.transform; |
655 | 5.26k | header.use_global_tree = true; |
656 | | |
657 | 5.26k | size_t image_width = 0; |
658 | 5.26k | size_t total_tokens = 0; |
659 | 23.1k | for (size_t i = 0; i < nb_channels; i++) { |
660 | 17.9k | if (i >= image.nb_meta_channels && |
661 | 17.9k | (image.channel[i].w > options.max_chan_size || |
662 | 17.9k | image.channel[i].h > options.max_chan_size)) { |
663 | 0 | break; |
664 | 0 | } |
665 | 17.9k | if (image.channel[i].w > image_width) image_width = image.channel[i].w; |
666 | 17.9k | total_tokens += image.channel[i].w * image.channel[i].h; |
667 | 17.9k | } |
668 | 5.26k | if (options.zero_tokens) { |
669 | 0 | tokens.resize(tokens.size() + total_tokens, {0, 0}); |
670 | 5.26k | } else { |
671 | | // Do one big allocation for all the tokens we'll need, |
672 | | // to avoid reallocs that might require copying. |
673 | 5.26k | size_t pos = tokens.size(); |
674 | 5.26k | tokens.resize(pos + total_tokens); |
675 | 5.26k | Token *tokenp = tokens.data() + pos; |
676 | 23.1k | for (size_t i = 0; i < nb_channels; i++) { |
677 | 17.9k | if (i >= image.nb_meta_channels && |
678 | 17.9k | (image.channel[i].w > options.max_chan_size || |
679 | 17.9k | image.channel[i].h > options.max_chan_size)) { |
680 | 0 | break; |
681 | 0 | } |
682 | 17.9k | if (!image.channel[i].w || !image.channel[i].h) { |
683 | 0 | continue; // skip empty channels |
684 | 0 | } |
685 | 17.9k | JXL_RETURN_IF_ERROR( |
686 | 17.9k | EncodeModularChannelMAANS(image, i, header.wp_header, tree, &tokenp, |
687 | 17.9k | group_id, options.skip_encoder_fast_path)); |
688 | 17.9k | } |
689 | | // Make sure we actually wrote all tokens |
690 | 5.26k | JXL_ENSURE(tokenp == tokens.data() + tokens.size()); |
691 | 5.26k | } |
692 | | |
693 | 5.26k | *width = image_width; |
694 | | |
695 | 5.26k | return true; |
696 | 5.26k | } |
697 | | |
698 | | Status ModularGenericCompress(const Image &image, const ModularOptions &opts, |
699 | | BitWriter &writer, AuxOut *aux_out, |
700 | 0 | LayerType layer, size_t group_id) { |
701 | 0 | size_t nb_channels = image.channel.size(); |
702 | |
|
703 | 0 | if (image.w == 0 || image.h == 0 || nb_channels < 1) |
704 | 0 | return true; // is there any use for a zero-channel image? |
705 | 0 | if (image.error) return JXL_FAILURE("Invalid image"); |
706 | | |
707 | 0 | ModularOptions options = opts; // Make a copy to modify it. |
708 | 0 | if (options.predictor == kUndefinedPredictor) { |
709 | 0 | options.predictor = Predictor::Gradient; |
710 | 0 | } |
711 | |
|
712 | 0 | size_t bits = writer.BitsWritten(); |
713 | |
|
714 | 0 | JxlMemoryManager *memory_manager = image.memory_manager(); |
715 | 0 | JXL_DEBUG_V( |
716 | 0 | 2, "Encoding %" PRIuS "-channel, %i-bit, %" PRIuS "x%" PRIuS " image.", |
717 | 0 | nb_channels, image.bitdepth, image.w, image.h); |
718 | | |
719 | | // encode transforms |
720 | 0 | GroupHeader header; |
721 | 0 | Bundle::Init(&header); |
722 | 0 | if (options.predictor == Predictor::Weighted) { |
723 | 0 | weighted::PredictorMode(options.wp_mode, &header.wp_header); |
724 | 0 | } |
725 | 0 | header.transforms = image.transform; |
726 | |
|
727 | 0 | JXL_RETURN_IF_ERROR(Bundle::Write(header, &writer, layer, aux_out)); |
728 | | |
729 | | // Compute tree. |
730 | 0 | Tree tree; |
731 | 0 | if (options.tree_kind == ModularOptions::TreeKind::kLearn) { |
732 | 0 | JXL_ASSIGN_OR_RETURN(tree, LearnTree(&image, &options, 0, 1)); |
733 | 0 | } else { |
734 | 0 | size_t total_pixels = 0; |
735 | 0 | for (size_t i = 0; i < nb_channels; i++) { |
736 | 0 | if (i >= image.nb_meta_channels && |
737 | 0 | (image.channel[i].w > options.max_chan_size || |
738 | 0 | image.channel[i].h > options.max_chan_size)) { |
739 | 0 | break; |
740 | 0 | } |
741 | 0 | total_pixels += image.channel[i].w * image.channel[i].h; |
742 | 0 | } |
743 | 0 | total_pixels = std::max<size_t>(total_pixels, 1); |
744 | |
|
745 | 0 | tree = PredefinedTree(options.tree_kind, total_pixels, image.bitdepth, |
746 | 0 | options.max_properties); |
747 | 0 | } |
748 | | |
749 | 0 | Tree decoded_tree; |
750 | 0 | std::vector<std::vector<Token>> tree_tokens(1); |
751 | 0 | JXL_RETURN_IF_ERROR(TokenizeTree(tree, tree_tokens.data(), &decoded_tree)); |
752 | 0 | JXL_ENSURE(tree.size() == decoded_tree.size()); |
753 | 0 | tree = std::move(decoded_tree); |
754 | | |
755 | | /* TODO(szabadka) Add text output callback |
756 | | if (kWantDebug && kPrintTree && WantDebugOutput(aux_out)) { |
757 | | PrintTree(*tree, aux_out->debug_prefix + "/tree_" + ToString(group_id)); |
758 | | } */ |
759 | | |
760 | | // Write tree |
761 | 0 | EntropyEncodingData code; |
762 | 0 | JXL_ASSIGN_OR_RETURN( |
763 | 0 | size_t cost, |
764 | 0 | BuildAndEncodeHistograms(memory_manager, options.histogram_params, |
765 | 0 | kNumTreeContexts, tree_tokens, &code, &writer, |
766 | 0 | LayerType::ModularTree, aux_out)); |
767 | 0 | JXL_RETURN_IF_ERROR(WriteTokens(tree_tokens[0], code, 0, &writer, |
768 | 0 | LayerType::ModularTree, aux_out)); |
769 | | |
770 | 0 | size_t image_width = 0; |
771 | 0 | std::vector<std::vector<Token>> tokens(1); |
772 | | // it puts `use_global_tree = true` in the header, but this is not used |
773 | | // further |
774 | 0 | JXL_RETURN_IF_ERROR(ModularCompress(image, options, group_id, tree, header, |
775 | 0 | tokens[0], &image_width)); |
776 | | |
777 | | // Write data |
778 | 0 | code = {}; |
779 | 0 | HistogramParams histo_params = options.histogram_params; |
780 | 0 | histo_params.image_widths.push_back(image_width); |
781 | 0 | JXL_ASSIGN_OR_RETURN( |
782 | 0 | cost, BuildAndEncodeHistograms(memory_manager, histo_params, |
783 | 0 | (tree.size() + 1) / 2, tokens, &code, |
784 | 0 | &writer, layer, aux_out)); |
785 | 0 | (void)cost; |
786 | 0 | JXL_RETURN_IF_ERROR(WriteTokens(tokens[0], code, 0, &writer, layer, aux_out)); |
787 | | |
788 | 0 | bits = writer.BitsWritten() - bits; |
789 | 0 | JXL_DEBUG_V(4, |
790 | 0 | "Modular-encoded a %" PRIuS "x%" PRIuS |
791 | 0 | " bitdepth=%i nbchans=%" PRIuS " image in %" PRIuS " bytes", |
792 | 0 | image.w, image.h, image.bitdepth, image.channel.size(), bits / 8); |
793 | 0 | (void)bits; |
794 | |
|
795 | 0 | return true; |
796 | 0 | } |
797 | | |
798 | | } // namespace jxl |