/src/libjxl/lib/jxl/dec_frame.h
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 | | #ifndef LIB_JXL_DEC_FRAME_H_ |
7 | | #define LIB_JXL_DEC_FRAME_H_ |
8 | | |
9 | | #include <jxl/decode.h> |
10 | | #include <jxl/types.h> |
11 | | |
12 | | #include <algorithm> |
13 | | #include <cstddef> |
14 | | #include <cstdint> |
15 | | #include <limits> |
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/status.h" |
23 | | #include "lib/jxl/common.h" // JXL_HIGH_PRECISION |
24 | | #include "lib/jxl/dec_bit_reader.h" |
25 | | #include "lib/jxl/dec_cache.h" |
26 | | #include "lib/jxl/dec_modular.h" |
27 | | #include "lib/jxl/frame_header.h" |
28 | | #include "lib/jxl/image_bundle.h" |
29 | | #include "lib/jxl/image_metadata.h" |
30 | | |
31 | | namespace jxl { |
32 | | |
33 | | // Decodes a frame. Groups may be processed in parallel by `pool`. |
34 | | // `metadata` is the metadata that applies to all frames of the codestream |
35 | | // `decoded->metadata` must already be set and must match metadata.m. |
36 | | // Used in the encoder to model decoder behaviour, and in tests. |
37 | | Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool, |
38 | | const uint8_t* next_in, size_t avail_in, |
39 | | FrameHeader* frame_header, ImageBundle* decoded, |
40 | | const CodecMetadata& metadata, |
41 | | bool use_slow_rendering_pipeline = false); |
42 | | |
43 | | // TODO(veluca): implement "forced drawing". |
44 | | class FrameDecoder { |
45 | | public: |
46 | | // All parameters must outlive the FrameDecoder. |
47 | | FrameDecoder(PassesDecoderState* dec_state, const CodecMetadata& metadata, |
48 | | ThreadPool* pool, bool use_slow_rendering_pipeline) |
49 | 110k | : dec_state_(dec_state), |
50 | 110k | pool_(pool), |
51 | 110k | frame_header_(&metadata), |
52 | 110k | modular_frame_decoder_(dec_state_->memory_manager()), |
53 | 110k | use_slow_rendering_pipeline_(use_slow_rendering_pipeline) {} |
54 | | |
55 | 88.0k | void SetRenderSpotcolors(bool rsc) { render_spotcolors_ = rsc; } |
56 | 88.0k | void SetCoalescing(bool c) { coalescing_ = c; } |
57 | | |
58 | | // Read FrameHeader and table of contents from the given BitReader. |
59 | | Status InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded, |
60 | | bool is_preview); |
61 | | |
62 | | // Checks frame dimensions for their limits, and sets the output |
63 | | // image buffer. |
64 | | Status InitFrameOutput(); |
65 | | |
66 | | struct SectionInfo { |
67 | | BitReader* JXL_RESTRICT br; |
68 | | // Logical index of the section, regardless of any permutation that may be |
69 | | // applied in the table of contents or of the physical position in the file. |
70 | | size_t id; |
71 | | // Index of the section in the order of the bytes inside the frame. |
72 | | size_t index; |
73 | | }; |
74 | | |
75 | | struct TocEntry { |
76 | | size_t size; |
77 | | size_t id; |
78 | | }; |
79 | | |
80 | | enum SectionStatus { |
81 | | // Processed correctly. |
82 | | kDone = 0, |
83 | | // Skipped because other required sections were not yet processed. |
84 | | kSkipped = 1, |
85 | | // Skipped because the section was already processed. |
86 | | kDuplicate = 2, |
87 | | // Only partially decoded: the section will need to be processed again. |
88 | | kPartial = 3, |
89 | | }; |
90 | | |
91 | | // Processes `num` sections; each SectionInfo contains the index |
92 | | // of the section and a BitReader that only contains the data of the section. |
93 | | // `section_status` should point to `num` elements, and will be filled with |
94 | | // information about whether each section was processed or not. |
95 | | // A section is a part of the encoded file that is indexed by the TOC. |
96 | | Status ProcessSections(const SectionInfo* sections, size_t num, |
97 | | SectionStatus* section_status); |
98 | | |
99 | | // Flushes all the data decoded so far to pixels. |
100 | | Status Flush(); |
101 | | |
102 | | // Runs final operations once a frame data is decoded. |
103 | | // Must be called exactly once per frame, after all calls to ProcessSections. |
104 | | Status FinalizeFrame(); |
105 | | |
106 | | // Returns dependencies of this frame on reference ids as a bit mask: bits 0-3 |
107 | | // indicate reference frame 0-3 for patches and blending, bits 4-7 indicate DC |
108 | | // frames this frame depends on. Only returns a valid result after all calls |
109 | | // to ProcessSections are finished and before FinalizeFrame. |
110 | | int References() const; |
111 | | |
112 | | // Returns reference id of storage location where this frame is stored as a |
113 | | // bit flag, or 0 if not stored. |
114 | | // Matches the bit mask used for GetReferences: bits 0-3 indicate it is stored |
115 | | // for patching or blending, bits 4-7 indicate DC frame. |
116 | | // Unlike References, can be ran at any time as |
117 | | // soon as the frame header is known. |
118 | | static int SavedAs(const FrameHeader& header); |
119 | | |
120 | 88.4k | uint64_t SumSectionSizes() const { return section_sizes_sum_; } |
121 | 134k | const std::vector<TocEntry>& Toc() const { return toc_; } |
122 | | |
123 | 88.4k | const FrameHeader& GetFrameHeader() const { return frame_header_; } |
124 | | |
125 | | // Returns whether a DC image has been decoded, accessible at low resolution |
126 | | // at passes.shared_storage.dc_storage |
127 | 3.09k | bool HasDecodedDC() const { return finalized_dc_; } |
128 | 39.3k | bool HasDecodedAll() const { return toc_.size() == num_sections_done_; } |
129 | | |
130 | 85.5k | size_t NumCompletePasses() const { |
131 | 85.5k | return *std::min_element(decoded_passes_per_ac_group_.begin(), |
132 | 85.5k | decoded_passes_per_ac_group_.end()); |
133 | 85.5k | } |
134 | | |
135 | | // If enabled, ProcessSections will stop and return true when the DC |
136 | | // sections have been processed, instead of starting the AC sections. This |
137 | | // will only occur if supported (that is, flushing will produce a valid |
138 | | // 1/8th*1/8th resolution image). The return value of true then does not mean |
139 | | // all sections have been processed, use HasDecodedDC and HasDecodedAll |
140 | | // to check the true finished state. |
141 | | // Returns the progressive detail that will be effective for the frame. |
142 | 0 | JxlProgressiveDetail SetPauseAtProgressive(JxlProgressiveDetail prog_detail) { |
143 | 0 | bool single_section = |
144 | 0 | frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1; |
145 | 0 | if (frame_header_.frame_type != kSkipProgressive && |
146 | | // If there's only one group and one pass, there is no separate section |
147 | | // for DC and the entire full resolution image is available at once. |
148 | 0 | !single_section && |
149 | | // If extra channels are encoded with modular without squeeze, they |
150 | | // don't support DC. If the are encoded with squeeze, DC works in theory |
151 | | // but the implementation may not yet correctly support this for Flush. |
152 | | // Therefore, can't correctly pause for a progressive step if there is |
153 | | // an extra channel (including alpha channel) |
154 | | // TODO(firsching): Check if this is still the case. |
155 | 0 | decoded_->metadata()->extra_channel_info.empty() && |
156 | | // DC is not guaranteed to be available in modular mode and may be a |
157 | | // black image. If squeeze is used, it may be available depending on the |
158 | | // current implementation. |
159 | | // TODO(lode): do return DC if it's known that flushing at this point |
160 | | // will produce a valid 1/8th downscaled image with modular encoding. |
161 | 0 | frame_header_.encoding == FrameEncoding::kVarDCT) { |
162 | 0 | progressive_detail_ = prog_detail; |
163 | 0 | } else { |
164 | 0 | progressive_detail_ = JxlProgressiveDetail::kFrames; |
165 | 0 | } |
166 | 0 | if (progressive_detail_ >= JxlProgressiveDetail::kPasses) { |
167 | 0 | for (size_t i = 1; i < frame_header_.passes.num_passes; ++i) { |
168 | 0 | passes_to_pause_.push_back(i); |
169 | 0 | } |
170 | 0 | } else if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) { |
171 | 0 | for (size_t i = 0; i < frame_header_.passes.num_downsample; ++i) { |
172 | 0 | passes_to_pause_.push_back(frame_header_.passes.last_pass[i] + 1); |
173 | 0 | } |
174 | | // The format does not guarantee that these values are sorted. |
175 | 0 | std::sort(passes_to_pause_.begin(), passes_to_pause_.end()); |
176 | 0 | } |
177 | 0 | return progressive_detail_; |
178 | 0 | } |
179 | | |
180 | 46.2k | size_t NextNumPassesToPause() const { |
181 | 46.2k | auto it = std::upper_bound(passes_to_pause_.begin(), passes_to_pause_.end(), |
182 | 46.2k | NumCompletePasses()); |
183 | 46.2k | return (it != passes_to_pause_.end() ? *it |
184 | 46.2k | : std::numeric_limits<size_t>::max()); |
185 | 46.2k | } |
186 | | |
187 | | // Sets the pixel callback or image buffer where the pixels will be decoded. |
188 | | // |
189 | | // @param undo_orientation: if true, indicates the frame decoder should apply |
190 | | // the exif orientation to bring the image to the intended display |
191 | | // orientation. |
192 | | void SetImageOutput(const PixelCallback& pixel_callback, void* image_buffer, |
193 | | size_t image_buffer_size, size_t xsize, size_t ysize, |
194 | | JxlPixelFormat format, size_t bits_per_sample, |
195 | 8.98k | bool unpremul_alpha, bool undo_orientation) const { |
196 | 8.98k | dec_state_->width = xsize; |
197 | 8.98k | dec_state_->height = ysize; |
198 | 8.98k | dec_state_->main_output.format = format; |
199 | 8.98k | dec_state_->main_output.bits_per_sample = bits_per_sample; |
200 | 8.98k | dec_state_->main_output.callback = pixel_callback; |
201 | 8.98k | dec_state_->main_output.buffer = image_buffer; |
202 | 8.98k | dec_state_->main_output.buffer_size = image_buffer_size; |
203 | 8.98k | dec_state_->main_output.stride = GetStride(xsize, format); |
204 | 8.98k | const jxl::ExtraChannelInfo* alpha = |
205 | 8.98k | decoded_->metadata()->Find(jxl::ExtraChannel::kAlpha); |
206 | 8.98k | if (alpha && alpha->alpha_associated && unpremul_alpha) { |
207 | 0 | dec_state_->unpremul_alpha = true; |
208 | 0 | } |
209 | 8.98k | if (undo_orientation) { |
210 | 0 | dec_state_->undo_orientation = decoded_->metadata()->GetOrientation(); |
211 | 0 | if (static_cast<int>(dec_state_->undo_orientation) > 4) { |
212 | 0 | std::swap(dec_state_->width, dec_state_->height); |
213 | 0 | } |
214 | 0 | } |
215 | 8.98k | dec_state_->extra_output.clear(); |
216 | | #if !JXL_HIGH_PRECISION |
217 | | if (dec_state_->main_output.buffer && |
218 | | (format.data_type == JXL_TYPE_UINT8) && (format.num_channels >= 3) && |
219 | | !dec_state_->unpremul_alpha && |
220 | | (dec_state_->undo_orientation == Orientation::kIdentity) && |
221 | | decoded_->metadata()->xyb_encoded && |
222 | | dec_state_->output_encoding_info.color_encoding.IsSRGB() && |
223 | | dec_state_->output_encoding_info.all_default_opsin && |
224 | | (dec_state_->output_encoding_info.desired_intensity_target == |
225 | | dec_state_->output_encoding_info.orig_intensity_target) && |
226 | | HasFastXYBTosRGB8() && frame_header_.needs_color_transform()) { |
227 | | dec_state_->fast_xyb_srgb8_conversion = true; |
228 | | } |
229 | | #endif |
230 | 8.98k | } |
231 | | |
232 | | void AddExtraChannelOutput(void* buffer, size_t buffer_size, size_t xsize, |
233 | 6 | JxlPixelFormat format, size_t bits_per_sample) { |
234 | 6 | ImageOutput out; |
235 | 6 | out.format = format; |
236 | 6 | out.bits_per_sample = bits_per_sample; |
237 | 6 | out.buffer = buffer; |
238 | 6 | out.buffer_size = buffer_size; |
239 | 6 | out.stride = GetStride(xsize, format); |
240 | 6 | dec_state_->extra_output.push_back(out); |
241 | 6 | } |
242 | | |
243 | | private: |
244 | | Status ProcessDCGlobal(BitReader* br); |
245 | | Status ProcessDCGroup(size_t dc_group_id, BitReader* br); |
246 | | Status FinalizeDC(); |
247 | | Status AllocateOutput(); |
248 | | Status ProcessACGlobal(BitReader* br); |
249 | | Status ProcessACGroup(size_t ac_group_id, BitReader* JXL_RESTRICT* br, |
250 | | size_t num_passes, size_t thread, bool force_draw, |
251 | | bool dc_only); |
252 | | void MarkSections(const SectionInfo* sections, size_t num, |
253 | | const SectionStatus* section_status); |
254 | | |
255 | | // Allocates storage for parallel decoding using up to `num_threads` threads |
256 | | // of up to `num_tasks` tasks. The value of `thread` passed to |
257 | | // `GetStorageLocation` must be smaller than the `num_threads` value passed |
258 | | // here. The value of `task` passed to `GetStorageLocation` must be smaller |
259 | | // than the value of `num_tasks` passed here. |
260 | 37.3k | Status PrepareStorage(size_t num_threads, size_t num_tasks) { |
261 | 37.3k | size_t storage_size = std::min(num_threads, num_tasks); |
262 | 37.3k | if (storage_size > group_dec_caches_.size()) { |
263 | 37.3k | group_dec_caches_.resize(storage_size); |
264 | 37.3k | } |
265 | 37.3k | use_task_id_ = num_threads > num_tasks; |
266 | 37.3k | bool use_noise = (frame_header_.flags & FrameHeader::kNoise) != 0; |
267 | 37.3k | bool use_group_ids = |
268 | 37.3k | (modular_frame_decoder_.UsesFullImage() && |
269 | 37.3k | (frame_header_.encoding == FrameEncoding::kVarDCT || use_noise)); |
270 | 37.3k | if (dec_state_->render_pipeline) { |
271 | 37.3k | JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads( |
272 | 37.3k | storage_size, use_group_ids)); |
273 | 37.3k | } |
274 | 37.3k | return true; |
275 | 37.3k | } |
276 | | |
277 | 44.3k | size_t GetStorageLocation(size_t thread, size_t task) const { |
278 | 44.3k | if (use_task_id_) return task; |
279 | 4.89k | return thread; |
280 | 44.3k | } |
281 | | |
282 | 8.98k | static size_t BytesPerChannel(JxlDataType data_type) { |
283 | 8.98k | return (data_type == JXL_TYPE_UINT8 ? 1u |
284 | 8.98k | : data_type == JXL_TYPE_FLOAT ? 4u |
285 | 1.66k | : 2u); |
286 | 8.98k | } |
287 | | |
288 | 8.98k | static size_t GetStride(const size_t xsize, JxlPixelFormat format) { |
289 | 8.98k | size_t stride = |
290 | 8.98k | (xsize * BytesPerChannel(format.data_type) * format.num_channels); |
291 | 8.98k | if (format.align > 1) { |
292 | 8.97k | stride = (jxl::DivCeil(stride, format.align) * format.align); |
293 | 8.97k | } |
294 | 8.98k | return stride; |
295 | 8.98k | } |
296 | | |
297 | 75.9k | bool HasDcGroupToDecode() const { |
298 | 75.9k | return std::any_of(decoded_dc_groups_.cbegin(), decoded_dc_groups_.cend(), |
299 | 78.7k | [](uint8_t ready) { return ready == 0; }); |
300 | 75.9k | } |
301 | | |
302 | | PassesDecoderState* dec_state_; |
303 | | ThreadPool* pool_; |
304 | | std::vector<TocEntry> toc_; |
305 | | uint64_t section_sizes_sum_; |
306 | | // TODO(veluca): figure out the duplication between these and dec_state_. |
307 | | FrameHeader frame_header_; |
308 | | FrameDimensions frame_dim_; |
309 | | ImageBundle* decoded_; |
310 | | ModularFrameDecoder modular_frame_decoder_; |
311 | | bool render_spotcolors_ = true; |
312 | | bool coalescing_ = true; |
313 | | |
314 | | std::vector<uint8_t> processed_section_; |
315 | | std::vector<uint8_t> decoded_passes_per_ac_group_; |
316 | | std::vector<uint8_t> decoded_dc_groups_; |
317 | | bool decoded_dc_global_; |
318 | | bool decoded_ac_global_; |
319 | | bool HasEverything() const; |
320 | | bool finalized_dc_ = true; |
321 | | size_t num_sections_done_ = 0; |
322 | | bool is_finalized_ = true; |
323 | | bool allocated_ = false; |
324 | | |
325 | | std::vector<GroupDecCache> group_dec_caches_; |
326 | | |
327 | | // Whether or not the task id should be used for storage indexing, instead of |
328 | | // the thread id. |
329 | | bool use_task_id_ = false; |
330 | | |
331 | | // Testing setting: whether or not to use the slow rendering pipeline. |
332 | | bool use_slow_rendering_pipeline_; |
333 | | |
334 | | JxlProgressiveDetail progressive_detail_ = kFrames; |
335 | | // Number of completed passes where section decoding should pause. |
336 | | // Used for progressive details at least kLastPasses. |
337 | | std::vector<int> passes_to_pause_; |
338 | | }; |
339 | | |
340 | | } // namespace jxl |
341 | | |
342 | | #endif // LIB_JXL_DEC_FRAME_H_ |