// 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 "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); // Leaves reader in the same state as DecodeFrame would. Used to skip preview. // Also updates `dec_state` with the new frame header. Status SkipFrame(const CodecMetadata& metadata, BitReader* JXL_RESTRICT reader, 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) : dec_state_(dec_state), pool_(pool), frame_header_(&metadata) {} // `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; } // 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); 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 frame_header_.encoding == FrameEncoding::kVarDCT && finalized_dc_; } // 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 == nullptr); #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 std::function& cb, bool is_rgba, bool undo_orientation) const { if (!CanDoLowMemoryPath(undo_orientation)) return; dec_state_->pixel_callback = cb; 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 != nullptr; } private: Status ProcessDCGlobal(BitReader* br); Status ProcessDCGroup(size_t dc_group_id, BitReader* br); void FinalizeDC(); void 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); // 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. void 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); } dec_state_->EnsureStorage(storage_size); use_task_id_ = num_threads > num_tasks; } 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 { if (undo_orientation && decoded_->metadata()->GetOrientation() != Orientation::kIdentity) { return false; } if (ImageBlender::NeedsBlending(dec_state_)) return false; if (frame_header_.CanBeReferenced()) return false; if (render_spotcolors_ && decoded_->metadata()->Find(ExtraChannel::kSpotColor)) { return false; } if (decoded_->AlphaIsPremultiplied()) return false; return true; } 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; 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 finalized_dc_ = true; 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; }; } // namespace jxl #endif // LIB_JXL_DEC_FRAME_H_