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