Coverage Report

Created: 2025-08-12 07:37

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