// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #ifndef LIB_JXL_DEC_FRAME_H_ #define LIB_JXL_DEC_FRAME_H_ #include #include "jxl/decode.h" #include "jxl/types.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/blending.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/common.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/dec_cache.h" #include "lib/jxl/dec_modular.h" #include "lib/jxl/dec_params.h" #include "lib/jxl/frame_header.h" #include "lib/jxl/headers.h" #include "lib/jxl/image_bundle.h" namespace jxl { // TODO(veluca): remove DecodeFrameHeader once the API migrates to FrameDecoder. // `frame_header` must have nonserialized_metadata and // nonserialized_is_preview set. Status DecodeFrameHeader(BitReader* JXL_RESTRICT reader, FrameHeader* JXL_RESTRICT frame_header); // Decodes a frame. Groups may be processed in parallel by `pool`. // See DecodeFile for explanation of c_decoded. // `io` is only used for reading maximum image size. Also updates // `dec_state` with the new frame header. // `metadata` is the metadata that applies to all frames of the codestream // `decoded->metadata` must already be set and must match metadata.m. Status DecodeFrame(const DecompressParams& dparams, PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool, BitReader* JXL_RESTRICT reader, ImageBundle* decoded, const CodecMetadata& metadata, const SizeConstraints* constraints, bool is_preview = false); // TODO(veluca): implement "forced drawing". class FrameDecoder { public: // All parameters must outlive the FrameDecoder. FrameDecoder(PassesDecoderState* dec_state, const CodecMetadata& metadata, ThreadPool* pool, bool use_slow_rendering_pipeline) : dec_state_(dec_state), pool_(pool), frame_header_(&metadata), use_slow_rendering_pipeline_(use_slow_rendering_pipeline) {} // `constraints` must outlive the FrameDecoder if not null, or stay alive // until the next call to SetFrameSizeLimits. void SetFrameSizeLimits(const SizeConstraints* constraints) { constraints_ = constraints; } void SetRenderSpotcolors(bool rsc) { render_spotcolors_ = rsc; } void SetCoalescing(bool c) { coalescing_ = c; } // Read FrameHeader and table of contents from the given BitReader. // Also checks frame dimensions for their limits, and sets the output // image buffer. // TODO(veluca): remove the `allow_partial_frames` flag - this should be moved // on callers. Status InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded, bool is_preview, bool allow_partial_frames, bool allow_partial_dc_global, bool output_needed); struct SectionInfo { BitReader* JXL_RESTRICT br; size_t id; }; enum SectionStatus { // Processed correctly. kDone = 0, // Skipped because other required sections were not yet processed. kSkipped = 1, // Skipped because the section was already processed. kDuplicate = 2, // Only partially decoded: the section will need to be processed again. kPartial = 3, }; // Processes `num` sections; each SectionInfo contains the index // of the section and a BitReader that only contains the data of the section. // `section_status` should point to `num` elements, and will be filled with // information about whether each section was processed or not. // A section is a part of the encoded file that is indexed by the TOC. Status ProcessSections(const SectionInfo* sections, size_t num, SectionStatus* section_status); // Flushes all the data decoded so far to pixels. Status Flush(); // Runs final operations once a frame data is decoded. // Must be called exactly once per frame, after all calls to ProcessSections. Status FinalizeFrame(); // Returns dependencies of this frame on reference ids as a bit mask: bits 0-3 // indicate reference frame 0-3 for patches and blending, bits 4-7 indicate DC // frames this frame depends on. Only returns a valid result after all calls // to ProcessSections are finished and before FinalizeFrame. int References() const; // Returns reference id of storage location where this frame is stored as a // bit flag, or 0 if not stored. // Matches the bit mask used for GetReferences: bits 0-3 indicate it is stored // for patching or blending, bits 4-7 indicate DC frame. // Unlike References, can be ran at any time as // soon as the frame header is known. static int SavedAs(const FrameHeader& header); // Returns offset of this section after the end of the TOC. The end of the TOC // is the byte position of the bit reader after InitFrame was called. const std::vector& SectionOffsets() const { return section_offsets_; } const std::vector& SectionSizes() const { return section_sizes_; } size_t NumSections() const { return section_sizes_.size(); } // TODO(veluca): remove once we remove --downsampling flag. void SetMaxPasses(size_t max_passes) { max_passes_ = max_passes; } const FrameHeader& GetFrameHeader() const { return frame_header_; } // Returns whether a DC image has been decoded, accessible at low resolution // at passes.shared_storage.dc_storage bool HasDecodedDC() const { return finalized_dc_; } bool HasDecodedAll() const { return NumSections() == num_sections_done_; } size_t NumCompletePasses() const { return *std::min_element(decoded_passes_per_ac_group_.begin(), decoded_passes_per_ac_group_.end()); }; // If enabled, ProcessSections will stop and return true when the DC // sections have been processed, instead of starting the AC sections. This // will only occur if supported (that is, flushing will produce a valid // 1/8th*1/8th resolution image). The return value of true then does not mean // all sections have been processed, use HasDecodedDC and HasDecodedAll // to check the true finished state. void SetPauseAtProgressive(JxlProgressiveDetail prog_detail) { pause_at_progressive_ = true; progressive_detail_ = prog_detail; } // Sets the buffer to which uint8 sRGB pixels will be decoded. This is not // supported for all images. If it succeeds, HasRGBBuffer() will return true. // If it does not succeed, the image is decoded to the ImageBundle passed to // InitFrame instead. // If an output callback is set, this function *may not* be called. // // @param undo_orientation: if true, indicates the frame decoder should apply // the exif orientation to bring the image to the intended display // orientation. Performing this operation is not yet supported, so this // results in not setting the buffer if the image has a non-identity EXIF // orientation. When outputting to the ImageBundle, no orientation is undone. void MaybeSetRGB8OutputBuffer(uint8_t* rgb_output, size_t stride, bool is_rgba, bool undo_orientation) const { if (!CanDoLowMemoryPath(undo_orientation)) return; dec_state_->rgb_output = rgb_output; dec_state_->rgb_output_is_rgba = is_rgba; dec_state_->rgb_stride = stride; JXL_ASSERT(!dec_state_->pixel_callback.IsPresent()); #if !JXL_HIGH_PRECISION if (decoded_->metadata()->xyb_encoded && dec_state_->output_encoding_info.color_encoding.IsSRGB() && dec_state_->output_encoding_info.all_default_opsin && HasFastXYBTosRGB8() && frame_header_.needs_color_transform()) { dec_state_->fast_xyb_srgb8_conversion = true; } #endif } // Same as MaybeSetRGB8OutputBuffer, but with a float callback. This is not // supported for all images. If it succeeds, HasRGBBuffer() will return true. // If it does not succeed, the image is decoded to the ImageBundle passed to // InitFrame instead. // If a RGB8 output buffer is set, this function *may not* be called. // // @param undo_orientation: if true, indicates the frame decoder should apply // the exif orientation to bring the image to the intended display // orientation. Performing this operation is not yet supported, so this // results in not setting the buffer if the image has a non-identity EXIF // orientation. When outputting to the ImageBundle, no orientation is undone. void MaybeSetFloatCallback(const PixelCallback& pixel_callback, bool is_rgba, bool undo_orientation) const { if (!CanDoLowMemoryPath(undo_orientation)) return; dec_state_->pixel_callback = pixel_callback; dec_state_->rgb_output_is_rgba = is_rgba; JXL_ASSERT(dec_state_->rgb_output == nullptr); } // Returns true if the rgb output buffer passed by MaybeSetRGB8OutputBuffer // has been/will be populated by Flush() / FinalizeFrame(), or if a pixel // callback has been used. bool HasRGBBuffer() const { return dec_state_->rgb_output != nullptr || dec_state_->pixel_callback.IsPresent(); } private: Status ProcessDCGlobal(BitReader* br); Status ProcessDCGroup(size_t dc_group_id, BitReader* br); void FinalizeDC(); Status AllocateOutput(); Status ProcessACGlobal(BitReader* br); Status ProcessACGroup(size_t ac_group_id, BitReader* JXL_RESTRICT* br, size_t num_passes, size_t thread, bool force_draw, bool dc_only); void MarkSections(const SectionInfo* sections, size_t num, SectionStatus* section_status); // Allocates storage for parallel decoding using up to `num_threads` threads // of up to `num_tasks` tasks. The value of `thread` passed to // `GetStorageLocation` must be smaller than the `num_threads` value passed // here. The value of `task` passed to `GetStorageLocation` must be smaller // than the value of `num_tasks` passed here. Status PrepareStorage(size_t num_threads, size_t num_tasks) { size_t storage_size = std::min(num_threads, num_tasks); if (storage_size > group_dec_caches_.size()) { group_dec_caches_.resize(storage_size); } use_task_id_ = num_threads > num_tasks; if (dec_state_->render_pipeline) { JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads( storage_size, /*use_group_ids=*/modular_frame_decoder_.UsesFullImage() && frame_header_.encoding == FrameEncoding::kVarDCT)); } return true; } size_t GetStorageLocation(size_t thread, size_t task) { if (use_task_id_) return task; return thread; } // If the image has default exif orientation (or has an orientation but should // not be undone) and no blending, the current frame cannot be referenced by // future frames, there are no spot colors to be rendered, and alpha is not // premultiplied, then low memory options can be used // (uint8 output buffer or float pixel callback). // TODO(veluca): reduce this set of restrictions. bool CanDoLowMemoryPath(bool undo_orientation) const { return !(undo_orientation && decoded_->metadata()->GetOrientation() != Orientation::kIdentity); } PassesDecoderState* dec_state_; ThreadPool* pool_; std::vector section_offsets_; std::vector section_sizes_; size_t max_passes_; // TODO(veluca): figure out the duplication between these and dec_state_. FrameHeader frame_header_; FrameDimensions frame_dim_; ImageBundle* decoded_; ModularFrameDecoder modular_frame_decoder_; bool allow_partial_frames_; bool allow_partial_dc_global_; bool render_spotcolors_ = true; bool coalescing_ = true; std::vector processed_section_; std::vector decoded_passes_per_ac_group_; std::vector decoded_dc_groups_; bool decoded_dc_global_; bool decoded_ac_global_; bool HasEverything() const; bool finalized_dc_ = true; size_t num_sections_done_ = 0; bool is_finalized_ = true; size_t num_renders_ = 0; bool allocated_ = false; std::vector group_dec_caches_; // Frame size limits. const SizeConstraints* constraints_ = nullptr; // Whether or not the task id should be used for storage indexing, instead of // the thread id. bool use_task_id_ = false; // Testing setting: whether or not to use the slow rendering pipeline. bool use_slow_rendering_pipeline_; bool pause_at_progressive_ = false; JxlProgressiveDetail progressive_detail_ = kDC; }; } // namespace jxl #endif // LIB_JXL_DEC_FRAME_H_