/src/libjxl/lib/jxl/enc_cache.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_cache.h" |
7 | | |
8 | | #include <jxl/cms_interface.h> |
9 | | #include <jxl/memory_manager.h> |
10 | | |
11 | | #include <algorithm> |
12 | | #include <cmath> |
13 | | #include <cstddef> |
14 | | #include <cstdint> |
15 | | #include <memory> |
16 | | #include <utility> |
17 | | #include <vector> |
18 | | |
19 | | #include "lib/jxl/base/common.h" |
20 | | #include "lib/jxl/base/compiler_specific.h" |
21 | | #include "lib/jxl/base/data_parallel.h" |
22 | | #include "lib/jxl/base/override.h" |
23 | | #include "lib/jxl/base/rect.h" |
24 | | #include "lib/jxl/base/span.h" |
25 | | #include "lib/jxl/base/status.h" |
26 | | #include "lib/jxl/color_encoding_internal.h" |
27 | | #include "lib/jxl/common.h" |
28 | | #include "lib/jxl/compressed_dc.h" |
29 | | #include "lib/jxl/dct_util.h" |
30 | | #include "lib/jxl/dec_cache.h" |
31 | | #include "lib/jxl/dec_frame.h" |
32 | | #include "lib/jxl/enc_aux_out.h" |
33 | | #include "lib/jxl/enc_frame.h" |
34 | | #include "lib/jxl/enc_group.h" |
35 | | #include "lib/jxl/enc_modular.h" |
36 | | #include "lib/jxl/enc_params.h" |
37 | | #include "lib/jxl/enc_quant_weights.h" |
38 | | #include "lib/jxl/frame_dimensions.h" |
39 | | #include "lib/jxl/frame_header.h" |
40 | | #include "lib/jxl/image.h" |
41 | | #include "lib/jxl/image_bundle.h" |
42 | | #include "lib/jxl/image_ops.h" |
43 | | #include "lib/jxl/passes_state.h" |
44 | | #include "lib/jxl/quantizer.h" |
45 | | |
46 | | namespace jxl { |
47 | | |
48 | | Status ComputeACMetadata(ThreadPool* pool, PassesEncoderState* enc_state, |
49 | 186 | ModularFrameEncoder* modular_frame_encoder) { |
50 | 186 | PassesSharedState& shared = enc_state->shared; |
51 | 186 | auto compute_ac_meta = [&](int group_index, int /* thread */) -> Status { |
52 | 186 | const Rect r = shared.frame_dim.DCGroupRect(group_index); |
53 | 186 | int modular_group_index = group_index; |
54 | 186 | if (enc_state->streaming_mode) { |
55 | 0 | JXL_ENSURE(group_index == 0); |
56 | 0 | modular_group_index = enc_state->dc_group_index; |
57 | 0 | } |
58 | 186 | JXL_RETURN_IF_ERROR(modular_frame_encoder->AddACMetadata( |
59 | 186 | r, modular_group_index, |
60 | 186 | /*jpeg_transcode=*/false, enc_state)); |
61 | 186 | return true; |
62 | 186 | }; |
63 | 186 | JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_dc_groups, |
64 | 186 | ThreadPool::NoInit, compute_ac_meta, |
65 | 186 | "Compute AC Metadata")); |
66 | 186 | return true; |
67 | 186 | } |
68 | | |
69 | | Status InitializePassesEncoder(const FrameHeader& frame_header, |
70 | | const Image3F& opsin, const Rect& rect, |
71 | | const JxlCmsInterface& cms, ThreadPool* pool, |
72 | | PassesEncoderState* enc_state, |
73 | | ModularFrameEncoder* modular_frame_encoder, |
74 | 186 | AuxOut* aux_out) { |
75 | 186 | PassesSharedState& JXL_RESTRICT shared = enc_state->shared; |
76 | 186 | JxlMemoryManager* memory_manager = enc_state->memory_manager(); |
77 | | |
78 | 186 | enc_state->x_qm_multiplier = std::pow(1.25f, frame_header.x_qm_scale - 2.0f); |
79 | 186 | enc_state->b_qm_multiplier = std::pow(1.25f, frame_header.b_qm_scale - 2.0f); |
80 | | |
81 | 186 | if (enc_state->coeffs.size() < frame_header.passes.num_passes) { |
82 | 186 | enc_state->coeffs.reserve(frame_header.passes.num_passes); |
83 | 186 | for (size_t i = enc_state->coeffs.size(); |
84 | 372 | i < frame_header.passes.num_passes; i++) { |
85 | | // Allocate enough coefficients for each group on every row. |
86 | 186 | JXL_ASSIGN_OR_RETURN( |
87 | 186 | std::unique_ptr<ACImageT<int32_t>> coeffs, |
88 | 186 | ACImageT<int32_t>::Make(memory_manager, kGroupDim * kGroupDim, |
89 | 186 | shared.frame_dim.num_groups)); |
90 | 186 | enc_state->coeffs.emplace_back(std::move(coeffs)); |
91 | 186 | } |
92 | 186 | } |
93 | 186 | while (enc_state->coeffs.size() > frame_header.passes.num_passes) { |
94 | 0 | enc_state->coeffs.pop_back(); |
95 | 0 | } |
96 | | |
97 | 186 | if (enc_state->initialize_global_state) { |
98 | 186 | float scale = |
99 | 186 | shared.quantizer.ScaleGlobalScale(enc_state->cparams.quant_ac_rescale); |
100 | 186 | JXL_RETURN_IF_ERROR( |
101 | 186 | DequantMatricesScaleDC(memory_manager, &shared.matrices, scale)); |
102 | 186 | shared.quantizer.RecomputeFromGlobalScale(); |
103 | 186 | } |
104 | | |
105 | 372 | JXL_ASSIGN_OR_RETURN( |
106 | 372 | Image3F dc, Image3F::Create(memory_manager, shared.frame_dim.xsize_blocks, |
107 | 372 | shared.frame_dim.ysize_blocks)); |
108 | 595 | const auto process_group = [&](size_t group_idx, size_t _) -> Status { |
109 | 595 | JXL_RETURN_IF_ERROR( |
110 | 595 | ComputeCoefficients(group_idx, enc_state, opsin, rect, &dc)); |
111 | 595 | return true; |
112 | 595 | }; |
113 | 372 | JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_groups, |
114 | 372 | ThreadPool::NoInit, process_group, |
115 | 372 | "Compute coeffs")); |
116 | | |
117 | 186 | if (frame_header.flags & FrameHeader::kUseDcFrame) { |
118 | 0 | CompressParams cparams = enc_state->cparams; |
119 | 0 | cparams.dots = Override::kOff; |
120 | 0 | cparams.noise = Override::kOff; |
121 | 0 | cparams.patches = Override::kOff; |
122 | 0 | cparams.gaborish = Override::kOff; |
123 | 0 | cparams.epf = 0; |
124 | 0 | cparams.resampling = 1; |
125 | 0 | cparams.ec_resampling = 1; |
126 | | // The DC frame will have alpha=0. Don't erase its contents. |
127 | 0 | cparams.keep_invisible = Override::kOn; |
128 | 0 | JXL_ENSURE(cparams.progressive_dc > 0); |
129 | 0 | cparams.progressive_dc--; |
130 | | // Use kVarDCT in max_error_mode for intermediate progressive DC, |
131 | | // and kModular for the smallest DC (first in the bitstream) |
132 | 0 | if (cparams.progressive_dc == 0) { |
133 | 0 | cparams.modular_mode = true; |
134 | 0 | cparams.speed_tier = static_cast<SpeedTier>( |
135 | 0 | std::max(static_cast<int>(SpeedTier::kTortoise), |
136 | 0 | static_cast<int>(cparams.speed_tier) - 1)); |
137 | 0 | cparams.butteraugli_distance = |
138 | 0 | std::max(kMinButteraugliDistance * 0.02f, |
139 | 0 | enc_state->cparams.butteraugli_distance * 0.02f); |
140 | 0 | } else { |
141 | 0 | cparams.max_error_mode = true; |
142 | 0 | for (size_t c = 0; c < 3; c++) { |
143 | 0 | cparams.max_error[c] = shared.quantizer.MulDC()[c]; |
144 | 0 | } |
145 | | // Guess a distance that produces good initial results. |
146 | 0 | cparams.butteraugli_distance = |
147 | 0 | std::max(kMinButteraugliDistance, |
148 | 0 | enc_state->cparams.butteraugli_distance * 0.1f); |
149 | 0 | } |
150 | 0 | auto ib = |
151 | 0 | jxl::make_unique<ImageBundle>(memory_manager, &shared.metadata->m); |
152 | | // This is a lie - dc is in XYB |
153 | | // (but EncodeFrame will skip RGB->XYB conversion anyway) |
154 | 0 | JXL_RETURN_IF_ERROR(ib->SetFromImage( |
155 | 0 | std::move(dc), |
156 | 0 | ColorEncoding::LinearSRGB(shared.metadata->m.color_encoding.IsGray()))); |
157 | 0 | if (!ib->metadata()->extra_channel_info.empty()) { |
158 | | // Add placeholder extra channels to the patch image: dc_level frames do |
159 | | // not yet support extra channels, but the codec expects that the amount |
160 | | // of extra channels in frames matches that in the metadata of the |
161 | | // codestream. |
162 | 0 | std::vector<ImageF> extra_channels; |
163 | 0 | extra_channels.reserve(ib->metadata()->extra_channel_info.size()); |
164 | 0 | for (size_t i = 0; i < ib->metadata()->extra_channel_info.size(); i++) { |
165 | 0 | JXL_ASSIGN_OR_RETURN( |
166 | 0 | ImageF ch, |
167 | 0 | ImageF::Create(memory_manager, ib->xsize(), ib->ysize())); |
168 | 0 | extra_channels.emplace_back(std::move(ch)); |
169 | | // Must initialize the image with data to not affect blending with |
170 | | // uninitialized memory. |
171 | | // TODO(lode): dc_level must copy and use the real extra channels |
172 | | // instead. |
173 | 0 | ZeroFillImage(&extra_channels.back()); |
174 | 0 | } |
175 | 0 | JXL_RETURN_IF_ERROR(ib->SetExtraChannels(std::move(extra_channels))); |
176 | 0 | } |
177 | 0 | auto special_frame = jxl::make_unique<BitWriter>(memory_manager); |
178 | 0 | FrameInfo dc_frame_info; |
179 | 0 | dc_frame_info.frame_type = FrameType::kDCFrame; |
180 | 0 | dc_frame_info.dc_level = frame_header.dc_level + 1; |
181 | 0 | dc_frame_info.ib_needs_color_transform = false; |
182 | 0 | dc_frame_info.save_before_color_transform = true; // Implicitly true |
183 | 0 | AuxOut dc_aux_out; |
184 | 0 | JXL_RETURN_IF_ERROR(EncodeFrame( |
185 | 0 | memory_manager, cparams, dc_frame_info, shared.metadata, *ib, cms, pool, |
186 | 0 | special_frame.get(), aux_out ? &dc_aux_out : nullptr)); |
187 | 0 | if (aux_out) { |
188 | 0 | for (const auto& l : dc_aux_out.layers) { |
189 | 0 | aux_out->layer(LayerType::Dc).Assimilate(l); |
190 | 0 | } |
191 | 0 | } |
192 | 0 | const Span<const uint8_t> encoded = special_frame->GetSpan(); |
193 | 0 | enc_state->special_frames.emplace_back(std::move(special_frame)); |
194 | |
|
195 | 0 | auto decoded = |
196 | 0 | jxl::make_unique<ImageBundle>(memory_manager, &shared.metadata->m); |
197 | 0 | std::unique_ptr<PassesDecoderState> dec_state = |
198 | 0 | jxl::make_unique<PassesDecoderState>(memory_manager); |
199 | 0 | JXL_RETURN_IF_ERROR( |
200 | 0 | dec_state->output_encoding_info.SetFromMetadata(*shared.metadata)); |
201 | 0 | const uint8_t* frame_start = encoded.data(); |
202 | 0 | size_t encoded_size = encoded.size(); |
203 | 0 | for (int i = 0; i <= cparams.progressive_dc; ++i) { |
204 | 0 | JXL_RETURN_IF_ERROR(DecodeFrame( |
205 | 0 | dec_state.get(), pool, frame_start, encoded_size, |
206 | 0 | /*frame_header=*/nullptr, decoded.get(), *shared.metadata)); |
207 | 0 | frame_start += decoded->decoded_bytes(); |
208 | 0 | encoded_size -= decoded->decoded_bytes(); |
209 | 0 | } |
210 | | // TODO(lode): frame_header.dc_level should be equal to |
211 | | // dec_state.frame_header.dc_level - 1 here, since above we set |
212 | | // dc_frame_info.dc_level = frame_header.dc_level + 1, and |
213 | | // dc_frame_info.dc_level is used by EncodeFrame. However, if EncodeFrame |
214 | | // outputs multiple frames, this assumption could be wrong. |
215 | 0 | const Image3F& dc_frame = |
216 | 0 | dec_state->shared->dc_frames[frame_header.dc_level]; |
217 | 0 | JXL_ASSIGN_OR_RETURN( |
218 | 0 | shared.dc_storage, |
219 | 0 | Image3F::Create(memory_manager, dc_frame.xsize(), dc_frame.ysize())); |
220 | 0 | JXL_RETURN_IF_ERROR(CopyImageTo(dc_frame, &shared.dc_storage)); |
221 | 0 | ZeroFillImage(&shared.quant_dc); |
222 | 0 | shared.dc = &shared.dc_storage; |
223 | 0 | JXL_ENSURE(encoded_size == 0); |
224 | 186 | } else { |
225 | 186 | auto compute_dc_coeffs = [&](int group_index, int /* thread */) -> Status { |
226 | 186 | const Rect r = enc_state->shared.frame_dim.DCGroupRect(group_index); |
227 | 186 | int modular_group_index = group_index; |
228 | 186 | if (enc_state->streaming_mode) { |
229 | 0 | JXL_ENSURE(group_index == 0); |
230 | 0 | modular_group_index = enc_state->dc_group_index; |
231 | 0 | } |
232 | 186 | JXL_RETURN_IF_ERROR(modular_frame_encoder->AddVarDCTDC( |
233 | 186 | frame_header, dc, r, modular_group_index, |
234 | 186 | enc_state->cparams.speed_tier < SpeedTier::kFalcon, enc_state, |
235 | 186 | /*jpeg_transcode=*/false)); |
236 | 186 | return true; |
237 | 186 | }; |
238 | 186 | JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_dc_groups, |
239 | 186 | ThreadPool::NoInit, compute_dc_coeffs, |
240 | 186 | "Compute DC coeffs")); |
241 | | // TODO(veluca): this is only useful in tests and if inspection is enabled. |
242 | 186 | if (!(frame_header.flags & FrameHeader::kSkipAdaptiveDCSmoothing)) { |
243 | 186 | JXL_RETURN_IF_ERROR(AdaptiveDCSmoothing( |
244 | 186 | memory_manager, shared.quantizer.MulDC(), &shared.dc_storage, pool)); |
245 | 186 | } |
246 | 186 | } |
247 | 186 | return true; |
248 | 186 | } |
249 | | |
250 | | } // namespace jxl |