Coverage Report

Created: 2024-05-21 06:24

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