// 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.
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet)
¤
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.