summaryrefslogtreecommitdiff
path: root/lib/jxl/decode.cc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/jxl/decode.cc')
-rw-r--r--lib/jxl/decode.cc2444
1 files changed, 1513 insertions, 931 deletions
diff --git a/lib/jxl/decode.cc b/lib/jxl/decode.cc
index ef8adf0..1a0facc 100644
--- a/lib/jxl/decode.cc
+++ b/lib/jxl/decode.cc
@@ -5,42 +5,27 @@
#include "jxl/decode.h"
+#include "jxl/types.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
+#include "lib/jxl/box_content_decoder.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/dec_frame.h"
#include "lib/jxl/dec_modular.h"
-#include "lib/jxl/dec_reconstruct.h"
#include "lib/jxl/decode_to_jpeg.h"
#include "lib/jxl/fields.h"
+#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/icc_codec.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/loop_filter.h"
#include "lib/jxl/memory_manager_internal.h"
+#include "lib/jxl/sanitizers.h"
#include "lib/jxl/toc.h"
namespace {
-// If set (by fuzzer) then some operations will fail, if those would require
-// allocating large objects. Actual memory usage might be two orders of
-// magnitude bigger.
-// TODO(eustas): this is a poor-mans replacement for memory-manager approach;
-// remove, once memory-manager actually works.
-size_t memory_limit_base_ = 0;
-size_t cpu_limit_base_ = 0;
-size_t used_cpu_base_ = 0;
-
-bool CheckSizeLimit(size_t xsize, size_t ysize) {
- if (!memory_limit_base_) return true;
- if (xsize == 0 || ysize == 0) return true;
- size_t num_pixels = xsize * ysize;
- if (num_pixels / xsize != ysize) return false; // overflow
- if (num_pixels > memory_limit_base_) return false;
- return true;
-}
-
// Checks if a + b > size, taking possible integer overflow into account.
bool OutOfBounds(size_t a, size_t b, size_t size) {
size_t pos = a + b;
@@ -49,16 +34,6 @@ bool OutOfBounds(size_t a, size_t b, size_t size) {
return false;
}
-// Checks if a + b + c > size, taking possible integer overflow into account.
-bool OutOfBounds(size_t a, size_t b, size_t c, size_t size) {
- size_t pos = a + b;
- if (pos < b) return true; // overflow happened
- pos += c;
- if (pos < c) return true; // overflow happened
- if (pos > size) return true;
- return false;
-}
-
bool SumOverflows(size_t a, size_t b, size_t c) {
size_t sum = a + b;
if (sum < b) return true;
@@ -156,135 +131,53 @@ namespace {
size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) {
- case JXL_TYPE_BOOLEAN:
- return 1;
case JXL_TYPE_UINT8:
return 8;
case JXL_TYPE_UINT16:
return 16;
- case JXL_TYPE_UINT32:
- return 32;
case JXL_TYPE_FLOAT:
return 32;
case JXL_TYPE_FLOAT16:
return 16;
- // No default, give compiler error if new type not handled.
+ default:
+ return 0; // signals unhandled JxlDataType
}
- return 0; // Indicate invalid data type.
}
enum class DecoderStage : uint32_t {
- kInited, // Decoder created, no JxlDecoderProcessInput called yet
- kStarted, // Running JxlDecoderProcessInput calls
- kFinished, // Everything done, nothing left to process
- kError, // Error occurred, decoder object no longer usable
+ kInited, // Decoder created, no JxlDecoderProcessInput called yet
+ kStarted, // Running JxlDecoderProcessInput calls
+ kCodestreamFinished, // Codestream done, but other boxes could still occur.
+ // This stage can also occur before having seen the
+ // entire codestream if the user didn't subscribe to any
+ // codestream events at all, e.g. only to box events,
+ // or, the user only subscribed to basic info, and only
+ // the header of the codestream was parsed.
+ kError, // Error occurred, decoder object no longer usable
};
enum class FrameStage : uint32_t {
- kHeader, // Must parse frame header. dec->frame_start must be set up
- // correctly already.
+ kHeader, // Must parse frame header.
kTOC, // Must parse TOC
kFull, // Must parse full pixels
kFullOutput, // Must output full pixels
};
-// Manages the sections for the FrameDecoder based on input bytes received.
-struct Sections {
- // sections_begin = position in the frame where the sections begin, after
- // the frame header and TOC, so sections_begin = sum of frame header size and
- // TOC size.
- Sections(jxl::FrameDecoder* frame_dec, size_t frame_size,
- size_t sections_begin)
- : frame_dec_(frame_dec),
- frame_size_(frame_size),
- sections_begin_(sections_begin) {}
-
- Sections(const Sections&) = delete;
- Sections& operator=(const Sections&) = delete;
- Sections(Sections&&) = delete;
- Sections& operator=(Sections&&) = delete;
-
- ~Sections() {
- // Avoid memory leaks if the JXL decoder quits early and doesn't end up
- // calling CloseInput().
- CloseInput();
- }
-
- // frame_dec_ must have been Inited already, but not yet done ProcessSections.
- JxlDecoderStatus Init() {
- section_received.resize(frame_dec_->NumSections(), 0);
-
- const auto& offsets = frame_dec_->SectionOffsets();
- const auto& sizes = frame_dec_->SectionSizes();
-
- // Ensure none of the sums of section offset and size overflow.
- for (size_t i = 0; i < frame_dec_->NumSections(); i++) {
- if (OutOfBounds(sections_begin_, offsets[i], sizes[i], frame_size_)) {
- return JXL_API_ERROR("section out of bounds");
- }
- }
-
- return JXL_DEC_SUCCESS;
- }
-
- // Sets the input data for the frame. The frame pointer must point to the
- // beginning of the frame, size is the amount of bytes gotten so far and
- // should increase with next calls until the full frame is loaded.
- // TODO(lode): allow caller to provide only later chunks of memory when
- // earlier sections are fully processed already.
- void SetInput(const uint8_t* frame, size_t size) {
- const auto& offsets = frame_dec_->SectionOffsets();
- const auto& sizes = frame_dec_->SectionSizes();
-
- for (size_t i = 0; i < frame_dec_->NumSections(); i++) {
- if (section_received[i]) continue;
- if (!OutOfBounds(sections_begin_, offsets[i], sizes[i], size)) {
- section_received[i] = 1;
- section_info.emplace_back(jxl::FrameDecoder::SectionInfo{nullptr, i});
- section_status.emplace_back();
- }
- }
- // Reset all the bitreaders, because the address of the frame pointer may
- // change, even if it always represents the same frame start.
- for (size_t i = 0; i < section_info.size(); i++) {
- size_t id = section_info[i].id;
- JXL_ASSERT(section_info[i].br == nullptr);
- section_info[i].br = new jxl::BitReader(jxl::Span<const uint8_t>(
- frame + sections_begin_ + offsets[id], sizes[id]));
- }
- }
-
- JxlDecoderStatus CloseInput() {
- bool out_of_bounds = false;
- for (size_t i = 0; i < section_info.size(); i++) {
- if (!section_info[i].br) continue;
- if (!section_info[i].br->AllReadsWithinBounds()) {
- // Mark out of bounds section, but keep closing and deleting the next
- // ones as well.
- out_of_bounds = true;
- }
- JXL_ASSERT(section_info[i].br->Close());
- delete section_info[i].br;
- section_info[i].br = nullptr;
- }
- if (out_of_bounds) {
- // If any bit reader indicates out of bounds, it's an error, not just
- // needing more input, since we ensure only bit readers containing
- // a complete section are provided to the FrameDecoder.
- return JXL_API_ERROR("frame out of bounds");
- }
- return JXL_DEC_SUCCESS;
- }
-
- // Not managed by us.
- jxl::FrameDecoder* frame_dec_;
-
- size_t frame_size_;
- size_t sections_begin_;
+enum class BoxStage : uint32_t {
+ kHeader, // Parsing box header of the next box, or start of non-container
+ // stream
+ kFtyp, // The ftyp box
+ kSkip, // Box whose contents are skipped
+ kCodestream, // Handling codestream box contents, or non-container stream
+ kPartialCodestream, // Handling the extra header of partial codestream box
+ kJpegRecon, // Handling jpeg reconstruction box
+};
- std::vector<jxl::FrameDecoder::SectionInfo> section_info;
- std::vector<jxl::FrameDecoder::SectionStatus> section_status;
- std::vector<char> section_received;
+enum class JpegReconStage : uint32_t {
+ kNone, // Not outputting
+ kSettingMetadata, // Ready to output, must set metadata to the jpeg_data
+ kOutputting, // Currently outputting the JPEG bytes
+ kFinished, // JPEG reconstruction fully handled
};
/*
@@ -373,6 +266,50 @@ struct ExtraChannelOutput {
} // namespace
+namespace jxl {
+
+typedef struct JxlDecoderFrameIndexBoxEntryStruct {
+ // OFFi: offset of start byte of this frame compared to start
+ // byte of previous frame from this index in the JPEG XL codestream. For the
+ // first frame, this is the offset from the first byte of the JPEG XL
+ // codestream.
+ uint64_t OFFi;
+ // Ti: duration in ticks between the start of this frame and
+ // the start of the next frame in the index. If this is the last frame in the
+ // index, this is the duration in ticks between the start of this frame and
+ // the end of the stream. A tick lasts TNUM / TDEN seconds.
+ uint32_t Ti;
+ // Fi: amount of frames the next frame in the index occurs
+ // after this frame. If this is the last frame in the index, this is the
+ // amount of frames after this frame in the remainder of the stream. Only
+ // frames that are presented by the decoder are counted for this purpose, this
+ // excludes frames that are not intended for display but for compositing with
+ // other frames, such as frames that aren't the last frame with a duration of
+ // 0 ticks.
+ uint32_t Fi;
+} JxlDecoderFrameIndexBoxEntry;
+
+typedef struct JxlDecoderFrameIndexBoxStruct {
+ int64_t NF() const { return entries.size(); }
+ int32_t TNUM = 1;
+ int32_t TDEN = 1000;
+
+ std::vector<JxlDecoderFrameIndexBoxEntry> entries;
+
+ // That way we can ensure that every index box will have the first frame.
+ // If the API user decides to mark it as an indexed frame, we call
+ // the AddFrame again, this time with requested.
+ void AddFrame(uint64_t OFFi, uint32_t Ti, uint32_t Fi) {
+ JxlDecoderFrameIndexBoxEntry e;
+ e.OFFi = OFFi;
+ e.Ti = Ti;
+ e.Fi = Fi;
+ entries.push_back(e);
+ }
+} JxlDecoderFrameIndexBox;
+
+} // namespace jxl
+
// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding)
struct JxlDecoderStruct {
JxlDecoderStruct() = default;
@@ -384,39 +321,63 @@ struct JxlDecoderStruct {
// Status of progression, internal.
bool got_signature;
- bool first_codestream_seen;
- // Indicates we know that we've seen the last codestream, however this is not
- // guaranteed to be true for the last box because a jxl file may have multiple
- // "jxlp" boxes and it is possible (and permitted) that the last one is not a
- // final box that uses size 0 to indicate the end.
+ // Indicates we know that we've seen the last codestream box: either this
+ // was a jxlc box, or a jxlp box that has its index indicated as last by
+ // having its most significant bit set, or no boxes are used at all. This
+ // does not indicate the full codestream has already been seen, only the
+ // last box of it has been initiated.
bool last_codestream_seen;
+ bool got_codestream_signature;
bool got_basic_info;
- size_t header_except_icc_bits = 0; // To skip everything before ICC.
+ bool got_transform_data; // To skip everything before ICC.
bool got_all_headers; // Codestream metadata headers.
bool post_headers; // Already decoding pixels.
jxl::ICCReader icc_reader;
-
+ jxl::JxlDecoderFrameIndexBox frame_index_box;
// This means either we actually got the preview image, or determined we
// cannot get it or there is none.
bool got_preview_image;
+ bool preview_frame;
// Position of next_in in the original file including box format if present
// (as opposed to position in the codestream)
size_t file_pos;
- size_t box_begin;
- size_t box_end;
- bool skip_box;
- // Begin and end of the content of the current codestream box. This could be
- // a partial codestream box.
- // codestream_begin 0 is used to indicate the begin is not yet known.
- // codestream_end 0 is used to indicate uncapped (until end of file, for the
- // last box if this box doesn't indicate its actual size).
- // Not used if the file is a direct codestream.
- size_t codestream_begin;
- size_t codestream_end;
+
+ size_t box_contents_begin;
+ size_t box_contents_end;
+ size_t box_contents_size;
+ size_t box_size;
+ size_t header_size;
+ // Either a final box that runs until EOF, or the case of no container format
+ // at all.
+ bool box_contents_unbounded;
+
+ JxlBoxType box_type;
+ JxlBoxType box_decoded_type; // Underlying type for brob boxes
+ // Set to true right after a JXL_DEC_BOX event only.
+ bool box_event;
+ bool decompress_boxes;
+
+ bool box_out_buffer_set;
+ // Whether the out buffer is set for the current box, if the user did not yet
+ // release the buffer while the next box is encountered, this will be set to
+ // false. If this is false, no JXL_DEC_NEED_MORE_INPUT is emitted
+ // (irrespective of the value of box_out_buffer_set), because not setting
+ // output indicates the user does not wish the data of this box.
+ bool box_out_buffer_set_current_box;
+ uint8_t* box_out_buffer;
+ size_t box_out_buffer_size;
+ // which byte of the full box content the start of the out buffer points to
+ size_t box_out_buffer_begin;
+ // which byte of box_out_buffer to write to next
+ size_t box_out_buffer_pos;
// Settings
bool keep_orientation;
+ bool unpremul_alpha;
+ bool render_spotcolors;
+ bool coalescing;
+ float desired_intensity_target;
// Bitfield, for which informative events (JXL_DEC_BASIC_INFO, etc...) the
// decoder returns a status. By default, do not return for any of the events,
@@ -428,19 +389,35 @@ struct JxlDecoderStruct {
// Fields for reading the basic info from the header.
size_t basic_info_size_hint;
bool have_container;
+ size_t box_count;
+
+ // The level of progressive detail in frame decoding.
+ JxlProgressiveDetail prog_detail = kDC;
+ // The progressive detail of the current frame.
+ JxlProgressiveDetail frame_prog_detail;
+ // The intended downsampling ratio for the current progression step.
+ size_t downsampling_target;
// Whether the preview out buffer was set. It is possible for the buffer to
// be nullptr and buffer_set to be true, indicating it was deliberately
// set to nullptr.
bool preview_out_buffer_set;
// Idem for the image buffer.
+ // Set to true if either an image out buffer or an image out callback was set.
bool image_out_buffer_set;
// Owned by the caller, buffers for DC image and full resolution images
void* preview_out_buffer;
void* image_out_buffer;
- JxlImageOutCallback image_out_callback;
- void* image_out_opaque;
+ JxlImageOutInitCallback image_out_init_callback;
+ JxlImageOutRunCallback image_out_run_callback;
+ JxlImageOutDestroyCallback image_out_destroy_callback;
+ void* image_out_init_opaque;
+ struct SimpleImageOutCallback {
+ JxlImageOutCallback callback;
+ void* opaque;
+ };
+ SimpleImageOutCallback simple_image_out_callback;
size_t preview_out_size;
size_t image_out_size;
@@ -453,13 +430,15 @@ struct JxlDecoderStruct {
std::vector<ExtraChannelOutput> extra_channel_output;
jxl::CodecMetadata metadata;
+ // Same as metadata.m, except for the color_encoding, which is set to the
+ // output encoding.
+ jxl::ImageMetadata image_metadata;
std::unique_ptr<jxl::ImageBundle> ib;
- // ColorEncoding to use for xyb encoded image with ICC profile.
- jxl::ColorEncoding default_enc;
std::unique_ptr<jxl::PassesDecoderState> passes_state;
std::unique_ptr<jxl::FrameDecoder> frame_dec;
- std::unique_ptr<Sections> sections;
+ size_t next_section;
+ std::vector<char> section_processed;
// The FrameDecoder is initialized, and not yet finalized
bool frame_dec_in_progress;
@@ -468,11 +447,9 @@ struct JxlDecoderStruct {
// that is, the displayed frame.
std::unique_ptr<jxl::FrameHeader> frame_header;
- // Start of the current frame being processed, as offset from the beginning of
- // the codestream.
- size_t frame_start;
- size_t frame_size;
+ size_t remaining_frame_size;
FrameStage frame_stage;
+ bool dc_frame_progression_done;
// The currently processed frame is the last of the current composite still,
// and so must be returned as pixels
bool is_last_of_still;
@@ -508,27 +485,174 @@ struct JxlDecoderStruct {
// vector, it must be treated as a required frame.
std::vector<char> frame_required;
- // Codestream input data is stored here, when the decoder takes in and stores
- // the user input bytes. If the decoder does not do that (e.g. in one-shot
- // case), this field is unused.
- // TODO(lode): avoid needing this field once the C++ decoder doesn't need
- // all bytes at once, to save memory. Find alternative to std::vector doubling
- // strategy to prevent some memory usage.
- std::vector<uint8_t> codestream;
+ // Codestream input data is copied here temporarily when the decoder needs
+ // more input bytes to process the next part of the stream. We copy the input
+ // data in order to be able to release it all through the API it when
+ // returning JXL_DEC_NEED_MORE_INPUT.
+ std::vector<uint8_t> codestream_copy;
+ // Number of bytes at the end of codestream_copy that were not yet consumed
+ // by calling AdvanceInput().
+ size_t codestream_unconsumed;
+ // Position in the codestream_copy vector that the decoder already finished
+ // processing. It can be greater than the current size of codestream_copy in
+ // case where the decoder skips some parts of the frame that were not yet
+ // provided.
+ size_t codestream_pos;
+ // Number of bits after codestream_pos that were already processed.
+ size_t codestream_bits_ahead;
- jxl::JxlToJpegDecoder jpeg_decoder;
+ BoxStage box_stage;
- // Position in the actual codestream, which codestream.begin() points to.
- // Non-zero once earlier parts of the codestream vector have been erased.
- size_t codestream_pos;
+ jxl::JxlToJpegDecoder jpeg_decoder;
+ jxl::JxlBoxContentDecoder box_content_decoder;
+ // Decodes Exif or XMP metadata for JPEG reconstruction
+ jxl::JxlBoxContentDecoder metadata_decoder;
+ std::vector<uint8_t> exif_metadata;
+ std::vector<uint8_t> xmp_metadata;
+ // must store JPEG reconstruction metadata from the current box
+ // 0 = not stored, 1 = currently storing, 2 = finished
+ int store_exif;
+ int store_xmp;
+ size_t recon_out_buffer_pos;
+ size_t recon_exif_size; // Expected exif size as read from the jbrd box
+ size_t recon_xmp_size; // Expected exif size as read from the jbrd box
+ JpegReconStage recon_output_jpeg;
+
+ bool JbrdNeedMoreBoxes() const {
+ // jbrd box wants exif but exif box not yet seen
+ if (store_exif < 2 && recon_exif_size > 0) return true;
+ // jbrd box wants xmp but xmp box not yet seen
+ if (store_xmp < 2 && recon_xmp_size > 0) return true;
+ return false;
+ }
// Statistics which CodecInOut can keep
uint64_t dec_pixels;
const uint8_t* next_in;
size_t avail_in;
+ bool input_closed;
+
+ void AdvanceInput(size_t size) {
+ JXL_DASSERT(avail_in >= size);
+ next_in += size;
+ avail_in -= size;
+ file_pos += size;
+ }
+
+ size_t AvailableCodestream() const {
+ size_t avail_codestream = avail_in;
+ if (!box_contents_unbounded) {
+ avail_codestream =
+ std::min<size_t>(avail_codestream, box_contents_end - file_pos);
+ }
+ return avail_codestream;
+ }
+
+ void AdvanceCodestream(size_t size) {
+ size_t avail_codestream = AvailableCodestream();
+ if (codestream_copy.empty()) {
+ if (size <= avail_codestream) {
+ AdvanceInput(size);
+ } else {
+ codestream_pos = size - avail_codestream;
+ AdvanceInput(avail_codestream);
+ }
+ } else {
+ codestream_pos += size;
+ if (codestream_pos + codestream_unconsumed >= codestream_copy.size()) {
+ size_t advance = std::min(
+ codestream_unconsumed,
+ codestream_unconsumed + codestream_pos - codestream_copy.size());
+ AdvanceInput(advance);
+ codestream_pos -= std::min(codestream_pos, codestream_copy.size());
+ codestream_unconsumed = 0;
+ codestream_copy.clear();
+ }
+ }
+ }
+
+ JxlDecoderStatus RequestMoreInput() {
+ if (codestream_copy.empty()) {
+ size_t avail_codestream = AvailableCodestream();
+ codestream_copy.insert(codestream_copy.end(), next_in,
+ next_in + avail_codestream);
+ AdvanceInput(avail_codestream);
+ } else {
+ AdvanceInput(codestream_unconsumed);
+ codestream_unconsumed = 0;
+ }
+ return JXL_DEC_NEED_MORE_INPUT;
+ }
+
+ JxlDecoderStatus GetCodestreamInput(jxl::Span<const uint8_t>* span) {
+ if (codestream_copy.empty() && codestream_pos > 0) {
+ size_t avail_codestream = AvailableCodestream();
+ size_t skip = std::min<size_t>(codestream_pos, avail_codestream);
+ AdvanceInput(skip);
+ codestream_pos -= skip;
+ if (codestream_pos > 0) {
+ return RequestMoreInput();
+ }
+ }
+ JXL_ASSERT(codestream_pos <= codestream_copy.size());
+ JXL_ASSERT(codestream_unconsumed <= codestream_copy.size());
+ size_t avail_codestream = AvailableCodestream();
+ if (codestream_copy.empty()) {
+ if (avail_codestream == 0) {
+ return RequestMoreInput();
+ }
+ *span = jxl::Span<const uint8_t>(next_in, avail_codestream);
+ return JXL_DEC_SUCCESS;
+ } else {
+ codestream_copy.insert(codestream_copy.end(),
+ next_in + codestream_unconsumed,
+ next_in + avail_codestream);
+ codestream_unconsumed = avail_codestream;
+ *span = jxl::Span<const uint8_t>(codestream_copy.data() + codestream_pos,
+ codestream_copy.size() - codestream_pos);
+ return JXL_DEC_SUCCESS;
+ }
+ }
+
+ // Whether the decoder can use more codestream input for a purpose it needs.
+ // This returns false if the user didn't subscribe to any events that
+ // require the codestream (e.g. only subscribed to metadata boxes), or all
+ // parts of the codestream that are subscribed to (e.g. only basic info) have
+ // already occured.
+ bool CanUseMoreCodestreamInput() const {
+ // The decoder can set this to finished early if all relevant events were
+ // processed, so this check works.
+ return stage != DecoderStage::kCodestreamFinished;
+ }
+
+ // If set then some operations will fail, if those would require
+ // allocating large objects. Actual memory usage might be two orders of
+ // magnitude bigger.
+ // TODO(eustas): remove once there is working API for memory / CPU limit.
+ size_t memory_limit_base = 0;
+ size_t cpu_limit_base = 0;
+ size_t used_cpu_base = 0;
};
+namespace {
+
+bool CheckSizeLimit(JxlDecoder* dec, size_t xsize, size_t ysize) {
+ if (!dec->memory_limit_base) return true;
+ if (xsize == 0 || ysize == 0) return true;
+ if (xsize >= dec->memory_limit_base || ysize >= dec->memory_limit_base) {
+ return false;
+ }
+ // Rough estimate of real row length.
+ xsize = jxl::DivCeil(xsize, 32) * 32;
+ size_t num_pixels = xsize * ysize;
+ if (num_pixels / xsize != ysize) return false; // overflow
+ if (num_pixels > dec->memory_limit_base) return false;
+ return true;
+}
+
+} // namespace
+
// TODO(zond): Make this depend on the data loaded into the decoder.
JxlDecoderStatus JxlDecoderDefaultPixelFormat(const JxlDecoder* dec,
JxlPixelFormat* format) {
@@ -537,66 +661,107 @@ JxlDecoderStatus JxlDecoderDefaultPixelFormat(const JxlDecoder* dec,
return JXL_DEC_SUCCESS;
}
-void JxlDecoderReset(JxlDecoder* dec) {
- dec->thread_pool.reset();
+// Resets the state that must be reset for both Rewind and Reset
+void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->stage = DecoderStage::kInited;
dec->got_signature = false;
- dec->first_codestream_seen = false;
dec->last_codestream_seen = false;
+ dec->got_codestream_signature = false;
dec->got_basic_info = false;
- dec->header_except_icc_bits = 0;
+ dec->got_transform_data = false;
dec->got_all_headers = false;
dec->post_headers = false;
dec->icc_reader.Reset();
dec->got_preview_image = false;
+ dec->preview_frame = false;
dec->file_pos = 0;
- dec->box_begin = 0;
- dec->box_end = 0;
- dec->skip_box = false;
- dec->codestream_pos = 0;
- dec->codestream_begin = 0;
- dec->codestream_end = 0;
- dec->keep_orientation = false;
+ dec->box_contents_begin = 0;
+ dec->box_contents_end = 0;
+ dec->box_contents_size = 0;
+ dec->box_size = 0;
+ dec->header_size = 0;
+ dec->box_contents_unbounded = false;
+ memset(dec->box_type, 0, sizeof(dec->box_type));
+ memset(dec->box_decoded_type, 0, sizeof(dec->box_decoded_type));
+ dec->box_event = false;
+ dec->box_stage = BoxStage::kHeader;
+ dec->box_out_buffer_set = false;
+ dec->box_out_buffer_set_current_box = false;
+ dec->box_out_buffer = nullptr;
+ dec->box_out_buffer_size = 0;
+ dec->box_out_buffer_begin = 0;
+ dec->box_out_buffer_pos = 0;
+ dec->exif_metadata.clear();
+ dec->xmp_metadata.clear();
+ dec->store_exif = 0;
+ dec->store_xmp = 0;
+ dec->recon_out_buffer_pos = 0;
+ dec->recon_exif_size = 0;
+ dec->recon_xmp_size = 0;
+ dec->recon_output_jpeg = JpegReconStage::kNone;
+
dec->events_wanted = 0;
- dec->orig_events_wanted = 0;
dec->basic_info_size_hint = InitialBasicInfoSizeHint();
dec->have_container = 0;
+ dec->box_count = 0;
+ dec->downsampling_target = 8;
dec->preview_out_buffer_set = false;
dec->image_out_buffer_set = false;
dec->preview_out_buffer = nullptr;
dec->image_out_buffer = nullptr;
- dec->image_out_callback = nullptr;
- dec->image_out_opaque = nullptr;
+ dec->image_out_init_callback = nullptr;
+ dec->image_out_run_callback = nullptr;
+ dec->image_out_destroy_callback = nullptr;
+ dec->image_out_init_opaque = nullptr;
dec->preview_out_size = 0;
dec->image_out_size = 0;
dec->extra_channel_output.clear();
dec->dec_pixels = 0;
dec->next_in = 0;
dec->avail_in = 0;
+ dec->input_closed = false;
dec->passes_state.reset(nullptr);
dec->frame_dec.reset(nullptr);
- dec->sections.reset(nullptr);
+ dec->next_section = 0;
+ dec->section_processed.clear();
dec->frame_dec_in_progress = false;
dec->ib.reset();
dec->metadata = jxl::CodecMetadata();
+ dec->image_metadata = dec->metadata.m;
dec->frame_header.reset(new jxl::FrameHeader(&dec->metadata));
- dec->codestream.clear();
+
+ dec->codestream_copy.clear();
+ dec->codestream_unconsumed = 0;
+ dec->codestream_pos = 0;
+ dec->codestream_bits_ahead = 0;
dec->frame_stage = FrameStage::kHeader;
- dec->frame_start = 0;
- dec->frame_size = 0;
+ dec->remaining_frame_size = 0;
dec->is_last_of_still = false;
dec->is_last_total = false;
dec->skip_frames = 0;
dec->skipping_frame = false;
dec->internal_frames = 0;
dec->external_frames = 0;
+}
+
+void JxlDecoderReset(JxlDecoder* dec) {
+ JxlDecoderRewindDecodingState(dec);
+
+ dec->thread_pool.reset();
+ dec->keep_orientation = false;
+ dec->unpremul_alpha = false;
+ dec->render_spotcolors = true;
+ dec->coalescing = true;
+ dec->desired_intensity_target = 0;
+ dec->orig_events_wanted = 0;
dec->frame_references.clear();
dec->frame_saved_as.clear();
dec->frame_external_to_internal.clear();
dec->frame_required.clear();
+ dec->decompress_boxes = false;
}
JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
@@ -611,6 +776,16 @@ JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
JxlDecoder* dec = new (alloc) JxlDecoder();
dec->memory_manager = local_memory_manager;
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ if (!memory_manager) {
+ dec->memory_limit_base = 53 << 16;
+ // Allow 5 x max_image_size processing units; every frame is accounted
+ // as W x H CPU processing units, so there could be numerous small frames
+ // or few larger ones.
+ dec->cpu_limit_base = 5 * dec->memory_limit_base;
+ }
+#endif
+
JxlDecoderReset(dec);
return dec;
@@ -618,33 +793,14 @@ JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
void JxlDecoderDestroy(JxlDecoder* dec) {
if (dec) {
+ JxlMemoryManager local_memory_manager = dec->memory_manager;
// Call destructor directly since custom free function is used.
dec->~JxlDecoder();
- jxl::MemoryManagerFree(&dec->memory_manager, dec);
+ jxl::MemoryManagerFree(&local_memory_manager, dec);
}
}
-void JxlDecoderRewind(JxlDecoder* dec) {
- int keep_orientation = dec->keep_orientation;
- int events_wanted = dec->orig_events_wanted;
- std::vector<int> frame_references;
- std::vector<int> frame_saved_as;
- std::vector<size_t> frame_external_to_internal;
- std::vector<char> frame_required;
- frame_references.swap(dec->frame_references);
- frame_saved_as.swap(dec->frame_saved_as);
- frame_external_to_internal.swap(dec->frame_external_to_internal);
- frame_required.swap(dec->frame_required);
-
- JxlDecoderReset(dec);
- dec->keep_orientation = keep_orientation;
- dec->events_wanted = events_wanted;
- dec->orig_events_wanted = events_wanted;
- frame_references.swap(dec->frame_references);
- frame_saved_as.swap(dec->frame_saved_as);
- frame_external_to_internal.swap(dec->frame_external_to_internal);
- frame_required.swap(dec->frame_required);
-}
+void JxlDecoderRewind(JxlDecoder* dec) { JxlDecoderRewindDecodingState(dec); }
void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount) {
// Increment amount, rather than set it: making the amount smaller is
@@ -675,10 +831,25 @@ void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount) {
}
}
+JxlDecoderStatus JxlDecoderSkipCurrentFrame(JxlDecoder* dec) {
+ if (!dec->frame_dec || !dec->frame_dec_in_progress) {
+ return JXL_DEC_ERROR;
+ }
+ dec->frame_stage = FrameStage::kHeader;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
+ dec->frame_dec_in_progress = false;
+ if (dec->is_last_of_still) {
+ dec->image_out_buffer_set = false;
+ }
+ return JXL_DEC_SUCCESS;
+}
+
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
void* parallel_runner_opaque) {
- if (dec->thread_pool) return JXL_API_ERROR("parallel runner already set");
+ if (dec->stage != DecoderStage::kInited) {
+ return JXL_API_ERROR("parallel_runner must be set before starting");
+ }
dec->thread_pool.reset(
new jxl::ThreadPool(parallel_runner, parallel_runner_opaque));
return JXL_DEC_SUCCESS;
@@ -710,6 +881,55 @@ JxlDecoderStatus JxlDecoderSetKeepOrientation(JxlDecoder* dec,
return JXL_DEC_SUCCESS;
}
+JxlDecoderStatus JxlDecoderSetUnpremultiplyAlpha(JxlDecoder* dec,
+ JXL_BOOL unpremul_alpha) {
+ if (dec->stage != DecoderStage::kInited) {
+ return JXL_API_ERROR("Must set unpremul_alpha option before starting");
+ }
+ dec->unpremul_alpha = !!unpremul_alpha;
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderSetRenderSpotcolors(JxlDecoder* dec,
+ JXL_BOOL render_spotcolors) {
+ if (dec->stage != DecoderStage::kInited) {
+ return JXL_API_ERROR("Must set render_spotcolors option before starting");
+ }
+ dec->render_spotcolors = !!render_spotcolors;
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec, JXL_BOOL coalescing) {
+ if (dec->stage != DecoderStage::kInited) {
+ return JXL_API_ERROR("Must set coalescing option before starting");
+ }
+ dec->coalescing = !!coalescing;
+ return JXL_DEC_SUCCESS;
+}
+
+namespace {
+// helper function to get the dimensions of the current image buffer
+void GetCurrentDimensions(const JxlDecoder* dec, size_t& xsize, size_t& ysize,
+ bool oriented) {
+ if (dec->frame_header->nonserialized_is_preview) {
+ xsize = dec->metadata.oriented_preview_xsize(dec->keep_orientation);
+ ysize = dec->metadata.oriented_preview_ysize(dec->keep_orientation);
+ return;
+ }
+ xsize = dec->metadata.oriented_xsize(dec->keep_orientation || !oriented);
+ ysize = dec->metadata.oriented_ysize(dec->keep_orientation || !oriented);
+ if (!dec->coalescing) {
+ const auto frame_dim = dec->frame_header->ToFrameDimensions();
+ xsize = frame_dim.xsize_upsampled;
+ ysize = frame_dim.ysize_upsampled;
+ if (!dec->keep_orientation && oriented &&
+ static_cast<int>(dec->metadata.m.GetOrientation()) > 4) {
+ std::swap(xsize, ysize);
+ }
+ }
+}
+} // namespace
+
namespace jxl {
namespace {
@@ -726,10 +946,10 @@ bool CanRead(Span<const uint8_t> data, BitReader* reader, T* JXL_RESTRICT t) {
// Returns JXL_DEC_SUCCESS if the full bundle was successfully read, status
// indicating either error or need more input otherwise.
template <class T>
-JxlDecoderStatus ReadBundle(Span<const uint8_t> data, BitReader* reader,
- T* JXL_RESTRICT t) {
+JxlDecoderStatus ReadBundle(JxlDecoder* dec, Span<const uint8_t> data,
+ BitReader* reader, T* JXL_RESTRICT t) {
if (!CanRead(data, reader, t)) {
- return JXL_DEC_NEED_MORE_INPUT;
+ return dec->RequestMoreInput();
}
if (!Bundle::Read(reader, t)) {
return JXL_DEC_ERROR;
@@ -758,35 +978,38 @@ std::unique_ptr<BitReader, std::function<void(BitReader*)>> GetBitReader(
});
}
-JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec, const uint8_t* in,
- size_t size) {
- size_t pos = 0;
-
- // Check and skip the codestream signature
- JxlSignature signature = ReadSignature(in, size, &pos);
- if (signature == JXL_SIG_NOT_ENOUGH_BYTES) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- if (signature == JXL_SIG_CONTAINER) {
- // There is a container signature where we expect a codestream, container
- // is handled at a higher level already.
- return JXL_API_ERROR("invalid: nested container");
- }
- if (signature != JXL_SIG_CODESTREAM) {
- return JXL_API_ERROR("invalid signature");
+JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec) {
+ if (!dec->got_codestream_signature) {
+ // Check and skip the codestream signature
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ if (span.size() < 2) {
+ return dec->RequestMoreInput();
+ }
+ if (span.data()[0] != 0xff || span.data()[1] != jxl::kCodestreamMarker) {
+ return JXL_API_ERROR("invalid signature");
+ }
+ dec->got_codestream_signature = true;
+ dec->AdvanceCodestream(2);
}
- Span<const uint8_t> span(in + pos, size - pos);
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
auto reader = GetBitReader(span);
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dec->metadata.size));
-
- dec->metadata.m.nonserialized_only_parse_basic_info = true;
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dec->metadata.m));
- dec->metadata.m.nonserialized_only_parse_basic_info = false;
+ JXL_API_RETURN_IF_ERROR(
+ ReadBundle(dec, span, reader.get(), &dec->metadata.size));
+ JXL_API_RETURN_IF_ERROR(
+ ReadBundle(dec, span, reader.get(), &dec->metadata.m));
+ size_t total_bits = reader->TotalBitsConsumed();
+ dec->AdvanceCodestream(total_bits / jxl::kBitsPerByte);
+ dec->codestream_bits_ahead = total_bits % jxl::kBitsPerByte;
dec->got_basic_info = true;
dec->basic_info_size_hint = 0;
+ dec->image_metadata = dec->metadata.m;
+ JXL_DEBUG_V(2, "Decoded BasicInfo: %s", dec->metadata.DebugString().c_str());
- if (!CheckSizeLimit(dec->metadata.size.xsize(), dec->metadata.size.ysize())) {
+ if (!CheckSizeLimit(dec, dec->metadata.size.xsize(),
+ dec->metadata.size.ysize())) {
return JXL_API_ERROR("image is too large");
}
@@ -794,62 +1017,48 @@ JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec, const uint8_t* in,
}
// Reads all codestream headers (but not frame headers)
-JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
- size_t size) {
- size_t pos = 0;
-
- // Check and skip the codestream signature
- JxlSignature signature = ReadSignature(in, size, &pos);
- if (signature == JXL_SIG_CONTAINER) {
- return JXL_API_ERROR("invalid: nested container");
- }
- if (signature != JXL_SIG_CODESTREAM) {
- return JXL_API_ERROR("invalid signature");
- }
-
- Span<const uint8_t> span(in + pos, size - pos);
- auto reader = GetBitReader(span);
-
- if (dec->header_except_icc_bits != 0) {
- // Headers were decoded already.
- reader->SkipBits(dec->header_except_icc_bits);
- } else {
- SizeHeader dummy_size_header;
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dummy_size_header));
-
- // We already decoded the metadata to dec->metadata.m, no reason to
- // overwrite it, use a dummy metadata instead.
- ImageMetadata dummy_metadata;
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dummy_metadata));
-
+JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec) {
+ if (!dec->got_transform_data) {
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ auto reader = GetBitReader(span);
+ reader->SkipBits(dec->codestream_bits_ahead);
+ dec->metadata.transform_data.nonserialized_xyb_encoded =
+ dec->metadata.m.xyb_encoded;
JXL_API_RETURN_IF_ERROR(
- ReadBundle(span, reader.get(), &dec->metadata.transform_data));
+ ReadBundle(dec, span, reader.get(), &dec->metadata.transform_data));
+ size_t total_bits = reader->TotalBitsConsumed();
+ dec->AdvanceCodestream(total_bits / jxl::kBitsPerByte);
+ dec->codestream_bits_ahead = total_bits % jxl::kBitsPerByte;
+ dec->got_transform_data = true;
}
- dec->header_except_icc_bits = reader->TotalBitsConsumed();
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ auto reader = GetBitReader(span);
+ reader->SkipBits(dec->codestream_bits_ahead);
if (dec->metadata.m.color_encoding.WantICC()) {
- jxl::Status status = dec->icc_reader.Init(reader.get(), memory_limit_base_);
+ jxl::Status status =
+ dec->icc_reader.Init(reader.get(), dec->memory_limit_base);
// Always check AllReadsWithinBounds, not all the C++ decoder implementation
// handles reader out of bounds correctly yet (e.g. context map). Not
// checking AllReadsWithinBounds can cause reader->Close() to trigger an
// assert, but we don't want library to quit program for invalid codestream.
- if (!reader->AllReadsWithinBounds()) {
- return JXL_DEC_NEED_MORE_INPUT;
+ if (!reader->AllReadsWithinBounds() ||
+ status.code() == StatusCode::kNotEnoughBytes) {
+ return dec->RequestMoreInput();
}
if (!status) {
- if (status.code() == StatusCode::kNotEnoughBytes) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
// Other non-successful status is an error
return JXL_DEC_ERROR;
}
PaddedBytes icc;
status = dec->icc_reader.Process(reader.get(), &icc);
+ if (status.code() == StatusCode::kNotEnoughBytes) {
+ return dec->RequestMoreInput();
+ }
if (!status) {
- if (status.code() == StatusCode::kNotEnoughBytes) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
// Other non-successful status is an error
return JXL_DEC_ERROR;
}
@@ -861,30 +1070,27 @@ JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
dec->got_all_headers = true;
JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary());
- dec->frame_start = pos + reader->TotalBitsConsumed() / jxl::kBitsPerByte;
+ dec->AdvanceCodestream(reader->TotalBitsConsumed() / jxl::kBitsPerByte);
+ dec->codestream_bits_ahead = 0;
if (!dec->passes_state) {
dec->passes_state.reset(new jxl::PassesDecoderState());
}
- dec->default_enc =
- ColorEncoding::LinearSRGB(dec->metadata.m.color_encoding.IsGray());
-
- JXL_API_RETURN_IF_ERROR(dec->passes_state->output_encoding_info.Set(
- dec->metadata, dec->default_enc));
+ JXL_API_RETURN_IF_ERROR(
+ dec->passes_state->output_encoding_info.SetFromMetadata(dec->metadata));
+ if (dec->desired_intensity_target > 0) {
+ dec->passes_state->output_encoding_info.desired_intensity_target =
+ dec->desired_intensity_target;
+ }
+ dec->image_metadata = dec->metadata.m;
return JXL_DEC_SUCCESS;
}
-static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format,
- const jxl::ImageBundle* frame = nullptr) {
- size_t xsize = dec->metadata.xsize();
- if (!dec->keep_orientation && dec->metadata.m.orientation > 4) {
- xsize = dec->metadata.ysize();
- }
- if (frame) {
- xsize = dec->keep_orientation ? frame->xsize() : frame->oriented_xsize();
- }
+static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format) {
+ size_t xsize, ysize;
+ GetCurrentDimensions(dec, xsize, ysize, true);
size_t stride = xsize * (BitsPerChannel(format.data_type) *
format.num_channels / jxl::kBitsPerByte);
if (format.align > 1) {
@@ -903,10 +1109,10 @@ static JxlDecoderStatus ConvertImageInternal(
const JxlDecoder* dec, const jxl::ImageBundle& frame,
const JxlPixelFormat& format, bool want_extra_channel,
size_t extra_channel_index, void* out_image, size_t out_size,
- JxlImageOutCallback out_callback, void* out_opaque) {
+ const PixelCallback& out_callback) {
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
// color/grayscale format
- const size_t stride = GetStride(dec, format, &frame);
+ const size_t stride = GetStride(dec, format);
bool float_format = format.data_type == JXL_TYPE_FLOAT ||
format.data_type == JXL_TYPE_FLOAT16;
@@ -917,94 +1123,89 @@ static JxlDecoderStatus ConvertImageInternal(
jxl::Status status(true);
if (want_extra_channel) {
- status = jxl::ConvertToExternal(
- frame.extra_channels()[extra_channel_index],
- BitsPerChannel(format.data_type), float_format, format.endianness,
- stride, dec->thread_pool.get(), out_image, out_size,
- /*out_callback=*/out_callback,
- /*out_opaque=*/out_opaque, undo_orientation);
+ JXL_ASSERT(extra_channel_index < frame.extra_channels().size());
+ status = jxl::ConvertToExternal(frame.extra_channels()[extra_channel_index],
+ BitsPerChannel(format.data_type),
+ float_format, format.endianness, stride,
+ dec->thread_pool.get(), out_image, out_size,
+ out_callback, undo_orientation);
} else {
status = jxl::ConvertToExternal(
frame, BitsPerChannel(format.data_type), float_format,
format.num_channels, format.endianness, stride, dec->thread_pool.get(),
- out_image, out_size,
- /*out_callback=*/out_callback,
- /*out_opaque=*/out_opaque, undo_orientation);
+ out_image, out_size, out_callback, undo_orientation,
+ dec->unpremul_alpha);
}
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
}
-// Parses the FrameHeader and the total frame_size, given the initial bytes
-// of the frame up to and including the TOC.
-// TODO(lode): merge this with FrameDecoder
-JxlDecoderStatus ParseFrameHeader(jxl::FrameHeader* frame_header,
- const uint8_t* in, size_t size, size_t pos,
- bool is_preview, size_t* frame_size,
- int* saved_as) {
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- Span<const uint8_t> span(in + pos, size - pos);
- auto reader = GetBitReader(span);
-
- frame_header->nonserialized_is_preview = is_preview;
- jxl::Status status = DecodeFrameHeader(reader.get(), frame_header);
- jxl::FrameDimensions frame_dim = frame_header->ToFrameDimensions();
- if (!CheckSizeLimit(frame_dim.xsize_upsampled_padded,
- frame_dim.ysize_upsampled_padded)) {
- return JXL_API_ERROR("frame is too large");
- }
-
- if (status.code() == StatusCode::kNotEnoughBytes) {
- // TODO(lode): prevent asking for way too much input bytes in case of
- // invalid header that the decoder thinks is a very long user extension
- // instead. Example: fields can currently print something like this:
- // "../lib/jxl/fields.cc:416: Skipping 71467322-bit extension(s)"
- // Maybe fields.cc should return error in the above case rather than
- // print a message.
- return JXL_DEC_NEED_MORE_INPUT;
- } else if (!status) {
- return JXL_API_ERROR("invalid frame header");
- }
-
- // Read TOC.
- uint64_t groups_total_size;
- const bool has_ac_global = true;
- const size_t toc_entries =
- NumTocEntries(frame_dim.num_groups, frame_dim.num_dc_groups,
- frame_header->passes.num_passes, has_ac_global);
-
- std::vector<uint64_t> group_offsets;
- std::vector<uint32_t> group_sizes;
- status = ReadGroupOffsets(toc_entries, reader.get(), &group_offsets,
- &group_sizes, &groups_total_size);
-
- // TODO(lode): we're actually relying on AllReadsWithinBounds() here
- // instead of on status.code(), change the internal TOC C++ code to
- // correctly set the status.code() instead so we can rely on that one.
- if (!reader->AllReadsWithinBounds() ||
- status.code() == StatusCode::kNotEnoughBytes) {
- return JXL_DEC_NEED_MORE_INPUT;
- } else if (!status) {
- return JXL_API_ERROR("invalid toc entries");
- }
-
- JXL_DASSERT((reader->TotalBitsConsumed() % kBitsPerByte) == 0);
- JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary());
- size_t header_size = (reader->TotalBitsConsumed() >> 3);
- *frame_size = header_size + groups_total_size;
-
- if (saved_as != nullptr) {
- *saved_as = FrameDecoder::SavedAs(*frame_header);
+JxlDecoderStatus JxlDecoderProcessSections(JxlDecoder* dec) {
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ const auto& toc = dec->frame_dec->Toc();
+ size_t pos = 0;
+ std::vector<jxl::FrameDecoder::SectionInfo> section_info;
+ std::vector<jxl::FrameDecoder::SectionStatus> section_status;
+ for (size_t i = dec->next_section; i < toc.size(); ++i) {
+ if (dec->section_processed[i]) continue;
+ size_t id = toc[i].id;
+ size_t size = toc[i].size;
+ if (OutOfBounds(pos, size, span.size())) {
+ break;
+ }
+ auto br =
+ new jxl::BitReader(jxl::Span<const uint8_t>(span.data() + pos, size));
+ section_info.emplace_back(jxl::FrameDecoder::SectionInfo{br, id});
+ section_status.emplace_back();
+ pos += size;
+ }
+ jxl::Status status = dec->frame_dec->ProcessSections(
+ section_info.data(), section_info.size(), section_status.data());
+ bool out_of_bounds = false;
+ for (const auto& info : section_info) {
+ if (!info.br->AllReadsWithinBounds()) {
+ // Mark out of bounds section, but keep closing and deleting the next
+ // ones as well.
+ out_of_bounds = true;
+ }
+ JXL_ASSERT(info.br->Close());
+ delete info.br;
+ }
+ if (out_of_bounds) {
+ // If any bit reader indicates out of bounds, it's an error, not just
+ // needing more input, since we ensure only bit readers containing
+ // a complete section are provided to the FrameDecoder.
+ return JXL_API_ERROR("frame out of bounds");
+ }
+ if (!status) {
+ return JXL_API_ERROR("frame processing failed");
+ }
+ bool found_skipped_section = false;
+ size_t num_done = 0;
+ size_t processed_bytes = 0;
+ for (size_t i = 0; i < section_status.size(); ++i) {
+ auto status = section_status[i];
+ if (status == jxl::FrameDecoder::kDone) {
+ if (!found_skipped_section) {
+ processed_bytes += toc[dec->next_section + i].size;
+ ++num_done;
+ }
+ dec->section_processed[dec->next_section + i] = 1;
+ } else if (status == jxl::FrameDecoder::kSkipped) {
+ found_skipped_section = true;
+ } else {
+ return JXL_API_ERROR("unexpected section status");
+ }
}
-
+ dec->next_section += num_done;
+ dec->remaining_frame_size -= processed_bytes;
+ dec->AdvanceCodestream(processed_bytes);
return JXL_DEC_SUCCESS;
}
// TODO(eustas): no CodecInOut -> no image size reinforcement -> possible OOM.
-JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
- size_t size) {
+JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
// If no parallel runner is set, use the default
// TODO(lode): move this initialization to an appropriate location once the
// runner is used to decode pixels.
@@ -1014,7 +1215,7 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
// No matter what events are wanted, the basic info is always required.
if (!dec->got_basic_info) {
- JxlDecoderStatus status = JxlDecoderReadBasicInfo(dec, in, size);
+ JxlDecoderStatus status = JxlDecoderReadBasicInfo(dec);
if (status != JXL_DEC_SUCCESS) return status;
}
@@ -1023,16 +1224,14 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
return JXL_DEC_BASIC_INFO;
}
- if (!dec->got_all_headers) {
- JxlDecoderStatus status = JxlDecoderReadAllHeaders(dec, in, size);
- if (status != JXL_DEC_SUCCESS) return status;
+ if (!dec->events_wanted) {
+ dec->stage = DecoderStage::kCodestreamFinished;
+ return JXL_DEC_SUCCESS;
}
- if (dec->events_wanted & JXL_DEC_EXTENSIONS) {
- dec->events_wanted &= ~JXL_DEC_EXTENSIONS;
- if (dec->metadata.m.extensions != 0) {
- return JXL_DEC_EXTENSIONS;
- }
+ if (!dec->got_all_headers) {
+ JxlDecoderStatus status = JxlDecoderReadAllHeaders(dec);
+ if (status != JXL_DEC_SUCCESS) return status;
}
if (dec->events_wanted & JXL_DEC_COLOR_ENCODING) {
@@ -1040,109 +1239,110 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
return JXL_DEC_COLOR_ENCODING;
}
- dec->post_headers = true;
-
- // Decode to pixels, only if required for the events the user wants.
- if (!dec->got_preview_image) {
- // Parse the preview, or at least its TOC to be able to skip the frame, if
- // any frame or image decoding is desired.
- bool parse_preview =
- (dec->events_wanted &
- (JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
-
- if (!dec->metadata.m.have_preview) {
- // There is no preview, mark this as done and go to next step
- dec->got_preview_image = true;
- } else if (!parse_preview) {
- // No preview parsing needed, mark this step as done
- dec->got_preview_image = true;
- } else {
- // Want to decode the preview, not just skip the frame
- bool want_preview = (dec->events_wanted & JXL_DEC_PREVIEW_IMAGE);
- size_t frame_size;
- size_t pos = dec->frame_start;
- dec->frame_header.reset(new FrameHeader(&dec->metadata));
- JxlDecoderStatus status = ParseFrameHeader(dec->frame_header.get(), in,
- size, pos, true, &frame_size,
- /*saved_as=*/nullptr);
- if (status != JXL_DEC_SUCCESS) return status;
- if (OutOfBounds(pos, frame_size, size)) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
-
- if (want_preview && !dec->preview_out_buffer_set) {
- return JXL_DEC_NEED_PREVIEW_OUT_BUFFER;
- }
+ if (!dec->events_wanted) {
+ dec->stage = DecoderStage::kCodestreamFinished;
+ return JXL_DEC_SUCCESS;
+ }
- jxl::Span<const uint8_t> compressed(in + dec->frame_start,
- size - dec->frame_start);
- auto reader = GetBitReader(compressed);
- jxl::DecompressParams dparams;
- dparams.preview = want_preview ? jxl::Override::kOn : jxl::Override::kOff;
- jxl::ImageBundle ib(&dec->metadata.m);
- PassesDecoderState preview_dec_state;
- JXL_API_RETURN_IF_ERROR(preview_dec_state.output_encoding_info.Set(
- dec->metadata,
- ColorEncoding::LinearSRGB(dec->metadata.m.color_encoding.IsGray())));
- if (!DecodeFrame(dparams, &preview_dec_state, dec->thread_pool.get(),
- reader.get(), &ib, dec->metadata,
- /*constraints=*/nullptr,
- /*is_preview=*/true)) {
- return JXL_API_ERROR("decoding preview failed");
- }
+ dec->post_headers = true;
- // Set frame_start to the first non-preview frame.
- dec->frame_start += DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
- dec->got_preview_image = true;
-
- if (want_preview) {
- if (dec->preview_out_buffer) {
- JxlDecoderStatus status = ConvertImageInternal(
- dec, ib, dec->preview_out_format, /*want_extra_channel=*/false,
- /*extra_channel_index=*/0, dec->preview_out_buffer,
- dec->preview_out_size, /*out_callback=*/nullptr,
- /*out_opaque=*/nullptr);
- if (status != JXL_DEC_SUCCESS) return status;
- }
- return JXL_DEC_PREVIEW_IMAGE;
- }
- }
+ if (!dec->got_preview_image && dec->metadata.m.have_preview) {
+ dec->preview_frame = true;
}
// Handle frames
for (;;) {
- if (!(dec->events_wanted & (JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME))) {
+ bool parse_frames =
+ (dec->events_wanted &
+ (JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
+ if (!parse_frames) {
break;
}
if (dec->frame_stage == FrameStage::kHeader && dec->is_last_total) {
break;
}
-
if (dec->frame_stage == FrameStage::kHeader) {
- size_t pos = dec->frame_start - dec->codestream_pos;
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
+ if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata ||
+ dec->recon_output_jpeg == JpegReconStage::kOutputting) {
+ // The image bundle contains the JPEG reconstruction frame, but the
+ // decoder is still waiting to decode an EXIF or XMP box. It's not
+ // implemented to decode additional frames during this, and a JPEG
+ // reconstruction image should have only one frame.
+ return JXL_API_ERROR(
+ "cannot decode a next frame after JPEG reconstruction frame");
+ }
+ if (!dec->ib) {
+ dec->ib.reset(new jxl::ImageBundle(&dec->image_metadata));
}
+ // If JPEG reconstruction is wanted and possible, set the jpeg_data of
+ // the ImageBundle.
+ if (!dec->jpeg_decoder.SetImageBundleJpegData(dec->ib.get()))
+ return JXL_DEC_ERROR;
+
+ dec->frame_dec.reset(new FrameDecoder(
+ dec->passes_state.get(), dec->metadata, dec->thread_pool.get(),
+ /*use_slow_rendering_pipeline=*/false));
dec->frame_header.reset(new FrameHeader(&dec->metadata));
- int saved_as = 0;
- JxlDecoderStatus status =
- ParseFrameHeader(dec->frame_header.get(), in, size, pos,
- /*is_preview=*/false, &dec->frame_size, &saved_as);
- if (status != JXL_DEC_SUCCESS) return status;
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ auto reader = GetBitReader(span);
+ bool output_needed =
+ (dec->preview_frame ? (dec->events_wanted & JXL_DEC_PREVIEW_IMAGE)
+ : (dec->events_wanted & JXL_DEC_FULL_IMAGE));
+ jxl::Status status = dec->frame_dec->InitFrame(
+ reader.get(), dec->ib.get(), dec->preview_frame, output_needed);
+ if (!reader->AllReadsWithinBounds() ||
+ status.code() == StatusCode::kNotEnoughBytes) {
+ return dec->RequestMoreInput();
+ } else if (!status) {
+ return JXL_API_ERROR("invalid frame header");
+ }
+ dec->AdvanceCodestream(reader->TotalBitsConsumed() / kBitsPerByte);
+ *dec->frame_header = dec->frame_dec->GetFrameHeader();
+ jxl::FrameDimensions frame_dim = dec->frame_header->ToFrameDimensions();
+ if (!CheckSizeLimit(dec, frame_dim.xsize_upsampled_padded,
+ frame_dim.ysize_upsampled_padded)) {
+ return JXL_API_ERROR("frame is too large");
+ }
+ if (dec->cpu_limit_base != 0) {
+ // No overflow, checked in CheckSizeLimit.
+ size_t num_pixels = frame_dim.xsize * frame_dim.ysize;
+ if (dec->used_cpu_base + num_pixels < dec->used_cpu_base) {
+ return JXL_API_ERROR("used too much CPU");
+ }
+ dec->used_cpu_base += num_pixels;
+ if (dec->used_cpu_base > dec->cpu_limit_base) {
+ return JXL_API_ERROR("used too much CPU");
+ }
+ }
+ dec->remaining_frame_size = dec->frame_dec->SumSectionSizes();
+ dec->frame_stage = FrameStage::kTOC;
+ if (dec->preview_frame) {
+ if (!(dec->events_wanted & JXL_DEC_PREVIEW_IMAGE)) {
+ dec->frame_stage = FrameStage::kHeader;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
+ dec->got_preview_image = true;
+ dec->preview_frame = false;
+ }
+ continue;
+ }
+
+ int saved_as = FrameDecoder::SavedAs(*dec->frame_header);
// is last in entire codestream
dec->is_last_total = dec->frame_header->is_last;
// is last of current still
dec->is_last_of_still =
dec->is_last_total || dec->frame_header->animation_frame.duration > 0;
-
+ // is kRegularFrame and coalescing is disabled
+ dec->is_last_of_still |=
+ (!dec->coalescing &&
+ dec->frame_header->frame_type == FrameType::kRegularFrame);
const size_t internal_frame_index = dec->internal_frames;
const size_t external_frame_index = dec->external_frames;
if (dec->is_last_of_still) dec->external_frames++;
dec->internal_frames++;
- dec->frame_stage = FrameStage::kTOC;
-
if (dec->skip_frames > 0) {
dec->skipping_frame = true;
if (dec->is_last_of_still) {
@@ -1185,7 +1385,7 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
// Skip all decoding for this frame, since the user is skipping this
// frame and no future frames can reference it.
dec->frame_stage = FrameStage::kHeader;
- dec->frame_start += dec->frame_size;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
continue;
}
}
@@ -1201,54 +1401,44 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
}
if (dec->frame_stage == FrameStage::kTOC) {
- size_t pos = dec->frame_start - dec->codestream_pos;
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- Span<const uint8_t> span(in + pos, size - pos);
- auto reader = GetBitReader(span);
+ dec->frame_dec->SetRenderSpotcolors(dec->render_spotcolors);
+ dec->frame_dec->SetCoalescing(dec->coalescing);
- if (!dec->passes_state) {
- dec->passes_state.reset(new jxl::PassesDecoderState());
- }
- if (!dec->ib) {
- dec->ib.reset(new jxl::ImageBundle(&dec->metadata.m));
+ if (!dec->preview_frame &&
+ (dec->events_wanted & JXL_DEC_FRAME_PROGRESSION)) {
+ dec->frame_prog_detail =
+ dec->frame_dec->SetPauseAtProgressive(dec->prog_detail);
+ } else {
+ dec->frame_prog_detail = JxlProgressiveDetail::kFrames;
}
+ dec->dc_frame_progression_done = 0;
- dec->frame_dec.reset(new FrameDecoder(
- dec->passes_state.get(), dec->metadata, dec->thread_pool.get()));
-
- // If JPEG reconstruction is wanted and possible, set the jpeg_data of
- // the ImageBundle.
- if (!dec->jpeg_decoder.SetImageBundleJpegData(dec->ib.get()))
- return JXL_DEC_ERROR;
-
- jxl::Status status = dec->frame_dec->InitFrame(
- reader.get(), dec->ib.get(), /*is_preview=*/false,
- /*allow_partial_frames=*/false, /*allow_partial_dc_global=*/false);
- if (!status) JXL_API_RETURN_IF_ERROR(status);
-
- size_t sections_begin =
- DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
-
- dec->sections.reset(
- new Sections(dec->frame_dec.get(), dec->frame_size, sections_begin));
- JXL_API_RETURN_IF_ERROR(dec->sections->Init());
+ dec->next_section = 0;
+ dec->section_processed.clear();
+ dec->section_processed.resize(dec->frame_dec->Toc().size(), 0);
// If we don't need pixels, we can skip actually decoding the frames
- // (kFull / kFullOut). By not updating frame_stage, none of
- // these stages will execute, and the loop will continue from the next
- // frame.
- if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
+ // (kFull / kFullOut).
+ if (dec->preview_frame || (dec->events_wanted & JXL_DEC_FULL_IMAGE)) {
dec->frame_dec_in_progress = true;
dec->frame_stage = FrameStage::kFull;
+ } else if (!dec->is_last_total) {
+ dec->frame_stage = FrameStage::kHeader;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
+ continue;
+ } else {
+ break;
}
}
bool return_full_image = false;
if (dec->frame_stage == FrameStage::kFull) {
- if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
+ if (dec->preview_frame) {
+ if (!dec->preview_out_buffer_set) {
+ return JXL_DEC_NEED_PREVIEW_OUT_BUFFER;
+ }
+ } else if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
if (!dec->image_out_buffer_set &&
(!dec->jpeg_decoder.IsOutputSet() ||
dec->ib->jpeg_data == nullptr) &&
@@ -1262,7 +1452,10 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
}
}
- if (dec->image_out_buffer_set && !!dec->image_out_buffer &&
+ dec->frame_dec->MaybeSetUnpremultiplyAlpha(dec->unpremul_alpha);
+
+ if (!dec->preview_frame && dec->image_out_buffer_set &&
+ !!dec->image_out_buffer &&
dec->image_out_format.data_type == JXL_TYPE_UINT8 &&
dec->image_out_format.num_channels >= 3 &&
dec->extra_channel_output.empty()) {
@@ -1281,71 +1474,84 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
// TODO(lode): Support more formats than just native endian float32 for
// the low-memory callback path
- if (dec->image_out_buffer_set && !!dec->image_out_callback &&
+ if (!dec->preview_frame && dec->image_out_buffer_set &&
+ !!dec->image_out_init_callback && !!dec->image_out_run_callback &&
dec->image_out_format.data_type == JXL_TYPE_FLOAT &&
- dec->image_out_format.num_channels >= 3 && !swap_endianness &&
+ dec->image_out_format.num_channels >= 3 &&
+ dec->extra_channel_output.empty() && !swap_endianness &&
dec->frame_dec_in_progress) {
bool is_rgba = dec->image_out_format.num_channels == 4;
dec->frame_dec->MaybeSetFloatCallback(
- [dec](const float* pixels, size_t x, size_t y, size_t num_pixels) {
- dec->image_out_callback(dec->image_out_opaque, x, y, num_pixels,
- pixels);
- },
- is_rgba, !dec->keep_orientation);
+ PixelCallback{
+ dec->image_out_init_callback, dec->image_out_run_callback,
+ dec->image_out_destroy_callback, dec->image_out_init_opaque},
+ is_rgba, dec->unpremul_alpha, !dec->keep_orientation);
}
- size_t pos = dec->frame_start - dec->codestream_pos;
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- dec->sections->SetInput(in + pos, size - pos);
+ size_t next_num_passes_to_pause = dec->frame_dec->NextNumPassesToPause();
- if (cpu_limit_base_ != 0) {
- FrameDimensions frame_dim = dec->frame_header->ToFrameDimensions();
- // No overflow, checked in ParseHeader.
- size_t num_pixels = frame_dim.xsize * frame_dim.ysize;
- if (used_cpu_base_ + num_pixels < used_cpu_base_) {
- return JXL_API_ERROR("used too much CPU");
- }
- used_cpu_base_ += num_pixels;
- if (used_cpu_base_ > cpu_limit_base_) {
- return JXL_API_ERROR("used too much CPU");
- }
- }
+ JXL_API_RETURN_IF_ERROR(JxlDecoderProcessSections(dec));
- jxl::Status status =
- dec->frame_dec->ProcessSections(dec->sections->section_info.data(),
- dec->sections->section_info.size(),
- dec->sections->section_status.data());
- JXL_API_RETURN_IF_ERROR(dec->sections->CloseInput());
- if (status.IsFatalError()) {
- return JXL_API_ERROR("decoding frame failed");
+ bool all_sections_done = dec->frame_dec->HasDecodedAll();
+ bool got_dc_only = !all_sections_done && dec->frame_dec->HasDecodedDC();
+
+ if (dec->frame_prog_detail >= JxlProgressiveDetail::kDC &&
+ !dec->dc_frame_progression_done && got_dc_only) {
+ dec->dc_frame_progression_done = true;
+ dec->downsampling_target = 8;
+ return JXL_DEC_FRAME_PROGRESSION;
}
- // TODO(lode): allow next_in to move forward if sections from the
- // beginning of the stream have been processed
+ bool new_progression_step_done =
+ dec->frame_dec->NumCompletePasses() >= next_num_passes_to_pause;
+
+ if (!all_sections_done &&
+ dec->frame_prog_detail >= JxlProgressiveDetail::kLastPasses &&
+ new_progression_step_done) {
+ dec->downsampling_target =
+ dec->frame_header->passes.GetDownsamplingTargetForCompletedPasses(
+ dec->frame_dec->NumCompletePasses());
+ return JXL_DEC_FRAME_PROGRESSION;
+ }
- if (status.code() == StatusCode::kNotEnoughBytes ||
- dec->sections->section_info.size() < dec->frame_dec->NumSections()) {
+ if (!all_sections_done) {
// Not all sections have been processed yet
- return JXL_DEC_NEED_MORE_INPUT;
+ return dec->RequestMoreInput();
+ }
+
+ if (!dec->preview_frame) {
+ size_t internal_index = dec->internal_frames - 1;
+ JXL_ASSERT(dec->frame_references.size() > internal_index);
+ // Always fill this in, even if it was already written, it could be that
+ // this frame was skipped before and set to 255, while only now we know
+ // the true value.
+ dec->frame_references[internal_index] = dec->frame_dec->References();
+ // Copy exif/xmp metadata from their boxes into the jpeg_data, if
+ // JPEG reconstruction is requested.
+ if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) {
+ }
}
- size_t internal_index = dec->internal_frames - 1;
- JXL_ASSERT(dec->frame_references.size() > internal_index);
- // Always fill this in, even if it was already written, it could be that
- // this frame was skipped before and set to 255, while only now we know
- // the true value.
- dec->frame_references[internal_index] = dec->frame_dec->References();
if (!dec->frame_dec->FinalizeFrame()) {
return JXL_API_ERROR("decoding frame failed");
}
+
dec->frame_dec_in_progress = false;
dec->frame_stage = FrameStage::kFullOutput;
}
+ bool output_jpeg_reconstruction = false;
+
if (dec->frame_stage == FrameStage::kFullOutput) {
- if (dec->is_last_of_still) {
+ if (dec->preview_frame) {
+ JxlDecoderStatus status =
+ ConvertImageInternal(dec, *dec->ib, dec->preview_out_format,
+ /*want_extra_channel=*/false,
+ /*extra_channel_index=*/0,
+ dec->preview_out_buffer, dec->preview_out_size,
+ /*out_callback=*/{});
+ if (status != JXL_DEC_SUCCESS) return status;
+ } else if (dec->is_last_of_still) {
if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
dec->events_wanted &= ~JXL_DEC_FULL_IMAGE;
return_full_image = true;
@@ -1354,14 +1560,13 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
// Frame finished, restore the events_wanted with the per-frame events
// from orig_events_wanted, in case there is a next frame.
dec->events_wanted |=
- (dec->orig_events_wanted & (JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME));
+ (dec->orig_events_wanted &
+ (JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME | JXL_DEC_FRAME_PROGRESSION));
// If no output buffer was set, we merely return the JXL_DEC_FULL_IMAGE
// status without outputting pixels.
if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) {
- JxlDecoderStatus status =
- dec->jpeg_decoder.WriteOutput(*dec->ib->jpeg_data);
- if (status != JXL_DEC_SUCCESS) return status;
+ output_jpeg_reconstruction = true;
} else if (return_full_image && dec->image_out_buffer_set) {
if (!dec->frame_dec->HasRGBBuffer()) {
// Copy pixels if desired.
@@ -1369,21 +1574,30 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
dec, *dec->ib, dec->image_out_format,
/*want_extra_channel=*/false,
/*extra_channel_index=*/0, dec->image_out_buffer,
- dec->image_out_size, dec->image_out_callback,
- dec->image_out_opaque);
+ dec->image_out_size,
+ PixelCallback{dec->image_out_init_callback,
+ dec->image_out_run_callback,
+ dec->image_out_destroy_callback,
+ dec->image_out_init_opaque});
if (status != JXL_DEC_SUCCESS) return status;
}
dec->image_out_buffer_set = false;
+ bool has_ec = !dec->ib->extra_channels().empty();
for (size_t i = 0; i < dec->extra_channel_output.size(); ++i) {
void* buffer = dec->extra_channel_output[i].buffer;
// buffer nullptr indicates this extra channel is not requested
if (!buffer) continue;
+ if (!has_ec) {
+ JXL_WARNING(
+ "Extra channels are not supported when callback is used");
+ return JXL_DEC_ERROR;
+ }
const JxlPixelFormat* format = &dec->extra_channel_output[i].format;
JxlDecoderStatus status = ConvertImageInternal(
dec, *dec->ib, *format,
- /*want_extra_channel=*/true, i, buffer,
- dec->extra_channel_output[i].buffer_size, nullptr, nullptr);
+ /*want_extra_channel=*/true, /*extra_channel_index=*/i, buffer,
+ dec->extra_channel_output[i].buffer_size, /*out_callback=*/{});
if (status != JXL_DEC_SUCCESS) return status;
}
@@ -1392,17 +1606,27 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
}
}
- // The pixels have been output or are not needed, do not keep them in
- // memory here.
- dec->ib.reset();
dec->frame_stage = FrameStage::kHeader;
- dec->frame_start += dec->frame_size;
- if (return_full_image && !dec->skipping_frame) {
+
+ if (output_jpeg_reconstruction) {
+ dec->recon_output_jpeg = JpegReconStage::kSettingMetadata;
return JXL_DEC_FULL_IMAGE;
+ } else {
+ // The pixels have been output or are not needed, do not keep them in
+ // memory here.
+ dec->ib.reset();
+ if (dec->preview_frame) {
+ dec->got_preview_image = true;
+ dec->preview_frame = false;
+ dec->events_wanted &= ~JXL_DEC_PREVIEW_IMAGE;
+ return JXL_DEC_PREVIEW_IMAGE;
+ } else if (return_full_image && !dec->skipping_frame) {
+ return JXL_DEC_FULL_IMAGE;
+ }
}
}
- dec->stage = DecoderStage::kFinished;
+ dec->stage = DecoderStage::kCodestreamFinished;
// Return success, this means there is nothing more to do.
return JXL_DEC_SUCCESS;
}
@@ -1412,7 +1636,12 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, const uint8_t* data,
size_t size) {
- if (dec->next_in) return JXL_DEC_ERROR;
+ if (dec->next_in) {
+ return JXL_API_ERROR("already set input, use JxlDecoderReleaseInput first");
+ }
+ if (dec->input_closed) {
+ return JXL_API_ERROR("input already closed");
+ }
dec->next_in = data;
dec->avail_in = size;
@@ -1426,8 +1655,19 @@ size_t JxlDecoderReleaseInput(JxlDecoder* dec) {
return result;
}
+void JxlDecoderCloseInput(JxlDecoder* dec) { dec->input_closed = true; }
+
JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, uint8_t* data,
size_t size) {
+ // JPEG reconstruction buffer can only set and updated before or during the
+ // first frame, the reconstruction box refers to the first frame and in
+ // theory multi-frame images should not be used with a jbrd box.
+ if (dec->internal_frames > 1) {
+ return JXL_API_ERROR("JPEG reconstruction only works for the first frame");
+ }
+ if (dec->jpeg_decoder.IsOutputSet()) {
+ return JXL_API_ERROR("Already set JPEG buffer");
+ }
return dec->jpeg_decoder.SetOutputBuffer(data, size);
}
@@ -1435,340 +1675,486 @@ size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec) {
return dec->jpeg_decoder.ReleaseOutputBuffer();
}
-JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) {
- const uint8_t** next_in = &dec->next_in;
- size_t* avail_in = &dec->avail_in;
- if (dec->stage == DecoderStage::kInited) {
- dec->stage = DecoderStage::kStarted;
- }
- if (dec->stage == DecoderStage::kError) {
- return JXL_API_ERROR(
- "Cannot keep using decoder after it encountered an error, use "
- "JxlDecoderReset to reset it");
+// Parses the header of the box, outputting the 4-character type and the box
+// size, including header size, as stored in the box header.
+// @param in current input bytes.
+// @param size available input size.
+// @param pos position in the input, must begin at the header of the box.
+// @param file_pos position of pos since the start of the JXL file, rather than
+// the current input, used for integer overflow checking.
+// @param type the output box type.
+// @param box_size output the total box size, including header, in bytes, or 0
+// if it's a final unbounded box.
+// @param header_size output size of the box header.
+// @return JXL_DEC_SUCCESS if the box header was fully parsed. In that case the
+// parsing position must be incremented by header_size bytes.
+// JXL_DEC_NEED_MORE_INPUT if not enough input bytes available, in that case
+// header_size indicates a lower bound for the known size the header has to be
+// at least. JXL_DEC_ERROR if the box header is invalid.
+static JxlDecoderStatus ParseBoxHeader(const uint8_t* in, size_t size,
+ size_t pos, size_t file_pos,
+ JxlBoxType type, uint64_t* box_size,
+ uint64_t* header_size) {
+ if (OutOfBounds(pos, 8, size)) {
+ *header_size = 8;
+ return JXL_DEC_NEED_MORE_INPUT;
}
- if (dec->stage == DecoderStage::kFinished) {
- return JXL_API_ERROR(
- "Cannot keep using decoder after it finished, use JxlDecoderReset to "
- "reset it");
+ size_t box_start = pos;
+ // Box size, including this header itself.
+ *box_size = LoadBE32(in + pos);
+ pos += 4;
+ if (*box_size == 1) {
+ *header_size = 16;
+ if (OutOfBounds(pos, 12, size)) return JXL_DEC_NEED_MORE_INPUT;
+ *box_size = LoadBE64(in + pos);
+ pos += 8;
+ }
+ memcpy(type, in + pos, 4);
+ pos += 4;
+ *header_size = pos - box_start;
+ if (*box_size > 0 && *box_size < *header_size) {
+ return JXL_API_ERROR("invalid box size");
+ }
+ if (SumOverflows(file_pos, pos, *box_size)) {
+ return JXL_API_ERROR("Box size overflow");
}
+ return JXL_DEC_SUCCESS;
+}
- if (!dec->got_signature) {
- JxlSignature sig = JxlSignatureCheck(*next_in, *avail_in);
- if (sig == JXL_SIG_INVALID) return JXL_API_ERROR("invalid signature");
- if (sig == JXL_SIG_NOT_ENOUGH_BYTES) return JXL_DEC_NEED_MORE_INPUT;
+// This includes handling the codestream if it is not a box-based jxl file.
+static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
+ // Box handling loop
+ for (;;) {
+ if (dec->box_stage != BoxStage::kHeader) {
+ dec->AdvanceInput(dec->header_size);
+ dec->header_size = 0;
+ if ((dec->events_wanted & JXL_DEC_BOX) &&
+ dec->box_out_buffer_set_current_box) {
+ uint8_t* next_out = dec->box_out_buffer + dec->box_out_buffer_pos;
+ size_t avail_out = dec->box_out_buffer_size - dec->box_out_buffer_pos;
+
+ JxlDecoderStatus box_result = dec->box_content_decoder.Process(
+ dec->next_in, dec->avail_in,
+ dec->file_pos - dec->box_contents_begin, &next_out, &avail_out);
+ size_t produced =
+ next_out - (dec->box_out_buffer + dec->box_out_buffer_pos);
+ dec->box_out_buffer_pos += produced;
+
+ // Don't return JXL_DEC_NEED_MORE_INPUT: the box stages below, instead,
+ // handle the input progression, and the above only outputs the part of
+ // the box seen so far.
+ if (box_result != JXL_DEC_SUCCESS &&
+ box_result != JXL_DEC_NEED_MORE_INPUT) {
+ return box_result;
+ }
+ }
- dec->got_signature = true;
+ if (dec->store_exif == 1 || dec->store_xmp == 1) {
+ std::vector<uint8_t>& metadata =
+ (dec->store_exif == 1) ? dec->exif_metadata : dec->xmp_metadata;
+ for (;;) {
+ if (metadata.empty()) metadata.resize(64);
+ uint8_t* orig_next_out = metadata.data() + dec->recon_out_buffer_pos;
+ uint8_t* next_out = orig_next_out;
+ size_t avail_out = metadata.size() - dec->recon_out_buffer_pos;
+ JxlDecoderStatus box_result = dec->metadata_decoder.Process(
+ dec->next_in, dec->avail_in,
+ dec->file_pos - dec->box_contents_begin, &next_out, &avail_out);
+ size_t produced = next_out - orig_next_out;
+ dec->recon_out_buffer_pos += produced;
+ if (box_result == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
+ metadata.resize(metadata.size() * 2);
+ } else if (box_result == JXL_DEC_NEED_MORE_INPUT) {
+ break; // box stage handling below will handle this instead
+ } else if (box_result == JXL_DEC_SUCCESS) {
+ size_t needed_size = (dec->store_exif == 1) ? dec->recon_exif_size
+ : dec->recon_xmp_size;
+ if (dec->box_contents_unbounded &&
+ dec->recon_out_buffer_pos < needed_size) {
+ // Unbounded box, but we know the expected size due to the jbrd
+ // box's data. Treat this as the JXL_DEC_NEED_MORE_INPUT case.
+ break;
+ } else {
+ metadata.resize(dec->recon_out_buffer_pos);
+ if (dec->store_exif == 1) dec->store_exif = 2;
+ if (dec->store_xmp == 1) dec->store_xmp = 2;
+ break;
+ }
+ } else {
+ // error
+ return box_result;
+ }
+ }
+ }
+ }
- if (sig == JXL_SIG_CONTAINER) {
- dec->have_container = 1;
+ if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata &&
+ !dec->JbrdNeedMoreBoxes()) {
+ jxl::jpeg::JPEGData* jpeg_data = dec->ib->jpeg_data.get();
+ if (dec->recon_exif_size) {
+ JxlDecoderStatus status = jxl::JxlToJpegDecoder::SetExif(
+ dec->exif_metadata.data(), dec->exif_metadata.size(), jpeg_data);
+ if (status != JXL_DEC_SUCCESS) return status;
+ }
+ if (dec->recon_xmp_size) {
+ JxlDecoderStatus status = jxl::JxlToJpegDecoder::SetXmp(
+ dec->xmp_metadata.data(), dec->xmp_metadata.size(), jpeg_data);
+ if (status != JXL_DEC_SUCCESS) return status;
+ }
+ dec->recon_output_jpeg = JpegReconStage::kOutputting;
}
- }
- // Available codestream bytes, may differ from *avail_in if there is another
- // box behind the current position, in the dec->have_container case.
- size_t csize = *avail_in;
-
- if (dec->have_container) {
- /*
- Process bytes as follows:
- *) find the box(es) containing the codestream
- *) support codestream split over multiple partial boxes
- *) avoid copying bytes to the codestream vector if the decoding will be
- one-shot, when the user already provided everything contiguously in
- memory
- *) copy to codestream vector, and update next_in so user can delete the data
- on their side, once we know it's not oneshot. This relieves the user from
- continuing to store the data.
- *) also copy to codestream if one-shot but the codestream is split across
- multiple boxes: this copying can be avoided in the future if the C++
- decoder is updated for streaming, but for now it requires all consecutive
- data at once.
- */
-
- if (dec->skip_box) {
- // Amount of remaining bytes in the box that is being skipped.
- size_t remaining = dec->box_end - dec->file_pos;
- if (*avail_in < remaining) {
- // Don't have the full box yet, skip all we have so far
- dec->file_pos += *avail_in;
- *next_in += *avail_in;
- *avail_in -= *avail_in;
+ if (dec->recon_output_jpeg == JpegReconStage::kOutputting &&
+ !dec->JbrdNeedMoreBoxes()) {
+ JxlDecoderStatus status =
+ dec->jpeg_decoder.WriteOutput(*dec->ib->jpeg_data);
+ if (status != JXL_DEC_SUCCESS) return status;
+ dec->recon_output_jpeg = JpegReconStage::kFinished;
+ dec->ib.reset();
+ if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
+ // Return the full image event here now, this may be delayed if this
+ // could only be done after decoding an exif or xmp box after the
+ // codestream.
+ return JXL_DEC_FULL_IMAGE;
+ }
+ }
+
+ if (dec->box_stage == BoxStage::kHeader) {
+ if (!dec->have_container) {
+ if (dec->stage == DecoderStage::kCodestreamFinished)
+ return JXL_DEC_SUCCESS;
+ dec->box_stage = BoxStage::kCodestream;
+ dec->box_contents_unbounded = true;
+ continue;
+ }
+ if (dec->avail_in == 0) {
+ if (dec->stage != DecoderStage::kCodestreamFinished) {
+ // Not yet seen (all) codestream boxes.
+ return JXL_DEC_NEED_MORE_INPUT;
+ }
+ if (dec->JbrdNeedMoreBoxes()) {
+ return JXL_DEC_NEED_MORE_INPUT;
+ }
+ if (dec->input_closed) {
+ return JXL_DEC_SUCCESS;
+ }
+ if (!(dec->events_wanted & JXL_DEC_BOX)) {
+ // All codestream and jbrd metadata boxes finished, and no individual
+ // boxes requested by user, so no need to request any more input.
+ // This returns success for backwards compatibility, when
+ // JxlDecoderCloseInput and JXL_DEC_BOX did not exist, as well
+ // as for efficiency.
+ return JXL_DEC_SUCCESS;
+ }
+ // Even though we are exactly at a box end, there still may be more
+ // boxes. The user may call JxlDecoderCloseInput to indicate the input
+ // is finished and get success instead.
return JXL_DEC_NEED_MORE_INPUT;
+ }
+
+ bool boxed_codestream_done =
+ ((dec->events_wanted & JXL_DEC_BOX) &&
+ dec->stage == DecoderStage::kCodestreamFinished &&
+ dec->last_codestream_seen && !dec->JbrdNeedMoreBoxes());
+ if (boxed_codestream_done && dec->avail_in >= 2 &&
+ dec->next_in[0] == 0xff &&
+ dec->next_in[1] == jxl::kCodestreamMarker) {
+ // We detected the start of the next naked codestream, so we can return
+ // success here.
+ return JXL_DEC_SUCCESS;
+ }
+
+ uint64_t box_size, header_size;
+ JxlDecoderStatus status =
+ ParseBoxHeader(dec->next_in, dec->avail_in, 0, dec->file_pos,
+ dec->box_type, &box_size, &header_size);
+ if (status != JXL_DEC_SUCCESS) {
+ if (status == JXL_DEC_NEED_MORE_INPUT) {
+ dec->basic_info_size_hint =
+ InitialBasicInfoSizeHint() + header_size - dec->file_pos;
+ }
+ return status;
+ }
+ if (memcmp(dec->box_type, "brob", 4) == 0) {
+ if (dec->avail_in < header_size + 4) {
+ return JXL_DEC_NEED_MORE_INPUT;
+ }
+ memcpy(dec->box_decoded_type, dec->next_in + header_size,
+ sizeof(dec->box_decoded_type));
} else {
- // Full box available, skip all its remaining bytes
- dec->file_pos += remaining;
- *next_in += remaining;
- *avail_in -= remaining;
- dec->skip_box = false;
+ memcpy(dec->box_decoded_type, dec->box_type,
+ sizeof(dec->box_decoded_type));
}
- }
- if (dec->first_codestream_seen && !dec->last_codestream_seen &&
- dec->codestream_end != 0 && dec->file_pos < dec->codestream_end &&
- dec->file_pos + *avail_in >= dec->codestream_end &&
- !dec->codestream.empty()) {
- // dec->file_pos in a codestream, not in surrounding box format bytes, but
- // the end of the current codestream part is in the current input, and
- // boxes that can contain a next part of the codestream could be present.
- // Therefore, store the known codestream part, and ensure processing of
- // boxes below will trigger. This is only done if
- // !dec->codestream.empty(), that is, we're already streaming.
-
- // Size of the codestream, excluding potential boxes that come after it.
- csize = *avail_in;
- if (dec->codestream_end && csize > dec->codestream_end - dec->file_pos) {
- csize = dec->codestream_end - dec->file_pos;
+ // Box order validity checks
+ // The signature box at box_count == 1 is not checked here since that's
+ // already done at the beginning.
+ dec->box_count++;
+ if (boxed_codestream_done && memcmp(dec->box_type, "JXL ", 4) == 0) {
+ // We detected the start of the next boxed stream, so we can return
+ // success here.
+ return JXL_DEC_SUCCESS;
+ }
+ if (dec->box_count == 2 && memcmp(dec->box_type, "ftyp", 4) != 0) {
+ return JXL_API_ERROR("the second box must be the ftyp box");
+ }
+ if (memcmp(dec->box_type, "ftyp", 4) == 0 && dec->box_count != 2) {
+ return JXL_API_ERROR("the ftyp box must come second");
}
- dec->codestream.insert(dec->codestream.end(), *next_in, *next_in + csize);
- dec->file_pos += csize;
- *next_in += csize;
- *avail_in -= csize;
- }
- if (dec->jpeg_decoder.IsParsingBox()) {
- // We are inside a JPEG reconstruction box.
- JxlDecoderStatus recon_result =
- dec->jpeg_decoder.Process(next_in, avail_in);
- if (recon_result == JXL_DEC_JPEG_RECONSTRUCTION) {
- // If successful JPEG reconstruction, return the success if the user
- // cares about it, otherwise continue.
- if (dec->events_wanted & recon_result) {
- dec->events_wanted &= ~recon_result;
- return recon_result;
+ dec->box_contents_unbounded = (box_size == 0);
+ dec->box_contents_begin = dec->file_pos + header_size;
+ dec->box_contents_end =
+ dec->box_contents_unbounded ? 0 : (dec->file_pos + box_size);
+ dec->box_contents_size =
+ dec->box_contents_unbounded ? 0 : (box_size - header_size);
+ dec->box_size = box_size;
+ dec->header_size = header_size;
+
+ if (dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) {
+ // Initiate storing of Exif or XMP data for JPEG reconstruction
+ if (dec->store_exif == 0 &&
+ memcmp(dec->box_decoded_type, "Exif", 4) == 0) {
+ dec->store_exif = 1;
+ dec->recon_out_buffer_pos = 0;
}
+ if (dec->store_xmp == 0 &&
+ memcmp(dec->box_decoded_type, "xml ", 4) == 0) {
+ dec->store_xmp = 1;
+ dec->recon_out_buffer_pos = 0;
+ }
+ }
+
+ if (dec->events_wanted & JXL_DEC_BOX) {
+ bool decompress =
+ dec->decompress_boxes && memcmp(dec->box_type, "brob", 4) == 0;
+ dec->box_content_decoder.StartBox(
+ decompress, dec->box_contents_unbounded, dec->box_contents_size);
+ }
+ if (dec->store_exif == 1 || dec->store_xmp == 1) {
+ bool brob = memcmp(dec->box_type, "brob", 4) == 0;
+ dec->metadata_decoder.StartBox(brob, dec->box_contents_unbounded,
+ dec->box_contents_size);
+ }
+
+ if (memcmp(dec->box_type, "ftyp", 4) == 0) {
+ dec->box_stage = BoxStage::kFtyp;
+ } else if (memcmp(dec->box_type, "jxlc", 4) == 0) {
+ if (dec->last_codestream_seen) {
+ return JXL_API_ERROR("there can only be one jxlc box");
+ }
+ dec->last_codestream_seen = true;
+ dec->box_stage = BoxStage::kCodestream;
+ } else if (memcmp(dec->box_type, "jxlp", 4) == 0) {
+ dec->box_stage = BoxStage::kPartialCodestream;
+ } else if ((dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) &&
+ memcmp(dec->box_type, "jbrd", 4) == 0) {
+ if (!(dec->events_wanted & JXL_DEC_JPEG_RECONSTRUCTION)) {
+ return JXL_API_ERROR(
+ "multiple JPEG reconstruction boxes not supported");
+ }
+ dec->box_stage = BoxStage::kJpegRecon;
} else {
- // If anything else, return the result.
- return recon_result;
+ dec->box_stage = BoxStage::kSkip;
}
- }
- if (!dec->last_codestream_seen &&
- (dec->codestream_begin == 0 ||
- (dec->codestream_end != 0 && dec->file_pos >= dec->codestream_end))) {
- size_t pos = 0;
- // after this for loop, either we should be in a part of the data that is
- // codestream (not boxes), or have returned that we need more input.
- for (;;) {
- const uint8_t* in = *next_in;
- size_t size = *avail_in;
- if (size == pos) {
- // If the remaining size is 0, we are exactly after a full box. We
- // can't know for sure if this is the last box or not since more bytes
- // can follow, but do not return NEED_MORE_INPUT, instead break and
- // let the codestream-handling code determine if we need more.
- break;
+ if (dec->events_wanted & JXL_DEC_BOX) {
+ dec->box_event = true;
+ dec->box_out_buffer_set_current_box = false;
+ return JXL_DEC_BOX;
+ }
+ } else if (dec->box_stage == BoxStage::kFtyp) {
+ if (dec->box_contents_size < 12) {
+ return JXL_API_ERROR("file type box too small");
+ }
+ if (dec->avail_in < 4) return JXL_DEC_NEED_MORE_INPUT;
+ if (memcmp(dec->next_in, "jxl ", 4) != 0) {
+ return JXL_API_ERROR("file type box major brand must be \"jxl \"");
+ }
+ dec->AdvanceInput(4);
+ dec->box_stage = BoxStage::kSkip;
+ } else if (dec->box_stage == BoxStage::kPartialCodestream) {
+ if (dec->last_codestream_seen) {
+ return JXL_API_ERROR("cannot have jxlp box after last jxlp box");
+ }
+ // TODO(lode): error if box is unbounded but last bit not set
+ if (dec->avail_in < 4) return JXL_DEC_NEED_MORE_INPUT;
+ if (!dec->box_contents_unbounded && dec->box_contents_size < 4) {
+ return JXL_API_ERROR("jxlp box too small to contain index");
+ }
+ size_t jxlp_index = LoadBE32(dec->next_in);
+ // The high bit of jxlp_index indicates whether this is the last
+ // jxlp box.
+ if (jxlp_index & 0x80000000) {
+ dec->last_codestream_seen = true;
+ }
+ dec->AdvanceInput(4);
+ dec->box_stage = BoxStage::kCodestream;
+ } else if (dec->box_stage == BoxStage::kCodestream) {
+ JxlDecoderStatus status = jxl::JxlDecoderProcessCodestream(dec);
+ if (status == JXL_DEC_FULL_IMAGE) {
+ if (dec->recon_output_jpeg != JpegReconStage::kNone) {
+ continue;
}
- if (OutOfBounds(pos, 8, size)) {
- dec->basic_info_size_hint =
- InitialBasicInfoSizeHint() + pos + 8 - dec->file_pos;
- return JXL_DEC_NEED_MORE_INPUT;
+ }
+ if (status == JXL_DEC_NEED_MORE_INPUT) {
+ if (dec->file_pos == dec->box_contents_end &&
+ !dec->box_contents_unbounded) {
+ dec->box_stage = BoxStage::kHeader;
+ continue;
}
- size_t box_start = pos;
- // Box size, including this header itself.
- uint64_t box_size = LoadBE32(in + pos);
- char type[5] = {0};
- memcpy(type, in + pos + 4, 4);
- pos += 8;
- if (box_size == 1) {
- if (OutOfBounds(pos, 8, size)) return JXL_DEC_NEED_MORE_INPUT;
- box_size = LoadBE64(in + pos);
- pos += 8;
+ }
+
+ if (status == JXL_DEC_SUCCESS) {
+ if (dec->JbrdNeedMoreBoxes()) {
+ dec->box_stage = BoxStage::kSkip;
+ continue;
}
- size_t header_size = pos - box_start;
- if (box_size > 0 && box_size < header_size) {
- return JXL_API_ERROR("invalid box size");
+ if (dec->box_contents_unbounded) {
+ // Last box reached and codestream done, nothing more to do.
+ break;
}
- if (SumOverflows(dec->file_pos, pos, box_size)) {
- return JXL_API_ERROR("Box size overflow");
+ if (dec->events_wanted & JXL_DEC_BOX) {
+ // Codestream done, but there may be more other boxes.
+ dec->box_stage = BoxStage::kSkip;
+ continue;
}
- size_t contents_size =
- (box_size == 0) ? 0 : (box_size - pos + box_start);
-
- dec->box_begin = box_start;
- dec->box_end = dec->file_pos + box_start + box_size;
- if (strcmp(type, "jxlc") == 0 || strcmp(type, "jxlp") == 0) {
- size_t codestream_size = contents_size;
- // Whether this is the last codestream box, either when it is a jxlc
- // box, or when it is a jxlp box that has the final bit set.
- // The codestream is either contained within a single jxlc box, or
- // within one or more jxlp boxes. The final jxlp box is marked as last
- // by setting the high bit of its 4-byte box-index value.
- bool last_codestream = false;
- if (strcmp(type, "jxlp") == 0) {
- if (OutOfBounds(pos, 4, size)) return JXL_DEC_NEED_MORE_INPUT;
- if (box_size != 0 && contents_size < 4) {
- return JXL_API_ERROR("jxlp box too small to contain index");
- }
- codestream_size -= 4;
- size_t jxlp_index = LoadBE32(in + pos);
- pos += 4;
- // The high bit of jxlp_index indicates whether this is the last
- // jxlp box.
- if (jxlp_index & 0x80000000) last_codestream = true;
- } else if (strcmp(type, "jxlc") == 0) {
- last_codestream = true;
- }
- if (!last_codestream && box_size == 0) {
+ }
+ return status;
+ } else if (dec->box_stage == BoxStage::kJpegRecon) {
+ if (!dec->jpeg_decoder.IsParsingBox()) {
+ // This is a new JPEG reconstruction metadata box.
+ dec->jpeg_decoder.StartBox(dec->box_contents_unbounded,
+ dec->box_contents_size);
+ }
+ const uint8_t* next_in = dec->next_in;
+ size_t avail_in = dec->avail_in;
+ JxlDecoderStatus recon_result =
+ dec->jpeg_decoder.Process(&next_in, &avail_in);
+ size_t consumed = next_in - dec->next_in;
+ dec->AdvanceInput(consumed);
+ if (recon_result == JXL_DEC_JPEG_RECONSTRUCTION) {
+ jxl::jpeg::JPEGData* jpeg_data = dec->jpeg_decoder.GetJpegData();
+ size_t num_exif = jxl::JxlToJpegDecoder::NumExifMarkers(*jpeg_data);
+ size_t num_xmp = jxl::JxlToJpegDecoder::NumXmpMarkers(*jpeg_data);
+ if (num_exif) {
+ if (num_exif > 1) {
return JXL_API_ERROR(
- "final box has unbounded size, but is a non-final codestream "
- "box");
+ "multiple exif markers for JPEG reconstruction not supported");
}
- dec->first_codestream_seen = true;
- if (last_codestream) dec->last_codestream_seen = true;
- if (dec->codestream_begin != 0 && dec->codestream.empty()) {
- // We've already seen a codestream part, so it's a stream spanning
- // multiple boxes.
- // We have no choice but to copy contents to the codestream
- // vector to make it a contiguous stream for the C++ decoder.
- // This appends the previous codestream box that we had seen to
- // dec->codestream.
- if (dec->codestream_begin < dec->file_pos) {
- return JXL_API_ERROR("earlier codestream box out of range");
- }
- size_t begin = dec->codestream_begin - dec->file_pos;
- size_t end = dec->codestream_end - dec->file_pos;
- JXL_ASSERT(end <= *avail_in);
- dec->codestream.insert(dec->codestream.end(), *next_in + begin,
- *next_in + end);
+ if (JXL_DEC_SUCCESS != jxl::JxlToJpegDecoder::ExifBoxContentSize(
+ *jpeg_data, &dec->recon_exif_size)) {
+ return JXL_API_ERROR("invalid jbrd exif size");
}
- dec->codestream_begin = dec->file_pos + pos;
- dec->codestream_end =
- (box_size == 0) ? 0 : (dec->codestream_begin + codestream_size);
- size_t avail_codestream_size =
- (box_size == 0)
- ? (size - pos)
- : std::min<size_t>(size - pos, box_size - pos + box_start);
- // If already appending codestream, append what we have here too
- if (!dec->codestream.empty()) {
- size_t begin = pos;
- size_t end =
- std::min<size_t>(*avail_in, begin + avail_codestream_size);
- dec->codestream.insert(dec->codestream.end(), *next_in + begin,
- *next_in + end);
- pos += (end - begin);
- dec->file_pos += pos;
- *next_in += pos;
- *avail_in -= pos;
- pos = 0;
- // TODO(lode): check if this should break always instead, and
- // process what we have of the codestream so far, to support
- // progressive decoding, and get events such as basic info faster.
- // The user could have given 1.5 boxes here, and the first one could
- // contain useful parts of codestream that can already be processed.
- // Similar to several other exact avail_size checks. This may not
- // need to be changed here, but instead at the point in this for
- // loop where it returns "NEED_MORE_INPUT", it could instead break
- // and allow decoding what we have of the codestream so far.
- if (*avail_in == 0) break;
- } else {
- // skip only the header, so next_in points to the start of this new
- // codestream part, for the one-shot case where user data is not
- // (yet) copied to dec->codestream.
- dec->file_pos += pos;
- *next_in += pos;
- *avail_in -= pos;
- pos = 0;
- // Update pos to be after the box contents with codestream
- if (avail_codestream_size == *avail_in) {
- break; // the rest is codestream, this loop is done
- }
- pos += avail_codestream_size;
- }
- } else if ((JPEGXL_ENABLE_TRANSCODE_JPEG) &&
- (dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) &&
- strcmp(type, "jbrd") == 0) {
- // This is a new JPEG reconstruction metadata box.
- dec->jpeg_decoder.StartBox(box_size, contents_size);
- dec->file_pos += pos;
- *next_in += pos;
- *avail_in -= pos;
- pos = 0;
- JxlDecoderStatus recon_result =
- dec->jpeg_decoder.Process(next_in, avail_in);
- if (recon_result == JXL_DEC_JPEG_RECONSTRUCTION) {
- // If successful JPEG reconstruction, return the success if the user
- // cares about it, otherwise continue.
- if (dec->events_wanted & recon_result) {
- dec->events_wanted &= ~recon_result;
- return recon_result;
- }
- } else {
- // If anything else, return the result.
- return recon_result;
- }
- } else {
- if (box_size == 0) {
- // Final box with unknown size, but it's not a codestream box, so
- // nothing more to do.
- if (!dec->first_codestream_seen) {
- return JXL_API_ERROR("didn't find any codestream box");
- }
- break;
- }
- if (OutOfBounds(pos, contents_size, size)) {
- dec->skip_box = true;
- dec->file_pos += pos;
- *next_in += pos;
- *avail_in -= pos;
- // Indicate how many more bytes needed starting from *next_in.
- dec->basic_info_size_hint = InitialBasicInfoSizeHint() + pos +
- contents_size - dec->file_pos;
- return JXL_DEC_NEED_MORE_INPUT;
+ }
+ if (num_xmp) {
+ if (num_xmp > 1) {
+ return JXL_API_ERROR(
+ "multiple XMP markers for JPEG reconstruction not supported");
}
- pos += contents_size;
- if (!(dec->codestream.empty() && dec->first_codestream_seen)) {
- // Last box no longer needed since we have copied the codestream
- // buffer, remove from input so user can release memory.
- dec->file_pos += pos;
- *next_in += pos;
- *avail_in -= pos;
- pos = 0;
+ if (JXL_DEC_SUCCESS != jxl::JxlToJpegDecoder::XmlBoxContentSize(
+ *jpeg_data, &dec->recon_xmp_size)) {
+ return JXL_API_ERROR("invalid jbrd XMP size");
}
}
+
+ dec->box_stage = BoxStage::kHeader;
+ // If successful JPEG reconstruction, return the success if the user
+ // cares about it, otherwise continue.
+ if (dec->events_wanted & recon_result) {
+ dec->events_wanted &= ~recon_result;
+ return recon_result;
+ }
+ } else {
+ // If anything else, return the result.
+ return recon_result;
+ }
+ } else if (dec->box_stage == BoxStage::kSkip) {
+ if (dec->box_contents_unbounded) {
+ if (dec->input_closed) {
+ return JXL_DEC_SUCCESS;
+ }
+ if (!(dec->box_out_buffer_set)) {
+ // An unbounded box is always the last box. Not requesting box data,
+ // so return success even if JxlDecoderCloseInput was not called for
+ // backwards compatibility as well as efficiency since this box is
+ // being skipped.
+ return JXL_DEC_SUCCESS;
+ }
+ // Arbitrarily more bytes may follow, only JxlDecoderCloseInput can
+ // mark the end.
+ dec->AdvanceInput(dec->avail_in);
+ return JXL_DEC_NEED_MORE_INPUT;
}
+ // Amount of remaining bytes in the box that is being skipped.
+ size_t remaining = dec->box_contents_end - dec->file_pos;
+ if (dec->avail_in < remaining) {
+ // Indicate how many more bytes needed starting from next_in.
+ dec->basic_info_size_hint =
+ InitialBasicInfoSizeHint() + dec->box_contents_end - dec->file_pos;
+ // Don't have the full box yet, skip all we have so far
+ dec->AdvanceInput(dec->avail_in);
+ return JXL_DEC_NEED_MORE_INPUT;
+ } else {
+ // Full box available, skip all its remaining bytes
+ dec->AdvanceInput(remaining);
+ dec->box_stage = BoxStage::kHeader;
+ }
+ } else {
+ JXL_DASSERT(false); // unknown box stage
}
+ }
- // Size of the codestream, excluding potential boxes that come after it.
- csize = *avail_in;
- if (dec->codestream_end && csize > dec->codestream_end - dec->file_pos) {
- csize = dec->codestream_end - dec->file_pos;
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) {
+ if (dec->stage == DecoderStage::kInited) {
+ dec->stage = DecoderStage::kStarted;
+ }
+ if (dec->stage == DecoderStage::kError) {
+ return JXL_API_ERROR(
+ "Cannot keep using decoder after it encountered an error, use "
+ "JxlDecoderReset to reset it");
+ }
+
+ if (!dec->got_signature) {
+ JxlSignature sig = JxlSignatureCheck(dec->next_in, dec->avail_in);
+ if (sig == JXL_SIG_INVALID) return JXL_API_ERROR("invalid signature");
+ if (sig == JXL_SIG_NOT_ENOUGH_BYTES) {
+ if (dec->input_closed) {
+ return JXL_API_ERROR("file too small for signature");
+ }
+ return JXL_DEC_NEED_MORE_INPUT;
+ }
+
+ dec->got_signature = true;
+
+ if (sig == JXL_SIG_CONTAINER) {
+ dec->have_container = 1;
+ } else {
+ dec->last_codestream_seen = true;
}
}
- // Whether we are taking the input directly from the user (oneshot case,
- // without copying bytes), or appending parts of input to dec->codestream
- // (streaming)
- bool detected_streaming = !dec->codestream.empty();
- JxlDecoderStatus result;
- JXL_DASSERT(csize <= *avail_in);
-
- if (detected_streaming) {
- dec->codestream.insert(dec->codestream.end(), *next_in, *next_in + csize);
- dec->file_pos += csize;
- *next_in += csize;
- *avail_in -= csize;
- result = jxl::JxlDecoderProcessInternal(dec, dec->codestream.data(),
- dec->codestream.size());
- } else {
- // No data copied to codestream buffer yet, the user input may contain the
- // full codestream.
- result = jxl::JxlDecoderProcessInternal(dec, *next_in, csize);
- // Copy the user's input bytes to the codestream once we are able to and
- // it is needed. Before we got the basic info, we're still parsing the box
- // format instead. If the result is not JXL_DEC_NEED_MORE_INPUT, then
- // there is no reason yet to copy since the user may have a full buffer
- // allowing one-shot. Once JXL_DEC_NEED_MORE_INPUT occurred at least once,
- // start copying over the codestream bytes and allow user to free them
- // instead. Next call, detected_streaming will be true.
- if (dec->got_basic_info && result == JXL_DEC_NEED_MORE_INPUT) {
- dec->codestream.insert(dec->codestream.end(), *next_in, *next_in + csize);
- dec->file_pos += csize;
- *next_in += csize;
- *avail_in -= csize;
+ JxlDecoderStatus status = HandleBoxes(dec);
+
+ if (status == JXL_DEC_NEED_MORE_INPUT && dec->input_closed) {
+ return JXL_API_ERROR("missing input");
+ }
+
+ // Even if the box handling returns success, certain types of
+ // data may be missing.
+ if (status == JXL_DEC_SUCCESS) {
+ if (dec->CanUseMoreCodestreamInput()) {
+ return JXL_API_ERROR("codestream never finished");
+ }
+ if (dec->JbrdNeedMoreBoxes()) {
+ return JXL_API_ERROR("missing metadata boxes for jpeg reconstruction");
}
}
- return result;
+ return status;
}
// To ensure ABI forward-compatibility, this struct has a constant size.
@@ -1780,6 +2166,8 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
if (!dec->got_basic_info) return JXL_DEC_NEED_MORE_INPUT;
if (info) {
+ memset(info, 0, sizeof(*info));
+
const jxl::ImageMetadata& meta = dec->metadata.m;
info->have_container = dec->have_container;
@@ -1792,7 +2180,6 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
info->have_preview = meta.have_preview;
info->have_animation = meta.have_animation;
- // TODO(janwas): intrinsic_size
info->orientation = static_cast<JxlOrientation>(meta.orientation);
if (!dec->keep_orientation) {
@@ -1803,6 +2190,9 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
}
info->intensity_target = meta.IntensityTarget();
+ if (dec->desired_intensity_target > 0) {
+ info->intensity_target = dec->desired_intensity_target;
+ }
info->min_nits = meta.tone_mapping.min_nits;
info->relative_to_max_display = meta.tone_mapping.relative_to_max_display;
info->linear_below = meta.tone_mapping.linear_below;
@@ -1835,6 +2225,14 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
info->animation.num_loops = dec->metadata.m.animation.num_loops;
info->animation.have_timecodes = dec->metadata.m.animation.have_timecodes;
}
+
+ if (meta.have_intrinsic_size) {
+ info->intrinsic_xsize = dec->metadata.m.intrinsic_size.xsize();
+ info->intrinsic_ysize = dec->metadata.m.intrinsic_size.ysize();
+ } else {
+ info->intrinsic_xsize = info->xsize;
+ info->intrinsic_ysize = info->ysize;
+ }
}
return JXL_DEC_SUCCESS;
@@ -1895,8 +2293,8 @@ namespace {
// but ensures that if the color encoding is not the encoding from the
// codestream header metadata, it cannot require ICC profile.
JxlDecoderStatus GetColorEncodingForTarget(
- const JxlDecoder* dec, const JxlPixelFormat* format,
- JxlColorProfileTarget target, const jxl::ColorEncoding** encoding) {
+ const JxlDecoder* dec, JxlColorProfileTarget target,
+ const jxl::ColorEncoding** encoding) {
if (!dec->got_all_headers) return JXL_DEC_NEED_MORE_INPUT;
*encoding = nullptr;
if (target == JXL_COLOR_PROFILE_TARGET_DATA && dec->metadata.m.xyb_encoded) {
@@ -1909,11 +2307,11 @@ JxlDecoderStatus GetColorEncodingForTarget(
} // namespace
JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile(
- const JxlDecoder* dec, const JxlPixelFormat* format,
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
JxlColorProfileTarget target, JxlColorEncoding* color_encoding) {
const jxl::ColorEncoding* jxl_color_encoding = nullptr;
JxlDecoderStatus status =
- GetColorEncodingForTarget(dec, format, target, &jxl_color_encoding);
+ GetColorEncodingForTarget(dec, target, &jxl_color_encoding);
if (status) return status;
if (jxl_color_encoding->WantICC())
@@ -1926,13 +2324,12 @@ JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile(
return JXL_DEC_SUCCESS;
}
-JxlDecoderStatus JxlDecoderGetICCProfileSize(const JxlDecoder* dec,
- const JxlPixelFormat* format,
- JxlColorProfileTarget target,
- size_t* size) {
+JxlDecoderStatus JxlDecoderGetICCProfileSize(
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
+ JxlColorProfileTarget target, size_t* size) {
const jxl::ColorEncoding* jxl_color_encoding = nullptr;
JxlDecoderStatus status =
- GetColorEncodingForTarget(dec, format, target, &jxl_color_encoding);
+ GetColorEncodingForTarget(dec, target, &jxl_color_encoding);
if (status != JXL_DEC_SUCCESS) return status;
if (jxl_color_encoding->WantICC()) {
@@ -1955,20 +2352,18 @@ JxlDecoderStatus JxlDecoderGetICCProfileSize(const JxlDecoder* dec,
return JXL_DEC_SUCCESS;
}
-JxlDecoderStatus JxlDecoderGetColorAsICCProfile(const JxlDecoder* dec,
- const JxlPixelFormat* format,
- JxlColorProfileTarget target,
- uint8_t* icc_profile,
- size_t size) {
+JxlDecoderStatus JxlDecoderGetColorAsICCProfile(
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
+ JxlColorProfileTarget target, uint8_t* icc_profile, size_t size) {
size_t wanted_size;
// This also checks the NEED_MORE_INPUT and the unknown/xyb cases
JxlDecoderStatus status =
- JxlDecoderGetICCProfileSize(dec, format, target, &wanted_size);
+ JxlDecoderGetICCProfileSize(dec, nullptr, target, &wanted_size);
if (status != JXL_DEC_SUCCESS) return status;
if (size < wanted_size) return JXL_API_ERROR("ICC profile output too small");
const jxl::ColorEncoding* jxl_color_encoding = nullptr;
- status = GetColorEncodingForTarget(dec, format, target, &jxl_color_encoding);
+ status = GetColorEncodingForTarget(dec, target, &jxl_color_encoding);
if (status != JXL_DEC_SUCCESS) return status;
memcpy(icc_profile, jxl_color_encoding->ICC().data(),
@@ -1987,53 +2382,48 @@ JxlDecoderStatus PrepareSizeCheck(const JxlDecoder* dec,
// Don't know image dimensions yet, cannot check for valid size.
return JXL_DEC_NEED_MORE_INPUT;
}
+ if (!dec->coalescing &&
+ (!dec->frame_header || dec->frame_stage == FrameStage::kHeader)) {
+ return JXL_API_ERROR("Don't know frame dimensions yet");
+ }
if (format->num_channels > 4) {
return JXL_API_ERROR("More than 4 channels not supported");
}
- if (format->data_type == JXL_TYPE_BOOLEAN) {
- return JXL_API_ERROR("Boolean data type not yet supported");
- }
- if (format->data_type == JXL_TYPE_UINT32) {
- return JXL_API_ERROR("uint32 data type not yet supported");
- }
*bits = BitsPerChannel(format->data_type);
if (*bits == 0) {
- return JXL_API_ERROR("Invalid data type");
+ return JXL_API_ERROR("Invalid/unsupported data type");
}
return JXL_DEC_SUCCESS;
}
+
} // namespace
+size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec) {
+ return dec->downsampling_target;
+}
+
JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
- if (!dec->image_out_buffer) return JXL_DEC_ERROR;
- if (!dec->sections || dec->sections->section_info.empty()) {
- return JXL_DEC_ERROR;
- }
+ if (!dec->image_out_buffer_set) return JXL_DEC_ERROR;
if (!dec->frame_dec || !dec->frame_dec_in_progress) {
return JXL_DEC_ERROR;
}
if (!dec->frame_dec->HasDecodedDC()) {
- // FrameDecoder::Fush currently requires DC to have been decoded already
+ // FrameDecoder::Flush currently requires DC to have been decoded already
// to work correctly.
return JXL_DEC_ERROR;
}
- if (dec->frame_header->encoding != jxl::FrameEncoding::kVarDCT) {
- // Flushing does not yet work correctly if the frame uses modular encoding.
- return JXL_DEC_ERROR;
- }
- if (dec->metadata.m.num_extra_channels > 0) {
- // Flushing does not yet work correctly if there are extra channels, which
- // use modular
- return JXL_DEC_ERROR;
- }
if (!dec->frame_dec->Flush()) {
return JXL_DEC_ERROR;
}
+ if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) {
+ return JXL_DEC_SUCCESS;
+ }
+
if (dec->frame_dec->HasRGBBuffer()) {
return JXL_DEC_SUCCESS;
}
@@ -2042,11 +2432,16 @@ JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
// ConvertImageInternal.
size_t xsize = dec->ib->xsize();
size_t ysize = dec->ib->ysize();
- dec->ib->ShrinkTo(dec->metadata.size.xsize(), dec->metadata.size.ysize());
+ size_t xsize_nopadding, ysize_nopadding;
+ GetCurrentDimensions(dec, xsize_nopadding, ysize_nopadding, false);
+ dec->ib->ShrinkTo(xsize_nopadding, ysize_nopadding);
JxlDecoderStatus status = jxl::ConvertImageInternal(
- dec, *dec->ib, dec->image_out_format, /*want_extra_channel=*/false,
+ dec, *dec->ib, dec->image_out_format,
+ /*want_extra_channel=*/false,
/*extra_channel_index=*/0, dec->image_out_buffer, dec->image_out_size,
- /*out_callback=*/nullptr, /*out_opaque=*/nullptr);
+ jxl::PixelCallback{
+ dec->image_out_init_callback, dec->image_out_run_callback,
+ dec->image_out_destroy_callback, dec->image_out_init_opaque});
dec->ib->ShrinkTo(xsize, ysize);
if (status != JXL_DEC_SUCCESS) return status;
return JXL_DEC_SUCCESS;
@@ -2057,8 +2452,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize(
size_t bits;
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits);
if (status != JXL_DEC_SUCCESS) return status;
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t xsize = dec->metadata.oriented_preview_xsize(dec->keep_orientation);
@@ -2066,10 +2462,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize(
size_t row_size =
jxl::DivCeil(xsize * format->num_channels * bits, jxl::kBitsPerByte);
+ size_t last_row_size = row_size;
if (format->align > 1) {
row_size = jxl::DivCeil(row_size, format->align) * format->align;
}
- *size = row_size * ysize;
+ *size = row_size * (ysize - 1) + last_row_size;
return JXL_DEC_SUCCESS;
}
@@ -2079,8 +2476,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer(
!(dec->orig_events_wanted & JXL_DEC_PREVIEW_IMAGE)) {
return JXL_API_ERROR("No preview out buffer needed at this time");
}
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t min_size;
@@ -2113,10 +2511,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderDCOutBufferSize(
size_t row_size =
jxl::DivCeil(xsize * format->num_channels * bits, jxl::kBitsPerByte);
+ size_t last_row_size = row_size;
if (format->align > 1) {
row_size = jxl::DivCeil(row_size, format->align) * format->align;
}
- *size = row_size * ysize;
+ *size = row_size * (ysize - 1) + last_row_size;
return JXL_DEC_SUCCESS;
}
@@ -2131,18 +2530,18 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize(
size_t bits;
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits);
if (status != JXL_DEC_SUCCESS) return status;
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
-
+ size_t xsize, ysize;
+ GetCurrentDimensions(dec, xsize, ysize, true);
size_t row_size =
- jxl::DivCeil(dec->metadata.oriented_xsize(dec->keep_orientation) *
- format->num_channels * bits,
- jxl::kBitsPerByte);
+ jxl::DivCeil(xsize * format->num_channels * bits, jxl::kBitsPerByte);
if (format->align > 1) {
row_size = jxl::DivCeil(row_size, format->align) * format->align;
}
- *size = row_size * dec->metadata.oriented_ysize(dec->keep_orientation);
+ *size = row_size * ysize;
return JXL_DEC_SUCCESS;
}
@@ -2153,12 +2552,13 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
if (!dec->got_basic_info || !(dec->orig_events_wanted & JXL_DEC_FULL_IMAGE)) {
return JXL_API_ERROR("No image out buffer needed at this time");
}
- if (dec->image_out_buffer_set && !!dec->image_out_callback) {
+ if (dec->image_out_buffer_set && !!dec->image_out_run_callback) {
return JXL_API_ERROR(
"Cannot change from image out callback to image out buffer");
}
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t min_size;
// This also checks whether the format is valid and supported and basic info
@@ -2195,13 +2595,14 @@ JxlDecoderStatus JxlDecoderExtraChannelBufferSize(const JxlDecoder* dec,
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits);
if (status != JXL_DEC_SUCCESS) return status;
- size_t row_size = jxl::DivCeil(
- dec->metadata.oriented_xsize(dec->keep_orientation) * num_channels * bits,
- jxl::kBitsPerByte);
+ size_t xsize, ysize;
+ GetCurrentDimensions(dec, xsize, ysize, true);
+ size_t row_size =
+ jxl::DivCeil(xsize * num_channels * bits, jxl::kBitsPerByte);
if (format->align > 1) {
row_size = jxl::DivCeil(row_size, format->align) * format->align;
}
- *size = row_size * dec->metadata.oriented_ysize(dec->keep_orientation);
+ *size = row_size * ysize;
return JXL_DEC_SUCCESS;
}
@@ -2238,19 +2639,51 @@ JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec,
const JxlPixelFormat* format,
JxlImageOutCallback callback,
void* opaque) {
+ dec->simple_image_out_callback.callback = callback;
+ dec->simple_image_out_callback.opaque = opaque;
+ const auto init_callback =
+ +[](void* init_opaque, size_t num_threads, size_t num_pixels_per_thread) {
+ // No initialization to do, just reuse init_opaque as run_opaque.
+ return init_opaque;
+ };
+ const auto run_callback =
+ +[](void* run_opaque, size_t thread_id, size_t x, size_t y,
+ size_t num_pixels, const void* pixels) {
+ const auto* const simple_callback =
+ static_cast<const JxlDecoder::SimpleImageOutCallback*>(run_opaque);
+ simple_callback->callback(simple_callback->opaque, x, y, num_pixels,
+ pixels);
+ };
+ const auto destroy_callback = +[](void* run_opaque) {};
+ return JxlDecoderSetMultithreadedImageOutCallback(
+ dec, format, init_callback, run_callback,
+ /*destroy_callback=*/destroy_callback, &dec->simple_image_out_callback);
+}
+
+JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback(
+ JxlDecoder* dec, const JxlPixelFormat* format,
+ JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback,
+ JxlImageOutDestroyCallback destroy_callback, void* init_opaque) {
if (dec->image_out_buffer_set && !!dec->image_out_buffer) {
return JXL_API_ERROR(
"Cannot change from image out buffer to image out callback");
}
+ if (init_callback == nullptr || run_callback == nullptr ||
+ destroy_callback == nullptr) {
+ return JXL_API_ERROR("All callbacks are required");
+ }
+
// Perform error checking for invalid format.
size_t bits_dummy;
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits_dummy);
if (status != JXL_DEC_SUCCESS) return status;
dec->image_out_buffer_set = true;
- dec->image_out_callback = callback;
- dec->image_out_opaque = opaque;
+ dec->image_out_init_callback = init_callback;
+ dec->image_out_run_callback = run_callback;
+ dec->image_out_destroy_callback = destroy_callback;
+ dec->image_out_init_opaque = init_opaque;
dec->image_out_format = *format;
return JXL_DEC_SUCCESS;
@@ -2262,6 +2695,7 @@ JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec,
return JXL_API_ERROR("no frame header available");
}
const auto& metadata = dec->metadata.m;
+ memset(header, 0, sizeof(*header));
if (metadata.have_animation) {
header->duration = dec->frame_header->animation_frame.duration;
if (metadata.animation.have_timecodes) {
@@ -2270,7 +2704,72 @@ JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec,
}
header->name_length = dec->frame_header->name.size();
header->is_last = dec->frame_header->is_last;
+ size_t xsize, ysize;
+ GetCurrentDimensions(dec, xsize, ysize, true);
+ header->layer_info.xsize = xsize;
+ header->layer_info.ysize = ysize;
+ if (!dec->coalescing && dec->frame_header->custom_size_or_origin) {
+ header->layer_info.crop_x0 = dec->frame_header->frame_origin.x0;
+ header->layer_info.crop_y0 = dec->frame_header->frame_origin.y0;
+ header->layer_info.have_crop = JXL_TRUE;
+ } else {
+ header->layer_info.crop_x0 = 0;
+ header->layer_info.crop_y0 = 0;
+ header->layer_info.have_crop = JXL_FALSE;
+ }
+ if (!dec->keep_orientation && !dec->coalescing) {
+ // orient the crop offset
+ size_t W = dec->metadata.oriented_xsize(false);
+ size_t H = dec->metadata.oriented_ysize(false);
+ if (metadata.orientation > 4) {
+ std::swap(header->layer_info.crop_x0, header->layer_info.crop_y0);
+ }
+ size_t o = (metadata.orientation - 1) & 3;
+ if (o > 0 && o < 3) {
+ header->layer_info.crop_x0 = W - xsize - header->layer_info.crop_x0;
+ }
+ if (o > 1) {
+ header->layer_info.crop_y0 = H - ysize - header->layer_info.crop_y0;
+ }
+ }
+ if (dec->coalescing) {
+ header->layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
+ header->layer_info.blend_info.source = 0;
+ header->layer_info.blend_info.alpha = 0;
+ header->layer_info.blend_info.clamp = JXL_FALSE;
+ header->layer_info.save_as_reference = 0;
+ } else {
+ header->layer_info.blend_info.blendmode =
+ static_cast<JxlBlendMode>(dec->frame_header->blending_info.mode);
+ header->layer_info.blend_info.source =
+ dec->frame_header->blending_info.source;
+ header->layer_info.blend_info.alpha =
+ dec->frame_header->blending_info.alpha_channel;
+ header->layer_info.blend_info.clamp =
+ dec->frame_header->blending_info.clamp;
+ header->layer_info.save_as_reference = dec->frame_header->save_as_reference;
+ }
+ return JXL_DEC_SUCCESS;
+}
+JxlDecoderStatus JxlDecoderGetExtraChannelBlendInfo(const JxlDecoder* dec,
+ size_t index,
+ JxlBlendInfo* blend_info) {
+ if (!dec->frame_header || dec->frame_stage == FrameStage::kHeader) {
+ return JXL_API_ERROR("no frame header available");
+ }
+ const auto& metadata = dec->metadata.m;
+ if (index >= metadata.num_extra_channels) {
+ return JXL_API_ERROR("Invalid extra channel index");
+ }
+ blend_info->blendmode = static_cast<JxlBlendMode>(
+ dec->frame_header->extra_channel_blending_info[index].mode);
+ blend_info->source =
+ dec->frame_header->extra_channel_blending_info[index].source;
+ blend_info->alpha =
+ dec->frame_header->extra_channel_blending_info[index].alpha_channel;
+ blend_info->clamp =
+ dec->frame_header->extra_channel_blending_info[index].clamp;
return JXL_DEC_SUCCESS;
}
@@ -2296,30 +2795,113 @@ JxlDecoderStatus JxlDecoderSetPreferredColorProfile(
if (dec->post_headers) {
return JXL_API_ERROR("too late to set the color encoding");
}
- if (dec->metadata.m.color_encoding.IsGray() !=
- (color_encoding->color_space == JXL_COLOR_SPACE_GRAY)) {
- return JXL_API_ERROR("grayscale mismatch");
+ if (dec->image_metadata.color_encoding.IsGray() &&
+ color_encoding->color_space != JXL_COLOR_SPACE_GRAY &&
+ ((dec->preview_out_buffer_set &&
+ dec->preview_out_format.num_channels < 3) ||
+ (dec->image_out_buffer_set && dec->image_out_format.num_channels < 3))) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
if (color_encoding->color_space == JXL_COLOR_SPACE_UNKNOWN ||
color_encoding->color_space == JXL_COLOR_SPACE_XYB) {
return JXL_API_ERROR("only RGB or grayscale output supported");
}
- JXL_API_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
- *color_encoding, &dec->default_enc));
- JXL_API_RETURN_IF_ERROR(dec->passes_state->output_encoding_info.Set(
- dec->metadata, dec->default_enc));
+ jxl::ColorEncoding c_out;
+ JXL_API_RETURN_IF_ERROR(
+ ConvertExternalToInternalColorEncoding(*color_encoding, &c_out));
+ auto& output_encoding = dec->passes_state->output_encoding_info;
+ if (!c_out.SameColorEncoding(output_encoding.color_encoding)) {
+ JXL_API_RETURN_IF_ERROR(output_encoding.MaybeSetColorEncoding(c_out));
+ dec->image_metadata.color_encoding = output_encoding.color_encoding;
+ }
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget(
+ JxlDecoder* dec, float desired_intensity_target) {
+ if (desired_intensity_target < 0) {
+ return JXL_API_ERROR("negative intensity target requested");
+ }
+ dec->desired_intensity_target = desired_intensity_target;
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderSetBoxBuffer(JxlDecoder* dec, uint8_t* data,
+ size_t size) {
+ if (dec->box_out_buffer_set) {
+ return JXL_API_ERROR("must release box buffer before setting it again");
+ }
+ if (!dec->box_event) {
+ return JXL_API_ERROR("can only set box buffer after box event");
+ }
+
+ dec->box_out_buffer_set = true;
+ dec->box_out_buffer_set_current_box = true;
+ dec->box_out_buffer = data;
+ dec->box_out_buffer_size = size;
+ dec->box_out_buffer_pos = 0;
+ return JXL_DEC_SUCCESS;
+}
+
+size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec) {
+ if (!dec->box_out_buffer_set) {
+ return 0;
+ }
+ size_t result = dec->box_out_buffer_size - dec->box_out_buffer_pos;
+ dec->box_out_buffer_set = false;
+ dec->box_out_buffer = nullptr;
+ dec->box_out_buffer_size = 0;
+ if (!dec->box_out_buffer_set_current_box) {
+ dec->box_out_buffer_begin = 0;
+ } else {
+ dec->box_out_buffer_begin += dec->box_out_buffer_pos;
+ }
+ dec->box_out_buffer_set_current_box = false;
+ return result;
+}
+
+JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec,
+ JXL_BOOL decompress) {
+ // TODO(lode): return error if libbrotli is not compiled in the jxl decoding
+ // library
+ dec->decompress_boxes = decompress;
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec, JxlBoxType type,
+ JXL_BOOL decompressed) {
+ if (!dec->box_event) {
+ return JXL_API_ERROR("can only get box info after JXL_DEC_BOX event");
+ }
+ if (decompressed) {
+ memcpy(type, dec->box_decoded_type, sizeof(dec->box_decoded_type));
+ } else {
+ memcpy(type, dec->box_type, sizeof(dec->box_type));
+ }
+
return JXL_DEC_SUCCESS;
}
-// This function is "package-private". It is only used by fuzzer to avoid
-// running cases that are too memory / CPU hungry. Limitations are applied
-// at mid-level API. In the future high-level API would also include the
-// means of limiting / throttling memory / CPU usage.
-void SetDecoderMemoryLimitBase_(size_t memory_limit_base) {
- memory_limit_base_ = memory_limit_base;
- // Allow 5 x max_image_size processing units; every frame is accounted
- // as W x H CPU processing units, so there could be numerous small frames
- // or few larger ones.
- cpu_limit_base_ = 5 * memory_limit_base;
+JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec,
+ uint64_t* size) {
+ if (!dec->box_event) {
+ return JXL_API_ERROR("can only get box info after JXL_DEC_BOX event");
+ }
+ if (size) {
+ *size = dec->box_size;
+ }
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderSetProgressiveDetail(JxlDecoder* dec,
+ JxlProgressiveDetail detail) {
+ if (detail != kDC && detail != kLastPasses && detail != kPasses) {
+ return JXL_API_ERROR(
+ "Values other than kDC (%d), kLastPasses (%d) and kPasses (%d), "
+ "like %d are not implemented.",
+ kDC, kLastPasses, kPasses, detail);
+ }
+ dec->prog_detail = detail;
+ return JXL_DEC_SUCCESS;
}