/src/libjxl/lib/jxl/enc_patch_dictionary.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/enc_patch_dictionary.h" |
7 | | |
8 | | #include <jxl/cms_interface.h> |
9 | | #include <jxl/memory_manager.h> |
10 | | #include <jxl/types.h> |
11 | | |
12 | | #include <algorithm> |
13 | | #include <atomic> |
14 | | #include <cmath> |
15 | | #include <cstdint> |
16 | | #include <cstdlib> |
17 | | #include <utility> |
18 | | #include <vector> |
19 | | |
20 | | #include "lib/jxl/base/common.h" |
21 | | #include "lib/jxl/base/compiler_specific.h" |
22 | | #include "lib/jxl/base/data_parallel.h" |
23 | | #include "lib/jxl/base/override.h" |
24 | | #include "lib/jxl/base/printf_macros.h" |
25 | | #include "lib/jxl/base/random.h" |
26 | | #include "lib/jxl/base/rect.h" |
27 | | #include "lib/jxl/base/span.h" |
28 | | #include "lib/jxl/base/status.h" |
29 | | #include "lib/jxl/common.h" |
30 | | #include "lib/jxl/dec_cache.h" |
31 | | #include "lib/jxl/dec_frame.h" |
32 | | #include "lib/jxl/dec_patch_dictionary.h" |
33 | | #include "lib/jxl/enc_ans.h" |
34 | | #include "lib/jxl/enc_ans_params.h" |
35 | | #include "lib/jxl/enc_aux_out.h" |
36 | | #include "lib/jxl/enc_bit_writer.h" |
37 | | #include "lib/jxl/enc_cache.h" |
38 | | #include "lib/jxl/enc_debug_image.h" |
39 | | #include "lib/jxl/enc_dot_dictionary.h" |
40 | | #include "lib/jxl/enc_frame.h" |
41 | | #include "lib/jxl/enc_params.h" |
42 | | #include "lib/jxl/frame_header.h" |
43 | | #include "lib/jxl/image.h" |
44 | | #include "lib/jxl/image_bundle.h" |
45 | | #include "lib/jxl/image_ops.h" |
46 | | #include "lib/jxl/modular/options.h" |
47 | | #include "lib/jxl/pack_signed.h" |
48 | | #include "lib/jxl/patch_dictionary_internal.h" |
49 | | |
50 | | namespace jxl { |
51 | | |
52 | | static constexpr size_t kPatchFrameReferenceId = 3; |
53 | | |
54 | | // static |
55 | | Status PatchDictionaryEncoder::Encode(const PatchDictionary& pdic, |
56 | | BitWriter* writer, LayerType layer, |
57 | 1.00k | AuxOut* aux_out) { |
58 | 1.00k | JXL_ENSURE(pdic.HasAny()); |
59 | 1.00k | JxlMemoryManager* memory_manager = writer->memory_manager(); |
60 | 1.00k | std::vector<std::vector<Token>> tokens(1); |
61 | | |
62 | 4.14M | auto add_num = [&](int context, size_t num) { |
63 | 4.14M | tokens[0].emplace_back(context, num); |
64 | 4.14M | }; |
65 | 1.00k | size_t num_ref_patch = 0; |
66 | 88.6k | for (size_t i = 0; i < pdic.positions_.size();) { |
67 | 87.6k | size_t ref_pos_idx = pdic.positions_[i].ref_pos_idx; |
68 | 1.29M | while (i < pdic.positions_.size() && |
69 | 1.29M | pdic.positions_[i].ref_pos_idx == ref_pos_idx) { |
70 | 1.20M | i++; |
71 | 1.20M | } |
72 | 87.6k | num_ref_patch++; |
73 | 87.6k | } |
74 | 1.00k | add_num(kNumRefPatchContext, num_ref_patch); |
75 | 1.00k | size_t blend_pos = 0; |
76 | 1.00k | size_t blending_stride = pdic.blendings_stride_; |
77 | | // blending_stride == num_ec + 1; num_ec > 1 => |
78 | 1.00k | bool choose_alpha = (blending_stride > 1 + 1); |
79 | 88.6k | for (size_t i = 0; i < pdic.positions_.size();) { |
80 | 87.6k | size_t i_start = i; |
81 | 87.6k | size_t ref_pos_idx = pdic.positions_[i].ref_pos_idx; |
82 | 87.6k | const auto& ref_pos = pdic.ref_positions_[ref_pos_idx]; |
83 | 1.29M | while (i < pdic.positions_.size() && |
84 | 1.29M | pdic.positions_[i].ref_pos_idx == ref_pos_idx) { |
85 | 1.20M | i++; |
86 | 1.20M | } |
87 | 87.6k | size_t num = i - i_start; |
88 | 87.6k | JXL_ENSURE(num > 0); |
89 | 87.6k | add_num(kReferenceFrameContext, ref_pos.ref); |
90 | 87.6k | add_num(kPatchReferencePositionContext, ref_pos.x0); |
91 | 87.6k | add_num(kPatchReferencePositionContext, ref_pos.y0); |
92 | 87.6k | add_num(kPatchSizeContext, ref_pos.xsize - 1); |
93 | 87.6k | add_num(kPatchSizeContext, ref_pos.ysize - 1); |
94 | 87.6k | add_num(kPatchCountContext, num - 1); |
95 | 1.29M | for (size_t j = i_start; j < i; j++) { |
96 | 1.20M | const PatchPosition& pos = pdic.positions_[j]; |
97 | 1.20M | if (j == i_start) { |
98 | 87.6k | add_num(kPatchPositionContext, pos.x); |
99 | 87.6k | add_num(kPatchPositionContext, pos.y); |
100 | 1.11M | } else { |
101 | 1.11M | add_num(kPatchOffsetContext, |
102 | 1.11M | PackSigned(pos.x - pdic.positions_[j - 1].x)); |
103 | 1.11M | add_num(kPatchOffsetContext, |
104 | 1.11M | PackSigned(pos.y - pdic.positions_[j - 1].y)); |
105 | 1.11M | } |
106 | 2.41M | for (size_t k = 0; k < blending_stride; ++k, ++blend_pos) { |
107 | 1.20M | const PatchBlending& info = pdic.blendings_[blend_pos]; |
108 | 1.20M | add_num(kPatchBlendModeContext, static_cast<uint32_t>(info.mode)); |
109 | 1.20M | if (UsesAlpha(info.mode) && choose_alpha) { |
110 | 0 | add_num(kPatchAlphaChannelContext, info.alpha_channel); |
111 | 0 | } |
112 | 1.20M | if (UsesClamp(info.mode)) { |
113 | 0 | add_num(kPatchClampContext, TO_JXL_BOOL(info.clamp)); |
114 | 0 | } |
115 | 1.20M | } |
116 | 1.20M | } |
117 | 87.6k | } |
118 | | |
119 | 1.00k | EntropyEncodingData codes; |
120 | 1.00k | JXL_ASSIGN_OR_RETURN( |
121 | 1.00k | size_t cost, BuildAndEncodeHistograms(memory_manager, HistogramParams(), |
122 | 1.00k | kNumPatchDictionaryContexts, tokens, |
123 | 1.00k | &codes, writer, layer, aux_out)); |
124 | 1.00k | (void)cost; |
125 | 1.00k | JXL_RETURN_IF_ERROR(WriteTokens(tokens[0], codes, 0, writer, layer, aux_out)); |
126 | 1.00k | return true; |
127 | 1.00k | } |
128 | | |
129 | | // static |
130 | | Status PatchDictionaryEncoder::SubtractFrom(const PatchDictionary& pdic, |
131 | 3.12k | Image3F* opsin) { |
132 | | // TODO(veluca): this can likely be optimized knowing it runs on full images. |
133 | 581k | for (size_t y = 0; y < opsin->ysize(); y++) { |
134 | 578k | float* JXL_RESTRICT rows[3] = { |
135 | 578k | opsin->PlaneRow(0, y), |
136 | 578k | opsin->PlaneRow(1, y), |
137 | 578k | opsin->PlaneRow(2, y), |
138 | 578k | }; |
139 | 578k | size_t blending_stride = pdic.blendings_stride_; |
140 | 3.08M | for (size_t pos_idx : pdic.GetPatchesForRow(y)) { |
141 | 3.08M | const size_t blending_idx = pos_idx * blending_stride; |
142 | 3.08M | const PatchPosition& pos = pdic.positions_[pos_idx]; |
143 | 3.08M | const PatchReferencePosition& ref_pos = |
144 | 3.08M | pdic.ref_positions_[pos.ref_pos_idx]; |
145 | 3.08M | const PatchBlendMode mode = pdic.blendings_[blending_idx].mode; |
146 | 3.08M | size_t by = pos.y; |
147 | 3.08M | size_t bx = pos.x; |
148 | 3.08M | size_t xsize = ref_pos.xsize; |
149 | 3.08M | JXL_ENSURE(y >= by); |
150 | 3.08M | JXL_ENSURE(y < by + ref_pos.ysize); |
151 | 3.08M | size_t iy = y - by; |
152 | 3.08M | size_t ref = ref_pos.ref; |
153 | 3.08M | const float* JXL_RESTRICT ref_rows[3] = { |
154 | 3.08M | pdic.reference_frames_->at(ref).frame->color()->ConstPlaneRow( |
155 | 3.08M | 0, ref_pos.y0 + iy) + |
156 | 3.08M | ref_pos.x0, |
157 | 3.08M | pdic.reference_frames_->at(ref).frame->color()->ConstPlaneRow( |
158 | 3.08M | 1, ref_pos.y0 + iy) + |
159 | 3.08M | ref_pos.x0, |
160 | 3.08M | pdic.reference_frames_->at(ref).frame->color()->ConstPlaneRow( |
161 | 3.08M | 2, ref_pos.y0 + iy) + |
162 | 3.08M | ref_pos.x0, |
163 | 3.08M | }; |
164 | 26.0M | for (size_t ix = 0; ix < xsize; ix++) { |
165 | 91.7M | for (size_t c = 0; c < 3; c++) { |
166 | 68.8M | if (mode == PatchBlendMode::kAdd) { |
167 | 68.8M | rows[c][bx + ix] -= ref_rows[c][ix]; |
168 | 68.8M | } else if (mode == PatchBlendMode::kReplace) { |
169 | 0 | rows[c][bx + ix] = 0; |
170 | 0 | } else if (mode == PatchBlendMode::kNone) { |
171 | | // Nothing to do. |
172 | 0 | } else { |
173 | 0 | return JXL_UNREACHABLE("blending mode %u not yet implemented", |
174 | 0 | static_cast<uint32_t>(mode)); |
175 | 0 | } |
176 | 68.8M | } |
177 | 22.9M | } |
178 | 3.08M | } |
179 | 578k | } |
180 | 3.12k | return true; |
181 | 3.12k | } |
182 | | |
183 | | namespace { |
184 | | |
185 | | struct PatchColorspaceInfo { |
186 | | float kChannelDequant[3]; |
187 | | float kChannelWeights[3]; |
188 | | |
189 | 2.13k | explicit PatchColorspaceInfo(bool is_xyb) { |
190 | 2.13k | if (is_xyb) { |
191 | 2.13k | kChannelDequant[0] = 0.01615; |
192 | 2.13k | kChannelDequant[1] = 0.08875; |
193 | 2.13k | kChannelDequant[2] = 0.1922; |
194 | 2.13k | kChannelWeights[0] = 30.0; |
195 | 2.13k | kChannelWeights[1] = 3.0; |
196 | 2.13k | kChannelWeights[2] = 1.0; |
197 | 2.13k | } else { |
198 | 0 | kChannelDequant[0] = 20.0f / 255; |
199 | 0 | kChannelDequant[1] = 22.0f / 255; |
200 | 0 | kChannelDequant[2] = 20.0f / 255; |
201 | 0 | kChannelWeights[0] = 0.017 * 255; |
202 | 0 | kChannelWeights[1] = 0.02 * 255; |
203 | 0 | kChannelWeights[2] = 0.017 * 255; |
204 | 0 | } |
205 | 2.13k | } |
206 | | |
207 | 115M | float ScaleForQuantization(float val, size_t c) { |
208 | 115M | return val / kChannelDequant[c]; |
209 | 115M | } |
210 | | |
211 | 115M | int Quantize(float val, size_t c) { |
212 | 115M | return std::trunc(ScaleForQuantization(val, c)); |
213 | 115M | } |
214 | | |
215 | 279M | bool is_similar_v(const Color& v1, const Color& v2, float threshold) { |
216 | 279M | float distance = 0; |
217 | 1.11G | for (size_t c = 0; c < 3; c++) { |
218 | 837M | distance += std::abs(v1[c] - v2[c]) * kChannelWeights[c]; |
219 | 837M | } |
220 | 279M | return distance <= threshold; |
221 | 279M | } |
222 | | }; |
223 | | |
224 | | using XY = std::pair<int32_t, int32_t>; |
225 | | constexpr const size_t kPatchSide = 4; |
226 | | |
227 | | StatusOr<std::vector<PatchInfo>> FindTextLikePatches( |
228 | | const CompressParams& cparams, const Image3F& opsin, |
229 | | const PassesEncoderState* JXL_RESTRICT state, ThreadPool* pool, |
230 | 3.12k | AuxOut* aux_out, bool is_xyb) { |
231 | 3.12k | std::vector<PatchInfo> info; |
232 | 3.12k | if (state->cparams.patches == Override::kOff) return info; |
233 | 2.13k | const auto& frame_dim = state->shared.frame_dim; |
234 | 2.13k | JxlMemoryManager* memory_manager = opsin.memory_manager(); |
235 | | |
236 | 2.13k | PatchColorspaceInfo pci(is_xyb); |
237 | 2.13k | float kSimilarThreshold = 0.8f; |
238 | | |
239 | 2.13k | auto is_similar_impl = [&pci](const XY& p1, const XY& p2, |
240 | 2.13k | const float* JXL_RESTRICT rows[3], |
241 | 185M | size_t stride, float threshold) { |
242 | 185M | size_t offset1 = p1.second * stride + p1.first; |
243 | 185M | Color v1{rows[0][offset1], rows[1][offset1], rows[2][offset1]}; |
244 | 185M | size_t offset2 = p2.second * stride + p2.first; |
245 | 185M | Color v2{rows[0][offset2], rows[1][offset2], rows[2][offset2]}; |
246 | 185M | return pci.is_similar_v(v1, v2, threshold); |
247 | 185M | }; |
248 | | |
249 | 2.13k | std::atomic<uint32_t> screenshot_area_seeds{0}; |
250 | 2.13k | const size_t opsin_stride = opsin.PixelsPerRow(); |
251 | 2.13k | const float* JXL_RESTRICT opsin_rows[3] = {opsin.ConstPlaneRow(0, 0), |
252 | 2.13k | opsin.ConstPlaneRow(1, 0), |
253 | 2.13k | opsin.ConstPlaneRow(2, 0)}; |
254 | 10.6M | const auto pick = [&opsin_rows, opsin_stride](const XY& p) -> Color { |
255 | 10.6M | size_t offset = p.second * opsin_stride + p.first; |
256 | 10.6M | return {opsin_rows[0][offset], opsin_rows[1][offset], |
257 | 10.6M | opsin_rows[2][offset]}; |
258 | 10.6M | }; |
259 | 2.13k | const auto is_same_color = [&opsin_rows, opsin_stride]( |
260 | 146M | const XY& p, const Color& c) -> size_t { |
261 | 146M | const size_t offset = p.second * opsin_stride + p.first; |
262 | 557M | for (size_t i = 0; i < c.size(); ++i) { |
263 | 420M | if (std::fabs(c[i] - opsin_rows[i][offset]) > 1e-4) { |
264 | 9.94M | return 0; |
265 | 9.94M | } |
266 | 420M | } |
267 | 136M | return 1; |
268 | 146M | }; |
269 | | |
270 | 128M | auto is_similar = [&](const XY& p1, const XY& p2) { |
271 | 128M | return is_similar_impl(p1, p2, opsin_rows, opsin_stride, kSimilarThreshold); |
272 | 128M | }; |
273 | | |
274 | | // Look for kPatchSide size squares, naturally aligned, that all have the same |
275 | | // pixel values. |
276 | 2.13k | JXL_ASSIGN_OR_RETURN( |
277 | 2.13k | ImageB is_screenshot_like, |
278 | 2.13k | ImageB::Create(memory_manager, DivCeil(frame_dim.xsize, kPatchSide), |
279 | 2.13k | DivCeil(frame_dim.ysize, kPatchSide))); |
280 | 2.13k | ZeroFillImage(&is_screenshot_like); |
281 | 2.13k | const size_t pw = frame_dim.xsize / kPatchSide; |
282 | 2.13k | const size_t ph = frame_dim.ysize / kPatchSide; |
283 | | |
284 | 10.6M | const auto flat_patch = [&](const XY& o, const Color& base) -> bool { |
285 | 32.9M | for (size_t iy = 0; iy < kPatchSide; iy++) { |
286 | 124M | for (size_t ix = 0; ix < kPatchSide; ix++) { |
287 | 102M | if (!is_same_color({o.first + ix, o.second + iy}, base)) { |
288 | 5.86M | return false; |
289 | 5.86M | } |
290 | 102M | } |
291 | 28.1M | } |
292 | 4.82M | return true; |
293 | 10.6M | }; |
294 | | |
295 | | // TODO(eustas): should do this in 2 phases: |
296 | | // 1) if patches are not enabled do sampling run for has_screenshot_areas |
297 | | // 2) if patches forced or not disables + has_screenshot_areas do |
298 | | // SIMDified full scan for is_screenshot_like |
299 | 2.13k | const auto process_row = [&](const uint32_t py, |
300 | 114k | size_t /* thread */) -> Status { |
301 | 114k | uint32_t found = 0; |
302 | 10.8M | for (size_t px = 1; px <= pw - 2; px++) { |
303 | 10.6M | XY o = {px * kPatchSide, py * kPatchSide}; |
304 | 10.6M | Color base = pick(o); |
305 | 10.6M | if (!flat_patch(o, base)) continue; |
306 | 4.82M | size_t num_same = 0; |
307 | 19.3M | for (size_t y = (py - 1) * kPatchSide; y <= (py + 1) * kPatchSide; |
308 | 14.4M | y += kPatchSide) { |
309 | 57.9M | for (size_t x = (px - 1) * kPatchSide; x <= (px + 1) * kPatchSide; |
310 | 43.4M | x += kPatchSide) { |
311 | 43.4M | num_same += is_same_color({x, y}, base); |
312 | 43.4M | } |
313 | 14.4M | } |
314 | | // Too few equal pixels nearby. |
315 | 4.82M | if (num_same < 8) continue; |
316 | 3.68M | is_screenshot_like.Row(py)[px] = 1; |
317 | 3.68M | found++; |
318 | 3.68M | } |
319 | 114k | screenshot_area_seeds.fetch_add(found); |
320 | 114k | return true; |
321 | 114k | }; |
322 | 2.13k | bool can_have_seeds = ((pw >= 3) && (ph >= 3)); |
323 | 2.13k | if (can_have_seeds) { |
324 | 1.89k | JXL_RETURN_IF_ERROR(RunOnPool(pool, 1, ph - 2, ThreadPool::NoInit, |
325 | 1.89k | process_row, "IsScreenshotLike")); |
326 | 1.89k | } |
327 | | |
328 | | // TODO(veluca): also parallelize the rest of this function. |
329 | 2.13k | if (WantDebugOutput(cparams)) { |
330 | 0 | JXL_RETURN_IF_ERROR( |
331 | 0 | DumpPlaneNormalized(cparams, "screenshot_like", is_screenshot_like)); |
332 | 0 | } |
333 | | |
334 | 2.13k | constexpr int kSearchRadius = 1; |
335 | | |
336 | 2.13k | size_t num_seeds = screenshot_area_seeds.load(); |
337 | 2.13k | if (!ApplyOverride(state->cparams.patches, (num_seeds > 0))) { |
338 | 563 | return info; |
339 | 563 | } |
340 | | |
341 | | // Search for "similar enough" pixels near the screenshot-like areas. |
342 | 3.13k | JXL_ASSIGN_OR_RETURN( |
343 | 3.13k | ImageB is_background, |
344 | 3.13k | ImageB::Create(memory_manager, frame_dim.xsize, frame_dim.ysize)); |
345 | 3.13k | ZeroFillImage(&is_background); |
346 | 3.13k | JXL_ASSIGN_OR_RETURN( |
347 | 1.56k | Image3F background, |
348 | 1.56k | Image3F::Create(memory_manager, frame_dim.xsize, frame_dim.ysize)); |
349 | 1.56k | ZeroFillImage(&background); |
350 | 1.56k | constexpr size_t kDistanceLimit = 50; |
351 | 1.56k | float* JXL_RESTRICT background_rows[3] = { |
352 | 1.56k | background.PlaneRow(0, 0), |
353 | 1.56k | background.PlaneRow(1, 0), |
354 | 1.56k | background.PlaneRow(2, 0), |
355 | 1.56k | }; |
356 | 1.56k | const size_t background_stride = background.PixelsPerRow(); |
357 | 1.56k | uint8_t* JXL_RESTRICT is_background_row = is_background.Row(0); |
358 | 1.56k | const size_t is_background_stride = is_background.PixelsPerRow(); |
359 | 1.19G | const auto is_bg = [&](const XY& p) -> uint8_t& { |
360 | 1.19G | return is_background_row[p.second * is_background_stride + p.first]; |
361 | 1.19G | }; |
362 | 1.56k | std::vector<std::pair<XY, XY>> queue; |
363 | 1.56k | queue.reserve(2 * num_seeds * kPatchSide * kPatchSide); |
364 | 1.56k | size_t queue_front = 0; |
365 | | // TODO(eustas): coalesce neighbours, leave only border. |
366 | 1.56k | if (can_have_seeds) { |
367 | 107k | for (size_t py = 1; py < ph - 1; py++) { |
368 | 105k | uint8_t* JXL_RESTRICT screenshot_row = is_screenshot_like.Row(py); |
369 | 10.0M | for (size_t px = 1; px < pw - 1; px++) { |
370 | 9.93M | if (!screenshot_row[px]) continue; |
371 | 18.4M | for (size_t y = py * kPatchSide; y < (py + 1) * kPatchSide; ++y) { |
372 | 73.7M | for (size_t x = px * kPatchSide; x < (px + 1) * kPatchSide; ++x) { |
373 | 59.0M | XY p = {x, y}; |
374 | 59.0M | queue.emplace_back(p, p); |
375 | 59.0M | is_bg(p) = 1; |
376 | 59.0M | } |
377 | 14.7M | } |
378 | 3.68M | } |
379 | 105k | } |
380 | 1.56k | } |
381 | 126M | while (queue_front < queue.size()) { |
382 | 126M | XY cur = queue[queue_front].first; |
383 | 126M | XY src = queue[queue_front].second; |
384 | 126M | queue_front++; |
385 | 126M | Color src_color; |
386 | 507M | for (size_t c = 0; c < 3; c++) { |
387 | 380M | float clr = opsin_rows[c][src.second * opsin_stride + src.first]; |
388 | 380M | src_color[c] = clr; |
389 | 380M | background_rows[c][cur.second * background_stride + cur.first] = clr; |
390 | 380M | } |
391 | 507M | for (int dx = -kSearchRadius; dx <= kSearchRadius; dx++) { |
392 | 1.52G | for (int dy = -kSearchRadius; dy <= kSearchRadius; dy++) { |
393 | 1.14G | XY next{cur.first + dx, cur.second + dy}; |
394 | 1.14G | if (next.first < 0 || next.second < 0 || |
395 | 1.14G | static_cast<uint32_t>(next.first) >= frame_dim.xsize || |
396 | 1.14G | static_cast<uint32_t>(next.second) >= frame_dim.ysize) { |
397 | 3.73M | continue; |
398 | 3.73M | } |
399 | 1.13G | uint8_t& bg = is_bg(next); |
400 | 1.13G | if (bg) continue; |
401 | 129M | if (static_cast<uint32_t>( |
402 | 129M | std::abs(next.first - static_cast<int>(src.first)) + |
403 | 129M | std::abs(next.second - static_cast<int>(src.second))) > |
404 | 129M | kDistanceLimit) { |
405 | 721k | continue; |
406 | 721k | } |
407 | 128M | if (is_similar(src, next)) { |
408 | 67.8M | queue.emplace_back(next, src); |
409 | 67.8M | bg = 1; |
410 | 67.8M | } |
411 | 128M | } |
412 | 380M | } |
413 | 126M | } |
414 | 1.56k | queue.clear(); |
415 | | |
416 | 1.56k | ImageF ccs; |
417 | 1.56k | Rng rng(0); |
418 | 1.56k | bool paint_ccs = false; |
419 | 1.56k | if (WantDebugOutput(cparams)) { |
420 | 0 | JXL_RETURN_IF_ERROR( |
421 | 0 | DumpPlaneNormalized(cparams, "is_background", is_background)); |
422 | 0 | if (is_xyb) { |
423 | 0 | JXL_RETURN_IF_ERROR(DumpXybImage(cparams, "background", background)); |
424 | 0 | } else { |
425 | 0 | JXL_RETURN_IF_ERROR(DumpImage(cparams, "background", background)); |
426 | 0 | } |
427 | 0 | JXL_ASSIGN_OR_RETURN( |
428 | 0 | ccs, ImageF::Create(memory_manager, frame_dim.xsize, frame_dim.ysize)); |
429 | 0 | ZeroFillImage(&ccs); |
430 | 0 | paint_ccs = true; |
431 | 0 | } |
432 | | |
433 | 1.56k | constexpr float kVerySimilarThreshold = 0.03f; |
434 | 1.56k | constexpr float kHasSimilarThreshold = 0.03f; |
435 | | |
436 | 1.56k | const float* JXL_RESTRICT const_background_rows[3] = { |
437 | 1.56k | background_rows[0], background_rows[1], background_rows[2]}; |
438 | 57.5M | auto is_similar_b = [&](std::pair<int, int> p1, std::pair<int, int> p2) { |
439 | 57.5M | return is_similar_impl(p1, p2, const_background_rows, background_stride, |
440 | 57.5M | kVerySimilarThreshold); |
441 | 57.5M | }; |
442 | | |
443 | 1.56k | constexpr int kMinPeak = 2; |
444 | 1.56k | constexpr int kHasSimilarRadius = 2; |
445 | | |
446 | | // Find small CC outside the "similar enough" areas, compute bounding boxes, |
447 | | // and run heuristics to exclude some patches. |
448 | 1.56k | JXL_ASSIGN_OR_RETURN( |
449 | 1.56k | ImageB visited, |
450 | 1.56k | ImageB::Create(memory_manager, frame_dim.xsize, frame_dim.ysize)); |
451 | 1.56k | ZeroFillImage(&visited); |
452 | 1.56k | uint8_t* JXL_RESTRICT visited_row = visited.Row(0); |
453 | 1.56k | const size_t visited_stride = visited.PixelsPerRow(); |
454 | 1.56k | std::vector<std::pair<uint32_t, uint32_t>> cc; |
455 | 1.56k | std::vector<std::pair<uint32_t, uint32_t>> stack; |
456 | 437k | for (size_t y = 0; y < frame_dim.ysize; y++) { |
457 | 167M | for (size_t x = 0; x < frame_dim.xsize; x++) { |
458 | 167M | if (is_background_row[y * is_background_stride + x]) continue; |
459 | 40.4M | cc.clear(); |
460 | 40.4M | stack.clear(); |
461 | 40.4M | stack.emplace_back(x, y); |
462 | 40.4M | size_t min_x = x; |
463 | 40.4M | size_t max_x = x; |
464 | 40.4M | size_t min_y = y; |
465 | 40.4M | size_t max_y = y; |
466 | 40.4M | std::pair<uint32_t, uint32_t> reference; |
467 | 40.4M | bool found_border = false; |
468 | 40.4M | bool all_similar = true; |
469 | 344M | while (!stack.empty()) { |
470 | 303M | std::pair<uint32_t, uint32_t> cur = stack.back(); |
471 | 303M | stack.pop_back(); |
472 | 303M | if (visited_row[cur.second * visited_stride + cur.first]) continue; |
473 | 40.4M | visited_row[cur.second * visited_stride + cur.first] = 1; |
474 | 40.4M | if (cur.first < min_x) min_x = cur.first; |
475 | 40.4M | if (cur.first > max_x) max_x = cur.first; |
476 | 40.4M | if (cur.second < min_y) min_y = cur.second; |
477 | 40.4M | if (cur.second > max_y) max_y = cur.second; |
478 | 40.4M | if (paint_ccs) { |
479 | 0 | cc.push_back(cur); |
480 | 0 | } |
481 | 161M | for (int dx = -kSearchRadius; dx <= kSearchRadius; dx++) { |
482 | 485M | for (int dy = -kSearchRadius; dy <= kSearchRadius; dy++) { |
483 | 364M | if (dx == 0 && dy == 0) continue; |
484 | 323M | int next_first = static_cast<int32_t>(cur.first) + dx; |
485 | 323M | int next_second = static_cast<int32_t>(cur.second) + dy; |
486 | 323M | if (next_first < 0 || next_second < 0 || |
487 | 323M | static_cast<uint32_t>(next_first) >= frame_dim.xsize || |
488 | 323M | static_cast<uint32_t>(next_second) >= frame_dim.ysize) { |
489 | 1.76M | continue; |
490 | 1.76M | } |
491 | 322M | std::pair<uint32_t, uint32_t> next{next_first, next_second}; |
492 | 322M | if (!is_background_row[next.second * is_background_stride + |
493 | 322M | next.first]) { |
494 | 263M | stack.push_back(next); |
495 | 263M | } else { |
496 | 58.9M | if (!found_border) { |
497 | 1.42M | reference = next; |
498 | 1.42M | found_border = true; |
499 | 57.5M | } else { |
500 | 57.5M | if (!is_similar_b(next, reference)) all_similar = false; |
501 | 57.5M | } |
502 | 58.9M | } |
503 | 322M | } |
504 | 121M | } |
505 | 40.4M | } |
506 | 40.4M | if (!found_border || !all_similar || max_x - min_x >= kMaxPatchSize || |
507 | 40.4M | max_y - min_y >= kMaxPatchSize) { |
508 | 39.1M | continue; |
509 | 39.1M | } |
510 | 1.36M | size_t bpos = background_stride * reference.second + reference.first; |
511 | 1.36M | Color ref = {background_rows[0][bpos], background_rows[1][bpos], |
512 | 1.36M | background_rows[2][bpos]}; |
513 | 1.36M | bool has_similar = false; |
514 | 1.36M | for (size_t iy = std::max<int>( |
515 | 1.36M | static_cast<int32_t>(min_y) - kHasSimilarRadius, 0); |
516 | 11.0M | iy < std::min(max_y + kHasSimilarRadius + 1, frame_dim.ysize); |
517 | 9.67M | iy++) { |
518 | 9.67M | for (size_t ix = std::max<int>( |
519 | 9.67M | static_cast<int32_t>(min_x) - kHasSimilarRadius, 0); |
520 | 103M | ix < std::min(max_x + kHasSimilarRadius + 1, frame_dim.xsize); |
521 | 93.4M | ix++) { |
522 | 93.4M | size_t opos = opsin_stride * iy + ix; |
523 | 93.4M | Color px = {opsin_rows[0][opos], opsin_rows[1][opos], |
524 | 93.4M | opsin_rows[2][opos]}; |
525 | 93.4M | if (pci.is_similar_v(ref, px, kHasSimilarThreshold)) { |
526 | 63.0M | has_similar = true; |
527 | 63.0M | } |
528 | 93.4M | } |
529 | 9.67M | } |
530 | 1.36M | if (!has_similar) continue; |
531 | 1.35M | info.emplace_back(); |
532 | 1.35M | info.back().second.emplace_back(min_x, min_y); |
533 | 1.35M | QuantizedPatch& patch = info.back().first; |
534 | 1.35M | patch.xsize = max_x - min_x + 1; |
535 | 1.35M | patch.ysize = max_y - min_y + 1; |
536 | 1.35M | int max_value = 0; |
537 | 4.06M | for (size_t c : {1, 0, 2}) { |
538 | 16.8M | for (size_t iy = min_y; iy <= max_y; iy++) { |
539 | 128M | for (size_t ix = min_x; ix <= max_x; ix++) { |
540 | 115M | size_t offset = (iy - min_y) * patch.xsize + ix - min_x; |
541 | 115M | patch.fpixels[c][offset] = |
542 | 115M | opsin_rows[c][iy * opsin_stride + ix] - ref[c]; |
543 | 115M | int val = pci.Quantize(patch.fpixels[c][offset], c); |
544 | 115M | patch.pixels[c][offset] = val; |
545 | 115M | if (std::abs(val) > max_value) max_value = std::abs(val); |
546 | 115M | } |
547 | 12.7M | } |
548 | 4.06M | } |
549 | 1.35M | if (max_value < kMinPeak) { |
550 | 6.82k | info.pop_back(); |
551 | 6.82k | continue; |
552 | 6.82k | } |
553 | 1.34M | if (paint_ccs) { |
554 | 0 | float cc_color = rng.UniformF(0.5, 1.0); |
555 | 0 | for (std::pair<uint32_t, uint32_t> p : cc) { |
556 | 0 | ccs.Row(p.second)[p.first] = cc_color; |
557 | 0 | } |
558 | 0 | } |
559 | 1.34M | } |
560 | 435k | } |
561 | | |
562 | 1.56k | if (paint_ccs) { |
563 | 0 | JXL_ENSURE(WantDebugOutput(cparams)); |
564 | 0 | JXL_RETURN_IF_ERROR(DumpPlaneNormalized(cparams, "ccs", ccs)); |
565 | 0 | } |
566 | 1.56k | if (info.empty()) { |
567 | 338 | return info; |
568 | 338 | } |
569 | | |
570 | | // Remove duplicates. |
571 | 1.23k | constexpr size_t kMinPatchOccurrences = 2; |
572 | 1.23k | std::sort(info.begin(), info.end()); |
573 | 1.23k | size_t unique = 0; |
574 | 1.34M | for (size_t i = 1; i < info.size(); i++) { |
575 | 1.34M | if (info[i].first == info[unique].first) { |
576 | 1.12M | info[unique].second.insert(info[unique].second.end(), |
577 | 1.12M | info[i].second.begin(), info[i].second.end()); |
578 | 1.12M | } else { |
579 | 223k | if (info[unique].second.size() >= kMinPatchOccurrences) { |
580 | 87.6k | unique++; |
581 | 87.6k | } |
582 | 223k | info[unique] = info[i]; |
583 | 223k | } |
584 | 1.34M | } |
585 | 1.23k | if (info[unique].second.size() >= kMinPatchOccurrences) { |
586 | 353 | unique++; |
587 | 353 | } |
588 | 1.23k | info.resize(unique); |
589 | | |
590 | 1.23k | size_t max_patch_size = 0; |
591 | | |
592 | 88.0k | for (const auto& patch : info) { |
593 | 88.0k | size_t pixels = patch.first.xsize * patch.first.ysize; |
594 | 88.0k | if (pixels > max_patch_size) max_patch_size = pixels; |
595 | 88.0k | } |
596 | | |
597 | | // don't use patches if all patches are smaller than this |
598 | 1.23k | constexpr size_t kMinMaxPatchSize = 20; |
599 | 1.23k | if (max_patch_size < kMinMaxPatchSize) { |
600 | 229 | info.clear(); |
601 | 229 | } |
602 | | |
603 | 1.23k | return info; |
604 | 1.56k | } |
605 | | |
606 | | } // namespace |
607 | | |
608 | | Status FindBestPatchDictionary(const Image3F& opsin, |
609 | | PassesEncoderState* JXL_RESTRICT state, |
610 | | const JxlCmsInterface& cms, ThreadPool* pool, |
611 | 3.12k | AuxOut* aux_out, bool is_xyb) { |
612 | 3.12k | JXL_ASSIGN_OR_RETURN( |
613 | 3.12k | std::vector<PatchInfo> info, |
614 | 3.12k | FindTextLikePatches(state->cparams, opsin, state, pool, aux_out, is_xyb)); |
615 | 3.12k | JxlMemoryManager* memory_manager = opsin.memory_manager(); |
616 | | |
617 | | // TODO(veluca): this doesn't work if both dots and patches are enabled. |
618 | | // For now, since dots and patches are not likely to occur in the same kind of |
619 | | // images, disable dots if some patches were found. |
620 | 3.12k | if (info.empty() && |
621 | 3.12k | ApplyOverride( |
622 | 2.12k | state->cparams.dots, |
623 | 2.12k | state->cparams.speed_tier <= SpeedTier::kSquirrel && |
624 | 2.12k | state->cparams.butteraugli_distance >= kMinButteraugliForDots && |
625 | 2.12k | !state->cparams.disable_perceptual_optimizations)) { |
626 | 0 | Rect rect(0, 0, state->shared.frame_dim.xsize, |
627 | 0 | state->shared.frame_dim.ysize); |
628 | 0 | JXL_ASSIGN_OR_RETURN(info, |
629 | 0 | FindDotDictionary(state->cparams, opsin, rect, |
630 | 0 | state->shared.cmap.base(), pool)); |
631 | 0 | } |
632 | | |
633 | 3.12k | if (info.empty()) return true; |
634 | | |
635 | 1.00k | std::sort( |
636 | 506k | info.begin(), info.end(), [&](const PatchInfo& a, const PatchInfo& b) { |
637 | 506k | return a.first.xsize * a.first.ysize > b.first.xsize * b.first.ysize; |
638 | 506k | }); |
639 | | |
640 | 1.00k | size_t max_x_size = 0; |
641 | 1.00k | size_t max_y_size = 0; |
642 | 1.00k | size_t total_pixels = 0; |
643 | | |
644 | 87.6k | for (const auto& patch : info) { |
645 | 87.6k | size_t pixels = patch.first.xsize * patch.first.ysize; |
646 | 87.6k | if (max_x_size < patch.first.xsize) max_x_size = patch.first.xsize; |
647 | 87.6k | if (max_y_size < patch.first.ysize) max_y_size = patch.first.ysize; |
648 | 87.6k | total_pixels += pixels; |
649 | 87.6k | } |
650 | | |
651 | | // Bin-packing & conversion of patches. |
652 | 1.00k | constexpr float kBinPackingSlackness = 1.05f; |
653 | 1.00k | size_t ref_xsize = std::max<float>(max_x_size, std::sqrt(total_pixels)); |
654 | 1.00k | size_t ref_ysize = std::max<float>(max_y_size, std::sqrt(total_pixels)); |
655 | 1.00k | std::vector<std::pair<size_t, size_t>> ref_positions(info.size()); |
656 | | // TODO(veluca): allow partial overlaps of patches that have the same pixels. |
657 | 1.00k | size_t max_y = 0; |
658 | 1.54k | do { |
659 | 1.54k | max_y = 0; |
660 | | // Increase packed image size. |
661 | 1.54k | ref_xsize = ref_xsize * kBinPackingSlackness + 1; |
662 | 1.54k | ref_ysize = ref_ysize * kBinPackingSlackness + 1; |
663 | | |
664 | 1.54k | JXL_ASSIGN_OR_RETURN(ImageB occupied, |
665 | 1.54k | ImageB::Create(memory_manager, ref_xsize, ref_ysize)); |
666 | 1.54k | ZeroFillImage(&occupied); |
667 | 1.54k | uint8_t* JXL_RESTRICT occupied_rows = occupied.Row(0); |
668 | 1.54k | size_t occupied_stride = occupied.PixelsPerRow(); |
669 | | |
670 | 1.54k | bool success = true; |
671 | | // For every patch... |
672 | 91.8k | for (size_t patch = 0; patch < info.size(); patch++) { |
673 | 90.8k | size_t x0 = 0; |
674 | 90.8k | size_t y0 = 0; |
675 | 90.8k | size_t xsize = info[patch].first.xsize; |
676 | 90.8k | size_t ysize = info[patch].first.ysize; |
677 | 90.8k | bool found = false; |
678 | | // For every possible start position ... |
679 | 6.57M | for (; y0 + ysize <= ref_ysize; y0++) { |
680 | 6.57M | x0 = 0; |
681 | 850M | for (; x0 + xsize <= ref_xsize; x0++) { |
682 | 843M | bool has_occupied_pixel = false; |
683 | 843M | size_t x = x0; |
684 | | // Check if it is possible to place the patch in this position in the |
685 | | // reference frame. |
686 | 6.76G | for (size_t y = y0; y < y0 + ysize; y++) { |
687 | 5.91G | x = x0; |
688 | 6.62G | for (; x < x0 + xsize; x++) { |
689 | 6.58G | if (occupied_rows[y * occupied_stride + x]) { |
690 | 5.88G | has_occupied_pixel = true; |
691 | 5.88G | break; |
692 | 5.88G | } |
693 | 6.58G | } |
694 | 5.91G | } // end of positioning check |
695 | 843M | if (!has_occupied_pixel) { |
696 | 90.3k | found = true; |
697 | 90.3k | break; |
698 | 90.3k | } |
699 | 843M | x0 = x; // Jump to next pixel after the occupied one. |
700 | 843M | } |
701 | 6.57M | if (found) break; |
702 | 6.57M | } // end of start position checking |
703 | | |
704 | | // We didn't find a possible position: repeat from the beginning with a |
705 | | // larger reference frame size. |
706 | 90.8k | if (!found) { |
707 | 545 | success = false; |
708 | 545 | break; |
709 | 545 | } |
710 | | |
711 | | // We found a position: mark the corresponding positions in the reference |
712 | | // image as used. |
713 | 90.3k | ref_positions[patch] = {x0, y0}; |
714 | 708k | for (size_t y = y0; y < y0 + ysize; y++) { |
715 | 8.52M | for (size_t x = x0; x < x0 + xsize; x++) { |
716 | 7.90M | occupied_rows[y * occupied_stride + x] = JXL_TRUE; |
717 | 7.90M | } |
718 | 618k | } |
719 | 90.3k | max_y = std::max(max_y, y0 + ysize); |
720 | 90.3k | } |
721 | | |
722 | 1.54k | if (success) break; |
723 | 1.54k | } while (true); |
724 | | |
725 | 1.00k | JXL_ENSURE(ref_ysize >= max_y); |
726 | | |
727 | 1.00k | ref_ysize = max_y; |
728 | | |
729 | 1.00k | JXL_ASSIGN_OR_RETURN(Image3F reference_frame, |
730 | 1.00k | Image3F::Create(memory_manager, ref_xsize, ref_ysize)); |
731 | | // TODO(veluca): figure out a better way to fill the image. |
732 | 1.00k | ZeroFillImage(&reference_frame); |
733 | 1.00k | std::vector<PatchPosition> positions; |
734 | 1.00k | std::vector<PatchReferencePosition> pref_positions; |
735 | 1.00k | std::vector<PatchBlending> blendings; |
736 | 1.00k | float* JXL_RESTRICT ref_rows[3] = { |
737 | 1.00k | reference_frame.PlaneRow(0, 0), |
738 | 1.00k | reference_frame.PlaneRow(1, 0), |
739 | 1.00k | reference_frame.PlaneRow(2, 0), |
740 | 1.00k | }; |
741 | 1.00k | size_t ref_stride = reference_frame.PixelsPerRow(); |
742 | 1.00k | size_t num_ec = state->shared.metadata->m.num_extra_channels; |
743 | | |
744 | 88.6k | for (size_t i = 0; i < info.size(); i++) { |
745 | 87.6k | PatchReferencePosition ref_pos; |
746 | 87.6k | ref_pos.xsize = info[i].first.xsize; |
747 | 87.6k | ref_pos.ysize = info[i].first.ysize; |
748 | 87.6k | ref_pos.x0 = ref_positions[i].first; |
749 | 87.6k | ref_pos.y0 = ref_positions[i].second; |
750 | 87.6k | ref_pos.ref = kPatchFrameReferenceId; |
751 | 672k | for (size_t y = 0; y < ref_pos.ysize; y++) { |
752 | 8.02M | for (size_t x = 0; x < ref_pos.xsize; x++) { |
753 | 29.7M | for (size_t c = 0; c < 3; c++) { |
754 | 22.3M | ref_rows[c][(y + ref_pos.y0) * ref_stride + x + ref_pos.x0] = |
755 | 22.3M | info[i].first.fpixels[c][y * ref_pos.xsize + x]; |
756 | 22.3M | } |
757 | 7.44M | } |
758 | 584k | } |
759 | 1.20M | for (const auto& pos : info[i].second) { |
760 | 1.20M | JXL_DEBUG_V(4, "Patch %" PRIuS "x%" PRIuS " at position %u,%u", |
761 | 1.20M | ref_pos.xsize, ref_pos.ysize, pos.first, pos.second); |
762 | 1.20M | positions.emplace_back( |
763 | 1.20M | PatchPosition{pos.first, pos.second, pref_positions.size()}); |
764 | | // Add blending for color channels, ignore other channels. |
765 | 1.20M | blendings.push_back({PatchBlendMode::kAdd, 0, false}); |
766 | 1.20M | for (size_t j = 0; j < num_ec; ++j) { |
767 | 0 | blendings.push_back({PatchBlendMode::kNone, 0, false}); |
768 | 0 | } |
769 | 1.20M | } |
770 | 87.6k | pref_positions.emplace_back(ref_pos); |
771 | 87.6k | } |
772 | | |
773 | 1.00k | CompressParams cparams = state->cparams; |
774 | | // Recursive application of patches could create very weird issues. |
775 | 1.00k | cparams.patches = Override::kOff; |
776 | | |
777 | 1.00k | if (WantDebugOutput(cparams)) { |
778 | 0 | if (is_xyb) { |
779 | 0 | JXL_RETURN_IF_ERROR( |
780 | 0 | DumpXybImage(cparams, "patch_reference", reference_frame)); |
781 | 0 | } else { |
782 | 0 | JXL_RETURN_IF_ERROR( |
783 | 0 | DumpImage(cparams, "patch_reference", reference_frame)); |
784 | 0 | } |
785 | 0 | } |
786 | | |
787 | 1.00k | JXL_RETURN_IF_ERROR(RoundtripPatchFrame(&reference_frame, state, |
788 | 1.00k | kPatchFrameReferenceId, cparams, cms, |
789 | 1.00k | pool, aux_out, /*subtract=*/true)); |
790 | | |
791 | | // TODO(veluca): this assumes that applying patches is commutative, which is |
792 | | // not true for all blending modes. This code only produces kAdd patches, so |
793 | | // this works out. |
794 | 1.00k | PatchDictionaryEncoder::SetPositions( |
795 | 1.00k | &state->shared.image_features.patches, std::move(positions), |
796 | 1.00k | std::move(pref_positions), std::move(blendings), num_ec + 1); |
797 | 1.00k | return true; |
798 | 1.00k | } |
799 | | |
800 | | Status RoundtripPatchFrame(Image3F* reference_frame, |
801 | | PassesEncoderState* JXL_RESTRICT state, int idx, |
802 | | CompressParams& cparams, const JxlCmsInterface& cms, |
803 | 1.00k | ThreadPool* pool, AuxOut* aux_out, bool subtract) { |
804 | 1.00k | JxlMemoryManager* memory_manager = state->memory_manager(); |
805 | 1.00k | FrameInfo patch_frame_info; |
806 | 1.00k | cparams.resampling = 1; |
807 | 1.00k | cparams.ec_resampling = 1; |
808 | 1.00k | cparams.dots = Override::kOff; |
809 | 1.00k | cparams.noise = Override::kOff; |
810 | 1.00k | cparams.modular_mode = true; |
811 | 1.00k | cparams.responsive = 0; |
812 | 1.00k | cparams.progressive_dc = 0; |
813 | 1.00k | cparams.progressive_mode = Override::kOff; |
814 | 1.00k | cparams.qprogressive_mode = Override::kOff; |
815 | | // Use gradient predictor and not Predictor::Best. |
816 | 1.00k | cparams.options.predictor = Predictor::Gradient; |
817 | 1.00k | patch_frame_info.save_as_reference = idx; // always saved. |
818 | 1.00k | patch_frame_info.frame_type = FrameType::kReferenceOnly; |
819 | 1.00k | patch_frame_info.save_before_color_transform = true; |
820 | 1.00k | ImageBundle ib(memory_manager, &state->shared.metadata->m); |
821 | | // TODO(veluca): metadata.color_encoding is a lie: ib is in XYB, but there is |
822 | | // no simple way to express that yet. |
823 | 1.00k | patch_frame_info.ib_needs_color_transform = false; |
824 | 1.00k | JXL_RETURN_IF_ERROR(ib.SetFromImage( |
825 | 1.00k | std::move(*reference_frame), state->shared.metadata->m.color_encoding)); |
826 | 1.00k | if (!ib.metadata()->extra_channel_info.empty()) { |
827 | | // Add placeholder extra channels to the patch image: patch encoding does |
828 | | // not yet support extra channels, but the codec expects that the amount of |
829 | | // extra channels in frames matches that in the metadata of the codestream. |
830 | 0 | std::vector<ImageF> extra_channels; |
831 | 0 | extra_channels.reserve(ib.metadata()->extra_channel_info.size()); |
832 | 0 | for (size_t i = 0; i < ib.metadata()->extra_channel_info.size(); i++) { |
833 | 0 | JXL_ASSIGN_OR_RETURN( |
834 | 0 | ImageF ch, ImageF::Create(memory_manager, ib.xsize(), ib.ysize())); |
835 | 0 | extra_channels.emplace_back(std::move(ch)); |
836 | | // Must initialize the image with data to not affect blending with |
837 | | // uninitialized memory. |
838 | | // TODO(lode): patches must copy and use the real extra channels instead. |
839 | 0 | ZeroFillImage(&extra_channels.back()); |
840 | 0 | } |
841 | 0 | JXL_RETURN_IF_ERROR(ib.SetExtraChannels(std::move(extra_channels))); |
842 | 0 | } |
843 | 1.00k | auto special_frame = jxl::make_unique<BitWriter>(memory_manager); |
844 | 1.00k | AuxOut patch_aux_out; |
845 | 1.00k | JXL_RETURN_IF_ERROR(EncodeFrame( |
846 | 1.00k | memory_manager, cparams, patch_frame_info, state->shared.metadata, ib, |
847 | 1.00k | cms, pool, special_frame.get(), aux_out ? &patch_aux_out : nullptr)); |
848 | 1.00k | if (aux_out) { |
849 | 0 | for (const auto& l : patch_aux_out.layers) { |
850 | 0 | aux_out->layer(LayerType::Dictionary).Assimilate(l); |
851 | 0 | } |
852 | 0 | } |
853 | 1.00k | const Span<const uint8_t> encoded = special_frame->GetSpan(); |
854 | 1.00k | state->special_frames.emplace_back(std::move(special_frame)); |
855 | 1.00k | if (subtract) { |
856 | 1.00k | ImageBundle decoded(memory_manager, &state->shared.metadata->m); |
857 | 1.00k | auto dec_state = jxl::make_unique<PassesDecoderState>(memory_manager); |
858 | 1.00k | JXL_RETURN_IF_ERROR(dec_state->output_encoding_info.SetFromMetadata( |
859 | 1.00k | *state->shared.metadata)); |
860 | 1.00k | const uint8_t* frame_start = encoded.data(); |
861 | 1.00k | size_t encoded_size = encoded.size(); |
862 | 1.00k | JXL_RETURN_IF_ERROR(DecodeFrame( |
863 | 1.00k | dec_state.get(), pool, frame_start, encoded_size, |
864 | 1.00k | /*frame_header=*/nullptr, &decoded, *state->shared.metadata)); |
865 | 1.00k | frame_start += decoded.decoded_bytes(); |
866 | 1.00k | encoded_size -= decoded.decoded_bytes(); |
867 | 1.00k | size_t ref_xsize = |
868 | 1.00k | dec_state->shared_storage.reference_frames[idx].frame->color()->xsize(); |
869 | | // if the frame itself uses patches, we need to decode another frame |
870 | 1.00k | if (!ref_xsize) { |
871 | 0 | JXL_RETURN_IF_ERROR(DecodeFrame( |
872 | 0 | dec_state.get(), pool, frame_start, encoded_size, |
873 | 0 | /*frame_header=*/nullptr, &decoded, *state->shared.metadata)); |
874 | 0 | } |
875 | 1.00k | JXL_ENSURE(encoded_size == 0); |
876 | 1.00k | state->shared.reference_frames[idx] = |
877 | 1.00k | std::move(dec_state->shared_storage.reference_frames[idx]); |
878 | 1.00k | } else { |
879 | 0 | *state->shared.reference_frames[idx].frame = std::move(ib); |
880 | 0 | } |
881 | 1.00k | return true; |
882 | 1.00k | } |
883 | | |
884 | | } // namespace jxl |