// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
// If we support seeking, output_position_ == position_. if (external_output_processor_ && external_output_processor_->seek) {
JXL_ENSURE(output_position_ == position_);
} // Otherwise, output_position_ <= position_.
JXL_ENSURE(output_position_ <= position_);
size_t additional_size = position_ - output_position_;
JXL_ENSURE(memory_manager_ != nullptr);
if (external_output_processor_) { // TODO(veluca): here, we cannot just ask for a larger buffer, as it will be // released with a prefix of the buffer that has not been written yet. // Figure out if there is a good way to do this more efficiently. if (additional_size == 0) {
size_t size = requested_size;
uint8_t* user_buffer = static_cast<uint8_t*>(external_output_processor_->get_buffer(
external_output_processor_->opaque, &size)); if (size == 0 || user_buffer == nullptr) {
stop_requested_ = true; return jxl::StatusCode::kNotEnoughBytes;
} if (size < min_size) {
external_output_processor_->release_buffer(
external_output_processor_->opaque, 0);
} else {
internal_buffers_.emplace(position_, InternalBuffer(memory_manager_));
has_buffer_ = true; return JxlOutputProcessorBuffer(user_buffer, size, 0, this);
}
}
} else { if (min_size + additional_size < *avail_out_) {
internal_buffers_.emplace(position_, InternalBuffer(memory_manager_));
has_buffer_ = true; return JxlOutputProcessorBuffer(*next_out_ + additional_size,
*avail_out_ - additional_size, 0, this);
}
}
// Otherwise, we need to allocate our own buffer. auto it =
internal_buffers_.emplace(position_, InternalBuffer(memory_manager_))
.first;
InternalBuffer& buffer = it->second;
size_t alloc_size = requested_size;
it++; if (it != internal_buffers_.end()) {
alloc_size = std::min(alloc_size, it->first - position_);
JXL_ENSURE(alloc_size >= min_size);
}
JXL_RETURN_IF_ERROR(buffer.owned_data.resize(alloc_size));
has_buffer_ = true; return JxlOutputProcessorBuffer(buffer.owned_data.data(), alloc_size, 0, this);
}
jxl::Status JxlEncoderOutputProcessorWrapper::ReleaseBuffer(size_t bytes_used) {
JXL_ENSURE(has_buffer_);
has_buffer_ = false; auto it = internal_buffers_.find(position_);
JXL_ENSURE(it != internal_buffers_.end()); if (bytes_used == 0) { if (external_output_processor_) {
external_output_processor_->release_buffer(
external_output_processor_->opaque, bytes_used);
}
internal_buffers_.erase(it); returntrue;
}
it->second.written_bytes = bytes_used;
position_ += bytes_used;
auto it_to_next = it;
it_to_next++; if (it_to_next != internal_buffers_.end()) {
JXL_ENSURE(it_to_next->first >= position_);
}
if (external_output_processor_) { // If the buffer was given by the user, tell the user it is not needed // anymore. if (it->second.owned_data.empty()) {
external_output_processor_->release_buffer(
external_output_processor_->opaque, bytes_used); // If we don't support seeking, this implies we will never modify again // the bytes that were written so far. Advance the finalized position and // flush the output to clean up the internal buffers. if (!external_output_processor_->seek) {
JXL_RETURN_IF_ERROR(SetFinalizedPosition());
JXL_ENSURE(output_position_ == finalized_position_);
JXL_ENSURE(output_position_ == position_);
} else { // Otherwise, advance the output position accordingly.
output_position_ += bytes_used;
JXL_ENSURE(output_position_ >= finalized_position_);
JXL_ENSURE(output_position_ == position_);
}
} elseif (external_output_processor_->seek) { // If we had buffered the data internally, flush it out to the external // processor if we can.
external_output_processor_->seek(external_output_processor_->opaque,
position_ - bytes_used);
output_position_ = position_ - bytes_used; while (output_position_ < position_) {
size_t num_to_write = position_ - output_position_; if (!AppendBufferToExternalProcessor(it->second.owned_data.data() +
output_position_ - position_ +
bytes_used,
num_to_write)) { returntrue;
}
}
it->second.owned_data.clear();
}
} returntrue;
}
// Tries to write all the bytes up to the finalized position.
jxl::Status JxlEncoderOutputProcessorWrapper::FlushOutput() {
JXL_ENSURE(!has_buffer_); while (output_position_ < finalized_position_ &&
(avail_out_ == nullptr || *avail_out_ > 0)) {
JXL_ENSURE(!internal_buffers_.empty()); auto it = internal_buffers_.begin(); // If this fails, we are trying to move the finalized position past data // that was not written yet. This is a library programming error.
JXL_ENSURE(output_position_ >= it->first);
JXL_ENSURE(it->second.written_bytes != 0);
size_t buffer_last_byte = it->first + it->second.written_bytes; if (!it->second.owned_data.empty()) {
size_t start_in_buffer = output_position_ - it->first; // Guaranteed by the invariant on `internal_buffers_`.
JXL_ENSURE(buffer_last_byte > output_position_);
size_t num_to_write =
std::min(buffer_last_byte, finalized_position_) - output_position_; if (avail_out_ != nullptr) {
size_t n = std::min(num_to_write, *avail_out_);
memcpy(*next_out_, it->second.owned_data.data() + start_in_buffer, n);
*avail_out_ -= n;
*next_out_ += n;
output_position_ += n;
} else {
JXL_ENSURE(external_output_processor_); if (!AppendBufferToExternalProcessor(
it->second.owned_data.data() + start_in_buffer, num_to_write)) { returntrue;
}
}
} else {
size_t advance =
std::min(buffer_last_byte, finalized_position_) - output_position_;
output_position_ += advance; if (avail_out_ != nullptr) {
*next_out_ += advance;
*avail_out_ -= advance;
}
} if (buffer_last_byte == output_position_) {
internal_buffers_.erase(it);
} if (external_output_processor_ && !external_output_processor_->seek) {
external_output_processor_->set_finalized_position(
external_output_processor_->opaque, output_position_);
}
} returntrue;
}
// The JXL codestream can have level 5 or level 10. Levels have certain // restrictions such as max allowed image dimensions. This function checks the // level required to support the current encoder settings. The debug_string is // intended to be used for developer API error messages, and may be set to // nullptr. int VerifyLevelSettings(const JxlEncoder* enc, std::string* debug_string) { constauto& m = enc->metadata.m;
uint64_t xsize = enc->metadata.size.xsize();
uint64_t ysize = enc->metadata.size.ysize(); // The uncompressed ICC size, if it is used.
size_t icc_size = 0; if (m.color_encoding.WantICC()) {
icc_size = m.color_encoding.ICC().size();
}
// Level 10 checks
if (xsize > (1ull << 30ull) || ysize > (1ull << 30ull) ||
xsize * ysize > (1ull << 40ull)) { if (debug_string) *debug_string = "Too large image dimensions"; return -1;
} if (icc_size > (1ull << 28)) { if (debug_string) *debug_string = "Too large ICC profile size"; return -1;
} if (m.num_extra_channels > 256) { if (debug_string) *debug_string = "Too many extra channels"; return -1;
}
// Level 5 checks
if (!m.modular_16_bit_buffer_sufficient) { if (debug_string) *debug_string = "Too high modular bit depth"; return 10;
} if (xsize > (1ull << 18ull) || ysize > (1ull << 18ull) ||
xsize * ysize > (1ull << 28ull)) { if (debug_string) *debug_string = "Too large image dimensions"; return 10;
} if (icc_size > (1ull << 22)) { if (debug_string) *debug_string = "Too large ICC profile"; return 10;
} if (m.num_extra_channels > 4) { if (debug_string) *debug_string = "Too many extra channels"; return 10;
} for (constauto& eci : m.extra_channel_info) { if (eci.type == jxl::ExtraChannel::kBlack) { if (debug_string) *debug_string = "CMYK channel not allowed"; return 10;
}
}
// TODO(lode): also need to check if consecutive composite-still frames total // pixel amount doesn't exceed 2**28 in the case of level 5. This should be // done when adding frame and requires ability to add composite still frames // to be added first.
// TODO(lode): also need to check animation duration of a frame. This should // be done when adding frame, but first requires implementing setting the // JxlFrameHeader for a frame.
// TODO(lode): also need to check properties such as num_splines, num_patches, // modular_16bit_buffers and multiple properties of modular trees. However // these are not user-set properties so cannot be checked here, but decisions // the C++ encoder should be able to make based on the level.
// All level 5 checks passes, so can return the more compatible level 5 return 5;
}
JxlEncoderStatus CheckValidBitdepth(uint32_t bits_per_sample,
uint32_t exponent_bits_per_sample) { if (!exponent_bits_per_sample) { // The spec allows up to 31 for bits_per_sample here, but // the code does not (yet) support it. if (!(bits_per_sample > 0 && bits_per_sample <= 24)) { return JXL_API_ERROR_NOSET("Invalid value for bits_per_sample");
}
} elseif ((exponent_bits_per_sample > 8) ||
(bits_per_sample > 24 + exponent_bits_per_sample) ||
(bits_per_sample < 3 + exponent_bits_per_sample)) { return JXL_API_ERROR_NOSET( "Invalid float description: bits per sample = %u, exp bits = %u",
bits_per_sample, exponent_bits_per_sample);
} return JxlErrorOrStatus::Success();
}
inlinebool EncodeVarInt(uint64_t value, size_t output_size, size_t* output_pos,
uint8_t* output) { // While more than 7 bits of data are left, // store 7 bits and set the next byte flag while (value > 127) { // TODO(eustas): should it be `>=` ? if (*output_pos > output_size) returnfalse; // |128: Set the next byte flag
output[(*output_pos)++] = (static_cast<uint8_t>(value & 127)) | 128; // Remove the seven bits we just wrote
value >>= 7;
} // TODO(eustas): should it be `>=` ? if (*output_pos > output_size) returnfalse;
output[(*output_pos)++] = static_cast<uint8_t>(value & 127); returntrue;
}
bool EncodeFrameIndexBox(const jxl::JxlEncoderFrameIndexBox& frame_index_box,
std::vector<uint8_t>& buffer_vec) { bool ok = true; int NF = 0; for (size_t i = 0; i < frame_index_box.entries.size(); ++i) { if (i == 0 || frame_index_box.entries[i].to_be_indexed) {
++NF;
}
} // Frame index box contents varint + 8 bytes // continue with NF * 3 * varint // varint max length is 10 for 64 bit numbers, and these numbers // are limited to 63 bits. staticconstint kVarintMaxLength = 10; staticconstint kFrameIndexBoxHeaderLength = kVarintMaxLength + 8; staticconstint kFrameIndexBoxElementLength = 3 * kVarintMaxLength; constint buffer_size =
kFrameIndexBoxHeaderLength + NF * kFrameIndexBoxElementLength;
buffer_vec.resize(buffer_size);
uint8_t* buffer = buffer_vec.data();
size_t output_pos = 0;
ok &= EncodeVarInt(NF, buffer_vec.size(), &output_pos, buffer);
StoreBE32(frame_index_box.TNUM, &buffer[output_pos]);
output_pos += 4;
StoreBE32(frame_index_box.TDEN, &buffer[output_pos]);
output_pos += 4; // When we record a frame in the index, the record needs to know // how many frames until the next indexed frame. That is why // we store the 'prev' record. That 'prev' record needs to store // the offset byte position to previously recorded indexed frame, // that's why we also trace previous to the previous frame. int prev_prev_ix = -1; // For position offset (OFFi) delta coding. int prev_ix = 0; int T_prev = 0; int T = 0; for (size_t i = 1; i < frame_index_box.entries.size(); ++i) { if (frame_index_box.entries[i].to_be_indexed) { // Now we can record the previous entry, since we need to store // there how many frames until the next one.
int64_t OFFi = frame_index_box.entries[prev_ix].OFFi; if (prev_prev_ix != -1) { // Offi needs to be 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.
OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
}
int32_t Ti = T_prev;
int32_t Fi = i - prev_ix;
ok &= EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
ok &= EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
ok &= EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
prev_prev_ix = prev_ix;
prev_ix = i;
T_prev = T;
T += frame_index_box.entries[i].duration;
}
}
{ // Last frame.
size_t i = frame_index_box.entries.size();
int64_t OFFi = frame_index_box.entries[prev_ix].OFFi; if (prev_prev_ix != -1) {
OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
}
int32_t Ti = T_prev;
int32_t Fi = i - prev_ix;
ok &= EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
ok &= EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
ok &= EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
} if (ok) buffer_vec.resize(output_pos); return ok;
}
// TODO(lode): split this into 3 functions: for adding the signature and other // initial headers (jbrd, ...), one for adding frame, and one for adding user // box.
if (!wrote_bytes) { // First time encoding any data, verify the level 5 vs level 10 settings
std::string level_message; int required_level = VerifyLevelSettings(this, &level_message); // Only level 5 and 10 are defined, and the function can return -1 to // indicate full incompatibility.
JXL_ENSURE(required_level == -1 || required_level == 5 ||
required_level == 10); // codestream_level == -1 means auto-set to the required level if (codestream_level == -1) codestream_level = required_level; if (codestream_level == 5 && required_level != 5) { // If the required level is 10, return error rather than automatically // setting the level to 10, to avoid inadvertently creating a level 10 // JXL file while intending to target a level 5 decoder. return JXL_API_ERROR( this, JXL_ENC_ERR_API_USAGE, "%s",
("Codestream level verification for level 5 failed: " + level_message)
.c_str());
} if (required_level == -1) { return JXL_API_ERROR( this, JXL_ENC_ERR_API_USAGE, "%s",
("Codestream level verification for level 10 failed: " +
level_message)
.c_str());
}
jxl::AuxOut* aux_out =
input.frame ? input.frame->option_values.aux_out : nullptr;
jxl::BitWriter writer{&memory_manager}; if (!WriteCodestreamHeaders(&metadata, &writer, aux_out)) { return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC, "Failed to write codestream header");
} // Only send ICC (at least several hundred bytes) if fields aren't enough. if (metadata.m.color_encoding.WantICC()) { if (!jxl::WriteICC(
jxl::Span<const uint8_t>(metadata.m.color_encoding.ICC()),
&writer, jxl::LayerType::Header, aux_out)) { return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC, "Failed to write ICC profile");
}
} // TODO(lode): preview should be added here if a preview image is added
// Not actually the end of frame, but the end of metadata/ICC, but helps // the next frame to start here for indexing purposes.
codestream_bytes_written_end_of_frame += header_bytes.size();
if (MustUseContainer()) { // Add "JXL " and ftyp box.
{
JXL_ASSIGN_OR_RETURN(auto buffer, output_processor.GetBuffer(
jxl::kContainerHeader.size()));
JXL_RETURN_IF_ERROR(buffer.append(jxl::kContainerHeader));
} if (codestream_level != 5) { // Add jxll box directly after the ftyp box to indicate the codestream // level.
JXL_ASSIGN_OR_RETURN(auto buffer, output_processor.GetBuffer(
jxl::kLevelBoxHeader.size() + 1));
JXL_RETURN_IF_ERROR(buffer.append(jxl::kLevelBoxHeader));
uint8_t cl = codestream_level;
JXL_RETURN_IF_ERROR(buffer.append(&cl, 1));
}
// Whether to write the basic info and color profile header of the // codestream into an early separate jxlp box, so that it comes before // metadata or jpeg reconstruction boxes. In theory this could simply // always be done, but there's no reason to add an extra box with box // header overhead if the codestream will already come immediately after // the signature and level boxes. bool partial_header =
store_jpeg_metadata ||
(use_boxes && (!input.frame && !input.fast_lossless_frame));
// Choose frame or box processing: exactly one of the two unique pointers (box // or frame) in the input queue item is non-null. if (input.frame || input.fast_lossless_frame) {
jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedFrame> input_frame =
std::move(input.frame);
jxl::FJXLFrameUniquePtr fast_lossless_frame =
std::move(input.fast_lossless_frame);
input_queue.erase(input_queue.begin());
num_queued_frames--; if (input_frame) { for (unsigned idx = 0; idx < input_frame->ec_initialized.size(); idx++) { if (!input_frame->ec_initialized[idx]) { return JXL_API_ERROR(this, JXL_ENC_ERR_API_USAGE, "Extra channel %u is not initialized", idx);
}
}
// TODO(zond): If the input queue is empty and the frames_closed is true, // then mark this frame as the last.
// TODO(zond): Handle progressive mode like EncodeFile does it. // TODO(zond): Handle animation like EncodeFile does it, by checking if // JxlEncoderCloseFrames has been called and if the frame // queue is empty (to see if it's the last animation frame).
if (metadata.m.xyb_encoded) {
input_frame->option_values.cparams.color_transform =
jxl::ColorTransform::kXYB;
} else { // TODO(zond): Figure out when to use kYCbCr instead.
input_frame->option_values.cparams.color_transform =
jxl::ColorTransform::kNone;
}
}
uint32_t duration;
uint32_t timecode; if (input_frame && metadata.m.have_animation) {
duration = input_frame->option_values.header.duration;
timecode = input_frame->option_values.header.timecode;
} else { // If have_animation is false, the encoder should ignore the duration and // timecode values. However, assigning them to ib will cause the encoder // to write an invalid frame header that can't be decoded so ensure // they're the default value of 0 here.
duration = 0;
timecode = 0;
}
uint32_t max_bits_per_sample = metadata.m.bit_depth.bits_per_sample; for (constauto& info : metadata.m.extra_channel_info) {
max_bits_per_sample =
std::max(max_bits_per_sample, info.bit_depth.bits_per_sample);
} // Heuristic upper bound on how many bits a single pixel in a single channel // can use.
uint32_t bits_per_channels_estimate =
std::max(24u, max_bits_per_sample + 3);
size_t upper_bound_on_compressed_size_bits =
metadata.xsize() * metadata.ysize() *
(metadata.m.color_encoding.Channels() + metadata.m.num_extra_channels) *
bits_per_channels_estimate; // Add a 1MB = 0x100000 for an heuristic upper bound on small sizes.
size_t upper_bound_on_compressed_size_bytes =
0x100000 + (upper_bound_on_compressed_size_bits >> 3); bool use_large_box = upper_bound_on_compressed_size_bytes >=
jxl::kLargeBoxContentSizeThreshold;
size_t box_header_size =
use_large_box ? jxl::kLargeBoxHeaderSize : jxl::kSmallBoxHeaderSize;
const size_t frame_start_pos = output_processor.CurrentPosition(); if (MustUseContainer()) { if (!last_frame || jxlp_counter > 0) { // If this is the last frame and no jxlp boxes were used yet, it's // slightly more efficient to write a jxlc box since it has 4 bytes // less overhead.
box_header_size += 4; // jxlp_counter field
}
JXL_RETURN_IF_ERROR(
output_processor.Seek(frame_start_pos + box_header_size));
} const size_t frame_codestream_start = output_processor.CurrentPosition();
if (MustUseContainer()) {
JXL_RETURN_IF_ERROR(output_processor.Seek(frame_start_pos));
std::vector<uint8_t> box_header(box_header_size); if (!use_large_box &&
frame_codestream_size >= jxl::kLargeBoxContentSizeThreshold) { // Assuming our upper bound estimate is correct, this should never // happen. return JXL_API_ERROR( this, JXL_ENC_ERR_GENERIC, "Box size was estimated to be small, but turned out to be large. " "Please file this error in size estimation as a bug.");
} if (last_frame && jxlp_counter == 0) {
size_t n = jxl::WriteBoxHeader(
jxl::MakeBoxType("jxlc"), frame_codestream_size, /*unbounded=*/false, use_large_box, box_header.data());
JXL_ENSURE(n == box_header_size);
} else {
size_t n = jxl::WriteBoxHeader(
jxl::MakeBoxType("jxlp"), frame_codestream_size + 4, /*unbounded=*/false, use_large_box, box_header.data());
JXL_ENSURE(n == box_header_size - 4);
WriteJxlpBoxCounter(jxlp_counter++, last_frame,
&box_header[box_header_size - 4]);
}
JXL_RETURN_IF_ERROR(AppendData(output_processor, box_header));
JXL_ENSURE(output_processor.CurrentPosition() == frame_codestream_start);
JXL_RETURN_IF_ERROR(output_processor.Seek(frame_codestream_end));
}
JXL_RETURN_IF_ERROR(output_processor.SetFinalizedPosition()); if (input_frame) {
last_used_cparams = input_frame->option_values.cparams;
} if (last_frame && frame_index_box.StoreFrameIndexBox()) {
std::vector<uint8_t> index_box_content; // Enough buffer has been allocated, this function should never fail in // writing.
JXL_ENSURE(EncodeFrameIndexBox(frame_index_box, index_box_content));
JXL_RETURN_IF_ERROR(AppendBoxWithContents(jxl::MakeBoxType("jxli"),
jxl::Bytes(index_box_content)));
}
} else { // Not a frame, so is a box instead
jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedBox> box =
std::move(input.box);
input_queue.erase(input_queue.begin());
num_queued_boxes--;
if (box->compress_box) {
jxl::PaddedBytes compressed(&memory_manager); // Prepend the original box type in the brob box contents
JXL_RETURN_IF_ERROR(compressed.append(box->type)); if (JXL_ENC_SUCCESS !=
BrotliCompress((brotli_effort >= 0 ? brotli_effort : 4),
box->contents.data(), box->contents.size(),
&compressed)) { return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC, "Brotli compression for brob box failed");
}
void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header) { // For each field, the default value of the specification is used. Depending // on whether an animation frame, or a composite still blending frame, // is used, different fields have to be set up by the user after initing // the frame header.
frame_header->duration = 0;
frame_header->timecode = 0;
frame_header->name_length = 0; // In the specification, the default value of is_last is !frame_type, and the // default frame_type is kRegularFrame which has value 0, so is_last is true // by default. However, the encoder does not use this value (the field exists // for the decoder to set) since last frame is determined by usage of // JxlEncoderCloseFrames instead.
frame_header->is_last = JXL_TRUE;
frame_header->layer_info.have_crop = JXL_FALSE;
frame_header->layer_info.crop_x0 = 0;
frame_header->layer_info.crop_y0 = 0; // These must be set if have_crop is enabled, but the default value has // have_crop false, and these dimensions 0. The user must set these to the // desired size after enabling have_crop (which is not yet implemented).
frame_header->layer_info.xsize = 0;
frame_header->layer_info.ysize = 0;
JxlEncoderInitBlendInfo(&frame_header->layer_info.blend_info);
frame_header->layer_info.save_as_reference = 0;
}
void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info) { // Default blend mode in the specification is 0. Note that combining // blend mode of replace with a duration is not useful, but the user has to // manually set duration in case of animation, or manually change the blend // mode in case of composite stills, so initing to a combination that is not // useful on its own is not an issue.
blend_info->blendmode = JXL_BLEND_REPLACE;
blend_info->source = 0;
blend_info->alpha = 0;
blend_info->clamp = 0;
}
JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc, const JxlBasicInfo* info) { if (!enc->metadata.size.Set(info->xsize, info->ysize)) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid dimensions");
} if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
info->exponent_bits_per_sample)) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
}
// The number of extra channels includes the alpha channel, so for example and // RGBA with no other extra channels, has exactly num_extra_channels == 1
enc->metadata.m.num_extra_channels = info->num_extra_channels;
enc->metadata.m.extra_channel_info.resize(enc->metadata.m.num_extra_channels); if (info->num_extra_channels == 0 && info->alpha_bits) { return JXL_API_ERROR(
enc, JXL_ENC_ERR_API_USAGE, "when alpha_bits is non-zero, the number of channels must be at least " "1");
} // If the user provides non-zero alpha_bits, we make the channel info at index // zero the appropriate alpha channel. if (info->alpha_bits) {
JxlExtraChannelInfo channel_info;
JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &channel_info);
channel_info.bits_per_sample = info->alpha_bits;
channel_info.exponent_bits_per_sample = info->alpha_exponent_bits; if (JxlEncoderSetExtraChannelInfo(enc, 0, &channel_info)) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Problem setting extra channel info for alpha");
}
}
enc->metadata.m.xyb_encoded = !FROM_JXL_BOOL(info->uses_original_profile); if (info->orientation > 0 && info->orientation <= 8) {
enc->metadata.m.orientation = info->orientation;
} else { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid value for orientation field");
} if (info->num_color_channels != 1 && info->num_color_channels != 3) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid number of color channels");
} if (info->intensity_target != 0) {
enc->metadata.m.SetIntensityTarget(info->intensity_target);
enc->intensity_target_set = true;
} elseif (enc->color_encoding_set) { // If this is false, JxlEncoderSetColorEncoding will be called later and we // will get one more chance to call jxl::SetIntensityTarget, after the color // encoding is indeed set.
jxl::SetIntensityTarget(&enc->metadata.m);
enc->intensity_target_set = true;
}
enc->metadata.m.tone_mapping.min_nits = info->min_nits;
enc->metadata.m.tone_mapping.relative_to_max_display =
FROM_JXL_BOOL(info->relative_to_max_display);
enc->metadata.m.tone_mapping.linear_below = info->linear_below;
enc->basic_info = *info;
enc->basic_info_set = true;
enc->metadata.m.have_animation = FROM_JXL_BOOL(info->have_animation); if (info->have_animation) { if (info->animation.tps_denominator < 1) { return JXL_API_ERROR(
enc, JXL_ENC_ERR_API_USAGE, "If animation is used, tps_denominator must be >= 1");
} if (info->animation.tps_numerator < 1) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "If animation is used, tps_numerator must be >= 1");
}
enc->metadata.m.animation.tps_numerator = info->animation.tps_numerator;
enc->metadata.m.animation.tps_denominator = info->animation.tps_denominator;
enc->metadata.m.animation.num_loops = info->animation.num_loops;
enc->metadata.m.animation.have_timecodes =
FROM_JXL_BOOL(info->animation.have_timecodes);
}
std::string level_message; int required_level = VerifyLevelSettings(enc, &level_message); if (required_level == -1 ||
(static_cast<int>(enc->codestream_level) < required_level &&
enc->codestream_level != -1)) { return JXL_API_ERROR(
enc, JXL_ENC_ERR_API_USAGE, "%s",
("Codestream level verification for level " +
std::to_string(enc->codestream_level) + " failed: " + level_message)
.c_str());
} return JxlErrorOrStatus::Success();
}
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info) { if (index >= enc->metadata.m.num_extra_channels) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid value for the index of extra channel");
} if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
info->exponent_bits_per_sample)) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
}
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
size_t index, constchar* name,
size_t size) { if (index >= enc->metadata.m.num_extra_channels) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid value for the index of extra channel");
}
enc->metadata.m.extra_channel_info[index].name =
std::string(name, name + size); return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderSetFrameDistance(
JxlEncoderFrameSettings* frame_settings, float distance) { if (distance < 0.f || distance > 25.f) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Distance has to be in [0.0..25.0] (corresponding to " "quality in [0.0..100.0])");
} if (distance > 0.f && distance < 0.01f) {
distance = 0.01f;
}
frame_settings->values.cparams.butteraugli_distance = distance; return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderSetExtraChannelDistance(
JxlEncoderFrameSettings* frame_settings, size_t index, float distance) { if (index >= frame_settings->enc->metadata.m.num_extra_channels) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Invalid value for the index of extra channel");
} if (distance != -1.f && (distance < 0.f || distance > 25.f)) { return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Distance has to be -1 or in [0.0..25.0] (corresponding to " "quality in [0.0..100.0])");
} if (distance > 0.f && distance < 0.01f) {
distance = 0.01f;
}
if (index >= frame_settings->values.cparams.ec_distance.size()) { // This can only happen if JxlEncoderFrameSettingsCreate() was called before // JxlEncoderSetBasicInfo().
frame_settings->values.cparams.ec_distance.resize(
frame_settings->enc->metadata.m.num_extra_channels, 0);
}
// check if value is -1, 0 or 1 for Override-type options switch (option) { case JXL_ENC_FRAME_SETTING_NOISE: case JXL_ENC_FRAME_SETTING_DOTS: case JXL_ENC_FRAME_SETTING_PATCHES: case JXL_ENC_FRAME_SETTING_GABORISH: case JXL_ENC_FRAME_SETTING_MODULAR: case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE: case JXL_ENC_FRAME_SETTING_GROUP_ORDER: case JXL_ENC_FRAME_SETTING_RESPONSIVE: case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC: case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC: case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE: case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL: case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES: case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF: case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP: case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF: if (value < -1 || value > 1) { return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be -1 (default), 0 (off) or 1 (on)");
} break; default: break;
}
switch (option) { case JXL_ENC_FRAME_SETTING_EFFORT: if (frame_settings->enc->allow_expert_options) { if (value < 1 || value > 11) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Encode effort has to be in [1..11]");
}
} else { if (value < 1 || value > 10) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Encode effort has to be in [1..10]");
}
}
frame_settings->values.cparams.speed_tier = static_cast<jxl::SpeedTier>(10 - value); break; case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT: if (value < -1 || value > 11) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Brotli effort has to be in [-1..11]");
} // set cparams for brotli use in JPEG frames
frame_settings->values.cparams.brotli_effort = value; // set enc option for brotli use in brob boxes
frame_settings->enc->brotli_effort = value; break; case JXL_ENC_FRAME_SETTING_DECODING_SPEED: if (value < 0 || value > 4) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Decoding speed has to be in [0..4]");
}
frame_settings->values.cparams.decoding_speed_tier = value; break; case JXL_ENC_FRAME_SETTING_RESAMPLING: if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Resampling factor has to be 1, 2, 4 or 8");
}
frame_settings->values.cparams.resampling = value; break; case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING: // TODO(lode): the jxl codestream allows choosing a different resampling // factor for each extra channel, independently per frame. Move this // option to a JxlEncoderFrameSettings-option that can be set per extra // channel, so needs its own function rather than // JxlEncoderFrameSettingsSetOption due to the extra channel index // argument required. if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Resampling factor has to be 1, 2, 4 or 8");
}
frame_settings->values.cparams.ec_resampling = value; break; case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED: if (value < 0 || value > 1) { return JxlErrorOrStatus::Error();
}
frame_settings->values.cparams.already_downsampled = (value == 1); break; case JXL_ENC_FRAME_SETTING_NOISE:
frame_settings->values.cparams.noise = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_DOTS:
frame_settings->values.cparams.dots = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_PATCHES:
frame_settings->values.cparams.patches = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_EPF: if (value < -1 || value > 3) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "EPF value has to be in [-1..3]");
}
frame_settings->values.cparams.epf = static_cast<int>(value); break; case JXL_ENC_FRAME_SETTING_GABORISH:
frame_settings->values.cparams.gaborish = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_MODULAR:
frame_settings->values.cparams.modular_mode = (value == 1); break; case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
frame_settings->values.cparams.keep_invisible = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
frame_settings->values.cparams.centerfirst = (value == 1); break; case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X: if (value < -1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Center x coordinate has to be -1 or positive");
}
frame_settings->values.cparams.center_x = static_cast<size_t>(value); break; case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y: if (value < -1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Center y coordinate has to be -1 or positive");
}
frame_settings->values.cparams.center_y = static_cast<size_t>(value); break; case JXL_ENC_FRAME_SETTING_RESPONSIVE:
frame_settings->values.cparams.responsive = value; break; case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
frame_settings->values.cparams.progressive_mode = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
frame_settings->values.cparams.qprogressive_mode = static_cast<jxl::Override>(value); break; case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC: if (value < -1 || value > 2) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Progressive DC has to be in [-1..2]");
}
frame_settings->values.cparams.progressive_dc = value; break; case JXL_ENC_FRAME_SETTING_PALETTE_COLORS: if (value < -1 || value > 70913) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..70913]");
} if (value == -1) {
frame_settings->values.cparams.palette_colors = 1 << 10;
} else {
frame_settings->values.cparams.palette_colors = value;
} break; case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE: // TODO(lode): the defaults of some palette settings depend on others. // See the logic in cjxl. Similar for other settings. This should be // handled in the encoder during JxlEncoderProcessOutput (or, // alternatively, in the cjxl binary like now)
frame_settings->values.cparams.lossy_palette = default_to_false(value); break; case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM: if (value < -1 || value > 2) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..2]");
} if (value == -1) {
frame_settings->values.cparams.color_transform =
jxl::ColorTransform::kXYB;
} else {
frame_settings->values.cparams.color_transform = static_cast<jxl::ColorTransform>(value);
} break; case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE: if (value < -1 || value > 41) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..41]");
}
frame_settings->values.cparams.colorspace = value; break; case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE: if (value < -1 || value > 3) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..3]");
}
frame_settings->values.cparams.modular_group_size_shift = value; break; case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR: if (value < -1 || value > 15) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..15]");
}
frame_settings->values.cparams.options.predictor = static_cast<jxl::Predictor>(value); break; case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS: // The max allowed value can in theory be higher. However, it depends on // the effort setting. 11 is the highest safe value that doesn't cause // tree_samples to be >= 64 in the encoder. The specification may allow // more than this. With more fine tuning higher values could be allowed. // For N-channel images, the largest useful value is N-1. if (value < -1 || value > 11) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..11]");
} if (value == -1) {
frame_settings->values.cparams.options.max_properties = 0;
} else {
frame_settings->values.cparams.options.max_properties = value;
} break; case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
frame_settings->values.cparams.force_cfl_jpeg_recompression =
default_to_true(value); break; case JXL_ENC_FRAME_INDEX_BOX: if (value < 0 || value > 1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Option value has to be 0 or 1");
}
frame_settings->values.frame_index_box = true; break; case JXL_ENC_FRAME_SETTING_PHOTON_NOISE: return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Float option, try setting it with " "JxlEncoderFrameSettingsSetFloatOption"); case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
frame_settings->values.cparams.jpeg_compress_boxes =
default_to_true(value); break; case JXL_ENC_FRAME_SETTING_BUFFERING: if (value < -1 || value > 3) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Buffering has to be in [-1..3]");
}
frame_settings->values.cparams.buffering = value; break; case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
frame_settings->values.cparams.jpeg_keep_exif = default_to_true(value); break; case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
frame_settings->values.cparams.jpeg_keep_xmp = default_to_true(value); break; case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
frame_settings->values.cparams.jpeg_keep_jumbf = default_to_true(value); break; case JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS: if (value < 0 || value > 1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Option value has to be 0 or 1");
}
frame_settings->values.cparams.use_full_image_heuristics =
default_to_false(value); break; case JXL_ENC_FRAME_SETTING_DISABLE_PERCEPTUAL_HEURISTICS: if (value < 0 || value > 1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Option value has to be 0 or 1");
}
frame_settings->values.cparams.disable_perceptual_optimizations =
default_to_false(value); if (frame_settings->values.cparams.disable_perceptual_optimizations &&
frame_settings->enc->basic_info_set &&
frame_settings->enc->metadata.m.xyb_encoded) { return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Set uses_original_profile=true for non-perceptual encoding");
} break;
JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption(
JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option, float value) { switch (option) { case JXL_ENC_FRAME_SETTING_PHOTON_NOISE: if (value < 0) return JXL_ENC_ERROR; // TODO(lode): add encoder setting to set the 8 floating point values of // the noise synthesis parameters per frame for more fine grained control.
frame_settings->values.cparams.photon_noise_iso = value; return JxlErrorOrStatus::Success(); case JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT: if (value < -1.f || value > 100.f) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be smaller than 100");
} // This value is called "iterations" or "nb_repeats" in cjxl, but is in // fact a fraction in range 0.0-1.0, with the default value 0.5. // Convert from floating point percentage to floating point fraction here. if (value < -.5f) { // TODO(lode): for this and many other settings (also in // JxlEncoderFrameSettingsSetOption), avoid duplicating the default // values here and in enc_params.h and options.h, have one location // where the defaults are specified.
frame_settings->values.cparams.options.nb_repeats = 0.5f;
} else {
frame_settings->values.cparams.options.nb_repeats = value * 0.01f;
} return JxlErrorOrStatus::Success(); case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT: if (value < -1.f || value > 100.f) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..100]");
} if (value < -.5f) {
frame_settings->values.cparams.channel_colors_pre_transform_percent =
95.0f;
} else {
frame_settings->values.cparams.channel_colors_pre_transform_percent =
value;
} return JxlErrorOrStatus::Success(); case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT: if (value < -1.f || value > 100.f) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Option value has to be in [-1..100]");
} if (value < -.5f) {
frame_settings->values.cparams.channel_colors_percent = 80.0f;
} else {
frame_settings->values.cparams.channel_colors_percent = value;
} return JxlErrorOrStatus::Success(); case JXL_ENC_FRAME_SETTING_EFFORT: case JXL_ENC_FRAME_SETTING_DECODING_SPEED: case JXL_ENC_FRAME_SETTING_RESAMPLING: case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING: case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED: case JXL_ENC_FRAME_SETTING_NOISE: case JXL_ENC_FRAME_SETTING_DOTS: case JXL_ENC_FRAME_SETTING_PATCHES: case JXL_ENC_FRAME_SETTING_EPF: case JXL_ENC_FRAME_SETTING_GABORISH: case JXL_ENC_FRAME_SETTING_MODULAR: case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE: case JXL_ENC_FRAME_SETTING_GROUP_ORDER: case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X: case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y: case JXL_ENC_FRAME_SETTING_RESPONSIVE: case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC: case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC: case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC: case JXL_ENC_FRAME_SETTING_PALETTE_COLORS: case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE: case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM: case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE: case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE: case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR: case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS: case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL: case JXL_ENC_FRAME_INDEX_BOX: case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT: case JXL_ENC_FRAME_SETTING_FILL_ENUM: case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES: case JXL_ENC_FRAME_SETTING_BUFFERING: case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF: case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP: case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF: case JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS: return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Int option, try setting it with " "JxlEncoderFrameSettingsSetOption"); default: return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED, "Unknown option");
}
}
JxlEncoder* JxlEncoderCreate(const JxlMemoryManager* memory_manager) {
JxlMemoryManager local_memory_manager; if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager)) { return nullptr;
}
void* alloc =
jxl::MemoryManagerAlloc(&local_memory_manager, sizeof(JxlEncoder)); if (!alloc) return nullptr;
JxlEncoder* enc = new (alloc) JxlEncoder();
enc->memory_manager = local_memory_manager; // TODO(sboukortt): add an API function to set this.
enc->cms = *JxlGetDefaultCms();
enc->cms_set = true;
// Initialize all the field values.
JxlEncoderReset(enc);
JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc,
JXL_BOOL use_container) { if (enc->wrote_bytes) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "this setting can only be set at the beginning");
}
enc->use_container = FROM_JXL_BOOL(use_container); return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc,
JXL_BOOL store_jpeg_metadata) { if (enc->wrote_bytes) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "this setting can only be set at the beginning");
}
enc->store_jpeg_metadata = FROM_JXL_BOOL(store_jpeg_metadata); return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level) { if (level != -1 && level != 5 && level != 10) { return JXL_API_ERROR(enc, JXL_ENC_ERR_NOT_SUPPORTED, "invalid level");
} if (enc->wrote_bytes) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "this setting can only be set at the beginning");
}
enc->codestream_level = level; return JxlErrorOrStatus::Success();
}
int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc) { return VerifyLevelSettings(enc, nullptr);
}
auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
&frame_settings->enc->memory_manager, // JxlEncoderQueuedFrame is a struct with no constructors, so we use the // default move constructor there.
jxl::JxlEncoderQueuedFrame{
frame_settings->values, std::move(frame_data), {}}); if (!queued_frame) { // TODO(jon): when can this happen? is this an API usage error? return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, "No frame queued?");
}
queued_frame->ec_initialized.resize(
frame_settings->enc->metadata.m.num_extra_channels);
if (!frame_settings->enc->basic_info_set) { // Basic Info must be set. Otherwise, this is an API misuse. return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Basic info or color encoding not set yet");
} if (frame_settings->enc->frames_closed) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Frame input already closed");
} if (num_channels < 3) { if (frame_settings->enc->basic_info.num_color_channels != 1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Grayscale pixel format input for an RGB image");
}
} else { if (frame_settings->enc->basic_info.num_color_channels != 3) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "RGB pixel format input for a grayscale image");
}
} if (frame_settings->values.lossless &&
frame_settings->enc->metadata.m.xyb_encoded) { return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Set uses_original_profile=true for lossless encoding");
} if (frame_settings->values.cparams.disable_perceptual_optimizations &&
frame_settings->enc->metadata.m.xyb_encoded) { return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Set uses_original_profile=true for non-perceptual encoding");
} if (JXL_ENC_SUCCESS !=
VerifyInputBitDepth(frame_settings->values.image_bit_depth,
pixel_format)) { return JXL_API_ERROR_NOSET("Invalid input bit depth");
} if (has_interleaved_alpha >
frame_settings->enc->metadata.m.num_extra_channels) { return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE, "number of extra channels mismatch (need 1 extra channel for alpha)");
}
if (!streaming) { // The input callbacks are only guaranteed to be available during frame // encoding when both the input and the output is streaming. In all other // cases we need to create an internal copy of the frame data. if (!frame_data.CopyBuffers()) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Invalid chunked frame input source");
}
}
auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
&frame_settings->enc->memory_manager, // JxlEncoderQueuedFrame is a struct with no constructors, so we use the // default move constructor there.
jxl::JxlEncoderQueuedFrame{
frame_settings->values, std::move(frame_data), {}});
if (!queued_frame) { // TODO(jon): when can this happen? is this an API usage error? return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, "No frame queued?");
}
for (auto& ec_info : frame_settings->enc->metadata.m.extra_channel_info) { if (has_interleaved_alpha && ec_info.type == jxl::ExtraChannel::kAlpha) {
queued_frame->ec_initialized.push_back(1);
has_interleaved_alpha = 0; // only first Alpha is initialized
} else {
queued_frame->ec_initialized.push_back(0);
}
}
queued_frame->option_values.cparams.level =
frame_settings->enc->codestream_level;
auto& queued_frame = frame_settings->enc->input_queue.back(); if (queued_frame.frame) { for (auto& val : queued_frame.frame->ec_initialized) val = 1;
}
if (is_last_frame) {
JxlEncoderCloseInput(frame_settings->enc);
} if (streaming) { return JxlEncoderFlushInput(frame_settings->enc);
} return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc) { if (enc->wrote_bytes) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "this setting can only be set at the beginning");
}
enc->use_boxes = true; return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, const JxlBoxType type, const uint8_t* contents, size_t size,
JXL_BOOL compress_box) { if (!enc->use_boxes) { return JXL_API_ERROR(
enc, JXL_ENC_ERR_API_USAGE, "must set JxlEncoderUseBoxes at the beginning to add boxes");
} if (enc->boxes_closed) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Box input already closed");
} if (compress_box) { if (memcmp("jxl", type, 3) == 0) { return JXL_API_ERROR(
enc, JXL_ENC_ERR_API_USAGE, "brob box may not contain a type starting with \"jxl\"");
} if (memcmp("jbrd", type, 4) == 0) { return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "jbrd box may not be brob compressed");
} if (memcmp("brob", type, 4) == 0) { // The compress_box will compress an existing non-brob box into a brob // box. If already giving a valid brotli-compressed brob box, set // compress_box to false since it is already compressed. return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "a brob box cannot contain another brob box");
}
}
auto box = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedBox>(
&enc->memory_manager);
JxlEncoderStatus JxlEncoderSetFrameHeader(
JxlEncoderFrameSettings* frame_settings, const JxlFrameHeader* frame_header) { if (frame_header->layer_info.blend_info.source > 3) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "invalid blending source index");
} // If there are no extra channels, it's ok for the value to be 0. if (frame_header->layer_info.blend_info.alpha != 0 &&
frame_header->layer_info.blend_info.alpha >=
frame_settings->enc->metadata.m.extra_channel_info.size()) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "alpha blend channel index out of bounds");
}
frame_settings->values.header = *frame_header; // Setting the frame header resets the frame name, it must be set again with // JxlEncoderSetFrameName if desired.
frame_settings->values.frame_name = "";
return JxlErrorOrStatus::Success();
}
JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo(
JxlEncoderFrameSettings* frame_settings, size_t index, const JxlBlendInfo* blend_info) { if (index >= frame_settings->enc->metadata.m.num_extra_channels) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Invalid value for the index of extra channel");
}
JXL_EXPORT size_t JxlEncoderStatsGet(const JxlEncoderStats* stats,
JxlEncoderStatsKey key) { if (!stats) return 0; const jxl::AuxOut& aux_out = *stats->aux_out; switch (key) { case JXL_ENC_STAT_HEADER_BITS: return aux_out.layer(jxl::LayerType::Header).total_bits; case JXL_ENC_STAT_TOC_BITS: return aux_out.layer(jxl::LayerType::Toc).total_bits; case JXL_ENC_STAT_DICTIONARY_BITS: return aux_out.layer(jxl::LayerType::Dictionary).total_bits; case JXL_ENC_STAT_SPLINES_BITS: return aux_out.layer(jxl::LayerType::Splines).total_bits; case JXL_ENC_STAT_NOISE_BITS: return aux_out.layer(jxl::LayerType::Noise).total_bits; case JXL_ENC_STAT_QUANT_BITS: return aux_out.layer(jxl::LayerType::Quant).total_bits; case JXL_ENC_STAT_MODULAR_TREE_BITS: return aux_out.layer(jxl::LayerType::ModularTree).total_bits; case JXL_ENC_STAT_MODULAR_GLOBAL_BITS: return aux_out.layer(jxl::LayerType::ModularGlobal).total_bits; case JXL_ENC_STAT_DC_BITS: return aux_out.layer(jxl::LayerType::Dc).total_bits; case JXL_ENC_STAT_MODULAR_DC_GROUP_BITS: return aux_out.layer(jxl::LayerType::ModularDcGroup).total_bits; case JXL_ENC_STAT_CONTROL_FIELDS_BITS: return aux_out.layer(jxl::LayerType::ControlFields).total_bits; case JXL_ENC_STAT_COEF_ORDER_BITS: return aux_out.layer(jxl::LayerType::Order).total_bits; case JXL_ENC_STAT_AC_HISTOGRAM_BITS: return aux_out.layer(jxl::LayerType::Ac).total_bits; case JXL_ENC_STAT_AC_BITS: return aux_out.layer(jxl::LayerType::AcTokens).total_bits; case JXL_ENC_STAT_MODULAR_AC_GROUP_BITS: return aux_out.layer(jxl::LayerType::ModularAcGroup).total_bits; case JXL_ENC_STAT_NUM_SMALL_BLOCKS: return aux_out.num_small_blocks; case JXL_ENC_STAT_NUM_DCT4X8_BLOCKS: return aux_out.num_dct4x8_blocks; case JXL_ENC_STAT_NUM_AFV_BLOCKS: return aux_out.num_afv_blocks; case JXL_ENC_STAT_NUM_DCT8_BLOCKS: return aux_out.num_dct8_blocks; case JXL_ENC_STAT_NUM_DCT8X32_BLOCKS: return aux_out.num_dct16_blocks; case JXL_ENC_STAT_NUM_DCT16_BLOCKS: return aux_out.num_dct16x32_blocks; case JXL_ENC_STAT_NUM_DCT16X32_BLOCKS: return aux_out.num_dct32_blocks; case JXL_ENC_STAT_NUM_DCT32_BLOCKS: return aux_out.num_dct32x64_blocks; case JXL_ENC_STAT_NUM_DCT32X64_BLOCKS: return aux_out.num_dct32x64_blocks; case JXL_ENC_STAT_NUM_DCT64_BLOCKS: return aux_out.num_dct64_blocks; case JXL_ENC_STAT_NUM_BUTTERAUGLI_ITERS: return aux_out.num_butteraugli_iters; default: return 0;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.