/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 | 6.88M | inline std::array<uint8_t, 3> PredictorColor(Predictor p) { |
48 | 6.88M | switch (p) { |
49 | 1.90M | case Predictor::Zero: |
50 | 1.90M | return {{0, 0, 0}}; |
51 | 722k | case Predictor::Left: |
52 | 722k | 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 | 4.25M | case Predictor::Gradient: |
62 | 4.25M | return {{255, 0, 255}}; |
63 | 1.67k | case Predictor::Weighted: |
64 | 1.67k | return {{0, 255, 255}}; |
65 | | // TODO(jon) |
66 | 0 | default: |
67 | 0 | return {{255, 255, 255}}; |
68 | 6.88M | }; |
69 | 0 | } |
70 | | |
71 | | // `cutoffs` must be sorted. |
72 | | Tree MakeFixedTree(int property, const std::vector<int32_t> &cutoffs, |
73 | 186 | Predictor pred, size_t num_pixels, int bitdepth) { |
74 | 186 | size_t log_px = CeilLog2Nonzero(num_pixels); |
75 | 186 | size_t min_gap = 0; |
76 | | // Reduce fixed tree height when encoding small images. |
77 | 186 | if (log_px < 14) { |
78 | 127 | min_gap = 8 * (14 - log_px); |
79 | 127 | } |
80 | 186 | const int shift = bitdepth > 11 ? std::min(4, bitdepth - 11) : 0; |
81 | 186 | const int mul = 1 << shift; |
82 | 186 | Tree tree; |
83 | 186 | struct NodeInfo { |
84 | 186 | size_t begin, end, pos; |
85 | 186 | }; |
86 | 186 | std::queue<NodeInfo> q; |
87 | | // Leaf IDs will be set by roundtrip decoding the tree. |
88 | 186 | tree.push_back(PropertyDecisionNode::Leaf(pred)); |
89 | 186 | q.push(NodeInfo{0, cutoffs.size(), 0}); |
90 | 4.74k | while (!q.empty()) { |
91 | 4.56k | NodeInfo info = q.front(); |
92 | 4.56k | q.pop(); |
93 | 4.56k | if (info.begin + min_gap >= info.end) continue; |
94 | 2.18k | uint32_t split = (info.begin + info.end) / 2; |
95 | 2.18k | int32_t cutoff = cutoffs[split] * mul; |
96 | 2.18k | tree[info.pos] = PropertyDecisionNode::Split(property, cutoff, tree.size()); |
97 | 2.18k | q.push(NodeInfo{split + 1, info.end, tree.size()}); |
98 | 2.18k | tree.push_back(PropertyDecisionNode::Leaf(pred)); |
99 | 2.18k | q.push(NodeInfo{info.begin, split, tree.size()}); |
100 | 2.18k | tree.push_back(PropertyDecisionNode::Leaf(pred)); |
101 | 2.18k | } |
102 | 186 | return tree; |
103 | 186 | } |
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 | 306 | size_t *total_pixels) { |
109 | 306 | const Channel &channel = image.channel[chan]; |
110 | 306 | JxlMemoryManager *memory_manager = channel.memory_manager(); |
111 | | |
112 | 306 | JXL_DEBUG_V(7, "Learning %" PRIuS "x%" PRIuS " channel %d", channel.w, |
113 | 306 | channel.h, chan); |
114 | | |
115 | 306 | std::array<pixel_type, kNumStaticProperties> static_props = { |
116 | 306 | {chan, static_cast<int>(group_id)}}; |
117 | 306 | Properties properties(kNumNonrefProperties + |
118 | 306 | kExtraPropsPerChannel * options.max_properties); |
119 | 306 | double pixel_fraction = std::min(1.0f, options.nb_repeats); |
120 | | // a fraction of 0 is used to disable learning entirely. |
121 | 306 | if (pixel_fraction > 0) { |
122 | 306 | pixel_fraction = std::max(pixel_fraction, |
123 | 306 | std::min(1.0, 1024.0 / (channel.w * channel.h))); |
124 | 306 | } |
125 | 306 | uint64_t threshold = |
126 | 306 | (std::numeric_limits<uint64_t>::max() >> 32) * pixel_fraction; |
127 | 306 | uint64_t s[2] = {static_cast<uint64_t>(0x94D049BB133111EBull), |
128 | 306 | static_cast<uint64_t>(0xBF58476D1CE4E5B9ull)}; |
129 | | // Xorshift128+ adapted from xorshift128+-inl.h |
130 | 1.53M | auto use_sample = [&]() { |
131 | 1.53M | auto s1 = s[0]; |
132 | 1.53M | const auto s0 = s[1]; |
133 | 1.53M | const auto bits = s1 + s0; // b, c |
134 | 1.53M | s[0] = s0; |
135 | 1.53M | s1 ^= s1 << 23; |
136 | 1.53M | s1 ^= s0 ^ (s1 >> 18) ^ (s0 >> 5); |
137 | 1.53M | s[1] = s1; |
138 | 1.53M | return (bits >> 32) <= threshold; |
139 | 1.53M | }; |
140 | | |
141 | 306 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
142 | 306 | JXL_ASSIGN_OR_RETURN( |
143 | 306 | Channel references, |
144 | 306 | Channel::Create(memory_manager, properties.size() - kNumNonrefProperties, |
145 | 306 | channel.w)); |
146 | 306 | weighted::State wp_state(wp_header, channel.w, channel.h); |
147 | 306 | tree_samples.PrepareForSamples(pixel_fraction * channel.h * channel.w + 64); |
148 | 306 | const bool multiple_predictors = tree_samples.NumPredictors() != 1; |
149 | 114k | auto compute_sample = [&](const pixel_type *p, size_t x, size_t y) { |
150 | 114k | pixel_type_w pred[kNumModularPredictors]; |
151 | 114k | if (multiple_predictors) { |
152 | 0 | PredictLearnAll(&properties, channel.w, p + x, onerow, x, y, references, |
153 | 0 | &wp_state, pred); |
154 | 114k | } else { |
155 | 114k | pred[static_cast<int>(tree_samples.PredictorFromIndex(0))] = |
156 | 114k | PredictLearn(&properties, channel.w, p + x, onerow, x, y, |
157 | 114k | tree_samples.PredictorFromIndex(0), references, |
158 | 114k | &wp_state) |
159 | 114k | .guess; |
160 | 114k | } |
161 | 114k | (*total_pixels)++; |
162 | 114k | if (use_sample()) { |
163 | 62.3k | tree_samples.AddSample(p[x], properties, pred); |
164 | 62.3k | } |
165 | 114k | wp_state.UpdateErrors(p[x], x, y, channel.w); |
166 | 114k | }; |
167 | | |
168 | 19.1k | for (size_t y = 0; y < channel.h; y++) { |
169 | 18.8k | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
170 | 18.8k | PrecomputeReferences(channel, y, image, chan, &references); |
171 | 18.8k | InitPropsRow(&properties, static_props, y); |
172 | | |
173 | | // TODO(veluca): avoid computing WP if we don't use its property or |
174 | | // predictions. |
175 | 18.8k | if (y > 1 && channel.w > 8 && references.w == 0) { |
176 | 54.4k | for (size_t x = 0; x < 2; x++) { |
177 | 36.3k | compute_sample(p, x, y); |
178 | 36.3k | } |
179 | 1.44M | for (size_t x = 2; x < channel.w - 2; x++) { |
180 | 1.42M | pixel_type_w pred[kNumModularPredictors]; |
181 | 1.42M | if (multiple_predictors) { |
182 | 0 | PredictLearnAllNEC(&properties, channel.w, p + x, onerow, x, y, |
183 | 0 | references, &wp_state, pred); |
184 | 1.42M | } else { |
185 | 1.42M | pred[static_cast<int>(tree_samples.PredictorFromIndex(0))] = |
186 | 1.42M | PredictLearnNEC(&properties, channel.w, p + x, onerow, x, y, |
187 | 1.42M | tree_samples.PredictorFromIndex(0), references, |
188 | 1.42M | &wp_state) |
189 | 1.42M | .guess; |
190 | 1.42M | } |
191 | 1.42M | (*total_pixels)++; |
192 | 1.42M | if (use_sample()) { |
193 | 743k | tree_samples.AddSample(p[x], properties, pred); |
194 | 743k | } |
195 | 1.42M | wp_state.UpdateErrors(p[x], x, y, channel.w); |
196 | 1.42M | } |
197 | 54.4k | for (size_t x = channel.w - 2; x < channel.w; x++) { |
198 | 36.3k | compute_sample(p, x, y); |
199 | 36.3k | } |
200 | 18.1k | } else { |
201 | 42.4k | for (size_t x = 0; x < channel.w; x++) { |
202 | 41.7k | compute_sample(p, x, y); |
203 | 41.7k | } |
204 | 707 | } |
205 | 18.8k | } |
206 | 306 | return true; |
207 | 306 | } |
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 | 111 | StaticPropRange static_prop_range = {}) { |
214 | 111 | Tree tree; |
215 | 333 | for (size_t i = 0; i < kNumStaticProperties; i++) { |
216 | 222 | if (static_prop_range[i][1] == 0) { |
217 | 0 | static_prop_range[i][1] = std::numeric_limits<uint32_t>::max(); |
218 | 0 | } |
219 | 222 | } |
220 | 111 | 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 | 111 | float pixel_fraction = tree_samples.NumSamples() * 1.0f / total_pixels; |
229 | 111 | float required_cost = pixel_fraction * 0.9 + 0.1; |
230 | 111 | tree_samples.AllSamplesDone(); |
231 | 111 | JXL_RETURN_IF_ERROR(ComputeBestTree( |
232 | 111 | tree_samples, options.splitting_heuristics_node_threshold * required_cost, |
233 | 111 | multiplier_info, static_prop_range, options.fast_decode_multiplier, |
234 | 111 | &tree)); |
235 | 111 | return tree; |
236 | 111 | } |
237 | | |
238 | | Status EncodeModularChannelMAANS(const Image &image, pixel_type chan, |
239 | | const weighted::Header &wp_header, |
240 | | const Tree &global_tree, Token **tokenpp, |
241 | 1.60k | size_t group_id, bool skip_encoder_fast_path) { |
242 | 1.60k | const Channel &channel = image.channel[chan]; |
243 | 1.60k | JxlMemoryManager *memory_manager = channel.memory_manager(); |
244 | 1.60k | Token *tokenp = *tokenpp; |
245 | 1.60k | JXL_ENSURE(channel.w != 0 && channel.h != 0); |
246 | | |
247 | 1.60k | Image3F predictor_img; |
248 | 1.60k | if (kWantDebug) { |
249 | 1.60k | JXL_ASSIGN_OR_RETURN(predictor_img, |
250 | 1.60k | Image3F::Create(memory_manager, channel.w, channel.h)); |
251 | 1.60k | } |
252 | | |
253 | 1.60k | JXL_DEBUG_V(6, |
254 | 1.60k | "Encoding %" PRIuS "x%" PRIuS |
255 | 1.60k | " channel %d, " |
256 | 1.60k | "(shift=%i,%i)", |
257 | 1.60k | channel.w, channel.h, chan, channel.hshift, channel.vshift); |
258 | | |
259 | 1.60k | std::array<pixel_type, kNumStaticProperties> static_props = { |
260 | 1.60k | {chan, static_cast<int>(group_id)}}; |
261 | 1.60k | bool use_wp; |
262 | 1.60k | bool is_wp_only; |
263 | 1.60k | bool is_gradient_only; |
264 | 1.60k | size_t num_props; |
265 | 1.60k | FlatTree tree = FilterTree(global_tree, static_props, &num_props, &use_wp, |
266 | 1.60k | &is_wp_only, &is_gradient_only); |
267 | 1.60k | MATreeLookup tree_lookup(tree); |
268 | 1.60k | 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 | 1.60k | auto tree_lut = jxl::make_unique<TreeLut<uint16_t, false, false>>(); |
274 | 1.60k | if (is_wp_only) { |
275 | 558 | is_wp_only = TreeToLookupTable(tree, *tree_lut); |
276 | 558 | } |
277 | 1.60k | if (is_gradient_only) { |
278 | 368 | is_gradient_only = TreeToLookupTable(tree, *tree_lut); |
279 | 368 | } |
280 | | |
281 | 1.60k | if (is_wp_only && !skip_encoder_fast_path) { |
282 | 2.23k | for (size_t c = 0; c < 3; c++) { |
283 | 1.67k | FillImage(static_cast<float>(PredictorColor(Predictor::Weighted)[c]), |
284 | 1.67k | &predictor_img.Plane(c)); |
285 | 1.67k | } |
286 | 558 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
287 | 558 | weighted::State wp_state(wp_header, channel.w, channel.h); |
288 | 558 | Properties properties(1); |
289 | 22.5k | for (size_t y = 0; y < channel.h; y++) { |
290 | 21.9k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
291 | 1.21M | for (size_t x = 0; x < channel.w; x++) { |
292 | 1.19M | size_t offset = 0; |
293 | 1.19M | pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0); |
294 | 1.19M | pixel_type_w top = (y ? *(r + x - onerow) : left); |
295 | 1.19M | pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left); |
296 | 1.19M | pixel_type_w topright = |
297 | 1.19M | (x + 1 < channel.w && y ? *(r + x + 1 - onerow) : top); |
298 | 1.19M | pixel_type_w toptop = (y > 1 ? *(r + x - onerow - onerow) : top); |
299 | 1.19M | int32_t guess = wp_state.Predict</*compute_properties=*/true>( |
300 | 1.19M | x, y, channel.w, top, left, topright, topleft, toptop, &properties, |
301 | 1.19M | offset); |
302 | 1.19M | uint32_t pos = |
303 | 1.19M | kPropRangeFast + |
304 | 1.19M | jxl::Clamp1(properties[0], -kPropRangeFast, kPropRangeFast - 1); |
305 | 1.19M | uint32_t ctx_id = tree_lut->context_lookup[pos]; |
306 | 1.19M | int32_t residual = r[x] - guess; |
307 | 1.19M | *tokenp++ = Token(ctx_id, PackSigned(residual)); |
308 | 1.19M | wp_state.UpdateErrors(r[x], x, y, channel.w); |
309 | 1.19M | } |
310 | 21.9k | } |
311 | 1.05k | } else if (tree.size() == 1 && tree[0].predictor == Predictor::Gradient && |
312 | 1.05k | tree[0].multiplier == 1 && tree[0].predictor_offset == 0 && |
313 | 1.05k | !skip_encoder_fast_path) { |
314 | 1.15k | for (size_t c = 0; c < 3; c++) { |
315 | 864 | FillImage(static_cast<float>(PredictorColor(Predictor::Gradient)[c]), |
316 | 864 | &predictor_img.Plane(c)); |
317 | 864 | } |
318 | 288 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
319 | 2.34k | for (size_t y = 0; y < channel.h; y++) { |
320 | 2.05k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
321 | 17.3k | for (size_t x = 0; x < channel.w; x++) { |
322 | 15.3k | pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0); |
323 | 15.3k | pixel_type_w top = (y ? *(r + x - onerow) : left); |
324 | 15.3k | pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left); |
325 | 15.3k | int32_t guess = ClampedGradient(top, left, topleft); |
326 | 15.3k | int32_t residual = r[x] - guess; |
327 | 15.3k | *tokenp++ = Token(tree[0].childID, PackSigned(residual)); |
328 | 15.3k | } |
329 | 2.05k | } |
330 | 762 | } else if (is_gradient_only && !skip_encoder_fast_path) { |
331 | 320 | for (size_t c = 0; c < 3; c++) { |
332 | 240 | FillImage(static_cast<float>(PredictorColor(Predictor::Gradient)[c]), |
333 | 240 | &predictor_img.Plane(c)); |
334 | 240 | } |
335 | 80 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
336 | 2.75k | for (size_t y = 0; y < channel.h; y++) { |
337 | 2.67k | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
338 | 124k | for (size_t x = 0; x < channel.w; x++) { |
339 | 121k | pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0); |
340 | 121k | pixel_type_w top = (y ? *(r + x - onerow) : left); |
341 | 121k | pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left); |
342 | 121k | int32_t guess = ClampedGradient(top, left, topleft); |
343 | 121k | uint32_t pos = |
344 | 121k | kPropRangeFast + |
345 | 121k | std::min<pixel_type_w>( |
346 | 121k | std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft), |
347 | 121k | kPropRangeFast - 1); |
348 | 121k | uint32_t ctx_id = tree_lut->context_lookup[pos]; |
349 | 121k | int32_t residual = r[x] - guess; |
350 | 121k | *tokenp++ = Token(ctx_id, PackSigned(residual)); |
351 | 121k | } |
352 | 2.67k | } |
353 | 682 | } else if (tree.size() == 1 && tree[0].predictor == Predictor::Zero && |
354 | 682 | tree[0].multiplier == 1 && tree[0].predictor_offset == 0 && |
355 | 682 | !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 | 682 | } else if (tree.size() == 1 && tree[0].predictor != Predictor::Weighted && |
367 | 682 | (tree[0].multiplier & (tree[0].multiplier - 1)) == 0 && |
368 | 682 | tree[0].predictor_offset == 0 && !skip_encoder_fast_path) { |
369 | | // multiplier is a power of 2. |
370 | 736 | for (size_t c = 0; c < 3; c++) { |
371 | 552 | FillImage(static_cast<float>(PredictorColor(tree[0].predictor)[c]), |
372 | 552 | &predictor_img.Plane(c)); |
373 | 552 | } |
374 | 184 | uint32_t mul_shift = |
375 | 184 | FloorLog2Nonzero(static_cast<uint32_t>(tree[0].multiplier)); |
376 | 184 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
377 | 560 | for (size_t y = 0; y < channel.h; y++) { |
378 | 376 | const pixel_type *JXL_RESTRICT r = channel.Row(y); |
379 | 4.86k | for (size_t x = 0; x < channel.w; x++) { |
380 | 4.49k | PredictionResult pred = PredictNoTreeNoWP(channel.w, r + x, onerow, x, |
381 | 4.49k | y, tree[0].predictor); |
382 | 4.49k | pixel_type_w residual = r[x] - pred.guess; |
383 | 4.49k | JXL_DASSERT((residual >> mul_shift) * tree[0].multiplier == residual); |
384 | 4.49k | *tokenp++ = Token(tree[0].childID, PackSigned(residual >> mul_shift)); |
385 | 4.49k | } |
386 | 376 | } |
387 | | |
388 | 498 | } else if (!use_wp && !skip_encoder_fast_path) { |
389 | 388 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
390 | 388 | Properties properties(num_props); |
391 | 388 | JXL_ASSIGN_OR_RETURN( |
392 | 388 | Channel references, |
393 | 388 | Channel::Create(memory_manager, |
394 | 388 | properties.size() - kNumNonrefProperties, channel.w)); |
395 | 14.8k | for (size_t y = 0; y < channel.h; y++) { |
396 | 14.4k | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
397 | 14.4k | PrecomputeReferences(channel, y, image, chan, &references); |
398 | 14.4k | float *pred_img_row[3]; |
399 | 14.4k | if (kWantDebug) { |
400 | 57.8k | for (size_t c = 0; c < 3; c++) { |
401 | 43.3k | pred_img_row[c] = predictor_img.PlaneRow(c, y); |
402 | 43.3k | } |
403 | 14.4k | } |
404 | 14.4k | InitPropsRow(&properties, static_props, y); |
405 | 1.45M | for (size_t x = 0; x < channel.w; x++) { |
406 | 1.44M | PredictionResult res = |
407 | 1.44M | PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, |
408 | 1.44M | tree_lookup, references); |
409 | 1.44M | if (kWantDebug) { |
410 | 5.77M | for (size_t i = 0; i < 3; i++) { |
411 | 4.32M | pred_img_row[i][x] = PredictorColor(res.predictor)[i]; |
412 | 4.32M | } |
413 | 1.44M | } |
414 | 1.44M | pixel_type_w residual = p[x] - res.guess; |
415 | 1.44M | JXL_DASSERT(residual % res.multiplier == 0); |
416 | 1.44M | *tokenp++ = Token(res.context, PackSigned(residual / res.multiplier)); |
417 | 1.44M | } |
418 | 14.4k | } |
419 | 388 | } else { |
420 | 110 | const intptr_t onerow = channel.plane.PixelsPerRow(); |
421 | 110 | Properties properties(num_props); |
422 | 110 | JXL_ASSIGN_OR_RETURN( |
423 | 110 | Channel references, |
424 | 110 | Channel::Create(memory_manager, |
425 | 110 | properties.size() - kNumNonrefProperties, channel.w)); |
426 | 110 | weighted::State wp_state(wp_header, channel.w, channel.h); |
427 | 9.13k | for (size_t y = 0; y < channel.h; y++) { |
428 | 9.02k | const pixel_type *JXL_RESTRICT p = channel.Row(y); |
429 | 9.02k | PrecomputeReferences(channel, y, image, chan, &references); |
430 | 9.02k | float *pred_img_row[3]; |
431 | 9.02k | if (kWantDebug) { |
432 | 36.0k | for (size_t c = 0; c < 3; c++) { |
433 | 27.0k | pred_img_row[c] = predictor_img.PlaneRow(c, y); |
434 | 27.0k | } |
435 | 9.02k | } |
436 | 9.02k | InitPropsRow(&properties, static_props, y); |
437 | 860k | for (size_t x = 0; x < channel.w; x++) { |
438 | 851k | PredictionResult res = |
439 | 851k | PredictTreeWP(&properties, channel.w, p + x, onerow, x, y, |
440 | 851k | tree_lookup, references, &wp_state); |
441 | 851k | if (kWantDebug) { |
442 | 3.40M | for (size_t i = 0; i < 3; i++) { |
443 | 2.55M | pred_img_row[i][x] = PredictorColor(res.predictor)[i]; |
444 | 2.55M | } |
445 | 851k | } |
446 | 851k | pixel_type_w residual = p[x] - res.guess; |
447 | 851k | JXL_DASSERT(residual % res.multiplier == 0); |
448 | 851k | *tokenp++ = Token(res.context, PackSigned(residual / res.multiplier)); |
449 | 851k | wp_state.UpdateErrors(p[x], x, y, channel.w); |
450 | 851k | } |
451 | 9.02k | } |
452 | 110 | } |
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 | 1.60k | *tokenpp = tokenp; |
462 | 1.60k | return true; |
463 | 1.60k | } |
464 | | |
465 | | } // namespace |
466 | | |
467 | | Tree PredefinedTree(ModularOptions::TreeKind tree_kind, size_t total_pixels, |
468 | 372 | int bitdepth, int prevprop) { |
469 | 372 | 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 | 186 | case ModularOptions::TreeKind::kACMeta: { |
481 | | // Small image. |
482 | 186 | if (total_pixels < 1024) { |
483 | 46 | return {PropertyDecisionNode::Leaf(Predictor::Left)}; |
484 | 46 | } |
485 | 140 | Tree tree; |
486 | | // 0: c > 1 |
487 | 140 | tree.push_back(PropertyDecisionNode::Split(0, 1, 1)); |
488 | | // 1: c > 2 |
489 | 140 | tree.push_back(PropertyDecisionNode::Split(0, 2, 3)); |
490 | | // 2: c > 0 |
491 | 140 | tree.push_back(PropertyDecisionNode::Split(0, 0, 5)); |
492 | | // 3: EPF control field (all 0 or 4), top > 3 |
493 | 140 | tree.push_back(PropertyDecisionNode::Split(6, 3, 21)); |
494 | | // 4: ACS+QF, y > 0 |
495 | 140 | tree.push_back(PropertyDecisionNode::Split(2, 0, 7)); |
496 | | // 5: CfL x |
497 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Gradient)); |
498 | | // 6: CfL b |
499 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Gradient)); |
500 | | // 7: QF: split according to the left quant value. |
501 | 140 | 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 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 5, 15)); |
505 | | // QF |
506 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 11, 11)); |
507 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 3, 13)); |
508 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
509 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
510 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
511 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Left)); |
512 | | // ACS |
513 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 11, 17)); |
514 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 3, 19)); |
515 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
516 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
517 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
518 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
519 | | // EPF, left > 3 |
520 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 3, 23)); |
521 | 140 | tree.push_back(PropertyDecisionNode::Split(7, 3, 25)); |
522 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
523 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
524 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
525 | 140 | tree.push_back(PropertyDecisionNode::Leaf(Predictor::Zero)); |
526 | 140 | return tree; |
527 | 186 | } |
528 | 186 | case ModularOptions::TreeKind::kWPFixedDC: { |
529 | 186 | std::vector<int32_t> cutoffs = { |
530 | 186 | -500, -392, -255, -191, -127, -95, -63, -47, -31, -23, -15, |
531 | 186 | -11, -7, -4, -3, -1, 0, 1, 3, 5, 7, 11, |
532 | 186 | 15, 23, 31, 47, 63, 95, 127, 191, 255, 392, 500}; |
533 | 186 | return MakeFixedTree(kWPProp, cutoffs, Predictor::Weighted, total_pixels, |
534 | 186 | bitdepth); |
535 | 186 | } |
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 | 186 | } |
545 | 0 | case ModularOptions::TreeKind::kLearn: { |
546 | 0 | JXL_DEBUG_ABORT("internal: kLearn is not predefined tree"); |
547 | 0 | return {}; |
548 | 186 | } |
549 | 372 | } |
550 | 0 | JXL_DEBUG_ABORT("internal: unexpected TreeKind: %d", |
551 | 0 | static_cast<int>(tree_kind)); |
552 | 0 | return {}; |
553 | 372 | } |
554 | | |
555 | | StatusOr<Tree> LearnTree( |
556 | | const Image *images, const ModularOptions *options, const uint32_t start, |
557 | | const uint32_t stop, |
558 | 111 | const std::vector<ModularMultiplierInfo> &multiplier_info = {}) { |
559 | 111 | TreeSamples tree_samples; |
560 | 111 | JXL_RETURN_IF_ERROR(tree_samples.SetPredictor(options[start].predictor, |
561 | 111 | options[start].wp_tree_mode)); |
562 | 111 | JXL_RETURN_IF_ERROR( |
563 | 111 | tree_samples.SetProperties(options[start].splitting_heuristics_properties, |
564 | 111 | options[start].wp_tree_mode)); |
565 | 111 | uint32_t max_c = 0; |
566 | 111 | std::vector<pixel_type> pixel_samples; |
567 | 111 | std::vector<pixel_type> diff_samples; |
568 | 111 | std::vector<uint32_t> group_pixel_count; |
569 | 111 | std::vector<uint32_t> channel_pixel_count; |
570 | 222 | for (uint32_t i = start; i < stop; i++) { |
571 | 111 | max_c = std::max<uint32_t>(images[i].channel.size(), max_c); |
572 | 111 | CollectPixelSamples(images[i], options[i], i, group_pixel_count, |
573 | 111 | channel_pixel_count, pixel_samples, diff_samples); |
574 | 111 | } |
575 | 111 | StaticPropRange range; |
576 | 111 | range[0] = {{0, max_c}}; |
577 | 111 | range[1] = {{start, stop}}; |
578 | | |
579 | 111 | tree_samples.PreQuantizeProperties( |
580 | 111 | range, multiplier_info, group_pixel_count, channel_pixel_count, |
581 | 111 | pixel_samples, diff_samples, options[start].max_property_values); |
582 | | |
583 | 111 | size_t total_pixels = 0; |
584 | 417 | for (size_t i = 0; i < images[start].channel.size(); i++) { |
585 | 306 | if (i >= images[start].nb_meta_channels && |
586 | 306 | (images[start].channel[i].w > options[start].max_chan_size || |
587 | 305 | images[start].channel[i].h > options[start].max_chan_size)) { |
588 | 0 | break; |
589 | 0 | } |
590 | 306 | total_pixels += images[start].channel[i].w * images[start].channel[i].h; |
591 | 306 | } |
592 | 111 | total_pixels = std::max<size_t>(total_pixels, 1); |
593 | | |
594 | 111 | weighted::Header wp_header; |
595 | | |
596 | 222 | for (size_t i = start; i < stop; i++) { |
597 | 111 | size_t nb_channels = images[i].channel.size(); |
598 | | |
599 | 111 | 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 | 111 | if (images[i].error) return JXL_FAILURE("Invalid image"); |
602 | 111 | JXL_ENSURE(options[i].tree_kind == ModularOptions::TreeKind::kLearn); |
603 | | |
604 | 111 | JXL_DEBUG_V( |
605 | 111 | 2, "Encoding %" PRIuS "-channel, %i-bit, %" PRIuS "x%" PRIuS " image.", |
606 | 111 | nb_channels, images[i].bitdepth, images[i].w, images[i].h); |
607 | | |
608 | | // encode transforms |
609 | 111 | Bundle::Init(&wp_header); |
610 | 111 | if (options[i].predictor == Predictor::Weighted) { |
611 | 0 | weighted::PredictorMode(options[i].wp_mode, &wp_header); |
612 | 0 | } |
613 | | |
614 | | // Gather tree data |
615 | 417 | for (size_t c = 0; c < nb_channels; c++) { |
616 | 306 | if (c >= images[i].nb_meta_channels && |
617 | 306 | (images[i].channel[c].w > options[i].max_chan_size || |
618 | 305 | images[i].channel[c].h > options[i].max_chan_size)) { |
619 | 0 | break; |
620 | 0 | } |
621 | 306 | if (!images[i].channel[c].w || !images[i].channel[c].h) { |
622 | 0 | continue; // skip empty channels |
623 | 0 | } |
624 | 306 | JXL_RETURN_IF_ERROR(GatherTreeData(images[i], c, i, wp_header, options[i], |
625 | 306 | tree_samples, &total_pixels)); |
626 | 306 | } |
627 | 111 | } |
628 | | |
629 | | // TODO(veluca): parallelize more. |
630 | 111 | JXL_ASSIGN_OR_RETURN(Tree tree, |
631 | 111 | LearnTree(std::move(tree_samples), total_pixels, |
632 | 111 | options[start], multiplier_info, range)); |
633 | 111 | return tree; |
634 | 111 | } |
635 | | |
636 | | Status ModularCompress(const Image &image, const ModularOptions &options, |
637 | | size_t group_id, const Tree &tree, GroupHeader &header, |
638 | 6.63k | std::vector<Token> &tokens, size_t *width) { |
639 | 6.63k | size_t nb_channels = image.channel.size(); |
640 | | |
641 | 6.63k | if (image.w == 0 || image.h == 0 || nb_channels < 1) |
642 | 6.15k | return true; // is there any use for a zero-channel image? |
643 | 483 | if (image.error) return JXL_FAILURE("Invalid image"); |
644 | | |
645 | 483 | JXL_DEBUG_V( |
646 | 483 | 2, "Encoding %" PRIuS "-channel, %i-bit, %" PRIuS "x%" PRIuS " image.", |
647 | 483 | nb_channels, image.bitdepth, image.w, image.h); |
648 | | |
649 | | // encode transforms |
650 | 483 | Bundle::Init(&header); |
651 | 483 | if (options.predictor == Predictor::Weighted) { |
652 | 186 | weighted::PredictorMode(options.wp_mode, &header.wp_header); |
653 | 186 | } |
654 | 483 | header.transforms = image.transform; |
655 | 483 | header.use_global_tree = true; |
656 | | |
657 | 483 | size_t image_width = 0; |
658 | 483 | size_t total_tokens = 0; |
659 | 2.09k | for (size_t i = 0; i < nb_channels; i++) { |
660 | 1.60k | if (i >= image.nb_meta_channels && |
661 | 1.60k | (image.channel[i].w > options.max_chan_size || |
662 | 1.60k | image.channel[i].h > options.max_chan_size)) { |
663 | 0 | break; |
664 | 0 | } |
665 | 1.60k | if (image.channel[i].w > image_width) image_width = image.channel[i].w; |
666 | 1.60k | total_tokens += image.channel[i].w * image.channel[i].h; |
667 | 1.60k | } |
668 | 483 | if (options.zero_tokens) { |
669 | 0 | tokens.resize(tokens.size() + total_tokens, {0, 0}); |
670 | 483 | } else { |
671 | | // Do one big allocation for all the tokens we'll need, |
672 | | // to avoid reallocs that might require copying. |
673 | 483 | size_t pos = tokens.size(); |
674 | 483 | tokens.resize(pos + total_tokens); |
675 | 483 | Token *tokenp = tokens.data() + pos; |
676 | 2.09k | for (size_t i = 0; i < nb_channels; i++) { |
677 | 1.60k | if (i >= image.nb_meta_channels && |
678 | 1.60k | (image.channel[i].w > options.max_chan_size || |
679 | 1.60k | image.channel[i].h > options.max_chan_size)) { |
680 | 0 | break; |
681 | 0 | } |
682 | 1.60k | if (!image.channel[i].w || !image.channel[i].h) { |
683 | 0 | continue; // skip empty channels |
684 | 0 | } |
685 | 1.60k | JXL_RETURN_IF_ERROR( |
686 | 1.60k | EncodeModularChannelMAANS(image, i, header.wp_header, tree, &tokenp, |
687 | 1.60k | group_id, options.skip_encoder_fast_path)); |
688 | 1.60k | } |
689 | | // Make sure we actually wrote all tokens |
690 | 483 | JXL_ENSURE(tokenp == tokens.data() + tokens.size()); |
691 | 483 | } |
692 | | |
693 | 483 | *width = image_width; |
694 | | |
695 | 483 | return true; |
696 | 483 | } |
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 |