libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

encode.cc (109217B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 
      6 #include <brotli/encode.h>
      7 #include <jxl/cms.h>
      8 #include <jxl/codestream_header.h>
      9 #include <jxl/encode.h>
     10 #include <jxl/types.h>
     11 #include <jxl/version.h>
     12 
     13 #include <algorithm>
     14 #include <cstddef>
     15 #include <cstdint>
     16 #include <cstring>
     17 
     18 #include "lib/jxl/base/byte_order.h"
     19 #include "lib/jxl/base/common.h"
     20 #include "lib/jxl/base/data_parallel.h"
     21 #include "lib/jxl/base/exif.h"
     22 #include "lib/jxl/base/printf_macros.h"
     23 #include "lib/jxl/base/span.h"
     24 #include "lib/jxl/base/status.h"
     25 #include "lib/jxl/codec_in_out.h"
     26 #include "lib/jxl/enc_aux_out.h"
     27 #include "lib/jxl/enc_bit_writer.h"
     28 #include "lib/jxl/enc_cache.h"
     29 #include "lib/jxl/enc_external_image.h"
     30 #include "lib/jxl/enc_fast_lossless.h"
     31 #include "lib/jxl/enc_fields.h"
     32 #include "lib/jxl/enc_frame.h"
     33 #include "lib/jxl/enc_icc_codec.h"
     34 #include "lib/jxl/enc_params.h"
     35 #include "lib/jxl/encode_internal.h"
     36 #include "lib/jxl/jpeg/enc_jpeg_data.h"
     37 #include "lib/jxl/luminance.h"
     38 #include "lib/jxl/memory_manager_internal.h"
     39 #include "lib/jxl/padded_bytes.h"
     40 #include "lib/jxl/sanitizers.h"
     41 
     42 struct JxlErrorOrStatus {
     43   // NOLINTNEXTLINE(google-explicit-constructor)
     44   operator jxl::Status() const {
     45     switch (error_) {
     46       case JXL_ENC_SUCCESS:
     47         return jxl::OkStatus();
     48       case JXL_ENC_NEED_MORE_OUTPUT:
     49         return jxl::StatusCode::kNotEnoughBytes;
     50       default:
     51         return jxl::StatusCode::kGenericError;
     52     }
     53   }
     54   // NOLINTNEXTLINE(google-explicit-constructor)
     55   operator JxlEncoderStatus() const { return error_; }
     56 
     57   static JxlErrorOrStatus Success() {
     58     return JxlErrorOrStatus(JXL_ENC_SUCCESS);
     59   }
     60 
     61   static JxlErrorOrStatus MoreOutput() {
     62     return JxlErrorOrStatus(JXL_ENC_NEED_MORE_OUTPUT);
     63   }
     64 
     65   static JxlErrorOrStatus Error() { return JxlErrorOrStatus(JXL_ENC_ERROR); }
     66 
     67  private:
     68   explicit JxlErrorOrStatus(JxlEncoderStatus error) : error_(error) {}
     69   JxlEncoderStatus error_;
     70 };
     71 
     72 // Debug-printing failure macro similar to JXL_FAILURE, but for the status code
     73 // JXL_ENC_ERROR
     74 #ifdef JXL_CRASH_ON_ERROR
     75 #define JXL_API_ERROR(enc, error_code, format, ...)                          \
     76   (enc->error = error_code,                                                  \
     77    ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
     78    ::jxl::Abort(), JxlErrorOrStatus::Error())
     79 #define JXL_API_ERROR_NOSET(format, ...)                                     \
     80   (::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
     81    ::jxl::Abort(), JxlErrorOrStatus::Error())
     82 #else  // JXL_CRASH_ON_ERROR
     83 #define JXL_API_ERROR(enc, error_code, format, ...)                            \
     84   (enc->error = error_code,                                                    \
     85    ((JXL_DEBUG_ON_ERROR) &&                                                    \
     86     ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__)), \
     87    JxlErrorOrStatus::Error())
     88 #define JXL_API_ERROR_NOSET(format, ...)                                     \
     89   (::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
     90    JxlErrorOrStatus::Error())
     91 #endif  // JXL_CRASH_ON_ERROR
     92 
     93 jxl::StatusOr<JxlOutputProcessorBuffer>
     94 JxlEncoderOutputProcessorWrapper::GetBuffer(size_t min_size,
     95                                             size_t requested_size) {
     96   JXL_ASSERT(min_size > 0);
     97   JXL_ASSERT(!has_buffer_);
     98   if (stop_requested_) return jxl::StatusCode::kNotEnoughBytes;
     99   requested_size = std::max(min_size, requested_size);
    100 
    101   // If we support seeking, output_position_ == position_.
    102   if (external_output_processor_ && external_output_processor_->seek) {
    103     JXL_ASSERT(output_position_ == position_);
    104   }
    105   // Otherwise, output_position_ <= position_.
    106   JXL_ASSERT(output_position_ <= position_);
    107   size_t additional_size = position_ - output_position_;
    108 
    109   if (external_output_processor_) {
    110     // TODO(veluca): here, we cannot just ask for a larger buffer, as it will be
    111     // released with a prefix of the buffer that has not been written yet.
    112     // Figure out if there is a good way to do this more efficiently.
    113     if (additional_size == 0) {
    114       size_t size = requested_size;
    115       uint8_t* user_buffer =
    116           static_cast<uint8_t*>(external_output_processor_->get_buffer(
    117               external_output_processor_->opaque, &size));
    118       if (size == 0 || user_buffer == nullptr) {
    119         stop_requested_ = true;
    120         return jxl::StatusCode::kNotEnoughBytes;
    121       }
    122       if (size < min_size) {
    123         external_output_processor_->release_buffer(
    124             external_output_processor_->opaque, 0);
    125       } else {
    126         internal_buffers_.emplace(position_, InternalBuffer());
    127         has_buffer_ = true;
    128         return JxlOutputProcessorBuffer(user_buffer, size, 0, this);
    129       }
    130     }
    131   } else {
    132     if (min_size + additional_size < *avail_out_) {
    133       internal_buffers_.emplace(position_, InternalBuffer());
    134       has_buffer_ = true;
    135       return JxlOutputProcessorBuffer(*next_out_ + additional_size,
    136                                       *avail_out_ - additional_size, 0, this);
    137     }
    138   }
    139 
    140   // Otherwise, we need to allocate our own buffer.
    141   auto it = internal_buffers_.emplace(position_, InternalBuffer()).first;
    142   InternalBuffer& buffer = it->second;
    143   size_t alloc_size = requested_size;
    144   it++;
    145   if (it != internal_buffers_.end()) {
    146     alloc_size = std::min(alloc_size, it->first - position_);
    147     JXL_ASSERT(alloc_size >= min_size);
    148   }
    149   buffer.owned_data.resize(alloc_size);
    150   has_buffer_ = true;
    151   return JxlOutputProcessorBuffer(buffer.owned_data.data(), alloc_size, 0,
    152                                   this);
    153 }
    154 
    155 void JxlEncoderOutputProcessorWrapper::Seek(size_t pos) {
    156   JXL_ASSERT(!has_buffer_);
    157   if (external_output_processor_ && external_output_processor_->seek) {
    158     external_output_processor_->seek(external_output_processor_->opaque, pos);
    159     output_position_ = pos;
    160   }
    161   JXL_ASSERT(pos >= finalized_position_);
    162   position_ = pos;
    163 }
    164 
    165 void JxlEncoderOutputProcessorWrapper::SetFinalizedPosition() {
    166   JXL_ASSERT(!has_buffer_);
    167   if (external_output_processor_ && external_output_processor_->seek) {
    168     external_output_processor_->set_finalized_position(
    169         external_output_processor_->opaque, position_);
    170   }
    171   finalized_position_ = position_;
    172   FlushOutput();
    173 }
    174 
    175 bool JxlEncoderOutputProcessorWrapper::SetAvailOut(uint8_t** next_out,
    176                                                    size_t* avail_out) {
    177   if (external_output_processor_) return false;
    178   avail_out_ = avail_out;
    179   next_out_ = next_out;
    180   FlushOutput();
    181   return true;
    182 }
    183 
    184 void JxlEncoderOutputProcessorWrapper::CopyOutput(std::vector<uint8_t>& output,
    185                                                   uint8_t* next_out,
    186                                                   size_t& avail_out) {
    187   while (HasOutputToWrite()) {
    188     SetAvailOut(&next_out, &avail_out);
    189     if (avail_out == 0) {
    190       size_t offset = next_out - output.data();
    191       output.resize(output.size() * 2);
    192       next_out = output.data() + offset;
    193       avail_out = output.size() - offset;
    194     }
    195   }
    196   output.resize(output.size() - avail_out);
    197 }
    198 
    199 void JxlEncoderOutputProcessorWrapper::ReleaseBuffer(size_t bytes_used) {
    200   JXL_ASSERT(has_buffer_);
    201   has_buffer_ = false;
    202   auto it = internal_buffers_.find(position_);
    203   JXL_ASSERT(it != internal_buffers_.end());
    204   if (bytes_used == 0) {
    205     if (external_output_processor_) {
    206       external_output_processor_->release_buffer(
    207           external_output_processor_->opaque, bytes_used);
    208     }
    209     internal_buffers_.erase(it);
    210     return;
    211   }
    212   it->second.written_bytes = bytes_used;
    213   position_ += bytes_used;
    214 
    215   auto it_to_next = it;
    216   it_to_next++;
    217   if (it_to_next != internal_buffers_.end()) {
    218     JXL_ASSERT(it_to_next->first >= position_);
    219   }
    220 
    221   if (external_output_processor_) {
    222     // If the buffer was given by the user, tell the user it is not needed
    223     // anymore.
    224     if (it->second.owned_data.empty()) {
    225       external_output_processor_->release_buffer(
    226           external_output_processor_->opaque, bytes_used);
    227       // If we don't support seeking, this implies we will never modify again
    228       // the bytes that were written so far. Advance the finalized position and
    229       // flush the output to clean up the internal buffers.
    230       if (!external_output_processor_->seek) {
    231         SetFinalizedPosition();
    232         JXL_ASSERT(output_position_ == finalized_position_);
    233         JXL_ASSERT(output_position_ == position_);
    234       } else {
    235         // Otherwise, advance the output position accordingly.
    236         output_position_ += bytes_used;
    237         JXL_ASSERT(output_position_ >= finalized_position_);
    238         JXL_ASSERT(output_position_ == position_);
    239       }
    240     } else if (external_output_processor_->seek) {
    241       // If we had buffered the data internally, flush it out to the external
    242       // processor if we can.
    243       external_output_processor_->seek(external_output_processor_->opaque,
    244                                        position_ - bytes_used);
    245       output_position_ = position_ - bytes_used;
    246       while (output_position_ < position_) {
    247         size_t num_to_write = position_ - output_position_;
    248         if (!AppendBufferToExternalProcessor(it->second.owned_data.data() +
    249                                                  output_position_ - position_ +
    250                                                  bytes_used,
    251                                              num_to_write)) {
    252           return;
    253         }
    254       }
    255       it->second.owned_data.clear();
    256     }
    257   }
    258 }
    259 
    260 // Tries to write all the bytes up to the finalized position.
    261 void JxlEncoderOutputProcessorWrapper::FlushOutput() {
    262   JXL_ASSERT(!has_buffer_);
    263   while (output_position_ < finalized_position_ &&
    264          (avail_out_ == nullptr || *avail_out_ > 0)) {
    265     JXL_ASSERT(!internal_buffers_.empty());
    266     auto it = internal_buffers_.begin();
    267     // If this fails, we are trying to move the finalized position past data
    268     // that was not written yet. This is a library programming error.
    269     JXL_ASSERT(output_position_ >= it->first);
    270     JXL_ASSERT(it->second.written_bytes != 0);
    271     size_t buffer_last_byte = it->first + it->second.written_bytes;
    272     if (!it->second.owned_data.empty()) {
    273       size_t start_in_buffer = output_position_ - it->first;
    274       // Guaranteed by the invariant on `internal_buffers_`.
    275       JXL_ASSERT(buffer_last_byte > output_position_);
    276       size_t num_to_write =
    277           std::min(buffer_last_byte, finalized_position_) - output_position_;
    278       if (avail_out_ != nullptr) {
    279         size_t n = std::min(num_to_write, *avail_out_);
    280         memcpy(*next_out_, it->second.owned_data.data() + start_in_buffer, n);
    281         *avail_out_ -= n;
    282         *next_out_ += n;
    283         output_position_ += n;
    284       } else {
    285         if (!AppendBufferToExternalProcessor(
    286                 it->second.owned_data.data() + start_in_buffer, num_to_write)) {
    287           return;
    288         }
    289       }
    290     } else {
    291       size_t advance =
    292           std::min(buffer_last_byte, finalized_position_) - output_position_;
    293       output_position_ += advance;
    294       if (avail_out_ != nullptr) {
    295         *next_out_ += advance;
    296         *avail_out_ -= advance;
    297       }
    298     }
    299     if (buffer_last_byte == output_position_) {
    300       internal_buffers_.erase(it);
    301     }
    302     if (external_output_processor_ && !external_output_processor_->seek) {
    303       external_output_processor_->set_finalized_position(
    304           external_output_processor_->opaque, output_position_);
    305     }
    306   }
    307 }
    308 
    309 bool JxlEncoderOutputProcessorWrapper::AppendBufferToExternalProcessor(
    310     void* data, size_t count) {
    311   JXL_ASSERT(external_output_processor_);
    312   size_t n = count;
    313   void* user_buffer = external_output_processor_->get_buffer(
    314       external_output_processor_->opaque, &n);
    315   if (user_buffer == nullptr || n == 0) {
    316     stop_requested_ = true;
    317     return false;
    318   }
    319   n = std::min(n, count);
    320   memcpy(user_buffer, data, n);
    321   external_output_processor_->release_buffer(external_output_processor_->opaque,
    322                                              n);
    323   output_position_ += n;
    324   return true;
    325 }
    326 
    327 namespace jxl {
    328 
    329 size_t WriteBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded,
    330                       bool force_large_box, uint8_t* output) {
    331   uint64_t box_size = 0;
    332   bool large_size = false;
    333   if (!unbounded) {
    334     if (box_size >= kLargeBoxContentSizeThreshold || force_large_box) {
    335       large_size = true;
    336       // TODO(firsching): send a separate CL for this (+ test),
    337       // quick fix in the old code: box_size += 8
    338       box_size = size + kLargeBoxHeaderSize;
    339     } else {
    340       box_size = size + kSmallBoxHeaderSize;
    341     }
    342   }
    343 
    344   size_t idx = 0;
    345   {
    346     const uint64_t store = large_size ? 1 : box_size;
    347     for (size_t i = 0; i < 4; i++) {
    348       output[idx++] = store >> (8 * (3 - i)) & 0xff;
    349     }
    350   }
    351   for (size_t i = 0; i < 4; i++) {
    352     output[idx++] = type[i];
    353   }
    354 
    355   if (large_size) {
    356     for (size_t i = 0; i < 8; i++) {
    357       output[idx++] = box_size >> (8 * (7 - i)) & 0xff;
    358     }
    359   }
    360   return idx;
    361 }
    362 }  // namespace jxl
    363 
    364 template <typename WriteBox>
    365 jxl::Status JxlEncoderStruct::AppendBox(const jxl::BoxType& type,
    366                                         bool unbounded, size_t box_max_size,
    367                                         const WriteBox& write_box) {
    368   size_t current_position = output_processor.CurrentPosition();
    369   bool large_box = false;
    370   size_t box_header_size = 0;
    371   if (box_max_size >= jxl::kLargeBoxContentSizeThreshold && !unbounded) {
    372     box_header_size = jxl::kLargeBoxHeaderSize;
    373     large_box = true;
    374   } else {
    375     box_header_size = jxl::kSmallBoxHeaderSize;
    376   }
    377   output_processor.Seek(current_position + box_header_size);
    378   size_t box_contents_start = output_processor.CurrentPosition();
    379   JXL_RETURN_IF_ERROR(write_box());
    380   size_t box_contents_end = output_processor.CurrentPosition();
    381   output_processor.Seek(current_position);
    382   JXL_ASSERT(box_contents_end >= box_contents_start);
    383   if (box_contents_end - box_contents_start > box_max_size) {
    384     return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
    385                          "Internal error: upper bound on box size was "
    386                          "violated, upper bound: %" PRIuS ", actual: %" PRIuS,
    387                          box_max_size, box_contents_end - box_contents_start);
    388   }
    389   // We need to release the buffer before Seek.
    390   {
    391     JXL_ASSIGN_OR_RETURN(
    392         auto buffer,
    393         output_processor.GetBuffer(box_contents_start - current_position));
    394     const size_t n =
    395         jxl::WriteBoxHeader(type, box_contents_end - box_contents_start,
    396                             unbounded, large_box, buffer.data());
    397     JXL_ASSERT(n == box_header_size);
    398     buffer.advance(n);
    399   }
    400   output_processor.Seek(box_contents_end);
    401   output_processor.SetFinalizedPosition();
    402   return jxl::OkStatus();
    403 }
    404 
    405 template <typename BoxContents>
    406 jxl::Status JxlEncoderStruct::AppendBoxWithContents(
    407     const jxl::BoxType& type, const BoxContents& contents) {
    408   size_t size = std::end(contents) - std::begin(contents);
    409   return AppendBox(type, /*unbounded=*/false, size,
    410                    [&]() { return AppendData(output_processor, contents); });
    411 }
    412 
    413 uint32_t JxlEncoderVersion(void) {
    414   return JPEGXL_MAJOR_VERSION * 1000000 + JPEGXL_MINOR_VERSION * 1000 +
    415          JPEGXL_PATCH_VERSION;
    416 }
    417 
    418 namespace {
    419 
    420 void WriteJxlpBoxCounter(uint32_t counter, bool last, uint8_t* buffer) {
    421   if (last) counter |= 0x80000000;
    422   for (size_t i = 0; i < 4; i++) {
    423     buffer[i] = counter >> (8 * (3 - i)) & 0xff;
    424   }
    425 }
    426 
    427 void WriteJxlpBoxCounter(uint32_t counter, bool last,
    428                          JxlOutputProcessorBuffer& buffer) {
    429   uint8_t buf[4];
    430   WriteJxlpBoxCounter(counter, last, buf);
    431   buffer.append(buf, 4);
    432 }
    433 
    434 void QueueFrame(
    435     const JxlEncoderFrameSettings* frame_settings,
    436     jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedFrame>& frame) {
    437   if (frame_settings->values.lossless) {
    438     frame->option_values.cparams.SetLossless();
    439   }
    440 
    441   jxl::JxlEncoderQueuedInput queued_input(frame_settings->enc->memory_manager);
    442   queued_input.frame = std::move(frame);
    443   frame_settings->enc->input_queue.emplace_back(std::move(queued_input));
    444   frame_settings->enc->num_queued_frames++;
    445 }
    446 
    447 void QueueFastLosslessFrame(const JxlEncoderFrameSettings* frame_settings,
    448                             JxlFastLosslessFrameState* fast_lossless_frame) {
    449   jxl::JxlEncoderQueuedInput queued_input(frame_settings->enc->memory_manager);
    450   queued_input.fast_lossless_frame.reset(fast_lossless_frame);
    451   frame_settings->enc->input_queue.emplace_back(std::move(queued_input));
    452   frame_settings->enc->num_queued_frames++;
    453 }
    454 
    455 void QueueBox(JxlEncoder* enc,
    456               jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedBox>& box) {
    457   jxl::JxlEncoderQueuedInput queued_input(enc->memory_manager);
    458   queued_input.box = std::move(box);
    459   enc->input_queue.emplace_back(std::move(queued_input));
    460   enc->num_queued_boxes++;
    461 }
    462 
    463 // TODO(lode): share this code and the Brotli compression code in enc_jpeg_data
    464 JxlEncoderStatus BrotliCompress(int quality, const uint8_t* in, size_t in_size,
    465                                 jxl::PaddedBytes* out) {
    466   std::unique_ptr<BrotliEncoderState, decltype(BrotliEncoderDestroyInstance)*>
    467       enc(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
    468           BrotliEncoderDestroyInstance);
    469   if (!enc) return JXL_API_ERROR_NOSET("BrotliEncoderCreateInstance failed");
    470 
    471   BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_QUALITY, quality);
    472   BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_SIZE_HINT, in_size);
    473 
    474   constexpr size_t kBufferSize = 128 * 1024;
    475   jxl::PaddedBytes temp_buffer(kBufferSize);
    476 
    477   size_t avail_in = in_size;
    478   const uint8_t* next_in = in;
    479 
    480   size_t total_out = 0;
    481 
    482   for (;;) {
    483     size_t avail_out = kBufferSize;
    484     uint8_t* next_out = temp_buffer.data();
    485     jxl::msan::MemoryIsInitialized(next_in, avail_in);
    486     if (!BrotliEncoderCompressStream(enc.get(), BROTLI_OPERATION_FINISH,
    487                                      &avail_in, &next_in, &avail_out, &next_out,
    488                                      &total_out)) {
    489       return JXL_API_ERROR_NOSET("Brotli compression failed");
    490     }
    491     size_t out_size = next_out - temp_buffer.data();
    492     jxl::msan::UnpoisonMemory(next_out - out_size, out_size);
    493     out->resize(out->size() + out_size);
    494     memcpy(out->data() + out->size() - out_size, temp_buffer.data(), out_size);
    495     if (BrotliEncoderIsFinished(enc.get())) break;
    496   }
    497 
    498   return JxlErrorOrStatus::Success();
    499 }
    500 
    501 // The JXL codestream can have level 5 or level 10. Levels have certain
    502 // restrictions such as max allowed image dimensions. This function checks the
    503 // level required to support the current encoder settings. The debug_string is
    504 // intended to be used for developer API error messages, and may be set to
    505 // nullptr.
    506 int VerifyLevelSettings(const JxlEncoder* enc, std::string* debug_string) {
    507   const auto& m = enc->metadata.m;
    508 
    509   uint64_t xsize = enc->metadata.size.xsize();
    510   uint64_t ysize = enc->metadata.size.ysize();
    511   // The uncompressed ICC size, if it is used.
    512   size_t icc_size = 0;
    513   if (m.color_encoding.WantICC()) {
    514     icc_size = m.color_encoding.ICC().size();
    515   }
    516 
    517   // Level 10 checks
    518 
    519   if (xsize > (1ull << 30ull) || ysize > (1ull << 30ull) ||
    520       xsize * ysize > (1ull << 40ull)) {
    521     if (debug_string) *debug_string = "Too large image dimensions";
    522     return -1;
    523   }
    524   if (icc_size > (1ull << 28)) {
    525     if (debug_string) *debug_string = "Too large ICC profile size";
    526     return -1;
    527   }
    528   if (m.num_extra_channels > 256) {
    529     if (debug_string) *debug_string = "Too many extra channels";
    530     return -1;
    531   }
    532 
    533   // Level 5 checks
    534 
    535   if (!m.modular_16_bit_buffer_sufficient) {
    536     if (debug_string) *debug_string = "Too high modular bit depth";
    537     return 10;
    538   }
    539   if (xsize > (1ull << 18ull) || ysize > (1ull << 18ull) ||
    540       xsize * ysize > (1ull << 28ull)) {
    541     if (debug_string) *debug_string = "Too large image dimensions";
    542     return 10;
    543   }
    544   if (icc_size > (1ull << 22)) {
    545     if (debug_string) *debug_string = "Too large ICC profile";
    546     return 10;
    547   }
    548   if (m.num_extra_channels > 4) {
    549     if (debug_string) *debug_string = "Too many extra channels";
    550     return 10;
    551   }
    552   for (size_t i = 0; i < m.extra_channel_info.size(); ++i) {
    553     if (m.extra_channel_info[i].type == jxl::ExtraChannel::kBlack) {
    554       if (debug_string) *debug_string = "CMYK channel not allowed";
    555       return 10;
    556     }
    557   }
    558 
    559   // TODO(lode): also need to check if consecutive composite-still frames total
    560   // pixel amount doesn't exceed 2**28 in the case of level 5. This should be
    561   // done when adding frame and requires ability to add composite still frames
    562   // to be added first.
    563 
    564   // TODO(lode): also need to check animation duration of a frame. This should
    565   // be done when adding frame, but first requires implementing setting the
    566   // JxlFrameHeader for a frame.
    567 
    568   // TODO(lode): also need to check properties such as num_splines, num_patches,
    569   // modular_16bit_buffers and multiple properties of modular trees. However
    570   // these are not user-set properties so cannot be checked here, but decisions
    571   // the C++ encoder should be able to make based on the level.
    572 
    573   // All level 5 checks passes, so can return the more compatible level 5
    574   return 5;
    575 }
    576 
    577 JxlEncoderStatus CheckValidBitdepth(uint32_t bits_per_sample,
    578                                     uint32_t exponent_bits_per_sample) {
    579   if (!exponent_bits_per_sample) {
    580     // The spec allows up to 31 for bits_per_sample here, but
    581     // the code does not (yet) support it.
    582     if (!(bits_per_sample > 0 && bits_per_sample <= 24)) {
    583       return JXL_API_ERROR_NOSET("Invalid value for bits_per_sample");
    584     }
    585   } else if ((exponent_bits_per_sample > 8) ||
    586              (bits_per_sample > 24 + exponent_bits_per_sample) ||
    587              (bits_per_sample < 3 + exponent_bits_per_sample)) {
    588     return JXL_API_ERROR_NOSET("Invalid float description");
    589   }
    590   return JxlErrorOrStatus::Success();
    591 }
    592 
    593 JxlEncoderStatus VerifyInputBitDepth(JxlBitDepth bit_depth,
    594                                      JxlPixelFormat format) {
    595   return JxlErrorOrStatus::Success();
    596 }
    597 
    598 inline bool EncodeVarInt(uint64_t value, size_t output_size, size_t* output_pos,
    599                          uint8_t* output) {
    600   // While more than 7 bits of data are left,
    601   // store 7 bits and set the next byte flag
    602   while (value > 127) {
    603     // TODO(eustas): should it be `>=` ?
    604     if (*output_pos > output_size) return false;
    605     // |128: Set the next byte flag
    606     output[(*output_pos)++] = (static_cast<uint8_t>(value & 127)) | 128;
    607     // Remove the seven bits we just wrote
    608     value >>= 7;
    609   }
    610   // TODO(eustas): should it be `>=` ?
    611   if (*output_pos > output_size) return false;
    612   output[(*output_pos)++] = static_cast<uint8_t>(value & 127);
    613   return true;
    614 }
    615 
    616 bool EncodeFrameIndexBox(const jxl::JxlEncoderFrameIndexBox& frame_index_box,
    617                          std::vector<uint8_t>& buffer_vec) {
    618   bool ok = true;
    619   int NF = 0;
    620   for (size_t i = 0; i < frame_index_box.entries.size(); ++i) {
    621     if (i == 0 || frame_index_box.entries[i].to_be_indexed) {
    622       ++NF;
    623     }
    624   }
    625   // Frame index box contents varint + 8 bytes
    626   // continue with NF * 3 * varint
    627   // varint max length is 10 for 64 bit numbers, and these numbers
    628   // are limited to 63 bits.
    629   static const int kVarintMaxLength = 10;
    630   static const int kFrameIndexBoxHeaderLength = kVarintMaxLength + 8;
    631   static const int kFrameIndexBoxElementLength = 3 * kVarintMaxLength;
    632   const int buffer_size =
    633       kFrameIndexBoxHeaderLength + NF * kFrameIndexBoxElementLength;
    634   buffer_vec.resize(buffer_size);
    635   uint8_t* buffer = buffer_vec.data();
    636   size_t output_pos = 0;
    637   ok &= EncodeVarInt(NF, buffer_vec.size(), &output_pos, buffer);
    638   StoreBE32(frame_index_box.TNUM, &buffer[output_pos]);
    639   output_pos += 4;
    640   StoreBE32(frame_index_box.TDEN, &buffer[output_pos]);
    641   output_pos += 4;
    642   // When we record a frame in the index, the record needs to know
    643   // how many frames until the next indexed frame. That is why
    644   // we store the 'prev' record. That 'prev' record needs to store
    645   // the offset byte position to previously recorded indexed frame,
    646   // that's why we also trace previous to the previous frame.
    647   int prev_prev_ix = -1;  // For position offset (OFFi) delta coding.
    648   int prev_ix = 0;
    649   int T_prev = 0;
    650   int T = 0;
    651   for (size_t i = 1; i < frame_index_box.entries.size(); ++i) {
    652     if (frame_index_box.entries[i].to_be_indexed) {
    653       // Now we can record the previous entry, since we need to store
    654       // there how many frames until the next one.
    655       int64_t OFFi = frame_index_box.entries[prev_ix].OFFi;
    656       if (prev_prev_ix != -1) {
    657         // Offi needs to be offset of start byte of this frame compared to start
    658         // byte of previous frame from this index in the JPEG XL codestream. For
    659         // the first frame, this is the offset from the first byte of the JPEG
    660         // XL codestream.
    661         OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
    662       }
    663       int32_t Ti = T_prev;
    664       int32_t Fi = i - prev_ix;
    665       ok &= EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
    666       ok &= EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
    667       ok &= EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
    668       prev_prev_ix = prev_ix;
    669       prev_ix = i;
    670       T_prev = T;
    671       T += frame_index_box.entries[i].duration;
    672     }
    673   }
    674   {
    675     // Last frame.
    676     size_t i = frame_index_box.entries.size();
    677     int64_t OFFi = frame_index_box.entries[prev_ix].OFFi;
    678     if (prev_prev_ix != -1) {
    679       OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
    680     }
    681     int32_t Ti = T_prev;
    682     int32_t Fi = i - prev_ix;
    683     ok &= EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
    684     ok &= EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
    685     ok &= EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
    686   }
    687   // Enough buffer has been allocated, this function should never fail in
    688   // writing.
    689   JXL_ASSERT(ok);
    690   buffer_vec.resize(output_pos);
    691   return ok;
    692 }
    693 
    694 }  // namespace
    695 
    696 jxl::Status JxlEncoderStruct::ProcessOneEnqueuedInput() {
    697   jxl::PaddedBytes header_bytes;
    698 
    699   jxl::JxlEncoderQueuedInput& input = input_queue[0];
    700 
    701   // TODO(lode): split this into 3 functions: for adding the signature and other
    702   // initial headers (jbrd, ...), one for adding frame, and one for adding user
    703   // box.
    704 
    705   if (!wrote_bytes) {
    706     // First time encoding any data, verify the level 5 vs level 10 settings
    707     std::string level_message;
    708     int required_level = VerifyLevelSettings(this, &level_message);
    709     // Only level 5 and 10 are defined, and the function can return -1 to
    710     // indicate full incompatibility.
    711     JXL_ASSERT(required_level == -1 || required_level == 5 ||
    712                required_level == 10);
    713     // codestream_level == -1 means auto-set to the required level
    714     if (codestream_level == -1) codestream_level = required_level;
    715     if (codestream_level == 5 && required_level != 5) {
    716       // If the required level is 10, return error rather than automatically
    717       // setting the level to 10, to avoid inadvertently creating a level 10
    718       // JXL file while intending to target a level 5 decoder.
    719       return JXL_API_ERROR(
    720           this, JXL_ENC_ERR_API_USAGE, "%s",
    721           ("Codestream level verification for level 5 failed: " + level_message)
    722               .c_str());
    723     }
    724     if (required_level == -1) {
    725       return JXL_API_ERROR(
    726           this, JXL_ENC_ERR_API_USAGE, "%s",
    727           ("Codestream level verification for level 10 failed: " +
    728            level_message)
    729               .c_str());
    730     }
    731     jxl::AuxOut* aux_out =
    732         input.frame ? input.frame->option_values.aux_out : nullptr;
    733     jxl::BitWriter writer;
    734     if (!WriteCodestreamHeaders(&metadata, &writer, aux_out)) {
    735       return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
    736                            "Failed to write codestream header");
    737     }
    738     // Only send ICC (at least several hundred bytes) if fields aren't enough.
    739     if (metadata.m.color_encoding.WantICC()) {
    740       if (!jxl::WriteICC(metadata.m.color_encoding.ICC(), &writer,
    741                          jxl::kLayerHeader, aux_out)) {
    742         return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
    743                              "Failed to write ICC profile");
    744       }
    745     }
    746     // TODO(lode): preview should be added here if a preview image is added
    747 
    748     jxl::BitWriter::Allotment allotment(&writer, 8);
    749     writer.ZeroPadToByte();
    750     allotment.ReclaimAndCharge(&writer, jxl::kLayerHeader, aux_out);
    751 
    752     header_bytes = std::move(writer).TakeBytes();
    753 
    754     // Not actually the end of frame, but the end of metadata/ICC, but helps
    755     // the next frame to start here for indexing purposes.
    756     codestream_bytes_written_end_of_frame += header_bytes.size();
    757 
    758     if (MustUseContainer()) {
    759       // Add "JXL " and ftyp box.
    760       {
    761         JXL_ASSIGN_OR_RETURN(auto buffer, output_processor.GetBuffer(
    762                                               jxl::kContainerHeader.size()));
    763         buffer.append(jxl::kContainerHeader);
    764       }
    765       if (codestream_level != 5) {
    766         // Add jxll box directly after the ftyp box to indicate the codestream
    767         // level.
    768         JXL_ASSIGN_OR_RETURN(auto buffer, output_processor.GetBuffer(
    769                                               jxl::kLevelBoxHeader.size() + 1));
    770         buffer.append(jxl::kLevelBoxHeader);
    771         uint8_t cl = codestream_level;
    772         buffer.append(&cl, 1);
    773       }
    774 
    775       // Whether to write the basic info and color profile header of the
    776       // codestream into an early separate jxlp box, so that it comes before
    777       // metadata or jpeg reconstruction boxes. In theory this could simply
    778       // always be done, but there's no reason to add an extra box with box
    779       // header overhead if the codestream will already come immediately after
    780       // the signature and level boxes.
    781       bool partial_header =
    782           store_jpeg_metadata ||
    783           (use_boxes && (!input.frame && !input.fast_lossless_frame));
    784 
    785       if (partial_header) {
    786         JXL_RETURN_IF_ERROR(AppendBox(
    787             jxl::MakeBoxType("jxlp"), /*unbounded=*/false,
    788             header_bytes.size() + 4, [&]() {
    789               JXL_ASSIGN_OR_RETURN(auto buffer, output_processor.GetBuffer(
    790                                                     header_bytes.size() + 4));
    791               WriteJxlpBoxCounter(jxlp_counter++, /*last=*/false, buffer);
    792               buffer.append(header_bytes);
    793               return jxl::OkStatus();
    794             }));
    795         header_bytes.clear();
    796       }
    797 
    798       if (store_jpeg_metadata && !jpeg_metadata.empty()) {
    799         JXL_RETURN_IF_ERROR(
    800             AppendBoxWithContents(jxl::MakeBoxType("jbrd"), jpeg_metadata));
    801       }
    802     }
    803     wrote_bytes = true;
    804   }
    805 
    806   output_processor.SetFinalizedPosition();
    807 
    808   // Choose frame or box processing: exactly one of the two unique pointers (box
    809   // or frame) in the input queue item is non-null.
    810   if (input.frame || input.fast_lossless_frame) {
    811     jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedFrame> input_frame =
    812         std::move(input.frame);
    813     jxl::FJXLFrameUniquePtr fast_lossless_frame =
    814         std::move(input.fast_lossless_frame);
    815     input_queue.erase(input_queue.begin());
    816     num_queued_frames--;
    817     if (input_frame) {
    818       for (unsigned idx = 0; idx < input_frame->ec_initialized.size(); idx++) {
    819         if (!input_frame->ec_initialized[idx]) {
    820           return JXL_API_ERROR(this, JXL_ENC_ERR_API_USAGE,
    821                                "Extra channel %u is not initialized", idx);
    822         }
    823       }
    824 
    825       // TODO(zond): If the input queue is empty and the frames_closed is true,
    826       // then mark this frame as the last.
    827 
    828       // TODO(zond): Handle progressive mode like EncodeFile does it.
    829       // TODO(zond): Handle animation like EncodeFile does it, by checking if
    830       //             JxlEncoderCloseFrames has been called and if the frame
    831       //             queue is empty (to see if it's the last animation frame).
    832 
    833       if (metadata.m.xyb_encoded) {
    834         input_frame->option_values.cparams.color_transform =
    835             jxl::ColorTransform::kXYB;
    836       } else {
    837         // TODO(zond): Figure out when to use kYCbCr instead.
    838         input_frame->option_values.cparams.color_transform =
    839             jxl::ColorTransform::kNone;
    840       }
    841     }
    842 
    843     uint32_t duration;
    844     uint32_t timecode;
    845     if (input_frame && metadata.m.have_animation) {
    846       duration = input_frame->option_values.header.duration;
    847       timecode = input_frame->option_values.header.timecode;
    848     } else {
    849       // If have_animation is false, the encoder should ignore the duration and
    850       // timecode values. However, assigning them to ib will cause the encoder
    851       // to write an invalid frame header that can't be decoded so ensure
    852       // they're the default value of 0 here.
    853       duration = 0;
    854       timecode = 0;
    855     }
    856 
    857     const bool last_frame = frames_closed && (num_queued_frames == 0);
    858 
    859     uint32_t max_bits_per_sample = metadata.m.bit_depth.bits_per_sample;
    860     for (const auto& info : metadata.m.extra_channel_info) {
    861       max_bits_per_sample =
    862           std::max(max_bits_per_sample, info.bit_depth.bits_per_sample);
    863     }
    864     // Heuristic upper bound on how many bits a single pixel in a single channel
    865     // can use.
    866     uint32_t bits_per_channels_estimate =
    867         std::max(24u, max_bits_per_sample + 3);
    868     size_t upper_bound_on_compressed_size_bits =
    869         metadata.xsize() * metadata.ysize() *
    870         (metadata.m.color_encoding.Channels() + metadata.m.num_extra_channels) *
    871         bits_per_channels_estimate;
    872     // Add a 1MB = 0x100000 for an heuristic upper bound on small sizes.
    873     size_t upper_bound_on_compressed_size_bytes =
    874         0x100000 + (upper_bound_on_compressed_size_bits >> 3);
    875     bool use_large_box = upper_bound_on_compressed_size_bytes >=
    876                          jxl::kLargeBoxContentSizeThreshold;
    877     size_t box_header_size =
    878         use_large_box ? jxl::kLargeBoxHeaderSize : jxl::kSmallBoxHeaderSize;
    879 
    880     const size_t frame_start_pos = output_processor.CurrentPosition();
    881     if (MustUseContainer()) {
    882       if (!last_frame || jxlp_counter > 0) {
    883         // If this is the last frame and no jxlp boxes were used yet, it's
    884         // slightly more efficient to write a jxlc box since it has 4 bytes
    885         // less overhead.
    886         box_header_size += 4;  // jxlp_counter field
    887       }
    888       output_processor.Seek(frame_start_pos + box_header_size);
    889     }
    890     const size_t frame_codestream_start = output_processor.CurrentPosition();
    891 
    892     JXL_RETURN_IF_ERROR(AppendData(output_processor, header_bytes));
    893 
    894     if (input_frame) {
    895       frame_index_box.AddFrame(codestream_bytes_written_end_of_frame, duration,
    896                                input_frame->option_values.frame_index_box);
    897 
    898       size_t save_as_reference =
    899           input_frame->option_values.header.layer_info.save_as_reference;
    900       if (save_as_reference >= 3) {
    901         return JXL_API_ERROR(
    902             this, JXL_ENC_ERR_API_USAGE,
    903             "Cannot use save_as_reference values >=3 (found: %d)",
    904             static_cast<int>(save_as_reference));
    905       }
    906 
    907       jxl::FrameInfo frame_info;
    908       frame_info.is_last = last_frame;
    909       frame_info.save_as_reference = save_as_reference;
    910       frame_info.source =
    911           input_frame->option_values.header.layer_info.blend_info.source;
    912       frame_info.clamp = FROM_JXL_BOOL(
    913           input_frame->option_values.header.layer_info.blend_info.clamp);
    914       frame_info.alpha_channel =
    915           input_frame->option_values.header.layer_info.blend_info.alpha;
    916       frame_info.extra_channel_blending_info.resize(
    917           metadata.m.num_extra_channels);
    918       // If extra channel blend info has not been set, use the blend mode from
    919       // the layer_info.
    920       JxlBlendInfo default_blend_info =
    921           input_frame->option_values.header.layer_info.blend_info;
    922       for (size_t i = 0; i < metadata.m.num_extra_channels; ++i) {
    923         auto& to = frame_info.extra_channel_blending_info[i];
    924         const auto& from =
    925             i < input_frame->option_values.extra_channel_blend_info.size()
    926                 ? input_frame->option_values.extra_channel_blend_info[i]
    927                 : default_blend_info;
    928         to.mode = static_cast<jxl::BlendMode>(from.blendmode);
    929         to.source = from.source;
    930         to.alpha_channel = from.alpha;
    931         to.clamp = (from.clamp != 0);
    932       }
    933       frame_info.origin.x0 =
    934           input_frame->option_values.header.layer_info.crop_x0;
    935       frame_info.origin.y0 =
    936           input_frame->option_values.header.layer_info.crop_y0;
    937       frame_info.blendmode = static_cast<jxl::BlendMode>(
    938           input_frame->option_values.header.layer_info.blend_info.blendmode);
    939       frame_info.blend =
    940           input_frame->option_values.header.layer_info.blend_info.blendmode !=
    941           JXL_BLEND_REPLACE;
    942       frame_info.image_bit_depth = input_frame->option_values.image_bit_depth;
    943       frame_info.duration = duration;
    944       frame_info.timecode = timecode;
    945       frame_info.name = input_frame->option_values.frame_name;
    946 
    947       if (!jxl::EncodeFrame(input_frame->option_values.cparams, frame_info,
    948                             &metadata, input_frame->frame_data, cms,
    949                             thread_pool.get(), &output_processor,
    950                             input_frame->option_values.aux_out)) {
    951         return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
    952                              "Failed to encode frame");
    953       }
    954     } else {
    955       JXL_CHECK(fast_lossless_frame);
    956       auto runner = +[](void* void_pool, void* opaque, void fun(void*, size_t),
    957                         size_t count) {
    958         auto* pool = reinterpret_cast<jxl::ThreadPool*>(void_pool);
    959         JXL_CHECK(jxl::RunOnPool(
    960             pool, 0, count, jxl::ThreadPool::NoInit,
    961             [&](size_t i, size_t) { fun(opaque, i); }, "Encode fast lossless"));
    962       };
    963       JxlFastLosslessProcessFrame(fast_lossless_frame.get(), last_frame,
    964                                   thread_pool.get(), runner, &output_processor);
    965     }
    966 
    967     const size_t frame_codestream_end = output_processor.CurrentPosition();
    968     const size_t frame_codestream_size =
    969         frame_codestream_end - frame_codestream_start;
    970 
    971     codestream_bytes_written_end_of_frame +=
    972         frame_codestream_size - header_bytes.size();
    973 
    974     if (MustUseContainer()) {
    975       output_processor.Seek(frame_start_pos);
    976       std::vector<uint8_t> box_header(box_header_size);
    977       if (!use_large_box &&
    978           frame_codestream_size >= jxl::kLargeBoxContentSizeThreshold) {
    979         // Assuming our upper bound estimate is correct, this should never
    980         // happen.
    981         return JXL_API_ERROR(
    982             this, JXL_ENC_ERR_GENERIC,
    983             "Box size was estimated to be small, but turned out to be large. "
    984             "Please file this error in size estimation as a bug.");
    985       }
    986       if (last_frame && jxlp_counter == 0) {
    987 #if JXL_ENABLE_ASSERT
    988         const size_t n =
    989 #endif
    990             jxl::WriteBoxHeader(jxl::MakeBoxType("jxlc"), frame_codestream_size,
    991                                 /*unbounded=*/false, use_large_box,
    992                                 box_header.data());
    993         JXL_ASSERT(n == box_header_size);
    994       } else {
    995 #if JXL_ENABLE_ASSERT
    996         const size_t n =
    997 #endif
    998             jxl::WriteBoxHeader(
    999                 jxl::MakeBoxType("jxlp"), frame_codestream_size + 4,
   1000                 /*unbounded=*/false, use_large_box, box_header.data());
   1001         JXL_ASSERT(n == box_header_size - 4);
   1002         WriteJxlpBoxCounter(jxlp_counter++, last_frame,
   1003                             &box_header[box_header_size - 4]);
   1004       }
   1005       JXL_RETURN_IF_ERROR(AppendData(output_processor, box_header));
   1006       JXL_ASSERT(output_processor.CurrentPosition() == frame_codestream_start);
   1007       output_processor.Seek(frame_codestream_end);
   1008     }
   1009     output_processor.SetFinalizedPosition();
   1010     if (input_frame) {
   1011       last_used_cparams = input_frame->option_values.cparams;
   1012     }
   1013     if (last_frame && frame_index_box.StoreFrameIndexBox()) {
   1014       std::vector<uint8_t> index_box_content;
   1015       EncodeFrameIndexBox(frame_index_box, index_box_content);
   1016       JXL_RETURN_IF_ERROR(AppendBoxWithContents(jxl::MakeBoxType("jxli"),
   1017                                                 jxl::Bytes(index_box_content)));
   1018     }
   1019   } else {
   1020     // Not a frame, so is a box instead
   1021     jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedBox> box =
   1022         std::move(input.box);
   1023     input_queue.erase(input_queue.begin());
   1024     num_queued_boxes--;
   1025 
   1026     if (box->compress_box) {
   1027       jxl::PaddedBytes compressed(4);
   1028       // Prepend the original box type in the brob box contents
   1029       for (size_t i = 0; i < 4; i++) {
   1030         compressed[i] = static_cast<uint8_t>(box->type[i]);
   1031       }
   1032       if (JXL_ENC_SUCCESS !=
   1033           BrotliCompress((brotli_effort >= 0 ? brotli_effort : 4),
   1034                          box->contents.data(), box->contents.size(),
   1035                          &compressed)) {
   1036         return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
   1037                              "Brotli compression for brob box failed");
   1038       }
   1039 
   1040       JXL_RETURN_IF_ERROR(
   1041           AppendBoxWithContents(jxl::MakeBoxType("brob"), compressed));
   1042     } else {
   1043       JXL_RETURN_IF_ERROR(AppendBoxWithContents(box->type, box->contents));
   1044     }
   1045   }
   1046 
   1047   return jxl::OkStatus();
   1048 }
   1049 
   1050 JxlEncoderStatus JxlEncoderSetColorEncoding(JxlEncoder* enc,
   1051                                             const JxlColorEncoding* color) {
   1052   if (!enc->basic_info_set) {
   1053     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Basic info not yet set");
   1054   }
   1055   if (enc->color_encoding_set) {
   1056     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1057                          "Color encoding is already set");
   1058   }
   1059   if (!enc->metadata.m.color_encoding.FromExternal(*color)) {
   1060     return JXL_API_ERROR(enc, JXL_ENC_ERR_GENERIC, "Error in color conversion");
   1061   }
   1062   if (enc->metadata.m.color_encoding.GetColorSpace() ==
   1063       jxl::ColorSpace::kGray) {
   1064     if (enc->basic_info.num_color_channels != 1) {
   1065       return JXL_API_ERROR(
   1066           enc, JXL_ENC_ERR_API_USAGE,
   1067           "Cannot use grayscale color encoding with num_color_channels != 1");
   1068     }
   1069   } else {
   1070     if (enc->basic_info.num_color_channels != 3) {
   1071       return JXL_API_ERROR(
   1072           enc, JXL_ENC_ERR_API_USAGE,
   1073           "Cannot use RGB color encoding with num_color_channels != 3");
   1074     }
   1075   }
   1076   enc->color_encoding_set = true;
   1077   if (!enc->intensity_target_set) {
   1078     jxl::SetIntensityTarget(&enc->metadata.m);
   1079   }
   1080   return JxlErrorOrStatus::Success();
   1081 }
   1082 
   1083 JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc,
   1084                                          const uint8_t* icc_profile,
   1085                                          size_t size) {
   1086   if (!enc->basic_info_set) {
   1087     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Basic info not yet set");
   1088   }
   1089   if (enc->color_encoding_set) {
   1090     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1091                          "ICC profile is already set");
   1092   }
   1093   if (size == 0) {
   1094     return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT, "Empty ICC profile");
   1095   }
   1096   jxl::IccBytes icc;
   1097   icc.assign(icc_profile, icc_profile + size);
   1098   if (enc->cms_set) {
   1099     if (!enc->metadata.m.color_encoding.SetICC(std::move(icc), &enc->cms)) {
   1100       return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT,
   1101                            "ICC profile could not be set");
   1102     }
   1103   } else {
   1104     enc->metadata.m.color_encoding.SetICCRaw(std::move(icc));
   1105   }
   1106   if (enc->metadata.m.color_encoding.GetColorSpace() ==
   1107       jxl::ColorSpace::kGray) {
   1108     if (enc->basic_info.num_color_channels != 1) {
   1109       return JXL_API_ERROR(
   1110           enc, JXL_ENC_ERR_BAD_INPUT,
   1111           "Cannot use grayscale ICC profile with num_color_channels != 1");
   1112     }
   1113   } else {
   1114     if (enc->basic_info.num_color_channels != 3) {
   1115       return JXL_API_ERROR(
   1116           enc, JXL_ENC_ERR_BAD_INPUT,
   1117           "Cannot use RGB ICC profile with num_color_channels != 3");
   1118     }
   1119     // TODO(jon): also check that a kBlack extra channel is provided in the CMYK
   1120     // case
   1121   }
   1122   enc->color_encoding_set = true;
   1123   if (!enc->intensity_target_set) {
   1124     jxl::SetIntensityTarget(&enc->metadata.m);
   1125   }
   1126 
   1127   if (!enc->basic_info.uses_original_profile && enc->cms_set) {
   1128     enc->metadata.m.color_encoding.DecideIfWantICC(enc->cms);
   1129   }
   1130 
   1131   return JxlErrorOrStatus::Success();
   1132 }
   1133 
   1134 void JxlEncoderInitBasicInfo(JxlBasicInfo* info) {
   1135   info->have_container = JXL_FALSE;
   1136   info->xsize = 0;
   1137   info->ysize = 0;
   1138   info->bits_per_sample = 8;
   1139   info->exponent_bits_per_sample = 0;
   1140   info->intensity_target = 0.f;
   1141   info->min_nits = 0.f;
   1142   info->relative_to_max_display = JXL_FALSE;
   1143   info->linear_below = 0.f;
   1144   info->uses_original_profile = JXL_FALSE;
   1145   info->have_preview = JXL_FALSE;
   1146   info->have_animation = JXL_FALSE;
   1147   info->orientation = JXL_ORIENT_IDENTITY;
   1148   info->num_color_channels = 3;
   1149   info->num_extra_channels = 0;
   1150   info->alpha_bits = 0;
   1151   info->alpha_exponent_bits = 0;
   1152   info->alpha_premultiplied = JXL_FALSE;
   1153   info->preview.xsize = 0;
   1154   info->preview.ysize = 0;
   1155   info->intrinsic_xsize = 0;
   1156   info->intrinsic_ysize = 0;
   1157   info->animation.tps_numerator = 10;
   1158   info->animation.tps_denominator = 1;
   1159   info->animation.num_loops = 0;
   1160   info->animation.have_timecodes = JXL_FALSE;
   1161 }
   1162 
   1163 void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header) {
   1164   // For each field, the default value of the specification is used. Depending
   1165   // on whether an animation frame, or a composite still blending frame,
   1166   // is used, different fields have to be set up by the user after initing
   1167   // the frame header.
   1168   frame_header->duration = 0;
   1169   frame_header->timecode = 0;
   1170   frame_header->name_length = 0;
   1171   // In the specification, the default value of is_last is !frame_type, and the
   1172   // default frame_type is kRegularFrame which has value 0, so is_last is true
   1173   // by default. However, the encoder does not use this value (the field exists
   1174   // for the decoder to set) since last frame is determined by usage of
   1175   // JxlEncoderCloseFrames instead.
   1176   frame_header->is_last = JXL_TRUE;
   1177   frame_header->layer_info.have_crop = JXL_FALSE;
   1178   frame_header->layer_info.crop_x0 = 0;
   1179   frame_header->layer_info.crop_y0 = 0;
   1180   // These must be set if have_crop is enabled, but the default value has
   1181   // have_crop false, and these dimensions 0. The user must set these to the
   1182   // desired size after enabling have_crop (which is not yet implemented).
   1183   frame_header->layer_info.xsize = 0;
   1184   frame_header->layer_info.ysize = 0;
   1185   JxlEncoderInitBlendInfo(&frame_header->layer_info.blend_info);
   1186   frame_header->layer_info.save_as_reference = 0;
   1187 }
   1188 
   1189 void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info) {
   1190   // Default blend mode in the specification is 0. Note that combining
   1191   // blend mode of replace with a duration is not useful, but the user has to
   1192   // manually set duration in case of animation, or manually change the blend
   1193   // mode in case of composite stills, so initing to a combination that is not
   1194   // useful on its own is not an issue.
   1195   blend_info->blendmode = JXL_BLEND_REPLACE;
   1196   blend_info->source = 0;
   1197   blend_info->alpha = 0;
   1198   blend_info->clamp = 0;
   1199 }
   1200 
   1201 JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
   1202                                         const JxlBasicInfo* info) {
   1203   if (!enc->metadata.size.Set(info->xsize, info->ysize)) {
   1204     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid dimensions");
   1205   }
   1206   if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
   1207                                             info->exponent_bits_per_sample)) {
   1208     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
   1209   }
   1210 
   1211   enc->metadata.m.bit_depth.bits_per_sample = info->bits_per_sample;
   1212   enc->metadata.m.bit_depth.exponent_bits_per_sample =
   1213       info->exponent_bits_per_sample;
   1214   enc->metadata.m.bit_depth.floating_point_sample =
   1215       (info->exponent_bits_per_sample != 0u);
   1216   enc->metadata.m.modular_16_bit_buffer_sufficient =
   1217       (!FROM_JXL_BOOL(info->uses_original_profile) ||
   1218        info->bits_per_sample <= 12) &&
   1219       info->alpha_bits <= 12;
   1220   if ((info->intrinsic_xsize > 0 || info->intrinsic_ysize > 0) &&
   1221       (info->intrinsic_xsize != info->xsize ||
   1222        info->intrinsic_ysize != info->ysize)) {
   1223     if (info->intrinsic_xsize > (1ull << 30ull) ||
   1224         info->intrinsic_ysize > (1ull << 30ull) ||
   1225         !enc->metadata.m.intrinsic_size.Set(info->intrinsic_xsize,
   1226                                             info->intrinsic_ysize)) {
   1227       return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1228                            "Invalid intrinsic dimensions");
   1229     }
   1230     enc->metadata.m.have_intrinsic_size = true;
   1231   }
   1232 
   1233   // The number of extra channels includes the alpha channel, so for example and
   1234   // RGBA with no other extra channels, has exactly num_extra_channels == 1
   1235   enc->metadata.m.num_extra_channels = info->num_extra_channels;
   1236   enc->metadata.m.extra_channel_info.resize(enc->metadata.m.num_extra_channels);
   1237   if (info->num_extra_channels == 0 && info->alpha_bits) {
   1238     return JXL_API_ERROR(
   1239         enc, JXL_ENC_ERR_API_USAGE,
   1240         "when alpha_bits is non-zero, the number of channels must be at least "
   1241         "1");
   1242   }
   1243   // If the user provides non-zero alpha_bits, we make the channel info at index
   1244   // zero the appropriate alpha channel.
   1245   if (info->alpha_bits) {
   1246     JxlExtraChannelInfo channel_info;
   1247     JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &channel_info);
   1248     channel_info.bits_per_sample = info->alpha_bits;
   1249     channel_info.exponent_bits_per_sample = info->alpha_exponent_bits;
   1250     if (JxlEncoderSetExtraChannelInfo(enc, 0, &channel_info)) {
   1251       return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1252                            "Problem setting extra channel info for alpha");
   1253     }
   1254   }
   1255 
   1256   enc->metadata.m.xyb_encoded = !FROM_JXL_BOOL(info->uses_original_profile);
   1257   if (info->orientation > 0 && info->orientation <= 8) {
   1258     enc->metadata.m.orientation = info->orientation;
   1259   } else {
   1260     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1261                          "Invalid value for orientation field");
   1262   }
   1263   if (info->num_color_channels != 1 && info->num_color_channels != 3) {
   1264     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1265                          "Invalid number of color channels");
   1266   }
   1267   if (info->intensity_target != 0) {
   1268     enc->metadata.m.SetIntensityTarget(info->intensity_target);
   1269     enc->intensity_target_set = true;
   1270   } else if (enc->color_encoding_set) {
   1271     // If this is false, JxlEncoderSetColorEncoding will be called later and we
   1272     // will get one more chance to call jxl::SetIntensityTarget, after the color
   1273     // encoding is indeed set.
   1274     jxl::SetIntensityTarget(&enc->metadata.m);
   1275     enc->intensity_target_set = true;
   1276   }
   1277   enc->metadata.m.tone_mapping.min_nits = info->min_nits;
   1278   enc->metadata.m.tone_mapping.relative_to_max_display =
   1279       FROM_JXL_BOOL(info->relative_to_max_display);
   1280   enc->metadata.m.tone_mapping.linear_below = info->linear_below;
   1281   enc->basic_info = *info;
   1282   enc->basic_info_set = true;
   1283 
   1284   enc->metadata.m.have_animation = FROM_JXL_BOOL(info->have_animation);
   1285   if (info->have_animation) {
   1286     if (info->animation.tps_denominator < 1) {
   1287       return JXL_API_ERROR(
   1288           enc, JXL_ENC_ERR_API_USAGE,
   1289           "If animation is used, tps_denominator must be >= 1");
   1290     }
   1291     if (info->animation.tps_numerator < 1) {
   1292       return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1293                            "If animation is used, tps_numerator must be >= 1");
   1294     }
   1295     enc->metadata.m.animation.tps_numerator = info->animation.tps_numerator;
   1296     enc->metadata.m.animation.tps_denominator = info->animation.tps_denominator;
   1297     enc->metadata.m.animation.num_loops = info->animation.num_loops;
   1298     enc->metadata.m.animation.have_timecodes =
   1299         FROM_JXL_BOOL(info->animation.have_timecodes);
   1300   }
   1301   std::string level_message;
   1302   int required_level = VerifyLevelSettings(enc, &level_message);
   1303   if (required_level == -1 ||
   1304       (static_cast<int>(enc->codestream_level) < required_level &&
   1305        enc->codestream_level != -1)) {
   1306     return JXL_API_ERROR(
   1307         enc, JXL_ENC_ERR_API_USAGE, "%s",
   1308         ("Codestream level verification for level " +
   1309          std::to_string(enc->codestream_level) + " failed: " + level_message)
   1310             .c_str());
   1311   }
   1312   return JxlErrorOrStatus::Success();
   1313 }
   1314 
   1315 void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type,
   1316                                     JxlExtraChannelInfo* info) {
   1317   info->type = type;
   1318   info->bits_per_sample = 8;
   1319   info->exponent_bits_per_sample = 0;
   1320   info->dim_shift = 0;
   1321   info->name_length = 0;
   1322   info->alpha_premultiplied = JXL_FALSE;
   1323   info->spot_color[0] = 0;
   1324   info->spot_color[1] = 0;
   1325   info->spot_color[2] = 0;
   1326   info->spot_color[3] = 0;
   1327   info->cfa_channel = 0;
   1328 }
   1329 
   1330 JXL_EXPORT JxlEncoderStatus JxlEncoderSetUpsamplingMode(JxlEncoder* enc,
   1331                                                         const int64_t factor,
   1332                                                         const int64_t mode) {
   1333   // for convenience, allow calling this with factor 1 and just make it a no-op
   1334   if (factor == 1) return JxlErrorOrStatus::Success();
   1335   if (factor != 2 && factor != 4 && factor != 8) {
   1336     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1337                          "Invalid upsampling factor");
   1338   }
   1339   if (mode < -1)
   1340     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid upsampling mode");
   1341   if (mode > 1) {
   1342     return JXL_API_ERROR(enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1343                          "Unsupported upsampling mode");
   1344   }
   1345 
   1346   const size_t count = (factor == 2 ? 15 : (factor == 4 ? 55 : 210));
   1347   auto& td = enc->metadata.transform_data;
   1348   float* weights = (factor == 2 ? td.upsampling2_weights
   1349                                 : (factor == 4 ? td.upsampling4_weights
   1350                                                : td.upsampling8_weights));
   1351   if (mode == -1) {
   1352     // Default fancy upsampling: don't signal custom weights
   1353     enc->metadata.transform_data.custom_weights_mask &= ~(factor >> 1);
   1354   } else if (mode == 0) {
   1355     // Nearest neighbor upsampling
   1356     enc->metadata.transform_data.custom_weights_mask |= (factor >> 1);
   1357     memset(weights, 0, sizeof(float) * count);
   1358     if (factor == 2) {
   1359       weights[9] = 1.f;
   1360     } else if (factor == 4) {
   1361       for (int i : {19, 24, 49}) weights[i] = 1.f;
   1362     } else if (factor == 8) {
   1363       for (int i : {39, 44, 49, 54, 119, 124, 129, 174, 179, 204}) {
   1364         weights[i] = 1.f;
   1365       }
   1366     }
   1367   } else if (mode == 1) {
   1368     // 'Pixel dots' upsampling (nearest-neighbor with cut corners)
   1369     JxlEncoderSetUpsamplingMode(enc, factor, 0);
   1370     if (factor == 4) {
   1371       weights[19] = 0.f;
   1372       weights[24] = 0.5f;
   1373     } else if (factor == 8) {
   1374       for (int i : {39, 44, 49, 119}) weights[i] = 0.f;
   1375       for (int i : {54, 124}) weights[i] = 0.5f;
   1376     }
   1377   }
   1378   return JxlErrorOrStatus::Success();
   1379 }
   1380 
   1381 JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
   1382     JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info) {
   1383   if (index >= enc->metadata.m.num_extra_channels) {
   1384     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1385                          "Invalid value for the index of extra channel");
   1386   }
   1387   if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
   1388                                             info->exponent_bits_per_sample)) {
   1389     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
   1390   }
   1391 
   1392   jxl::ExtraChannelInfo& channel = enc->metadata.m.extra_channel_info[index];
   1393   channel.type = static_cast<jxl::ExtraChannel>(info->type);
   1394   channel.bit_depth.bits_per_sample = info->bits_per_sample;
   1395   enc->metadata.m.modular_16_bit_buffer_sufficient &=
   1396       info->bits_per_sample <= 12;
   1397   channel.bit_depth.exponent_bits_per_sample = info->exponent_bits_per_sample;
   1398   channel.bit_depth.floating_point_sample = info->exponent_bits_per_sample != 0;
   1399   channel.dim_shift = info->dim_shift;
   1400   channel.name = "";
   1401   channel.alpha_associated = (info->alpha_premultiplied != 0);
   1402   channel.cfa_channel = info->cfa_channel;
   1403   channel.spot_color[0] = info->spot_color[0];
   1404   channel.spot_color[1] = info->spot_color[1];
   1405   channel.spot_color[2] = info->spot_color[2];
   1406   channel.spot_color[3] = info->spot_color[3];
   1407   std::string level_message;
   1408   int required_level = VerifyLevelSettings(enc, &level_message);
   1409   if (required_level == -1 ||
   1410       (static_cast<int>(enc->codestream_level) < required_level &&
   1411        enc->codestream_level != -1)) {
   1412     return JXL_API_ERROR(
   1413         enc, JXL_ENC_ERR_API_USAGE, "%s",
   1414         ("Codestream level verification for level " +
   1415          std::to_string(enc->codestream_level) + " failed: " + level_message)
   1416             .c_str());
   1417   }
   1418   return JxlErrorOrStatus::Success();
   1419 }
   1420 
   1421 JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
   1422                                                           size_t index,
   1423                                                           const char* name,
   1424                                                           size_t size) {
   1425   if (index >= enc->metadata.m.num_extra_channels) {
   1426     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1427                          "Invalid value for the index of extra channel");
   1428   }
   1429   enc->metadata.m.extra_channel_info[index].name =
   1430       std::string(name, name + size);
   1431   return JxlErrorOrStatus::Success();
   1432 }
   1433 
   1434 JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate(
   1435     JxlEncoder* enc, const JxlEncoderFrameSettings* source) {
   1436   auto opts = jxl::MemoryManagerMakeUnique<JxlEncoderFrameSettings>(
   1437       &enc->memory_manager);
   1438   if (!opts) return nullptr;
   1439   opts->enc = enc;
   1440   if (source != nullptr) {
   1441     opts->values = source->values;
   1442   } else {
   1443     opts->values.lossless = false;
   1444   }
   1445   opts->values.cparams.level = enc->codestream_level;
   1446   opts->values.cparams.ec_distance.resize(enc->metadata.m.num_extra_channels,
   1447                                           0);
   1448 
   1449   JxlEncoderFrameSettings* ret = opts.get();
   1450   enc->encoder_options.emplace_back(std::move(opts));
   1451   return ret;
   1452 }
   1453 
   1454 JxlEncoderStatus JxlEncoderSetFrameLossless(
   1455     JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) {
   1456   if (lossless && frame_settings->enc->basic_info_set &&
   1457       frame_settings->enc->metadata.m.xyb_encoded) {
   1458     return JXL_API_ERROR(
   1459         frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1460         "Set uses_original_profile=true for lossless encoding");
   1461   }
   1462   frame_settings->values.lossless = FROM_JXL_BOOL(lossless);
   1463   return JxlErrorOrStatus::Success();
   1464 }
   1465 
   1466 JxlEncoderStatus JxlEncoderSetFrameDistance(
   1467     JxlEncoderFrameSettings* frame_settings, float distance) {
   1468   if (distance < 0.f || distance > 25.f) {
   1469     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1470                          "Distance has to be in [0.0..25.0] (corresponding to "
   1471                          "quality in [0.0..100.0])");
   1472   }
   1473   if (distance > 0.f && distance < 0.01f) {
   1474     distance = 0.01f;
   1475   }
   1476   frame_settings->values.cparams.butteraugli_distance = distance;
   1477   return JxlErrorOrStatus::Success();
   1478 }
   1479 
   1480 JxlEncoderStatus JxlEncoderSetExtraChannelDistance(
   1481     JxlEncoderFrameSettings* frame_settings, size_t index, float distance) {
   1482   if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
   1483     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1484                          "Invalid value for the index of extra channel");
   1485   }
   1486   if (distance != -1.f && (distance < 0.f || distance > 25.f)) {
   1487     return JXL_API_ERROR(
   1488         frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1489         "Distance has to be -1 or in [0.0..25.0] (corresponding to "
   1490         "quality in [0.0..100.0])");
   1491   }
   1492   if (distance > 0.f && distance < 0.01f) {
   1493     distance = 0.01f;
   1494   }
   1495 
   1496   if (index >= frame_settings->values.cparams.ec_distance.size()) {
   1497     // This can only happen if JxlEncoderFrameSettingsCreate() was called before
   1498     // JxlEncoderSetBasicInfo().
   1499     frame_settings->values.cparams.ec_distance.resize(
   1500         frame_settings->enc->metadata.m.num_extra_channels, 0);
   1501   }
   1502 
   1503   frame_settings->values.cparams.ec_distance[index] = distance;
   1504   return JxlErrorOrStatus::Success();
   1505 }
   1506 
   1507 float JxlEncoderDistanceFromQuality(float quality) {
   1508   return quality >= 100.0 ? 0.0
   1509          : quality >= 30
   1510              ? 0.1 + (100 - quality) * 0.09
   1511              : 53.0 / 3000.0 * quality * quality - 23.0 / 20.0 * quality + 25.0;
   1512 }
   1513 
   1514 JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
   1515     JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
   1516     int64_t value) {
   1517   // check if value is -1, 0 or 1 for Override-type options
   1518   switch (option) {
   1519     case JXL_ENC_FRAME_SETTING_NOISE:
   1520     case JXL_ENC_FRAME_SETTING_DOTS:
   1521     case JXL_ENC_FRAME_SETTING_PATCHES:
   1522     case JXL_ENC_FRAME_SETTING_GABORISH:
   1523     case JXL_ENC_FRAME_SETTING_MODULAR:
   1524     case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
   1525     case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
   1526     case JXL_ENC_FRAME_SETTING_RESPONSIVE:
   1527     case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
   1528     case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
   1529     case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
   1530     case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
   1531     case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
   1532     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
   1533     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
   1534     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
   1535       if (value < -1 || value > 1) {
   1536         return JXL_API_ERROR(
   1537             frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1538             "Option value has to be -1 (default), 0 (off) or 1 (on)");
   1539       }
   1540       break;
   1541     default:
   1542       break;
   1543   }
   1544 
   1545   switch (option) {
   1546     case JXL_ENC_FRAME_SETTING_EFFORT:
   1547       if (frame_settings->enc->allow_expert_options) {
   1548         if (value < 1 || value > 11) {
   1549           return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1550                                "Encode effort has to be in [1..11]");
   1551         }
   1552       } else {
   1553         if (value < 1 || value > 10) {
   1554           return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1555                                "Encode effort has to be in [1..10]");
   1556         }
   1557       }
   1558       frame_settings->values.cparams.speed_tier =
   1559           static_cast<jxl::SpeedTier>(10 - value);
   1560       break;
   1561     case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
   1562       if (value < -1 || value > 11) {
   1563         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1564                              "Brotli effort has to be in [-1..11]");
   1565       }
   1566       // set cparams for brotli use in JPEG frames
   1567       frame_settings->values.cparams.brotli_effort = value;
   1568       // set enc option for brotli use in brob boxes
   1569       frame_settings->enc->brotli_effort = value;
   1570       break;
   1571     case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
   1572       if (value < 0 || value > 4) {
   1573         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1574                              "Decoding speed has to be in [0..4]");
   1575       }
   1576       frame_settings->values.cparams.decoding_speed_tier = value;
   1577       break;
   1578     case JXL_ENC_FRAME_SETTING_RESAMPLING:
   1579       if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) {
   1580         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1581                              "Resampling factor has to be 1, 2, 4 or 8");
   1582       }
   1583       frame_settings->values.cparams.resampling = value;
   1584       break;
   1585     case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING:
   1586       // TODO(lode): the jxl codestream allows choosing a different resampling
   1587       // factor for each extra channel, independently per frame. Move this
   1588       // option to a JxlEncoderFrameSettings-option that can be set per extra
   1589       // channel, so needs its own function rather than
   1590       // JxlEncoderFrameSettingsSetOption due to the extra channel index
   1591       // argument required.
   1592       if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) {
   1593         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1594                              "Resampling factor has to be 1, 2, 4 or 8");
   1595       }
   1596       frame_settings->values.cparams.ec_resampling = value;
   1597       break;
   1598     case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED:
   1599       if (value < 0 || value > 1) {
   1600         return JxlErrorOrStatus::Error();
   1601       }
   1602       frame_settings->values.cparams.already_downsampled = (value == 1);
   1603       break;
   1604     case JXL_ENC_FRAME_SETTING_NOISE:
   1605       frame_settings->values.cparams.noise = static_cast<jxl::Override>(value);
   1606       break;
   1607     case JXL_ENC_FRAME_SETTING_DOTS:
   1608       frame_settings->values.cparams.dots = static_cast<jxl::Override>(value);
   1609       break;
   1610     case JXL_ENC_FRAME_SETTING_PATCHES:
   1611       frame_settings->values.cparams.patches =
   1612           static_cast<jxl::Override>(value);
   1613       break;
   1614     case JXL_ENC_FRAME_SETTING_EPF:
   1615       if (value < -1 || value > 3) {
   1616         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1617                              "EPF value has to be in [-1..3]");
   1618       }
   1619       frame_settings->values.cparams.epf = static_cast<int>(value);
   1620       break;
   1621     case JXL_ENC_FRAME_SETTING_GABORISH:
   1622       frame_settings->values.cparams.gaborish =
   1623           static_cast<jxl::Override>(value);
   1624       break;
   1625     case JXL_ENC_FRAME_SETTING_MODULAR:
   1626       frame_settings->values.cparams.modular_mode = (value == 1);
   1627       break;
   1628     case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
   1629       frame_settings->values.cparams.keep_invisible =
   1630           static_cast<jxl::Override>(value);
   1631       break;
   1632     case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
   1633       frame_settings->values.cparams.centerfirst = (value == 1);
   1634       break;
   1635     case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X:
   1636       if (value < -1) {
   1637         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1638                              "Center x coordinate has to be -1 or positive");
   1639       }
   1640       frame_settings->values.cparams.center_x = static_cast<size_t>(value);
   1641       break;
   1642     case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y:
   1643       if (value < -1) {
   1644         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1645                              "Center y coordinate has to be -1 or positive");
   1646       }
   1647       frame_settings->values.cparams.center_y = static_cast<size_t>(value);
   1648       break;
   1649     case JXL_ENC_FRAME_SETTING_RESPONSIVE:
   1650       frame_settings->values.cparams.responsive = value;
   1651       break;
   1652     case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
   1653       frame_settings->values.cparams.progressive_mode =
   1654           static_cast<jxl::Override>(value);
   1655       break;
   1656     case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
   1657       frame_settings->values.cparams.qprogressive_mode =
   1658           static_cast<jxl::Override>(value);
   1659       break;
   1660     case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC:
   1661       if (value < -1 || value > 2) {
   1662         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1663                              "Progressive DC has to be in [-1..2]");
   1664       }
   1665       frame_settings->values.cparams.progressive_dc = value;
   1666       break;
   1667     case JXL_ENC_FRAME_SETTING_PALETTE_COLORS:
   1668       if (value < -1 || value > 70913) {
   1669         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1670                              "Option value has to be in [-1..70913]");
   1671       }
   1672       if (value == -1) {
   1673         frame_settings->values.cparams.palette_colors = 1 << 10;
   1674       } else {
   1675         frame_settings->values.cparams.palette_colors = value;
   1676       }
   1677       break;
   1678     case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
   1679       // TODO(lode): the defaults of some palette settings depend on others.
   1680       // See the logic in cjxl. Similar for other settings. This should be
   1681       // handled in the encoder during JxlEncoderProcessOutput (or,
   1682       // alternatively, in the cjxl binary like now)
   1683       frame_settings->values.cparams.lossy_palette = (value == 1);
   1684       break;
   1685     case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
   1686       if (value < -1 || value > 2) {
   1687         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1688                              "Option value has to be in [-1..2]");
   1689       }
   1690       if (value == -1) {
   1691         frame_settings->values.cparams.color_transform =
   1692             jxl::ColorTransform::kXYB;
   1693       } else {
   1694         frame_settings->values.cparams.color_transform =
   1695             static_cast<jxl::ColorTransform>(value);
   1696       }
   1697       break;
   1698     case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE:
   1699       if (value < -1 || value > 41) {
   1700         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1701                              "Option value has to be in [-1..41]");
   1702       }
   1703       frame_settings->values.cparams.colorspace = value;
   1704       break;
   1705     case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE:
   1706       if (value < -1 || value > 3) {
   1707         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1708                              "Option value has to be in [-1..3]");
   1709       }
   1710       frame_settings->values.cparams.modular_group_size_shift = value;
   1711       break;
   1712     case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR:
   1713       if (value < -1 || value > 15) {
   1714         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1715                              "Option value has to be in [-1..15]");
   1716       }
   1717       frame_settings->values.cparams.options.predictor =
   1718           static_cast<jxl::Predictor>(value);
   1719       break;
   1720     case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS:
   1721       // The max allowed value can in theory be higher. However, it depends on
   1722       // the effort setting. 11 is the highest safe value that doesn't cause
   1723       // tree_samples to be >= 64 in the encoder. The specification may allow
   1724       // more than this. With more fine tuning higher values could be allowed.
   1725       // For N-channel images, the largest useful value is N-1.
   1726       if (value < -1 || value > 11) {
   1727         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1728                              "Option value has to be in [-1..11]");
   1729       }
   1730       if (value == -1) {
   1731         frame_settings->values.cparams.options.max_properties = 0;
   1732       } else {
   1733         frame_settings->values.cparams.options.max_properties = value;
   1734       }
   1735       break;
   1736     case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
   1737       if (value == -1) {
   1738         frame_settings->values.cparams.force_cfl_jpeg_recompression = true;
   1739       } else {
   1740         frame_settings->values.cparams.force_cfl_jpeg_recompression = value;
   1741       }
   1742       break;
   1743     case JXL_ENC_FRAME_INDEX_BOX:
   1744       if (value < 0 || value > 1) {
   1745         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1746                              "Option value has to be 0 or 1");
   1747       }
   1748       frame_settings->values.frame_index_box = true;
   1749       break;
   1750     case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
   1751       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1752                            "Float option, try setting it with "
   1753                            "JxlEncoderFrameSettingsSetFloatOption");
   1754     case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
   1755       frame_settings->values.cparams.jpeg_compress_boxes = value;
   1756       break;
   1757     case JXL_ENC_FRAME_SETTING_BUFFERING:
   1758       if (value < -1 || value > 3) {
   1759         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1760                              "Buffering has to be in [-1..3]");
   1761       }
   1762       frame_settings->values.cparams.buffering = value;
   1763       break;
   1764     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
   1765       frame_settings->values.cparams.jpeg_keep_exif = value;
   1766       break;
   1767     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
   1768       frame_settings->values.cparams.jpeg_keep_xmp = value;
   1769       break;
   1770     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
   1771       frame_settings->values.cparams.jpeg_keep_jumbf = value;
   1772       break;
   1773     case JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS:
   1774       if (value < 0 || value > 1) {
   1775         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1776                              "Option value has to be 0 or 1");
   1777       }
   1778       frame_settings->values.cparams.use_full_image_heuristics = value;
   1779       break;
   1780 
   1781     default:
   1782       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1783                            "Unknown option");
   1784   }
   1785   return JxlErrorOrStatus::Success();
   1786 }
   1787 
   1788 JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption(
   1789     JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
   1790     float value) {
   1791   switch (option) {
   1792     case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
   1793       if (value < 0) return JXL_ENC_ERROR;
   1794       // TODO(lode): add encoder setting to set the 8 floating point values of
   1795       // the noise synthesis parameters per frame for more fine grained control.
   1796       frame_settings->values.cparams.photon_noise_iso = value;
   1797       return JxlErrorOrStatus::Success();
   1798     case JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT:
   1799       if (value < -1.f || value > 100.f) {
   1800         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1801                              "Option value has to be smaller than 100");
   1802       }
   1803       // This value is called "iterations" or "nb_repeats" in cjxl, but is in
   1804       // fact a fraction in range 0.0-1.0, with the default value 0.5.
   1805       // Convert from floating point percentage to floating point fraction here.
   1806       if (value < -.5f) {
   1807         // TODO(lode): for this and many other settings (also in
   1808         // JxlEncoderFrameSettingsSetOption), avoid duplicating the default
   1809         // values here and in enc_params.h and options.h, have one location
   1810         // where the defaults are specified.
   1811         frame_settings->values.cparams.options.nb_repeats = 0.5f;
   1812       } else {
   1813         frame_settings->values.cparams.options.nb_repeats = value * 0.01f;
   1814       }
   1815       return JxlErrorOrStatus::Success();
   1816     case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT:
   1817       if (value < -1.f || value > 100.f) {
   1818         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1819                              "Option value has to be in [-1..100]");
   1820       }
   1821       if (value < -.5f) {
   1822         frame_settings->values.cparams.channel_colors_pre_transform_percent =
   1823             95.0f;
   1824       } else {
   1825         frame_settings->values.cparams.channel_colors_pre_transform_percent =
   1826             value;
   1827       }
   1828       return JxlErrorOrStatus::Success();
   1829     case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT:
   1830       if (value < -1.f || value > 100.f) {
   1831         return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   1832                              "Option value has to be in [-1..100]");
   1833       }
   1834       if (value < -.5f) {
   1835         frame_settings->values.cparams.channel_colors_percent = 80.0f;
   1836       } else {
   1837         frame_settings->values.cparams.channel_colors_percent = value;
   1838       }
   1839       return JxlErrorOrStatus::Success();
   1840     case JXL_ENC_FRAME_SETTING_EFFORT:
   1841     case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
   1842     case JXL_ENC_FRAME_SETTING_RESAMPLING:
   1843     case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING:
   1844     case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED:
   1845     case JXL_ENC_FRAME_SETTING_NOISE:
   1846     case JXL_ENC_FRAME_SETTING_DOTS:
   1847     case JXL_ENC_FRAME_SETTING_PATCHES:
   1848     case JXL_ENC_FRAME_SETTING_EPF:
   1849     case JXL_ENC_FRAME_SETTING_GABORISH:
   1850     case JXL_ENC_FRAME_SETTING_MODULAR:
   1851     case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
   1852     case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
   1853     case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X:
   1854     case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y:
   1855     case JXL_ENC_FRAME_SETTING_RESPONSIVE:
   1856     case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
   1857     case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
   1858     case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC:
   1859     case JXL_ENC_FRAME_SETTING_PALETTE_COLORS:
   1860     case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
   1861     case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
   1862     case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE:
   1863     case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE:
   1864     case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR:
   1865     case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS:
   1866     case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
   1867     case JXL_ENC_FRAME_INDEX_BOX:
   1868     case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
   1869     case JXL_ENC_FRAME_SETTING_FILL_ENUM:
   1870     case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
   1871     case JXL_ENC_FRAME_SETTING_BUFFERING:
   1872     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
   1873     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
   1874     case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
   1875     case JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS:
   1876       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1877                            "Int option, try setting it with "
   1878                            "JxlEncoderFrameSettingsSetOption");
   1879     default:
   1880       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
   1881                            "Unknown option");
   1882   }
   1883 }
   1884 JxlEncoder* JxlEncoderCreate(const JxlMemoryManager* memory_manager) {
   1885   JxlMemoryManager local_memory_manager;
   1886   if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager)) {
   1887     return nullptr;
   1888   }
   1889 
   1890   void* alloc =
   1891       jxl::MemoryManagerAlloc(&local_memory_manager, sizeof(JxlEncoder));
   1892   if (!alloc) return nullptr;
   1893   JxlEncoder* enc = new (alloc) JxlEncoder();
   1894   enc->memory_manager = local_memory_manager;
   1895   // TODO(sboukortt): add an API function to set this.
   1896   enc->cms = *JxlGetDefaultCms();
   1897   enc->cms_set = true;
   1898 
   1899   // Initialize all the field values.
   1900   JxlEncoderReset(enc);
   1901 
   1902   return enc;
   1903 }
   1904 
   1905 void JxlEncoderReset(JxlEncoder* enc) {
   1906   enc->thread_pool.reset();
   1907   enc->input_queue.clear();
   1908   enc->num_queued_frames = 0;
   1909   enc->num_queued_boxes = 0;
   1910   enc->encoder_options.clear();
   1911   enc->codestream_bytes_written_end_of_frame = 0;
   1912   enc->wrote_bytes = false;
   1913   enc->jxlp_counter = 0;
   1914   enc->metadata = jxl::CodecMetadata();
   1915   enc->last_used_cparams = jxl::CompressParams();
   1916   enc->frames_closed = false;
   1917   enc->boxes_closed = false;
   1918   enc->basic_info_set = false;
   1919   enc->color_encoding_set = false;
   1920   enc->intensity_target_set = false;
   1921   enc->use_container = false;
   1922   enc->use_boxes = false;
   1923   enc->codestream_level = -1;
   1924   enc->output_processor = JxlEncoderOutputProcessorWrapper();
   1925   JxlEncoderInitBasicInfo(&enc->basic_info);
   1926 }
   1927 
   1928 void JxlEncoderDestroy(JxlEncoder* enc) {
   1929   if (enc) {
   1930     JxlMemoryManager local_memory_manager = enc->memory_manager;
   1931     // Call destructor directly since custom free function is used.
   1932     enc->~JxlEncoder();
   1933     jxl::MemoryManagerFree(&local_memory_manager, enc);
   1934   }
   1935 }
   1936 
   1937 JxlEncoderError JxlEncoderGetError(JxlEncoder* enc) { return enc->error; }
   1938 
   1939 JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc,
   1940                                         JXL_BOOL use_container) {
   1941   if (enc->wrote_bytes) {
   1942     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1943                          "this setting can only be set at the beginning");
   1944   }
   1945   enc->use_container = FROM_JXL_BOOL(use_container);
   1946   return JxlErrorOrStatus::Success();
   1947 }
   1948 
   1949 JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc,
   1950                                              JXL_BOOL store_jpeg_metadata) {
   1951   if (enc->wrote_bytes) {
   1952     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1953                          "this setting can only be set at the beginning");
   1954   }
   1955   enc->store_jpeg_metadata = FROM_JXL_BOOL(store_jpeg_metadata);
   1956   return JxlErrorOrStatus::Success();
   1957 }
   1958 
   1959 JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level) {
   1960   if (level != -1 && level != 5 && level != 10) {
   1961     return JXL_API_ERROR(enc, JXL_ENC_ERR_NOT_SUPPORTED, "invalid level");
   1962   }
   1963   if (enc->wrote_bytes) {
   1964     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1965                          "this setting can only be set at the beginning");
   1966   }
   1967   enc->codestream_level = level;
   1968   return JxlErrorOrStatus::Success();
   1969 }
   1970 
   1971 int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc) {
   1972   return VerifyLevelSettings(enc, nullptr);
   1973 }
   1974 
   1975 void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) {
   1976   jxl::msan::MemoryIsInitialized(&cms, sizeof(cms));
   1977   enc->cms = cms;
   1978   enc->cms_set = true;
   1979 }
   1980 
   1981 JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc,
   1982                                              JxlParallelRunner parallel_runner,
   1983                                              void* parallel_runner_opaque) {
   1984   if (enc->thread_pool) {
   1985     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   1986                          "parallel runner already set");
   1987   }
   1988   enc->thread_pool = jxl::MemoryManagerMakeUnique<jxl::ThreadPool>(
   1989       &enc->memory_manager, parallel_runner, parallel_runner_opaque);
   1990   if (!enc->thread_pool) {
   1991     return JXL_API_ERROR(enc, JXL_ENC_ERR_GENERIC,
   1992                          "error setting parallel runner");
   1993   }
   1994   return JxlErrorOrStatus::Success();
   1995 }
   1996 
   1997 namespace {
   1998 JxlEncoderStatus GetCurrentDimensions(
   1999     const JxlEncoderFrameSettings* frame_settings, size_t& xsize,
   2000     size_t& ysize) {
   2001   xsize = frame_settings->enc->metadata.xsize();
   2002   ysize = frame_settings->enc->metadata.ysize();
   2003   if (frame_settings->values.header.layer_info.have_crop) {
   2004     xsize = frame_settings->values.header.layer_info.xsize;
   2005     ysize = frame_settings->values.header.layer_info.ysize;
   2006   }
   2007   if (frame_settings->values.cparams.already_downsampled) {
   2008     size_t factor = frame_settings->values.cparams.resampling;
   2009     xsize = jxl::DivCeil(xsize, factor);
   2010     ysize = jxl::DivCeil(ysize, factor);
   2011   }
   2012   if (xsize == 0 || ysize == 0) {
   2013     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2014                          "zero-sized frame is not allowed");
   2015   }
   2016   return JxlErrorOrStatus::Success();
   2017 }
   2018 }  // namespace
   2019 
   2020 JxlEncoderStatus JxlEncoderAddJPEGFrame(
   2021     const JxlEncoderFrameSettings* frame_settings, const uint8_t* buffer,
   2022     size_t size) {
   2023   if (frame_settings->enc->frames_closed) {
   2024     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2025                          "Frame input is already closed");
   2026   }
   2027 
   2028   jxl::CodecInOut io;
   2029   if (!jxl::jpeg::DecodeImageJPG(jxl::Bytes(buffer, size), &io)) {
   2030     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT,
   2031                          "Error during decode of input JPEG");
   2032   }
   2033 
   2034   if (!frame_settings->enc->color_encoding_set) {
   2035     SetColorEncodingFromJpegData(
   2036         *io.Main().jpeg_data, &frame_settings->enc->metadata.m.color_encoding);
   2037     frame_settings->enc->color_encoding_set = true;
   2038   }
   2039 
   2040   if (!frame_settings->enc->basic_info_set) {
   2041     JxlBasicInfo basic_info;
   2042     JxlEncoderInitBasicInfo(&basic_info);
   2043     basic_info.xsize = io.Main().jpeg_data->width;
   2044     basic_info.ysize = io.Main().jpeg_data->height;
   2045     basic_info.uses_original_profile = JXL_TRUE;
   2046     if (JxlEncoderSetBasicInfo(frame_settings->enc, &basic_info) !=
   2047         JXL_ENC_SUCCESS) {
   2048       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2049                            "Error setting basic info");
   2050     }
   2051   }
   2052 
   2053   size_t xsize;
   2054   size_t ysize;
   2055   if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
   2056     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2057                          "bad dimensions");
   2058   }
   2059   if (xsize != static_cast<size_t>(io.Main().jpeg_data->width) ||
   2060       ysize != static_cast<size_t>(io.Main().jpeg_data->height)) {
   2061     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2062                          "JPEG dimensions don't match frame dimensions");
   2063   }
   2064 
   2065   if (frame_settings->enc->metadata.m.xyb_encoded) {
   2066     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2067                          "Can't XYB encode a lossless JPEG");
   2068   }
   2069   if (!io.blobs.exif.empty()) {
   2070     JxlOrientation orientation = static_cast<JxlOrientation>(
   2071         frame_settings->enc->metadata.m.orientation);
   2072     jxl::InterpretExif(io.blobs.exif, &orientation);
   2073     frame_settings->enc->metadata.m.orientation = orientation;
   2074   }
   2075   if (!io.blobs.exif.empty() && frame_settings->values.cparams.jpeg_keep_exif) {
   2076     size_t exif_size = io.blobs.exif.size();
   2077     // Exif data in JPEG is limited to 64k
   2078     if (exif_size > 0xFFFF) {
   2079       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2080                            "Exif larger than possible in JPEG?");
   2081     }
   2082     exif_size += 4;  // prefix 4 zero bytes for tiff offset
   2083     std::vector<uint8_t> exif(exif_size);
   2084     memcpy(exif.data() + 4, io.blobs.exif.data(), io.blobs.exif.size());
   2085     JxlEncoderUseBoxes(frame_settings->enc);
   2086     JxlEncoderAddBox(
   2087         frame_settings->enc, "Exif", exif.data(), exif_size,
   2088         TO_JXL_BOOL(frame_settings->values.cparams.jpeg_compress_boxes));
   2089   }
   2090   if (!io.blobs.xmp.empty() && frame_settings->values.cparams.jpeg_keep_xmp) {
   2091     JxlEncoderUseBoxes(frame_settings->enc);
   2092     JxlEncoderAddBox(
   2093         frame_settings->enc, "xml ", io.blobs.xmp.data(), io.blobs.xmp.size(),
   2094         TO_JXL_BOOL(frame_settings->values.cparams.jpeg_compress_boxes));
   2095   }
   2096   if (!io.blobs.jumbf.empty() &&
   2097       frame_settings->values.cparams.jpeg_keep_jumbf) {
   2098     JxlEncoderUseBoxes(frame_settings->enc);
   2099     JxlEncoderAddBox(
   2100         frame_settings->enc, "jumb", io.blobs.jumbf.data(),
   2101         io.blobs.jumbf.size(),
   2102         TO_JXL_BOOL(frame_settings->values.cparams.jpeg_compress_boxes));
   2103   }
   2104   if (frame_settings->enc->store_jpeg_metadata) {
   2105     if (!frame_settings->values.cparams.jpeg_keep_exif ||
   2106         !frame_settings->values.cparams.jpeg_keep_xmp) {
   2107       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2108                            "Need to preserve EXIF and XMP to allow JPEG "
   2109                            "bitstream reconstruction");
   2110     }
   2111     jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
   2112     std::vector<uint8_t> jpeg_data;
   2113     if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data,
   2114                                    frame_settings->values.cparams)) {
   2115       return JXL_API_ERROR(
   2116           frame_settings->enc, JXL_ENC_ERR_JBRD,
   2117           "JPEG bitstream reconstruction data cannot be encoded");
   2118     }
   2119     frame_settings->enc->jpeg_metadata = jpeg_data;
   2120   }
   2121 
   2122   jxl::JxlEncoderChunkedFrameAdapter frame_data(
   2123       xsize, ysize, frame_settings->enc->metadata.m.num_extra_channels);
   2124   frame_data.SetJPEGData(*io.Main().jpeg_data);
   2125 
   2126   auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
   2127       &frame_settings->enc->memory_manager,
   2128       // JxlEncoderQueuedFrame is a struct with no constructors, so we use the
   2129       // default move constructor there.
   2130       jxl::JxlEncoderQueuedFrame{
   2131           frame_settings->values, std::move(frame_data), {}});
   2132   if (!queued_frame) {
   2133     // TODO(jon): when can this happen? is this an API usage error?
   2134     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2135                          "No frame queued?");
   2136   }
   2137   queued_frame->ec_initialized.resize(
   2138       frame_settings->enc->metadata.m.num_extra_channels);
   2139 
   2140   QueueFrame(frame_settings, queued_frame);
   2141   return JxlErrorOrStatus::Success();
   2142 }
   2143 
   2144 static bool CanDoFastLossless(const JxlEncoderFrameSettings* frame_settings,
   2145                               const JxlPixelFormat* pixel_format,
   2146                               bool has_alpha) {
   2147   if (!frame_settings->values.lossless) {
   2148     return false;
   2149   }
   2150   // TODO(veluca): many of the following options could be made to work, but are
   2151   // just not implemented in FJXL's frame header handling yet.
   2152   if (frame_settings->values.frame_index_box) {
   2153     return false;
   2154   }
   2155   if (frame_settings->values.header.layer_info.have_crop) {
   2156     return false;
   2157   }
   2158   if (frame_settings->enc->metadata.m.have_animation) {
   2159     return false;
   2160   }
   2161   if (frame_settings->values.cparams.speed_tier != jxl::SpeedTier::kLightning) {
   2162     return false;
   2163   }
   2164   if (frame_settings->values.image_bit_depth.type ==
   2165           JxlBitDepthType::JXL_BIT_DEPTH_CUSTOM &&
   2166       frame_settings->values.image_bit_depth.bits_per_sample !=
   2167           frame_settings->enc->metadata.m.bit_depth.bits_per_sample) {
   2168     return false;
   2169   }
   2170   // TODO(veluca): implement support for LSB-padded input in fast_lossless.
   2171   if (frame_settings->values.image_bit_depth.type ==
   2172           JxlBitDepthType::JXL_BIT_DEPTH_FROM_PIXEL_FORMAT &&
   2173       frame_settings->values.image_bit_depth.bits_per_sample % 8 != 0) {
   2174     return false;
   2175   }
   2176   if (!frame_settings->values.frame_name.empty()) {
   2177     return false;
   2178   }
   2179   // No extra channels other than alpha.
   2180   if (!(has_alpha && frame_settings->enc->metadata.m.num_extra_channels == 1) &&
   2181       frame_settings->enc->metadata.m.num_extra_channels != 0) {
   2182     return false;
   2183   }
   2184   if (frame_settings->enc->metadata.m.bit_depth.bits_per_sample > 16) {
   2185     return false;
   2186   }
   2187   if (pixel_format->data_type != JxlDataType::JXL_TYPE_FLOAT16 &&
   2188       pixel_format->data_type != JxlDataType::JXL_TYPE_UINT16 &&
   2189       pixel_format->data_type != JxlDataType::JXL_TYPE_UINT8) {
   2190     return false;
   2191   }
   2192   if ((frame_settings->enc->metadata.m.bit_depth.bits_per_sample > 8) !=
   2193       (pixel_format->data_type == JxlDataType::JXL_TYPE_UINT16 ||
   2194        pixel_format->data_type == JxlDataType::JXL_TYPE_FLOAT16)) {
   2195     return false;
   2196   }
   2197   if (!((pixel_format->num_channels == 1 || pixel_format->num_channels == 3) &&
   2198         !has_alpha) &&
   2199       !((pixel_format->num_channels == 2 || pixel_format->num_channels == 4) &&
   2200         has_alpha)) {
   2201     return false;
   2202   }
   2203 
   2204   return true;
   2205 }
   2206 
   2207 namespace {
   2208 JxlEncoderStatus JxlEncoderAddImageFrameInternal(
   2209     const JxlEncoderFrameSettings* frame_settings, size_t xsize, size_t ysize,
   2210     bool streaming, jxl::JxlEncoderChunkedFrameAdapter frame_data) {
   2211   JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
   2212   {
   2213     JxlChunkedFrameInputSource input = frame_data.GetInputSource();
   2214     input.get_color_channels_pixel_format(input.opaque, &pixel_format);
   2215   }
   2216   uint32_t num_channels = pixel_format.num_channels;
   2217   size_t has_interleaved_alpha =
   2218       static_cast<size_t>(num_channels == 2 || num_channels == 4);
   2219 
   2220   if (!frame_settings->enc->basic_info_set) {
   2221     // Basic Info must be set. Otherwise, this is an API misuse.
   2222     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2223                          "Basic info or color encoding not set yet");
   2224   }
   2225   if (frame_settings->enc->frames_closed) {
   2226     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2227                          "Frame input already closed");
   2228   }
   2229   if (num_channels < 3) {
   2230     if (frame_settings->enc->basic_info.num_color_channels != 1) {
   2231       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2232                            "Grayscale pixel format input for an RGB image");
   2233     }
   2234   } else {
   2235     if (frame_settings->enc->basic_info.num_color_channels != 3) {
   2236       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2237                            "RGB pixel format input for a grayscale image");
   2238     }
   2239   }
   2240   if (frame_settings->values.lossless &&
   2241       frame_settings->enc->metadata.m.xyb_encoded) {
   2242     return JXL_API_ERROR(
   2243         frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2244         "Set uses_original_profile=true for lossless encoding");
   2245   }
   2246   if (JXL_ENC_SUCCESS !=
   2247       VerifyInputBitDepth(frame_settings->values.image_bit_depth,
   2248                           pixel_format)) {
   2249     return JXL_API_ERROR_NOSET("Invalid input bit depth");
   2250   }
   2251   if (has_interleaved_alpha >
   2252       frame_settings->enc->metadata.m.num_extra_channels) {
   2253     return JXL_API_ERROR(
   2254         frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2255         "number of extra channels mismatch (need 1 extra channel for alpha)");
   2256   }
   2257 
   2258   bool has_alpha = frame_settings->enc->metadata.m.HasAlpha();
   2259 
   2260   // All required conditions to do fast-lossless.
   2261   if (CanDoFastLossless(frame_settings, &pixel_format, has_alpha)) {
   2262     const bool big_endian =
   2263         pixel_format.endianness == JXL_BIG_ENDIAN ||
   2264         (pixel_format.endianness == JXL_NATIVE_ENDIAN && !IsLittleEndian());
   2265 
   2266     auto runner = +[](void* void_pool, void* opaque, void fun(void*, size_t),
   2267                       size_t count) {
   2268       auto* pool = reinterpret_cast<jxl::ThreadPool*>(void_pool);
   2269       JXL_CHECK(jxl::RunOnPool(
   2270           pool, 0, count, jxl::ThreadPool::NoInit,
   2271           [&](size_t i, size_t) { fun(opaque, i); }, "Encode fast lossless"));
   2272     };
   2273     JXL_BOOL oneshot = TO_JXL_BOOL(!frame_data.StreamingInput());
   2274     auto* frame_state = JxlFastLosslessPrepareFrame(
   2275         frame_data.GetInputSource(), xsize, ysize, num_channels,
   2276         frame_settings->enc->metadata.m.bit_depth.bits_per_sample,
   2277         TO_JXL_BOOL(big_endian),
   2278         /*effort=*/2, oneshot);
   2279     if (!streaming) {
   2280       JxlFastLosslessProcessFrame(frame_state, /*is_last=*/false,
   2281                                   frame_settings->enc->thread_pool.get(),
   2282                                   runner, nullptr);
   2283     }
   2284     QueueFastLosslessFrame(frame_settings, frame_state);
   2285     return JxlErrorOrStatus::Success();
   2286   }
   2287 
   2288   if (!streaming) {
   2289     // The input callbacks are only guaranteed to be available during frame
   2290     // encoding when both the input and the output is streaming. In all other
   2291     // cases we need to create an internal copy of the frame data.
   2292     if (!frame_data.CopyBuffers()) {
   2293       return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2294                            "Invalid chunked frame input source");
   2295     }
   2296   }
   2297 
   2298   if (!frame_settings->enc->color_encoding_set) {
   2299     jxl::ColorEncoding c_current;
   2300     if ((pixel_format.data_type == JXL_TYPE_FLOAT) ||
   2301         (pixel_format.data_type == JXL_TYPE_FLOAT16)) {
   2302       c_current = jxl::ColorEncoding::LinearSRGB(num_channels < 3);
   2303     } else {
   2304       c_current = jxl::ColorEncoding::SRGB(num_channels < 3);
   2305     }
   2306     frame_settings->enc->metadata.m.color_encoding = c_current;
   2307   }
   2308 
   2309   auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
   2310       &frame_settings->enc->memory_manager,
   2311       // JxlEncoderQueuedFrame is a struct with no constructors, so we use the
   2312       // default move constructor there.
   2313       jxl::JxlEncoderQueuedFrame{
   2314           frame_settings->values, std::move(frame_data), {}});
   2315 
   2316   if (!queued_frame) {
   2317     // TODO(jon): when can this happen? is this an API usage error?
   2318     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2319                          "No frame queued?");
   2320   }
   2321 
   2322   for (auto& ec_info : frame_settings->enc->metadata.m.extra_channel_info) {
   2323     if (has_interleaved_alpha && ec_info.type == jxl::ExtraChannel::kAlpha) {
   2324       queued_frame->ec_initialized.push_back(1);
   2325       has_interleaved_alpha = 0;  // only first Alpha is initialized
   2326     } else {
   2327       queued_frame->ec_initialized.push_back(0);
   2328     }
   2329   }
   2330   queued_frame->option_values.cparams.level =
   2331       frame_settings->enc->codestream_level;
   2332 
   2333   QueueFrame(frame_settings, queued_frame);
   2334   return JxlErrorOrStatus::Success();
   2335 }
   2336 }  // namespace
   2337 
   2338 JxlEncoderStatus JxlEncoderAddImageFrame(
   2339     const JxlEncoderFrameSettings* frame_settings,
   2340     const JxlPixelFormat* pixel_format, const void* buffer, size_t size) {
   2341   size_t xsize;
   2342   size_t ysize;
   2343   if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
   2344     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2345                          "bad dimensions");
   2346   }
   2347   jxl::JxlEncoderChunkedFrameAdapter frame_data(
   2348       xsize, ysize, frame_settings->enc->metadata.m.num_extra_channels);
   2349   if (!frame_data.SetFromBuffer(0, reinterpret_cast<const uint8_t*>(buffer),
   2350                                 size, *pixel_format)) {
   2351     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2352                          "provided image buffer too small");
   2353   }
   2354   return JxlEncoderAddImageFrameInternal(frame_settings, xsize, ysize,
   2355                                          /*streaming=*/false,
   2356                                          std::move(frame_data));
   2357 }
   2358 
   2359 JxlEncoderStatus JxlEncoderAddChunkedFrame(
   2360     const JxlEncoderFrameSettings* frame_settings, JXL_BOOL is_last_frame,
   2361     JxlChunkedFrameInputSource chunked_frame_input) {
   2362   size_t xsize;
   2363   size_t ysize;
   2364   if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
   2365     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
   2366                          "bad dimensions");
   2367   }
   2368   bool streaming = frame_settings->enc->output_processor.OutputProcessorSet();
   2369   jxl::JxlEncoderChunkedFrameAdapter frame_data(
   2370       xsize, ysize, frame_settings->enc->metadata.m.num_extra_channels);
   2371   frame_data.SetInputSource(chunked_frame_input);
   2372   auto status = JxlEncoderAddImageFrameInternal(frame_settings, xsize, ysize,
   2373                                                 streaming, frame_data);
   2374   if (status != JXL_ENC_SUCCESS) return status;
   2375 
   2376   auto& queued_frame = frame_settings->enc->input_queue.back();
   2377   if (queued_frame.frame) {
   2378     for (auto& val : queued_frame.frame->ec_initialized) val = 1;
   2379   }
   2380 
   2381   if (is_last_frame) {
   2382     JxlEncoderCloseInput(frame_settings->enc);
   2383   }
   2384   if (streaming) {
   2385     return JxlEncoderFlushInput(frame_settings->enc);
   2386   }
   2387   return JxlErrorOrStatus::Success();
   2388 }
   2389 
   2390 JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc) {
   2391   if (enc->wrote_bytes) {
   2392     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2393                          "this setting can only be set at the beginning");
   2394   }
   2395   enc->use_boxes = true;
   2396   return JxlErrorOrStatus::Success();
   2397 }
   2398 
   2399 JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, const JxlBoxType type,
   2400                                   const uint8_t* contents, size_t size,
   2401                                   JXL_BOOL compress_box) {
   2402   if (!enc->use_boxes) {
   2403     return JXL_API_ERROR(
   2404         enc, JXL_ENC_ERR_API_USAGE,
   2405         "must set JxlEncoderUseBoxes at the beginning to add boxes");
   2406   }
   2407   if (enc->boxes_closed) {
   2408     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2409                          "Box input already closed");
   2410   }
   2411   if (compress_box) {
   2412     if (memcmp("jxl", type, 3) == 0) {
   2413       return JXL_API_ERROR(
   2414           enc, JXL_ENC_ERR_API_USAGE,
   2415           "brob box may not contain a type starting with \"jxl\"");
   2416     }
   2417     if (memcmp("jbrd", type, 4) == 0) {
   2418       return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2419                            "jbrd box may not be brob compressed");
   2420     }
   2421     if (memcmp("brob", type, 4) == 0) {
   2422       // The compress_box will compress an existing non-brob box into a brob
   2423       // box. If already giving a valid brotli-compressed brob box, set
   2424       // compress_box to false since it is already compressed.
   2425       return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2426                            "a brob box cannot contain another brob box");
   2427     }
   2428   }
   2429 
   2430   auto box = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedBox>(
   2431       &enc->memory_manager);
   2432 
   2433   box->type = jxl::MakeBoxType(type);
   2434   box->contents.assign(contents, contents + size);
   2435   box->compress_box = FROM_JXL_BOOL(compress_box);
   2436   QueueBox(enc, box);
   2437   return JxlErrorOrStatus::Success();
   2438 }
   2439 
   2440 JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
   2441     const JxlEncoderFrameSettings* frame_settings,
   2442     const JxlPixelFormat* pixel_format, const void* buffer, size_t size,
   2443     uint32_t index) {
   2444   if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
   2445     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2446                          "Invalid value for the index of extra channel");
   2447   }
   2448   if (!frame_settings->enc->basic_info_set ||
   2449       !frame_settings->enc->color_encoding_set) {
   2450     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2451                          "Basic info has to be set first");
   2452   }
   2453   if (frame_settings->enc->input_queue.empty()) {
   2454     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2455                          "First add image frame, then extra channels");
   2456   }
   2457   if (frame_settings->enc->frames_closed) {
   2458     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2459                          "Frame input already closed");
   2460   }
   2461   JxlPixelFormat ec_format = *pixel_format;
   2462   ec_format.num_channels = 1;
   2463   if (JXL_ENC_SUCCESS !=
   2464       VerifyInputBitDepth(frame_settings->values.image_bit_depth, ec_format)) {
   2465     return JXL_API_ERROR_NOSET("Invalid input bit depth");
   2466   }
   2467   const uint8_t* uint8_buffer = reinterpret_cast<const uint8_t*>(buffer);
   2468   auto* queued_frame = frame_settings->enc->input_queue.back().frame.get();
   2469   if (!queued_frame->frame_data.SetFromBuffer(1 + index, uint8_buffer, size,
   2470                                               ec_format)) {
   2471     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2472                          "provided image buffer too small");
   2473   }
   2474   queued_frame->ec_initialized[index] = 1;
   2475 
   2476   return JxlErrorOrStatus::Success();
   2477 }
   2478 
   2479 void JxlEncoderCloseFrames(JxlEncoder* enc) { enc->frames_closed = true; }
   2480 
   2481 void JxlEncoderCloseBoxes(JxlEncoder* enc) { enc->boxes_closed = true; }
   2482 
   2483 void JxlEncoderCloseInput(JxlEncoder* enc) {
   2484   JxlEncoderCloseFrames(enc);
   2485   JxlEncoderCloseBoxes(enc);
   2486 }
   2487 
   2488 JXL_EXPORT JxlEncoderStatus JxlEncoderFlushInput(JxlEncoder* enc) {
   2489   if (!enc->output_processor.OutputProcessorSet()) {
   2490     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2491                          "Cannot flush input without setting output "
   2492                          "processor with JxlEncoderSetOutputProcessor");
   2493   }
   2494   while (!enc->input_queue.empty()) {
   2495     if (!enc->ProcessOneEnqueuedInput()) {
   2496       return JxlErrorOrStatus::Error();
   2497     }
   2498   }
   2499   return JxlErrorOrStatus::Success();
   2500 }
   2501 
   2502 JXL_EXPORT JxlEncoderStatus JxlEncoderSetOutputProcessor(
   2503     JxlEncoder* enc, JxlEncoderOutputProcessor output_processor) {
   2504   if (enc->output_processor.HasAvailOut()) {
   2505     return JXL_API_ERROR(
   2506         enc, JXL_ENC_ERR_API_USAGE,
   2507         "Cannot set an output processor when some output was already produced");
   2508   }
   2509   if (!output_processor.set_finalized_position ||
   2510       !output_processor.get_buffer || !output_processor.release_buffer) {
   2511     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2512                          "Missing output processor functions");
   2513   }
   2514   enc->output_processor = JxlEncoderOutputProcessorWrapper(output_processor);
   2515   return JxlErrorOrStatus::Success();
   2516 }
   2517 
   2518 JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out,
   2519                                          size_t* avail_out) {
   2520   if (!enc->output_processor.SetAvailOut(next_out, avail_out)) {
   2521     return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
   2522                          "Cannot call JxlEncoderProcessOutput after calling "
   2523                          "JxlEncoderSetOutputProcessor");
   2524   }
   2525   while (*avail_out != 0 && !enc->input_queue.empty()) {
   2526     if (!enc->ProcessOneEnqueuedInput()) {
   2527       return JxlErrorOrStatus::Error();
   2528     }
   2529   }
   2530 
   2531   if (!enc->input_queue.empty() || enc->output_processor.HasOutputToWrite()) {
   2532     return JxlErrorOrStatus::MoreOutput();
   2533   }
   2534   return JxlErrorOrStatus::Success();
   2535 }
   2536 
   2537 JxlEncoderStatus JxlEncoderSetFrameHeader(
   2538     JxlEncoderFrameSettings* frame_settings,
   2539     const JxlFrameHeader* frame_header) {
   2540   if (frame_header->layer_info.blend_info.source > 3) {
   2541     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2542                          "invalid blending source index");
   2543   }
   2544   // If there are no extra channels, it's ok for the value to be 0.
   2545   if (frame_header->layer_info.blend_info.alpha != 0 &&
   2546       frame_header->layer_info.blend_info.alpha >=
   2547           frame_settings->enc->metadata.m.extra_channel_info.size()) {
   2548     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2549                          "alpha blend channel index out of bounds");
   2550   }
   2551 
   2552   frame_settings->values.header = *frame_header;
   2553   // Setting the frame header resets the frame name, it must be set again with
   2554   // JxlEncoderSetFrameName if desired.
   2555   frame_settings->values.frame_name = "";
   2556 
   2557   return JxlErrorOrStatus::Success();
   2558 }
   2559 
   2560 JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo(
   2561     JxlEncoderFrameSettings* frame_settings, size_t index,
   2562     const JxlBlendInfo* blend_info) {
   2563   if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
   2564     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2565                          "Invalid value for the index of extra channel");
   2566   }
   2567 
   2568   if (frame_settings->values.extra_channel_blend_info.size() !=
   2569       frame_settings->enc->metadata.m.num_extra_channels) {
   2570     JxlBlendInfo default_blend_info;
   2571     JxlEncoderInitBlendInfo(&default_blend_info);
   2572     frame_settings->values.extra_channel_blend_info.resize(
   2573         frame_settings->enc->metadata.m.num_extra_channels, default_blend_info);
   2574   }
   2575   frame_settings->values.extra_channel_blend_info[index] = *blend_info;
   2576   return JxlErrorOrStatus::Success();
   2577 }
   2578 
   2579 JxlEncoderStatus JxlEncoderSetFrameName(JxlEncoderFrameSettings* frame_settings,
   2580                                         const char* frame_name) {
   2581   std::string str = frame_name ? frame_name : "";
   2582   if (str.size() > 1071) {
   2583     return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
   2584                          "frame name can be max 1071 bytes long");
   2585   }
   2586   frame_settings->values.frame_name = str;
   2587   frame_settings->values.header.name_length = str.size();
   2588   return JxlErrorOrStatus::Success();
   2589 }
   2590 
   2591 JxlEncoderStatus JxlEncoderSetFrameBitDepth(
   2592     JxlEncoderFrameSettings* frame_settings, const JxlBitDepth* bit_depth) {
   2593   if (bit_depth->type != JXL_BIT_DEPTH_FROM_PIXEL_FORMAT &&
   2594       bit_depth->type != JXL_BIT_DEPTH_FROM_CODESTREAM) {
   2595     return JXL_API_ERROR_NOSET(
   2596         "Only JXL_BIT_DEPTH_FROM_PIXEL_FORMAT and "
   2597         "JXL_BIT_DEPTH_FROM_CODESTREAM is implemented "
   2598         "for input buffers.");
   2599   }
   2600   frame_settings->values.image_bit_depth = *bit_depth;
   2601   return JxlErrorOrStatus::Success();
   2602 }
   2603 
   2604 void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding,
   2605                                JXL_BOOL is_gray) {
   2606   *color_encoding =
   2607       jxl::ColorEncoding::SRGB(FROM_JXL_BOOL(is_gray)).ToExternal();
   2608 }
   2609 
   2610 void JxlColorEncodingSetToLinearSRGB(JxlColorEncoding* color_encoding,
   2611                                      JXL_BOOL is_gray) {
   2612   *color_encoding =
   2613       jxl::ColorEncoding::LinearSRGB(FROM_JXL_BOOL(is_gray)).ToExternal();
   2614 }
   2615 
   2616 void JxlEncoderAllowExpertOptions(JxlEncoder* enc) {
   2617   enc->allow_expert_options = true;
   2618 }
   2619 
   2620 JXL_EXPORT void JxlEncoderSetDebugImageCallback(
   2621     JxlEncoderFrameSettings* frame_settings, JxlDebugImageCallback callback,
   2622     void* opaque) {
   2623   frame_settings->values.cparams.debug_image = callback;
   2624   frame_settings->values.cparams.debug_image_opaque = opaque;
   2625 }
   2626 
   2627 JXL_EXPORT JxlEncoderStats* JxlEncoderStatsCreate() {
   2628   return new JxlEncoderStats();
   2629 }
   2630 
   2631 JXL_EXPORT void JxlEncoderStatsDestroy(JxlEncoderStats* stats) { delete stats; }
   2632 
   2633 JXL_EXPORT void JxlEncoderCollectStats(JxlEncoderFrameSettings* frame_settings,
   2634                                        JxlEncoderStats* stats) {
   2635   if (!stats) return;
   2636   frame_settings->values.aux_out = &stats->aux_out;
   2637 }
   2638 
   2639 JXL_EXPORT size_t JxlEncoderStatsGet(const JxlEncoderStats* stats,
   2640                                      JxlEncoderStatsKey key) {
   2641   if (!stats) return 0;
   2642   const jxl::AuxOut& aux_out = stats->aux_out;
   2643   switch (key) {
   2644     case JXL_ENC_STAT_HEADER_BITS:
   2645       return aux_out.layers[jxl::kLayerHeader].total_bits;
   2646     case JXL_ENC_STAT_TOC_BITS:
   2647       return aux_out.layers[jxl::kLayerTOC].total_bits;
   2648     case JXL_ENC_STAT_DICTIONARY_BITS:
   2649       return aux_out.layers[jxl::kLayerDictionary].total_bits;
   2650     case JXL_ENC_STAT_SPLINES_BITS:
   2651       return aux_out.layers[jxl::kLayerSplines].total_bits;
   2652     case JXL_ENC_STAT_NOISE_BITS:
   2653       return aux_out.layers[jxl::kLayerNoise].total_bits;
   2654     case JXL_ENC_STAT_QUANT_BITS:
   2655       return aux_out.layers[jxl::kLayerQuant].total_bits;
   2656     case JXL_ENC_STAT_MODULAR_TREE_BITS:
   2657       return aux_out.layers[jxl::kLayerModularTree].total_bits;
   2658     case JXL_ENC_STAT_MODULAR_GLOBAL_BITS:
   2659       return aux_out.layers[jxl::kLayerModularGlobal].total_bits;
   2660     case JXL_ENC_STAT_DC_BITS:
   2661       return aux_out.layers[jxl::kLayerDC].total_bits;
   2662     case JXL_ENC_STAT_MODULAR_DC_GROUP_BITS:
   2663       return aux_out.layers[jxl::kLayerModularDcGroup].total_bits;
   2664     case JXL_ENC_STAT_CONTROL_FIELDS_BITS:
   2665       return aux_out.layers[jxl::kLayerControlFields].total_bits;
   2666     case JXL_ENC_STAT_COEF_ORDER_BITS:
   2667       return aux_out.layers[jxl::kLayerOrder].total_bits;
   2668     case JXL_ENC_STAT_AC_HISTOGRAM_BITS:
   2669       return aux_out.layers[jxl::kLayerAC].total_bits;
   2670     case JXL_ENC_STAT_AC_BITS:
   2671       return aux_out.layers[jxl::kLayerACTokens].total_bits;
   2672     case JXL_ENC_STAT_MODULAR_AC_GROUP_BITS:
   2673       return aux_out.layers[jxl::kLayerModularAcGroup].total_bits;
   2674     case JXL_ENC_STAT_NUM_SMALL_BLOCKS:
   2675       return aux_out.num_small_blocks;
   2676     case JXL_ENC_STAT_NUM_DCT4X8_BLOCKS:
   2677       return aux_out.num_dct4x8_blocks;
   2678     case JXL_ENC_STAT_NUM_AFV_BLOCKS:
   2679       return aux_out.num_afv_blocks;
   2680     case JXL_ENC_STAT_NUM_DCT8_BLOCKS:
   2681       return aux_out.num_dct8_blocks;
   2682     case JXL_ENC_STAT_NUM_DCT8X32_BLOCKS:
   2683       return aux_out.num_dct16_blocks;
   2684     case JXL_ENC_STAT_NUM_DCT16_BLOCKS:
   2685       return aux_out.num_dct16x32_blocks;
   2686     case JXL_ENC_STAT_NUM_DCT16X32_BLOCKS:
   2687       return aux_out.num_dct32_blocks;
   2688     case JXL_ENC_STAT_NUM_DCT32_BLOCKS:
   2689       return aux_out.num_dct32x64_blocks;
   2690     case JXL_ENC_STAT_NUM_DCT32X64_BLOCKS:
   2691       return aux_out.num_dct32x64_blocks;
   2692     case JXL_ENC_STAT_NUM_DCT64_BLOCKS:
   2693       return aux_out.num_dct64_blocks;
   2694     case JXL_ENC_STAT_NUM_BUTTERAUGLI_ITERS:
   2695       return aux_out.num_butteraugli_iters;
   2696     default:
   2697       return 0;
   2698   }
   2699 }
   2700 
   2701 JXL_EXPORT void JxlEncoderStatsMerge(JxlEncoderStats* stats,
   2702                                      const JxlEncoderStats* other) {
   2703   if (!stats || !other) return;
   2704   stats->aux_out.Assimilate(other->aux_out);
   2705 }