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