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 }