Coverage Report

Created: 2026-11-12 07:35

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