Coverage Report

Created: 2022-08-24 06:33

/src/libjxl/lib/jxl/encode.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include "jxl/encode.h"
7
8
#include <brotli/encode.h>
9
10
#include <algorithm>
11
#include <cstddef>
12
#include <cstring>
13
14
#include "jxl/codestream_header.h"
15
#include "jxl/types.h"
16
#include "lib/jxl/aux_out.h"
17
#include "lib/jxl/base/byte_order.h"
18
#include "lib/jxl/base/span.h"
19
#include "lib/jxl/codec_in_out.h"
20
#include "lib/jxl/enc_color_management.h"
21
#include "lib/jxl/enc_external_image.h"
22
#include "lib/jxl/enc_file.h"
23
#include "lib/jxl/enc_icc_codec.h"
24
#include "lib/jxl/encode_internal.h"
25
#include "lib/jxl/exif.h"
26
#include "lib/jxl/jpeg/enc_jpeg_data.h"
27
#include "lib/jxl/sanitizers.h"
28
29
// Debug-printing failure macro similar to JXL_FAILURE, but for the status code
30
// JXL_ENC_ERROR
31
#ifdef JXL_CRASH_ON_ERROR
32
#define JXL_API_ERROR(enc, error_code, format, ...)                          \
33
  (enc->error = error_code,                                                  \
34
   ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
35
   ::jxl::Abort(), JXL_ENC_ERROR)
36
#define JXL_API_ERROR_NOSET(format, ...)                                     \
37
  (::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
38
   ::jxl::Abort(), JXL_ENC_ERROR)
39
#else  // JXL_CRASH_ON_ERROR
40
#define JXL_API_ERROR(enc, error_code, format, ...)                            \
41
0
  (enc->error = error_code,                                                    \
42
0
   ((JXL_DEBUG_ON_ERROR) &&                                                    \
43
0
    ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__)), \
44
0
   JXL_ENC_ERROR)
45
#define JXL_API_ERROR_NOSET(format, ...)                                     \
46
0
  (::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
47
0
   JXL_ENC_ERROR)
48
#endif  // JXL_CRASH_ON_ERROR
49
50
namespace jxl {}  // namespace jxl
51
52
0
uint32_t JxlEncoderVersion(void) {
53
0
  return JPEGXL_MAJOR_VERSION * 1000000 + JPEGXL_MINOR_VERSION * 1000 +
54
0
         JPEGXL_PATCH_VERSION;
55
0
}
56
57
namespace {
58
template <typename T>
59
0
void AppendJxlpBoxCounter(uint32_t counter, bool last, T* output) {
60
0
  if (last) counter |= 0x80000000;
61
0
  for (size_t i = 0; i < 4; i++) {
62
0
    output->push_back(counter >> (8 * (3 - i)) & 0xff);
63
0
  }
64
0
}
65
66
void QueueFrame(
67
    const JxlEncoderFrameSettings* frame_settings,
68
92
    jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedFrame>& frame) {
69
92
  if (frame_settings->values.lossless) {
70
0
    frame->option_values.cparams.SetLossless();
71
0
  }
72
73
92
  jxl::JxlEncoderQueuedInput queued_input(frame_settings->enc->memory_manager);
74
92
  queued_input.frame = std::move(frame);
75
92
  frame_settings->enc->input_queue.emplace_back(std::move(queued_input));
76
92
  frame_settings->enc->num_queued_frames++;
77
92
}
78
79
void QueueBox(JxlEncoder* enc,
80
0
              jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedBox>& box) {
81
0
  jxl::JxlEncoderQueuedInput queued_input(enc->memory_manager);
82
0
  queued_input.box = std::move(box);
83
0
  enc->input_queue.emplace_back(std::move(queued_input));
84
0
  enc->num_queued_boxes++;
85
0
}
86
87
// TODO(lode): share this code and the Brotli compression code in enc_jpeg_data
88
JxlEncoderStatus BrotliCompress(int quality, const uint8_t* in, size_t in_size,
89
0
                                jxl::PaddedBytes* out) {
90
0
  std::unique_ptr<BrotliEncoderState, decltype(BrotliEncoderDestroyInstance)*>
91
0
      enc(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
92
0
          BrotliEncoderDestroyInstance);
93
0
  if (!enc) return JXL_API_ERROR_NOSET("BrotliEncoderCreateInstance failed");
94
95
0
  BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_QUALITY, quality);
96
0
  BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_SIZE_HINT, in_size);
97
98
0
  constexpr size_t kBufferSize = 128 * 1024;
99
0
  jxl::PaddedBytes temp_buffer(kBufferSize);
100
101
0
  size_t avail_in = in_size;
102
0
  const uint8_t* next_in = in;
103
104
0
  size_t total_out = 0;
105
106
0
  for (;;) {
107
0
    size_t avail_out = kBufferSize;
108
0
    uint8_t* next_out = temp_buffer.data();
109
0
    jxl::msan::MemoryIsInitialized(next_in, avail_in);
110
0
    if (!BrotliEncoderCompressStream(enc.get(), BROTLI_OPERATION_FINISH,
111
0
                                     &avail_in, &next_in, &avail_out, &next_out,
112
0
                                     &total_out)) {
113
0
      return JXL_API_ERROR_NOSET("Brotli compression failed");
114
0
    }
115
0
    size_t out_size = next_out - temp_buffer.data();
116
0
    jxl::msan::UnpoisonMemory(next_out - out_size, out_size);
117
0
    out->resize(out->size() + out_size);
118
0
    memcpy(out->data() + out->size() - out_size, temp_buffer.data(), out_size);
119
0
    if (BrotliEncoderIsFinished(enc.get())) break;
120
0
  }
121
122
0
  return JXL_ENC_SUCCESS;
123
0
}
124
125
// The JXL codestream can have level 5 or level 10. Levels have certain
126
// restrictions such as max allowed image dimensions. This function checks the
127
// level required to support the current encoder settings. The debug_string is
128
// intended to be used for developer API error messages, and may be set to
129
// nullptr.
130
184
int VerifyLevelSettings(const JxlEncoder* enc, std::string* debug_string) {
131
184
  const auto& m = enc->metadata.m;
132
133
184
  uint64_t xsize = enc->metadata.size.xsize();
134
184
  uint64_t ysize = enc->metadata.size.ysize();
135
  // The uncompressed ICC size, if it is used.
136
184
  size_t icc_size = 0;
137
184
  if (m.color_encoding.WantICC()) {
138
0
    icc_size = m.color_encoding.ICC().size();
139
0
  }
140
141
  // Level 10 checks
142
143
184
  if (xsize > (1ull << 30ull) || ysize > (1ull << 30ull) ||
144
184
      xsize * ysize > (1ull << 40ull)) {
145
0
    if (debug_string) *debug_string = "Too large image dimensions";
146
0
    return -1;
147
0
  }
148
184
  if (icc_size > (1ull << 28)) {
149
0
    if (debug_string) *debug_string = "Too large ICC profile size";
150
0
    return -1;
151
0
  }
152
184
  if (m.num_extra_channels > 256) {
153
0
    if (debug_string) *debug_string = "Too many extra channels";
154
0
    return -1;
155
0
  }
156
157
  // Level 5 checks
158
159
184
  if (!m.modular_16_bit_buffer_sufficient) {
160
0
    if (debug_string) *debug_string = "Too high modular bit depth";
161
0
    return 10;
162
0
  }
163
184
  if (xsize > (1ull << 18ull) || ysize > (1ull << 18ull) ||
164
184
      xsize * ysize > (1ull << 28ull)) {
165
0
    if (debug_string) *debug_string = "Too large image dimensions";
166
0
    return 10;
167
0
  }
168
184
  if (icc_size > (1ull << 22)) {
169
0
    if (debug_string) *debug_string = "Too large ICC profile";
170
0
    return 10;
171
0
  }
172
184
  if (m.num_extra_channels > 4) {
173
0
    if (debug_string) *debug_string = "Too many extra channels";
174
0
    return 10;
175
0
  }
176
184
  for (size_t i = 0; i < m.extra_channel_info.size(); ++i) {
177
0
    if (m.extra_channel_info[i].type == jxl::ExtraChannel::kBlack) {
178
0
      if (debug_string) *debug_string = "CMYK channel not allowed";
179
0
      return 10;
180
0
    }
181
0
  }
182
183
  // TODO(lode): also need to check if consecutive composite-still frames total
184
  // pixel amount doesn't exceed 2**28 in the case of level 5. This should be
185
  // done when adding frame and requires ability to add composite still frames
186
  // to be added first.
187
188
  // TODO(lode): also need to check animation duration of a frame. This should
189
  // be done when adding frame, but first requires implementing setting the
190
  // JxlFrameHeader for a frame.
191
192
  // TODO(lode): also need to check properties such as num_splines, num_patches,
193
  // modular_16bit_buffers and multiple properties of modular trees. However
194
  // these are not user-set properties so cannot be checked here, but decisions
195
  // the C++ encoder should be able to make based on the level.
196
197
  // All level 5 checks passes, so can return the more compatible level 5
198
184
  return 5;
199
184
}
200
JxlEncoderStatus CheckValidBitdepth(uint32_t bits_per_sample,
201
92
                                    uint32_t exponent_bits_per_sample) {
202
92
  if (!exponent_bits_per_sample) {
203
    // The spec allows up to 31 for bits_per_sample here, but
204
    // the code does not (yet) support it.
205
92
    if (!(bits_per_sample > 0 && bits_per_sample <= 24)) {
206
0
      return JXL_API_ERROR_NOSET("Invalid value for bits_per_sample");
207
0
    }
208
92
  } else if ((exponent_bits_per_sample > 8) ||
209
0
             (bits_per_sample > 24 + exponent_bits_per_sample) ||
210
0
             (bits_per_sample < 3 + exponent_bits_per_sample)) {
211
0
    return JXL_API_ERROR_NOSET("Invalid float description");
212
0
  }
213
92
  return JXL_ENC_SUCCESS;
214
92
}
215
216
bool EncodeFrameIndexBox(const jxl::JxlEncoderFrameIndexBox& frame_index_box,
217
0
                         jxl::BitWriter& writer) {
218
0
  bool ok = true;
219
0
  int NF = 0;
220
0
  for (size_t i = 0; i < frame_index_box.entries.size(); ++i) {
221
0
    if (i == 0 || frame_index_box.entries[i].to_be_indexed) {
222
0
      ++NF;
223
0
    }
224
0
  }
225
  // Frame index box contents varint + 8 bytes
226
  // continue with NF * 3 * varint
227
  // varint max length is 10 for 64 bit numbers, and these numbers
228
  // are limited to 63 bits.
229
0
  static const int kVarintMaxLength = 10;
230
0
  static const int kFrameIndexBoxHeaderLength = kVarintMaxLength + 8;
231
0
  static const int kFrameIndexBoxElementLength = 3 * kVarintMaxLength;
232
0
  const int buffer_size =
233
0
      kFrameIndexBoxHeaderLength + NF * kFrameIndexBoxElementLength;
234
0
  std::vector<uint8_t> buffer_vec(buffer_size);
235
0
  uint8_t* buffer = buffer_vec.data();
236
0
  size_t output_pos = 0;
237
0
  ok &= jxl::EncodeVarInt(NF, buffer_vec.size(), &output_pos, buffer);
238
0
  StoreBE32(frame_index_box.TNUM, &buffer[output_pos]);
239
0
  output_pos += 4;
240
0
  StoreBE32(frame_index_box.TDEN, &buffer[output_pos]);
241
0
  output_pos += 4;
242
  // When we record a frame in the index, the record needs to know
243
  // how many frames until the next indexed frame. That is why
244
  // we store the 'prev' record. That 'prev' record needs to store
245
  // the offset byte position to previously recorded indexed frame,
246
  // that's why we also trace previous to the previous frame.
247
0
  int prev_prev_ix = -1;  // For position offset (OFFi) delta coding.
248
0
  int prev_ix = 0;
249
0
  int T_prev = 0;
250
0
  int T = 0;
251
0
  for (size_t i = 1; i < frame_index_box.entries.size(); ++i) {
252
0
    if (frame_index_box.entries[i].to_be_indexed) {
253
      // Now we can record the previous entry, since we need to store
254
      // there how many frames until the next one.
255
0
      int64_t OFFi = frame_index_box.entries[prev_ix].OFFi;
256
0
      if (prev_prev_ix != -1) {
257
        // Offi needs to be offset of start byte of this frame compared to start
258
        // byte of previous frame from this index in the JPEG XL codestream. For
259
        // the first frame, this is the offset from the first byte of the JPEG
260
        // XL codestream.
261
0
        OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
262
0
      }
263
0
      int32_t Ti = T_prev;
264
0
      int32_t Fi = i - prev_ix;
265
0
      ok &= jxl::EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
266
0
      ok &= jxl::EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
267
0
      ok &= jxl::EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
268
0
      prev_prev_ix = prev_ix;
269
0
      prev_ix = i;
270
0
      T_prev = T;
271
0
      T += frame_index_box.entries[i].duration;
272
0
    }
273
0
  }
274
0
  {
275
    // Last frame.
276
0
    size_t i = frame_index_box.entries.size();
277
0
    int64_t OFFi = frame_index_box.entries[prev_ix].OFFi;
278
0
    if (prev_prev_ix != -1) {
279
0
      OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
280
0
    }
281
0
    int32_t Ti = T_prev;
282
0
    int32_t Fi = i - prev_ix;
283
0
    ok &= jxl::EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
284
0
    ok &= jxl::EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
285
0
    ok &= jxl::EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
286
0
  }
287
  // Enough buffer has been allocated, this function should never fail in
288
  // writing.
289
0
  JXL_ASSERT(ok);
290
0
  return ok;
291
0
}
292
293
}  // namespace
294
295
92
JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
296
92
  jxl::PaddedBytes bytes;
297
298
92
  jxl::JxlEncoderQueuedInput& input = input_queue[0];
299
300
  // TODO(lode): split this into 3 functions: for adding the signature and other
301
  // initial headers (jbrd, ...), one for adding frame, and one for adding user
302
  // box.
303
304
92
  if (!wrote_bytes) {
305
    // First time encoding any data, verify the level 5 vs level 10 settings
306
92
    std::string level_message;
307
92
    int required_level = VerifyLevelSettings(this, &level_message);
308
    // Only level 5 and 10 are defined, and the function can return -1 to
309
    // indicate full incompatibility.
310
92
    JXL_ASSERT(required_level == -1 || required_level == 5 ||
311
92
               required_level == 10);
312
    // codestream_level == -1 means auto-set to the required level
313
92
    if (codestream_level == -1) codestream_level = required_level;
314
92
    if (codestream_level == 5 && required_level != 5) {
315
      // If the required level is 10, return error rather than automatically
316
      // setting the level to 10, to avoid inadvertently creating a level 10
317
      // JXL file while intending to target a level 5 decoder.
318
0
      return JXL_API_ERROR(
319
0
          this, JXL_ENC_ERR_API_USAGE, "%s",
320
0
          ("Codestream level verification for level 5 failed: " + level_message)
321
0
              .c_str());
322
0
    }
323
92
    if (required_level == -1) {
324
0
      return JXL_API_ERROR(
325
0
          this, JXL_ENC_ERR_API_USAGE, "%s",
326
0
          ("Codestream level verification for level 10 failed: " +
327
0
           level_message)
328
0
              .c_str());
329
0
    }
330
331
92
    jxl::BitWriter writer;
332
92
    if (!WriteHeaders(&metadata, &writer, nullptr)) {
333
0
      return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
334
0
                           "Failed to write codestream header");
335
0
    }
336
    // Only send ICC (at least several hundred bytes) if fields aren't enough.
337
92
    if (metadata.m.color_encoding.WantICC()) {
338
0
      if (!jxl::WriteICC(metadata.m.color_encoding.ICC(), &writer,
339
0
                         jxl::kLayerHeader, nullptr)) {
340
0
        return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
341
0
                             "Failed to write ICC profile");
342
0
      }
343
0
    }
344
    // TODO(lode): preview should be added here if a preview image is added
345
346
92
    writer.ZeroPadToByte();
347
348
    // Not actually the end of frame, but the end of metadata/ICC, but helps
349
    // the next frame to start here for indexing purposes.
350
92
    codestream_bytes_written_end_of_frame +=
351
92
        jxl::DivCeil(writer.BitsWritten(), 8);
352
353
92
    bytes = std::move(writer).TakeBytes();
354
355
92
    if (MustUseContainer()) {
356
      // Add "JXL " and ftyp box.
357
0
      output_byte_queue.insert(
358
0
          output_byte_queue.end(), jxl::kContainerHeader,
359
0
          jxl::kContainerHeader + sizeof(jxl::kContainerHeader));
360
0
      if (codestream_level != 5) {
361
        // Add jxll box directly after the ftyp box to indicate the codestream
362
        // level.
363
0
        output_byte_queue.insert(
364
0
            output_byte_queue.end(), jxl::kLevelBoxHeader,
365
0
            jxl::kLevelBoxHeader + sizeof(jxl::kLevelBoxHeader));
366
0
        output_byte_queue.push_back(codestream_level);
367
0
      }
368
369
      // Whether to write the basic info and color profile header of the
370
      // codestream into an early separate jxlp box, so that it comes before
371
      // metadata or jpeg reconstruction boxes. In theory this could simply
372
      // always be done, but there's no reason to add an extra box with box
373
      // header overhead if the codestream will already come immediately after
374
      // the signature and level boxes.
375
0
      bool partial_header = store_jpeg_metadata || (use_boxes && !input.frame);
376
377
0
      if (partial_header) {
378
0
        jxl::AppendBoxHeader(jxl::MakeBoxType("jxlp"), bytes.size() + 4,
379
0
                             /*unbounded=*/false, &output_byte_queue);
380
0
        AppendJxlpBoxCounter(jxlp_counter++, /*last=*/false,
381
0
                             &output_byte_queue);
382
0
        output_byte_queue.insert(output_byte_queue.end(), bytes.data(),
383
0
                                 bytes.data() + bytes.size());
384
0
        bytes.clear();
385
0
      }
386
387
0
      if (store_jpeg_metadata && !jpeg_metadata.empty()) {
388
0
        jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_metadata.size(),
389
0
                             false, &output_byte_queue);
390
0
        output_byte_queue.insert(output_byte_queue.end(), jpeg_metadata.begin(),
391
0
                                 jpeg_metadata.end());
392
0
      }
393
0
    }
394
92
    wrote_bytes = true;
395
92
  }
396
397
  // Choose frame or box processing: exactly one of the two unique pointers (box
398
  // or frame) in the input queue item is non-null.
399
92
  if (input.frame) {
400
92
    jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedFrame> input_frame =
401
92
        std::move(input.frame);
402
92
    input_queue.erase(input_queue.begin());
403
92
    num_queued_frames--;
404
92
    for (unsigned idx = 0; idx < input_frame->ec_initialized.size(); idx++) {
405
0
      if (!input_frame->ec_initialized[idx]) {
406
0
        return JXL_API_ERROR(this, JXL_ENC_ERR_API_USAGE,
407
0
                             "Extra channel %u is not initialized", idx);
408
0
      }
409
0
    }
410
411
    // TODO(zond): If the input queue is empty and the frames_closed is true,
412
    // then mark this frame as the last.
413
414
    // TODO(zond): Handle progressive mode like EncodeFile does it.
415
    // TODO(zond): Handle animation like EncodeFile does it, by checking if
416
    //             JxlEncoderCloseFrames has been called and if the frame queue
417
    //             is empty (to see if it's the last animation frame).
418
419
92
    if (metadata.m.xyb_encoded) {
420
92
      input_frame->option_values.cparams.color_transform =
421
92
          jxl::ColorTransform::kXYB;
422
92
    } else {
423
      // TODO(zond): Figure out when to use kYCbCr instead.
424
0
      input_frame->option_values.cparams.color_transform =
425
0
          jxl::ColorTransform::kNone;
426
0
    }
427
428
92
    jxl::BitWriter writer;
429
92
    jxl::PassesEncoderState enc_state;
430
431
    // EncodeFrame creates jxl::FrameHeader object internally based on the
432
    // FrameInfo, imagebundle, cparams and metadata. Copy the information to
433
    // these.
434
92
    jxl::ImageBundle& ib = input_frame->frame;
435
92
    ib.name = input_frame->option_values.frame_name;
436
92
    if (metadata.m.have_animation) {
437
0
      ib.duration = input_frame->option_values.header.duration;
438
0
      ib.timecode = input_frame->option_values.header.timecode;
439
92
    } else {
440
      // If have_animation is false, the encoder should ignore the duration and
441
      // timecode values. However, assigning them to ib will cause the encoder
442
      // to write an invalid frame header that can't be decoded so ensure
443
      // they're the default value of 0 here.
444
92
      ib.duration = 0;
445
92
      ib.timecode = 0;
446
92
    }
447
92
    frame_index_box.AddFrame(codestream_bytes_written_end_of_frame, ib.duration,
448
92
                             input_frame->option_values.frame_index_box);
449
92
    ib.blendmode = static_cast<jxl::BlendMode>(
450
92
        input_frame->option_values.header.layer_info.blend_info.blendmode);
451
92
    ib.blend =
452
92
        input_frame->option_values.header.layer_info.blend_info.blendmode !=
453
92
        JXL_BLEND_REPLACE;
454
455
92
    size_t save_as_reference =
456
92
        input_frame->option_values.header.layer_info.save_as_reference;
457
92
    ib.use_for_next_frame = !!save_as_reference;
458
459
92
    jxl::FrameInfo frame_info;
460
92
    bool last_frame = frames_closed && !num_queued_frames;
461
92
    frame_info.is_last = last_frame;
462
92
    frame_info.save_as_reference = save_as_reference;
463
92
    frame_info.source =
464
92
        input_frame->option_values.header.layer_info.blend_info.source;
465
92
    frame_info.clamp =
466
92
        input_frame->option_values.header.layer_info.blend_info.clamp;
467
92
    frame_info.alpha_channel =
468
92
        input_frame->option_values.header.layer_info.blend_info.alpha;
469
92
    frame_info.extra_channel_blending_info.resize(
470
92
        metadata.m.num_extra_channels);
471
    // If extra channel blend info has not been set, use the blend mode from the
472
    // layer_info.
473
92
    JxlBlendInfo default_blend_info =
474
92
        input_frame->option_values.header.layer_info.blend_info;
475
92
    for (size_t i = 0; i < metadata.m.num_extra_channels; ++i) {
476
0
      auto& to = frame_info.extra_channel_blending_info[i];
477
0
      const auto& from =
478
0
          i < input_frame->option_values.extra_channel_blend_info.size()
479
0
              ? input_frame->option_values.extra_channel_blend_info[i]
480
0
              : default_blend_info;
481
0
      to.mode = static_cast<jxl::BlendMode>(from.blendmode);
482
0
      to.source = from.source;
483
0
      to.alpha_channel = from.alpha;
484
0
      to.clamp = (from.clamp != 0);
485
0
    }
486
487
92
    if (input_frame->option_values.header.layer_info.have_crop) {
488
0
      ib.origin.x0 = input_frame->option_values.header.layer_info.crop_x0;
489
0
      ib.origin.y0 = input_frame->option_values.header.layer_info.crop_y0;
490
0
    }
491
92
    JXL_ASSERT(writer.BitsWritten() == 0);
492
92
    if (!jxl::EncodeFrame(input_frame->option_values.cparams, frame_info,
493
92
                          &metadata, input_frame->frame, &enc_state, cms,
494
92
                          thread_pool.get(), &writer,
495
92
                          /*aux_out=*/nullptr)) {
496
0
      return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC, "Failed to encode frame");
497
0
    }
498
92
    codestream_bytes_written_beginning_of_frame =
499
92
        codestream_bytes_written_end_of_frame;
500
92
    codestream_bytes_written_end_of_frame +=
501
92
        jxl::DivCeil(writer.BitsWritten(), 8);
502
503
    // Possibly bytes already contains the codestream header: in case this is
504
    // the first frame, and the codestream header was not encoded as jxlp above.
505
92
    bytes.append(std::move(writer).TakeBytes());
506
92
    if (MustUseContainer()) {
507
0
      if (last_frame && jxlp_counter == 0) {
508
        // If this is the last frame and no jxlp boxes were used yet, it's
509
        // slighly more efficient to write a jxlc box since it has 4 bytes less
510
        // overhead.
511
0
        jxl::AppendBoxHeader(jxl::MakeBoxType("jxlc"), bytes.size(),
512
0
                             /*unbounded=*/false, &output_byte_queue);
513
0
      } else {
514
0
        jxl::AppendBoxHeader(jxl::MakeBoxType("jxlp"), bytes.size() + 4,
515
0
                             /*unbounded=*/false, &output_byte_queue);
516
0
        AppendJxlpBoxCounter(jxlp_counter++, last_frame, &output_byte_queue);
517
0
      }
518
0
    }
519
520
92
    output_byte_queue.insert(output_byte_queue.end(), bytes.data(),
521
92
                             bytes.data() + bytes.size());
522
523
92
    last_used_cparams = input_frame->option_values.cparams;
524
92
    if (last_frame && frame_index_box.StoreFrameIndexBox()) {
525
0
      bytes.clear();
526
0
      EncodeFrameIndexBox(frame_index_box, writer);
527
0
      jxl::AppendBoxHeader(jxl::MakeBoxType("jxli"), bytes.size(),
528
0
                           /*unbounded=*/false, &output_byte_queue);
529
0
    }
530
92
  } else {
531
    // Not a frame, so is a box instead
532
0
    jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedBox> box =
533
0
        std::move(input.box);
534
0
    input_queue.erase(input_queue.begin());
535
0
    num_queued_boxes--;
536
537
0
    if (box->compress_box) {
538
0
      jxl::PaddedBytes compressed(4);
539
      // Prepend the original box type in the brob box contents
540
0
      for (size_t i = 0; i < 4; i++) {
541
0
        compressed[i] = static_cast<uint8_t>(box->type[i]);
542
0
      }
543
0
      if (JXL_ENC_SUCCESS !=
544
0
          BrotliCompress((brotli_effort >= 0 ? brotli_effort : 4),
545
0
                         box->contents.data(), box->contents.size(),
546
0
                         &compressed)) {
547
0
        return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
548
0
                             "Brotli compression for brob box failed");
549
0
      }
550
0
      jxl::AppendBoxHeader(jxl::MakeBoxType("brob"), compressed.size(), false,
551
0
                           &output_byte_queue);
552
0
      output_byte_queue.insert(output_byte_queue.end(), compressed.data(),
553
0
                               compressed.data() + compressed.size());
554
0
    } else {
555
0
      jxl::AppendBoxHeader(box->type, box->contents.size(), false,
556
0
                           &output_byte_queue);
557
0
      output_byte_queue.insert(output_byte_queue.end(), box->contents.data(),
558
0
                               box->contents.data() + box->contents.size());
559
0
    }
560
0
  }
561
562
92
  return JXL_ENC_SUCCESS;
563
92
}
564
565
JxlEncoderStatus JxlEncoderSetColorEncoding(JxlEncoder* enc,
566
92
                                            const JxlColorEncoding* color) {
567
92
  if (!enc->basic_info_set) {
568
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Basic info not yet set");
569
0
  }
570
92
  if (enc->color_encoding_set) {
571
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
572
0
                         "Color encoding is already set");
573
0
  }
574
92
  if (!jxl::ConvertExternalToInternalColorEncoding(
575
92
          *color, &enc->metadata.m.color_encoding)) {
576
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_GENERIC, "Error in color conversion");
577
0
  }
578
92
  if (enc->metadata.m.color_encoding.GetColorSpace() ==
579
92
      jxl::ColorSpace::kGray) {
580
0
    if (enc->basic_info.num_color_channels != 1)
581
0
      return JXL_API_ERROR(
582
0
          enc, JXL_ENC_ERR_API_USAGE,
583
0
          "Cannot use grayscale color encoding with num_color_channels != 1");
584
92
  } else {
585
92
    if (enc->basic_info.num_color_channels != 3)
586
0
      return JXL_API_ERROR(
587
92
          enc, JXL_ENC_ERR_API_USAGE,
588
92
          "Cannot use RGB color encoding with num_color_channels != 3");
589
92
  }
590
92
  enc->color_encoding_set = true;
591
92
  if (!enc->intensity_target_set) {
592
0
    jxl::SetIntensityTarget(&enc->metadata.m);
593
0
  }
594
92
  return JXL_ENC_SUCCESS;
595
92
}
596
597
JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc,
598
                                         const uint8_t* icc_profile,
599
0
                                         size_t size) {
600
0
  if (!enc->basic_info_set) {
601
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Basic info not yet set");
602
0
  }
603
0
  if (enc->color_encoding_set) {
604
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
605
0
                         "ICC profile is already set");
606
0
  }
607
0
  jxl::PaddedBytes icc;
608
0
  icc.assign(icc_profile, icc_profile + size);
609
0
  if (!enc->metadata.m.color_encoding.SetICC(std::move(icc))) {
610
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT,
611
0
                         "ICC profile could not be set");
612
0
  }
613
0
  if (enc->metadata.m.color_encoding.GetColorSpace() ==
614
0
      jxl::ColorSpace::kGray) {
615
0
    if (enc->basic_info.num_color_channels != 1)
616
0
      return JXL_API_ERROR(
617
0
          enc, JXL_ENC_ERR_BAD_INPUT,
618
0
          "Cannot use grayscale ICC profile with num_color_channels != 1");
619
0
  } else {
620
0
    if (enc->basic_info.num_color_channels != 3)
621
0
      return JXL_API_ERROR(
622
0
          enc, JXL_ENC_ERR_BAD_INPUT,
623
0
          "Cannot use RGB ICC profile with num_color_channels != 3");
624
    // TODO(jon): also check that a kBlack extra channel is provided in the CMYK
625
    // case
626
0
  }
627
0
  enc->color_encoding_set = true;
628
0
  if (!enc->intensity_target_set) {
629
0
    jxl::SetIntensityTarget(&enc->metadata.m);
630
0
  }
631
632
0
  if (!enc->basic_info.uses_original_profile) {
633
0
    enc->metadata.m.color_encoding.DecideIfWantICC();
634
0
  }
635
636
0
  return JXL_ENC_SUCCESS;
637
0
}
638
639
230
void JxlEncoderInitBasicInfo(JxlBasicInfo* info) {
640
230
  info->have_container = JXL_FALSE;
641
230
  info->xsize = 0;
642
230
  info->ysize = 0;
643
230
  info->bits_per_sample = 8;
644
230
  info->exponent_bits_per_sample = 0;
645
230
  info->intensity_target = 0.f;
646
230
  info->min_nits = 0.f;
647
230
  info->relative_to_max_display = JXL_FALSE;
648
230
  info->linear_below = 0.f;
649
230
  info->uses_original_profile = JXL_FALSE;
650
230
  info->have_preview = JXL_FALSE;
651
230
  info->have_animation = JXL_FALSE;
652
230
  info->orientation = JXL_ORIENT_IDENTITY;
653
230
  info->num_color_channels = 3;
654
230
  info->num_extra_channels = 0;
655
230
  info->alpha_bits = 0;
656
230
  info->alpha_exponent_bits = 0;
657
230
  info->alpha_premultiplied = JXL_FALSE;
658
230
  info->preview.xsize = 0;
659
230
  info->preview.ysize = 0;
660
230
  info->intrinsic_xsize = 0;
661
230
  info->intrinsic_ysize = 0;
662
230
  info->animation.tps_numerator = 10;
663
230
  info->animation.tps_denominator = 1;
664
230
  info->animation.num_loops = 0;
665
230
  info->animation.have_timecodes = JXL_FALSE;
666
230
}
667
668
92
void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header) {
669
  // For each field, the default value of the specification is used. Depending
670
  // on wheter an animation frame, or a composite still blending frame, is used,
671
  // different fields have to be set up by the user after initing the frame
672
  // header.
673
92
  frame_header->duration = 0;
674
92
  frame_header->timecode = 0;
675
92
  frame_header->name_length = 0;
676
  // In the specification, the default value of is_last is !frame_type, and the
677
  // default frame_type is kRegularFrame which has value 0, so is_last is true
678
  // by default. However, the encoder does not use this value (the field exists
679
  // for the decoder to set) since last frame is determined by usage of
680
  // JxlEncoderCloseFrames instead.
681
92
  frame_header->is_last = JXL_TRUE;
682
92
  frame_header->layer_info.have_crop = JXL_FALSE;
683
92
  frame_header->layer_info.crop_x0 = 0;
684
92
  frame_header->layer_info.crop_y0 = 0;
685
  // These must be set if have_crop is enabled, but the default value has
686
  // have_crop false, and these dimensions 0. The user must set these to the
687
  // desired size after enabling have_crop (which is not yet implemented).
688
92
  frame_header->layer_info.xsize = 0;
689
92
  frame_header->layer_info.ysize = 0;
690
92
  JxlEncoderInitBlendInfo(&frame_header->layer_info.blend_info);
691
92
  frame_header->layer_info.save_as_reference = 0;
692
92
}
693
694
92
void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info) {
695
  // Default blend mode in the specification is 0. Note that combining
696
  // blend mode of replace with a duration is not useful, but the user has to
697
  // manually set duration in case of animation, or manually change the blend
698
  // mode in case of composite stills, so initing to a combination that is not
699
  // useful on its own is not an issue.
700
92
  blend_info->blendmode = JXL_BLEND_REPLACE;
701
92
  blend_info->source = 0;
702
92
  blend_info->alpha = 0;
703
92
  blend_info->clamp = 0;
704
92
}
705
706
JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
707
92
                                        const JxlBasicInfo* info) {
708
92
  if (!enc->metadata.size.Set(info->xsize, info->ysize)) {
709
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid dimensions");
710
0
  }
711
92
  if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
712
92
                                            info->exponent_bits_per_sample)) {
713
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
714
0
  }
715
92
  enc->metadata.m.bit_depth.bits_per_sample = info->bits_per_sample;
716
92
  enc->metadata.m.bit_depth.exponent_bits_per_sample =
717
92
      info->exponent_bits_per_sample;
718
92
  enc->metadata.m.bit_depth.floating_point_sample =
719
92
      (info->exponent_bits_per_sample != 0u);
720
92
  enc->metadata.m.modular_16_bit_buffer_sufficient =
721
92
      (!info->uses_original_profile || info->bits_per_sample <= 12) &&
722
92
      info->alpha_bits <= 12;
723
724
  // The number of extra channels includes the alpha channel, so for example and
725
  // RGBA with no other extra channels, has exactly num_extra_channels == 1
726
92
  enc->metadata.m.num_extra_channels = info->num_extra_channels;
727
92
  enc->metadata.m.extra_channel_info.resize(enc->metadata.m.num_extra_channels);
728
92
  if (info->num_extra_channels == 0 && info->alpha_bits) {
729
0
    return JXL_API_ERROR(
730
0
        enc, JXL_ENC_ERR_API_USAGE,
731
0
        "when alpha_bits is non-zero, the number of channels must be at least "
732
0
        "1");
733
0
  }
734
  // If the user provides non-zero alpha_bits, we make the channel info at index
735
  // zero the appropriate alpha channel.
736
92
  if (info->alpha_bits) {
737
0
    JxlExtraChannelInfo channel_info;
738
0
    JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &channel_info);
739
0
    channel_info.bits_per_sample = info->alpha_bits;
740
0
    channel_info.exponent_bits_per_sample = info->alpha_exponent_bits;
741
0
    if (JxlEncoderSetExtraChannelInfo(enc, 0, &channel_info)) {
742
0
      return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
743
0
                           "Problem setting extra channel info for alpha");
744
0
    }
745
0
  }
746
747
92
  enc->metadata.m.xyb_encoded = !info->uses_original_profile;
748
92
  if (info->orientation > 0 && info->orientation <= 8) {
749
92
    enc->metadata.m.orientation = info->orientation;
750
92
  } else {
751
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
752
0
                         "Invalid value for orientation field");
753
0
  }
754
92
  if (info->num_color_channels != 1 && info->num_color_channels != 3) {
755
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
756
0
                         "Invalid number of color channels");
757
0
  }
758
92
  if (info->intensity_target != 0) {
759
0
    enc->metadata.m.SetIntensityTarget(info->intensity_target);
760
0
    enc->intensity_target_set = true;
761
92
  } else if (enc->color_encoding_set || enc->metadata.m.xyb_encoded) {
762
    // If both conditions are false, JxlEncoderSetColorEncoding will be called
763
    // later and we will get one more chance to call jxl::SetIntensityTarget,
764
    // after the color encoding is indeed set.
765
92
    jxl::SetIntensityTarget(&enc->metadata.m);
766
92
    enc->intensity_target_set = true;
767
92
  }
768
92
  enc->metadata.m.tone_mapping.min_nits = info->min_nits;
769
92
  enc->metadata.m.tone_mapping.relative_to_max_display =
770
92
      info->relative_to_max_display;
771
92
  enc->metadata.m.tone_mapping.linear_below = info->linear_below;
772
92
  enc->basic_info = *info;
773
92
  enc->basic_info_set = true;
774
775
92
  enc->metadata.m.have_animation = info->have_animation;
776
92
  if (info->have_animation) {
777
0
    if (info->animation.tps_denominator < 1) {
778
0
      return JXL_API_ERROR(
779
0
          enc, JXL_ENC_ERR_API_USAGE,
780
0
          "If animation is used, tps_denominator must be >= 1");
781
0
    }
782
0
    if (info->animation.tps_numerator < 1) {
783
0
      return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
784
0
                           "If animation is used, tps_numerator must be >= 1");
785
0
    }
786
0
    enc->metadata.m.animation.tps_numerator = info->animation.tps_numerator;
787
0
    enc->metadata.m.animation.tps_denominator = info->animation.tps_denominator;
788
0
    enc->metadata.m.animation.num_loops = info->animation.num_loops;
789
0
    enc->metadata.m.animation.have_timecodes = info->animation.have_timecodes;
790
0
  }
791
92
  std::string level_message;
792
92
  int required_level = VerifyLevelSettings(enc, &level_message);
793
92
  if (required_level == -1 ||
794
92
      (static_cast<int>(enc->codestream_level) < required_level &&
795
92
       enc->codestream_level != -1)) {
796
0
    return JXL_API_ERROR(
797
0
        enc, JXL_ENC_ERR_API_USAGE, "%s",
798
0
        ("Codestream level verification for level " +
799
0
         std::to_string(enc->codestream_level) + " failed: " + level_message)
800
0
            .c_str());
801
0
  }
802
92
  return JXL_ENC_SUCCESS;
803
92
}
804
805
void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type,
806
0
                                    JxlExtraChannelInfo* info) {
807
0
  info->type = type;
808
0
  info->bits_per_sample = 8;
809
0
  info->exponent_bits_per_sample = 0;
810
0
  info->dim_shift = 0;
811
0
  info->name_length = 0;
812
0
  info->alpha_premultiplied = JXL_FALSE;
813
0
  info->spot_color[0] = 0;
814
0
  info->spot_color[1] = 0;
815
0
  info->spot_color[2] = 0;
816
0
  info->spot_color[3] = 0;
817
0
  info->cfa_channel = 0;
818
0
}
819
820
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
821
0
    JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info) {
822
0
  if (index >= enc->metadata.m.num_extra_channels) {
823
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
824
0
                         "Invalid value for the index of extra channel");
825
0
  }
826
0
  if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
827
0
                                            info->exponent_bits_per_sample)) {
828
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
829
0
  }
830
831
0
  jxl::ExtraChannelInfo& channel = enc->metadata.m.extra_channel_info[index];
832
0
  channel.type = static_cast<jxl::ExtraChannel>(info->type);
833
0
  channel.bit_depth.bits_per_sample = info->bits_per_sample;
834
0
  enc->metadata.m.modular_16_bit_buffer_sufficient &=
835
0
      info->bits_per_sample <= 12;
836
0
  channel.bit_depth.exponent_bits_per_sample = info->exponent_bits_per_sample;
837
0
  channel.bit_depth.floating_point_sample = info->exponent_bits_per_sample != 0;
838
0
  channel.dim_shift = info->dim_shift;
839
0
  channel.name = "";
840
0
  channel.alpha_associated = (info->alpha_premultiplied != 0);
841
0
  channel.cfa_channel = info->cfa_channel;
842
0
  channel.spot_color[0] = info->spot_color[0];
843
0
  channel.spot_color[1] = info->spot_color[1];
844
0
  channel.spot_color[2] = info->spot_color[2];
845
0
  channel.spot_color[3] = info->spot_color[3];
846
0
  std::string level_message;
847
0
  int required_level = VerifyLevelSettings(enc, &level_message);
848
0
  if (required_level == -1 ||
849
0
      (static_cast<int>(enc->codestream_level) < required_level &&
850
0
       enc->codestream_level != -1)) {
851
0
    return JXL_API_ERROR(
852
0
        enc, JXL_ENC_ERR_API_USAGE, "%s",
853
0
        ("Codestream level verification for level " +
854
0
         std::to_string(enc->codestream_level) + " failed: " + level_message)
855
0
            .c_str());
856
0
  }
857
0
  return JXL_ENC_SUCCESS;
858
0
}
859
860
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
861
                                                          size_t index,
862
                                                          const char* name,
863
0
                                                          size_t size) {
864
0
  if (index >= enc->metadata.m.num_extra_channels) {
865
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
866
0
                         "Invalid value for the index of extra channel");
867
0
  }
868
0
  enc->metadata.m.extra_channel_info[index].name =
869
0
      std::string(name, name + size);
870
0
  return JXL_ENC_SUCCESS;
871
0
}
872
873
JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate(
874
92
    JxlEncoder* enc, const JxlEncoderFrameSettings* source) {
875
92
  auto opts = jxl::MemoryManagerMakeUnique<JxlEncoderFrameSettings>(
876
92
      &enc->memory_manager);
877
92
  if (!opts) return nullptr;
878
92
  opts->enc = enc;
879
92
  if (source != nullptr) {
880
0
    opts->values = source->values;
881
92
  } else {
882
92
    opts->values.lossless = false;
883
92
  }
884
92
  opts->values.cparams.level = enc->codestream_level;
885
92
  JxlEncoderFrameSettings* ret = opts.get();
886
92
  enc->encoder_options.emplace_back(std::move(opts));
887
92
  return ret;
888
92
}
889
890
JxlEncoderFrameSettings* JxlEncoderOptionsCreate(
891
0
    JxlEncoder* enc, const JxlEncoderFrameSettings* source) {
892
  // Deprecated function name, call the non-deprecated function
893
0
  return JxlEncoderFrameSettingsCreate(enc, source);
894
0
}
895
896
JxlEncoderStatus JxlEncoderSetFrameLossless(
897
0
    JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) {
898
0
  if (lossless && frame_settings->enc->basic_info_set &&
899
0
      frame_settings->enc->metadata.m.xyb_encoded) {
900
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
901
0
                         "Set use_original_profile=true for lossless encoding");
902
0
  }
903
0
  frame_settings->values.lossless = lossless;
904
0
  return JXL_ENC_SUCCESS;
905
0
}
906
907
JxlEncoderStatus JxlEncoderOptionsSetLossless(
908
0
    JxlEncoderFrameSettings* frame_settings, JXL_BOOL lossless) {
909
  // Deprecated function name, call the non-deprecated function
910
0
  return JxlEncoderSetFrameLossless(frame_settings, lossless);
911
0
}
912
913
JxlEncoderStatus JxlEncoderOptionsSetEffort(
914
0
    JxlEncoderFrameSettings* frame_settings, const int effort) {
915
0
  return JxlEncoderFrameSettingsSetOption(frame_settings,
916
0
                                          JXL_ENC_FRAME_SETTING_EFFORT, effort);
917
0
}
918
919
JxlEncoderStatus JxlEncoderSetFrameDistance(
920
0
    JxlEncoderFrameSettings* frame_settings, float distance) {
921
0
  if (distance < 0.f || distance > 25.f) {
922
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
923
0
                         "Distance has to be in [0.0..25.0]");
924
0
  }
925
0
  if (distance > 0.f && distance < 0.01f) {
926
0
    distance = 0.01f;
927
0
  }
928
0
  frame_settings->values.cparams.butteraugli_distance = distance;
929
0
  return JXL_ENC_SUCCESS;
930
0
}
931
932
JxlEncoderStatus JxlEncoderOptionsSetDistance(
933
0
    JxlEncoderFrameSettings* frame_settings, float distance) {
934
  // Deprecated function name, call the non-deprecated function
935
0
  return JxlEncoderSetFrameDistance(frame_settings, distance);
936
0
}
937
938
JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed(
939
0
    JxlEncoderFrameSettings* frame_settings, int tier) {
940
0
  return JxlEncoderFrameSettingsSetOption(
941
0
      frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier);
942
0
}
943
944
JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
945
    JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
946
0
    int64_t value) {
947
  // check if value is -1, 0 or 1 for Override-type options
948
0
  switch (option) {
949
0
    case JXL_ENC_FRAME_SETTING_NOISE:
950
0
    case JXL_ENC_FRAME_SETTING_DOTS:
951
0
    case JXL_ENC_FRAME_SETTING_PATCHES:
952
0
    case JXL_ENC_FRAME_SETTING_GABORISH:
953
0
    case JXL_ENC_FRAME_SETTING_MODULAR:
954
0
    case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
955
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
956
0
    case JXL_ENC_FRAME_SETTING_RESPONSIVE:
957
0
    case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
958
0
    case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
959
0
    case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
960
0
    case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
961
0
      if (value < -1 || value > 1) {
962
0
        return JXL_API_ERROR(
963
0
            frame_settings->enc, JXL_ENC_ERR_API_USAGE,
964
0
            "Option value has to be -1 (default), 0 (off) or 1 (on)");
965
0
      }
966
0
      break;
967
0
    default:
968
0
      break;
969
0
  }
970
971
0
  switch (option) {
972
0
    case JXL_ENC_FRAME_SETTING_EFFORT:
973
0
      if (value < 1 || value > 9) {
974
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
975
0
                             "Encode effort has to be in [1..9]");
976
0
      }
977
0
      frame_settings->values.cparams.speed_tier =
978
0
          static_cast<jxl::SpeedTier>(10 - value);
979
0
      return JXL_ENC_SUCCESS;
980
0
    case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
981
0
      if (value < -1 || value > 11) {
982
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
983
0
                             "Brotli effort has to be in [-1..11]");
984
0
      }
985
      // set cparams for brotli use in JPEG frames
986
0
      frame_settings->values.cparams.brotli_effort = value;
987
      // set enc option for brotli use in brob boxes
988
0
      frame_settings->enc->brotli_effort = value;
989
0
      return JXL_ENC_SUCCESS;
990
0
    case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
991
0
      if (value < 0 || value > 4) {
992
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
993
0
                             "Decoding speed has to be in [0..4]");
994
0
      }
995
0
      frame_settings->values.cparams.decoding_speed_tier = value;
996
0
      return JXL_ENC_SUCCESS;
997
0
    case JXL_ENC_FRAME_SETTING_RESAMPLING:
998
0
      if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) {
999
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1000
0
                             "Resampling factor has to be 1, 2, 4 or 8");
1001
0
      }
1002
0
      frame_settings->values.cparams.resampling = value;
1003
0
      return JXL_ENC_SUCCESS;
1004
0
    case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING:
1005
      // TOOD(lode): the jxl codestream allows choosing a different resampling
1006
      // factor for each extra channel, independently per frame. Move this
1007
      // option to a JxlEncoderFrameSettings-option that can be set per extra
1008
      // channel, so needs its own function rather than
1009
      // JxlEncoderFrameSettingsSetOption due to the extra channel index
1010
      // argument required.
1011
0
      if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) {
1012
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1013
0
                             "Resampling factor has to be 1, 2, 4 or 8");
1014
0
      }
1015
0
      frame_settings->values.cparams.ec_resampling = value;
1016
0
      return JXL_ENC_SUCCESS;
1017
0
    case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED:
1018
0
      if (value < 0 || value > 1) {
1019
0
        return JXL_ENC_ERROR;
1020
0
      }
1021
0
      frame_settings->values.cparams.already_downsampled = (value == 1);
1022
0
      return JXL_ENC_SUCCESS;
1023
0
    case JXL_ENC_FRAME_SETTING_NOISE:
1024
0
      frame_settings->values.cparams.noise = static_cast<jxl::Override>(value);
1025
0
      return JXL_ENC_SUCCESS;
1026
0
    case JXL_ENC_FRAME_SETTING_DOTS:
1027
0
      frame_settings->values.cparams.dots = static_cast<jxl::Override>(value);
1028
0
      return JXL_ENC_SUCCESS;
1029
0
    case JXL_ENC_FRAME_SETTING_PATCHES:
1030
0
      frame_settings->values.cparams.patches =
1031
0
          static_cast<jxl::Override>(value);
1032
0
      return JXL_ENC_SUCCESS;
1033
0
    case JXL_ENC_FRAME_SETTING_EPF:
1034
0
      if (value < -1 || value > 3) {
1035
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1036
0
                             "EPF value has to be in [-1..3]");
1037
0
      }
1038
0
      frame_settings->values.cparams.epf = static_cast<int>(value);
1039
0
      return JXL_ENC_SUCCESS;
1040
0
    case JXL_ENC_FRAME_SETTING_GABORISH:
1041
0
      frame_settings->values.cparams.gaborish =
1042
0
          static_cast<jxl::Override>(value);
1043
0
      return JXL_ENC_SUCCESS;
1044
0
    case JXL_ENC_FRAME_SETTING_MODULAR:
1045
0
      frame_settings->values.cparams.modular_mode = (value == 1);
1046
0
      return JXL_ENC_SUCCESS;
1047
0
    case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
1048
0
      frame_settings->values.cparams.keep_invisible =
1049
0
          static_cast<jxl::Override>(value);
1050
0
      return JXL_ENC_SUCCESS;
1051
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
1052
0
      frame_settings->values.cparams.centerfirst = (value == 1);
1053
0
      return JXL_ENC_SUCCESS;
1054
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X:
1055
0
      if (value < -1) {
1056
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1057
0
                             "Center x coordinate has to be -1 or positive");
1058
0
      }
1059
0
      frame_settings->values.cparams.center_x = static_cast<size_t>(value);
1060
0
      return JXL_ENC_SUCCESS;
1061
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y:
1062
0
      if (value < -1) {
1063
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1064
0
                             "Center y coordinate has to be -1 or positive");
1065
0
      }
1066
0
      frame_settings->values.cparams.center_y = static_cast<size_t>(value);
1067
0
      return JXL_ENC_SUCCESS;
1068
0
    case JXL_ENC_FRAME_SETTING_RESPONSIVE:
1069
0
      frame_settings->values.cparams.responsive = value;
1070
0
      return JXL_ENC_SUCCESS;
1071
0
    case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
1072
0
      frame_settings->values.cparams.progressive_mode = value;
1073
0
      return JXL_ENC_SUCCESS;
1074
0
    case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
1075
0
      frame_settings->values.cparams.qprogressive_mode = value;
1076
0
      return JXL_ENC_SUCCESS;
1077
0
    case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC:
1078
0
      if (value < -1 || value > 2) {
1079
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1080
0
                             "Progressive DC has to be in [-1..2]");
1081
0
      }
1082
0
      frame_settings->values.cparams.progressive_dc = value;
1083
0
      return JXL_ENC_SUCCESS;
1084
0
    case JXL_ENC_FRAME_SETTING_PALETTE_COLORS:
1085
0
      if (value < -1 || value > 70913) {
1086
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1087
0
                             "Option value has to be in [-1..70913]");
1088
0
      }
1089
0
      if (value == -1) {
1090
0
        frame_settings->values.cparams.palette_colors = 1 << 10;
1091
0
      } else {
1092
0
        frame_settings->values.cparams.palette_colors = value;
1093
0
      }
1094
0
      return JXL_ENC_SUCCESS;
1095
0
    case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
1096
      // TODO(lode): the defaults of some palette settings depend on others.
1097
      // See the logic in cjxl. Similar for other settings. This should be
1098
      // handled in the encoder during JxlEncoderProcessOutput (or,
1099
      // alternatively, in the cjxl binary like now)
1100
0
      frame_settings->values.cparams.lossy_palette = (value == 1);
1101
0
      return JXL_ENC_SUCCESS;
1102
0
    case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
1103
0
      if (value < -1 || value > 2) {
1104
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1105
0
                             "Option value has to be in [-1..2]");
1106
0
      }
1107
0
      if (value == -1) {
1108
0
        frame_settings->values.cparams.color_transform =
1109
0
            jxl::ColorTransform::kXYB;
1110
0
      } else {
1111
0
        frame_settings->values.cparams.color_transform =
1112
0
            static_cast<jxl::ColorTransform>(value);
1113
0
      }
1114
0
      return JXL_ENC_SUCCESS;
1115
0
    case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE:
1116
0
      if (value < -1 || value > 41) {
1117
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1118
0
                             "Option value has to be in [-1..41]");
1119
0
      }
1120
0
      frame_settings->values.cparams.colorspace = value;
1121
0
      return JXL_ENC_SUCCESS;
1122
0
    case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE:
1123
0
      if (value < -1 || value > 3) {
1124
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1125
0
                             "Option value has to be in [-1..3]");
1126
0
      }
1127
      // TODO(lode): the default behavior of this parameter for cjxl is
1128
      // to choose 1 or 2 depending on the situation. This behavior needs to be
1129
      // implemented either in the C++ library by allowing to set this to -1, or
1130
      // kept in cjxl and set it to 1 or 2 using this API.
1131
0
      if (value == -1) {
1132
0
        frame_settings->values.cparams.modular_group_size_shift = 1;
1133
0
      } else {
1134
0
        frame_settings->values.cparams.modular_group_size_shift = value;
1135
0
      }
1136
0
      return JXL_ENC_SUCCESS;
1137
0
    case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR:
1138
0
      if (value < -1 || value > 15) {
1139
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1140
0
                             "Option value has to be in [-1..15]");
1141
0
      }
1142
0
      frame_settings->values.cparams.options.predictor =
1143
0
          static_cast<jxl::Predictor>(value);
1144
0
      return JXL_ENC_SUCCESS;
1145
0
    case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS:
1146
      // The max allowed value can in theory be higher. However, it depends on
1147
      // the effort setting. 11 is the highest safe value that doesn't cause
1148
      // tree_samples to be >= 64 in the encoder. The specification may allow
1149
      // more than this. With more fine tuning higher values could be allowed.
1150
      // For N-channel images, the largest useful value is N-1.
1151
0
      if (value < -1 || value > 11) {
1152
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1153
0
                             "Option value has to be in [-1..11]");
1154
0
      }
1155
0
      if (value == -1) {
1156
0
        frame_settings->values.cparams.options.max_properties = 0;
1157
0
      } else {
1158
0
        frame_settings->values.cparams.options.max_properties = value;
1159
0
      }
1160
0
      return JXL_ENC_SUCCESS;
1161
0
    case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
1162
0
      if (value == -1) {
1163
0
        frame_settings->values.cparams.force_cfl_jpeg_recompression = true;
1164
0
      } else {
1165
0
        frame_settings->values.cparams.force_cfl_jpeg_recompression = value;
1166
0
      }
1167
0
      return JXL_ENC_SUCCESS;
1168
0
    case JXL_ENC_FRAME_INDEX_BOX:
1169
0
      frame_settings->values.frame_index_box = true;
1170
0
      return JXL_ENC_SUCCESS;
1171
0
    case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
1172
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
1173
0
                           "Float option, try setting it with "
1174
0
                           "JxlEncoderFrameSettingsSetFloatOption");
1175
0
    default:
1176
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
1177
0
                           "Unknown option");
1178
0
  }
1179
0
}
1180
1181
JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption(
1182
    JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
1183
0
    float value) {
1184
0
  switch (option) {
1185
0
    case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
1186
0
      if (value < 0) return JXL_ENC_ERROR;
1187
      // TODO(lode): add encoder setting to set the 8 floating point values of
1188
      // the noise synthesis parameters per frame for more fine grained control.
1189
0
      frame_settings->values.cparams.photon_noise_iso = value;
1190
0
      return JXL_ENC_SUCCESS;
1191
0
    case JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT:
1192
0
      if (value < -1.f || value > 100.f) {
1193
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1194
0
                             "Option value has to be smaller than 100");
1195
0
      }
1196
      // This value is called "iterations" or "nb_repeats" in cjxl, but is in
1197
      // fact a fraction in range 0.0-1.0, with the default value 0.5.
1198
      // Convert from floating point percentage to floating point fraction here.
1199
0
      if (value < -.5f) {
1200
        // TODO(lode): for this and many other settings (also in
1201
        // JxlEncoderFrameSettingsSetOption), avoid duplicating the default
1202
        // values here and in enc_params.h and options.h, have one location
1203
        // where the defaults are specified.
1204
0
        frame_settings->values.cparams.options.nb_repeats = 0.5f;
1205
0
      } else {
1206
0
        frame_settings->values.cparams.options.nb_repeats = value * 0.01f;
1207
0
      }
1208
0
      return JXL_ENC_SUCCESS;
1209
0
    case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT:
1210
0
      if (value < -1.f || value > 100.f) {
1211
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1212
0
                             "Option value has to be in [-1..100]");
1213
0
      }
1214
0
      if (value < -.5f) {
1215
0
        frame_settings->values.cparams.channel_colors_pre_transform_percent =
1216
0
            95.0f;
1217
0
      } else {
1218
0
        frame_settings->values.cparams.channel_colors_pre_transform_percent =
1219
0
            value;
1220
0
      }
1221
0
      return JXL_ENC_SUCCESS;
1222
0
    case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT:
1223
0
      if (value < -1.f || value > 100.f) {
1224
0
        return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1225
0
                             "Option value has to be in [-1..100]");
1226
0
      }
1227
0
      if (value < -.5f) {
1228
0
        frame_settings->values.cparams.channel_colors_percent = 80.0f;
1229
0
      } else {
1230
0
        frame_settings->values.cparams.channel_colors_percent = value;
1231
0
      }
1232
0
      return JXL_ENC_SUCCESS;
1233
0
    case JXL_ENC_FRAME_SETTING_EFFORT:
1234
0
    case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
1235
0
    case JXL_ENC_FRAME_SETTING_RESAMPLING:
1236
0
    case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING:
1237
0
    case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED:
1238
0
    case JXL_ENC_FRAME_SETTING_NOISE:
1239
0
    case JXL_ENC_FRAME_SETTING_DOTS:
1240
0
    case JXL_ENC_FRAME_SETTING_PATCHES:
1241
0
    case JXL_ENC_FRAME_SETTING_EPF:
1242
0
    case JXL_ENC_FRAME_SETTING_GABORISH:
1243
0
    case JXL_ENC_FRAME_SETTING_MODULAR:
1244
0
    case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
1245
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
1246
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X:
1247
0
    case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y:
1248
0
    case JXL_ENC_FRAME_SETTING_RESPONSIVE:
1249
0
    case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
1250
0
    case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
1251
0
    case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC:
1252
0
    case JXL_ENC_FRAME_SETTING_PALETTE_COLORS:
1253
0
    case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
1254
0
    case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
1255
0
    case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE:
1256
0
    case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE:
1257
0
    case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR:
1258
0
    case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS:
1259
0
    case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
1260
0
    case JXL_ENC_FRAME_INDEX_BOX:
1261
0
    case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
1262
0
    case JXL_ENC_FRAME_SETTING_FILL_ENUM:
1263
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
1264
0
                           "Int option, try setting it with "
1265
0
                           "JxlEncoderFrameSettingsSetOption");
1266
0
    default:
1267
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
1268
0
                           "Unknown option");
1269
0
  }
1270
0
}
1271
46
JxlEncoder* JxlEncoderCreate(const JxlMemoryManager* memory_manager) {
1272
46
  JxlMemoryManager local_memory_manager;
1273
46
  if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager)) {
1274
0
    return nullptr;
1275
0
  }
1276
1277
46
  void* alloc =
1278
46
      jxl::MemoryManagerAlloc(&local_memory_manager, sizeof(JxlEncoder));
1279
46
  if (!alloc) return nullptr;
1280
46
  JxlEncoder* enc = new (alloc) JxlEncoder();
1281
46
  enc->memory_manager = local_memory_manager;
1282
  // TODO(sboukortt): add an API function to set this.
1283
46
  enc->cms = jxl::GetJxlCms();
1284
1285
  // Initialize all the field values.
1286
46
  JxlEncoderReset(enc);
1287
1288
46
  return enc;
1289
46
}
1290
1291
138
void JxlEncoderReset(JxlEncoder* enc) {
1292
138
  enc->thread_pool.reset();
1293
138
  enc->input_queue.clear();
1294
138
  enc->num_queued_frames = 0;
1295
138
  enc->num_queued_boxes = 0;
1296
138
  enc->encoder_options.clear();
1297
138
  enc->output_byte_queue.clear();
1298
138
  enc->codestream_bytes_written_beginning_of_frame = 0;
1299
138
  enc->codestream_bytes_written_end_of_frame = 0;
1300
138
  enc->wrote_bytes = false;
1301
138
  enc->jxlp_counter = 0;
1302
138
  enc->metadata = jxl::CodecMetadata();
1303
138
  enc->last_used_cparams = jxl::CompressParams();
1304
138
  enc->frames_closed = false;
1305
138
  enc->boxes_closed = false;
1306
138
  enc->basic_info_set = false;
1307
138
  enc->color_encoding_set = false;
1308
138
  enc->intensity_target_set = false;
1309
138
  enc->use_container = false;
1310
138
  enc->use_boxes = false;
1311
138
  enc->codestream_level = -1;
1312
138
  JxlEncoderInitBasicInfo(&enc->basic_info);
1313
138
}
1314
1315
46
void JxlEncoderDestroy(JxlEncoder* enc) {
1316
46
  if (enc) {
1317
46
    JxlMemoryManager local_memory_manager = enc->memory_manager;
1318
    // Call destructor directly since custom free function is used.
1319
46
    enc->~JxlEncoder();
1320
46
    jxl::MemoryManagerFree(&local_memory_manager, enc);
1321
46
  }
1322
46
}
1323
1324
0
JxlEncoderError JxlEncoderGetError(JxlEncoder* enc) { return enc->error; }
1325
1326
JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc,
1327
0
                                        JXL_BOOL use_container) {
1328
0
  if (enc->wrote_bytes) {
1329
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1330
0
                         "this setting can only be set at the beginning");
1331
0
  }
1332
0
  enc->use_container = static_cast<bool>(use_container);
1333
0
  return JXL_ENC_SUCCESS;
1334
0
}
1335
1336
JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc,
1337
0
                                             JXL_BOOL store_jpeg_metadata) {
1338
0
  if (enc->wrote_bytes) {
1339
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1340
0
                         "this setting can only be set at the beginning");
1341
0
  }
1342
0
  enc->store_jpeg_metadata = static_cast<bool>(store_jpeg_metadata);
1343
0
  return JXL_ENC_SUCCESS;
1344
0
}
1345
1346
92
JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level) {
1347
92
  if (level != -1 && level != 5 && level != 10) {
1348
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_NOT_SUPPORTED, "invalid level");
1349
0
  }
1350
92
  if (enc->wrote_bytes) {
1351
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1352
0
                         "this setting can only be set at the beginning");
1353
0
  }
1354
92
  enc->codestream_level = level;
1355
92
  return JXL_ENC_SUCCESS;
1356
92
}
1357
1358
0
int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc) {
1359
0
  return VerifyLevelSettings(enc, nullptr);
1360
0
}
1361
1362
0
void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) {
1363
0
  jxl::msan::MemoryIsInitialized(&cms, sizeof(cms));
1364
0
  enc->cms = cms;
1365
0
}
1366
1367
JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc,
1368
                                             JxlParallelRunner parallel_runner,
1369
92
                                             void* parallel_runner_opaque) {
1370
92
  if (enc->thread_pool) {
1371
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1372
0
                         "parallel runner already set");
1373
0
  }
1374
92
  enc->thread_pool = jxl::MemoryManagerMakeUnique<jxl::ThreadPool>(
1375
92
      &enc->memory_manager, parallel_runner, parallel_runner_opaque);
1376
92
  if (!enc->thread_pool) {
1377
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_GENERIC,
1378
0
                         "error setting parallel runner");
1379
0
  }
1380
92
  return JXL_ENC_SUCCESS;
1381
92
}
1382
1383
namespace {
1384
JxlEncoderStatus GetCurrentDimensions(
1385
    const JxlEncoderFrameSettings* frame_settings, size_t& xsize,
1386
92
    size_t& ysize) {
1387
92
  xsize = frame_settings->enc->metadata.xsize();
1388
92
  ysize = frame_settings->enc->metadata.ysize();
1389
92
  if (frame_settings->values.header.layer_info.have_crop) {
1390
0
    xsize = frame_settings->values.header.layer_info.xsize;
1391
0
    ysize = frame_settings->values.header.layer_info.ysize;
1392
0
  }
1393
92
  if (frame_settings->values.cparams.already_downsampled) {
1394
0
    size_t factor = frame_settings->values.cparams.resampling;
1395
0
    xsize = jxl::DivCeil(xsize, factor);
1396
0
    ysize = jxl::DivCeil(ysize, factor);
1397
0
  }
1398
92
  if (xsize == 0 || ysize == 0) {
1399
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1400
0
                         "zero-sized frame is not allowed");
1401
0
  }
1402
92
  return JXL_ENC_SUCCESS;
1403
92
}
1404
}  // namespace
1405
1406
JxlEncoderStatus JxlEncoderAddJPEGFrame(
1407
    const JxlEncoderFrameSettings* frame_settings, const uint8_t* buffer,
1408
0
    size_t size) {
1409
0
  if (frame_settings->enc->frames_closed) {
1410
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1411
0
                         "Frame input is already closed");
1412
0
  }
1413
1414
0
  jxl::CodecInOut io;
1415
0
  if (!jxl::jpeg::DecodeImageJPG(jxl::Span<const uint8_t>(buffer, size), &io)) {
1416
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT,
1417
0
                         "Error during decode of input JPEG");
1418
0
  }
1419
1420
0
  if (!frame_settings->enc->color_encoding_set) {
1421
0
    if (!SetColorEncodingFromJpegData(
1422
0
            *io.Main().jpeg_data,
1423
0
            &frame_settings->enc->metadata.m.color_encoding)) {
1424
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT,
1425
0
                           "Error in input JPEG color space");
1426
0
    }
1427
0
  }
1428
1429
0
  if (!frame_settings->enc->basic_info_set) {
1430
0
    JxlBasicInfo basic_info;
1431
0
    JxlEncoderInitBasicInfo(&basic_info);
1432
0
    basic_info.xsize = io.Main().jpeg_data->width;
1433
0
    basic_info.ysize = io.Main().jpeg_data->height;
1434
0
    basic_info.uses_original_profile = true;
1435
0
    if (JxlEncoderSetBasicInfo(frame_settings->enc, &basic_info) !=
1436
0
        JXL_ENC_SUCCESS) {
1437
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1438
0
                           "Error setting basic info");
1439
0
    }
1440
0
  }
1441
1442
0
  if (frame_settings->enc->metadata.m.xyb_encoded) {
1443
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1444
0
                         "Can't XYB encode a lossless JPEG");
1445
0
  }
1446
0
  if (!io.blobs.exif.empty()) {
1447
0
    JxlOrientation orientation = static_cast<JxlOrientation>(
1448
0
        frame_settings->enc->metadata.m.orientation);
1449
0
    jxl::InterpretExif(io.blobs.exif, &orientation);
1450
0
    frame_settings->enc->metadata.m.orientation = orientation;
1451
1452
0
    size_t exif_size = io.blobs.exif.size();
1453
    // Exif data in JPEG is limited to 64k
1454
0
    if (exif_size > 0xFFFF) {
1455
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1456
0
                           "Exif larger than possible in JPEG?");
1457
0
    }
1458
0
    exif_size += 4;  // prefix 4 zero bytes for tiff offset
1459
0
    std::vector<uint8_t> exif(exif_size);
1460
0
    memcpy(exif.data() + 4, io.blobs.exif.data(), io.blobs.exif.size());
1461
0
    JxlEncoderUseBoxes(frame_settings->enc);
1462
0
    JxlEncoderAddBox(frame_settings->enc, "Exif", exif.data(), exif_size,
1463
                     /*compress_box=*/JXL_TRUE);
1464
0
  }
1465
0
  if (!io.blobs.xmp.empty()) {
1466
0
    JxlEncoderUseBoxes(frame_settings->enc);
1467
0
    JxlEncoderAddBox(frame_settings->enc, "xml ", io.blobs.xmp.data(),
1468
0
                     io.blobs.xmp.size(), /*compress_box=*/JXL_TRUE);
1469
0
  }
1470
0
  if (!io.blobs.jumbf.empty()) {
1471
0
    JxlEncoderUseBoxes(frame_settings->enc);
1472
0
    JxlEncoderAddBox(frame_settings->enc, "jumb", io.blobs.jumbf.data(),
1473
0
                     io.blobs.jumbf.size(), /*compress_box=*/JXL_TRUE);
1474
0
  }
1475
0
  if (frame_settings->enc->store_jpeg_metadata) {
1476
0
    jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
1477
0
    jxl::PaddedBytes jpeg_data;
1478
0
    if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data,
1479
0
                                   frame_settings->values.cparams)) {
1480
0
      return JXL_API_ERROR(
1481
0
          frame_settings->enc, JXL_ENC_ERR_JBRD,
1482
0
          "JPEG bitstream reconstruction data cannot be encoded");
1483
0
    }
1484
0
    frame_settings->enc->jpeg_metadata = std::vector<uint8_t>(
1485
0
        jpeg_data.data(), jpeg_data.data() + jpeg_data.size());
1486
0
  }
1487
1488
0
  auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
1489
0
      &frame_settings->enc->memory_manager,
1490
      // JxlEncoderQueuedFrame is a struct with no constructors, so we use the
1491
      // default move constructor there.
1492
0
      jxl::JxlEncoderQueuedFrame{
1493
0
          frame_settings->values,
1494
0
          jxl::ImageBundle(&frame_settings->enc->metadata.m),
1495
0
          {}});
1496
0
  if (!queued_frame) {
1497
    // TODO(jon): when can this happen? is this an API usage error?
1498
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1499
0
                         "No frame queued?");
1500
0
  }
1501
0
  queued_frame->frame.SetFromImage(std::move(*io.Main().color()),
1502
0
                                   io.Main().c_current());
1503
0
  size_t xsize, ysize;
1504
0
  if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
1505
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1506
0
                         "bad dimensions");
1507
0
  }
1508
0
  if (xsize != static_cast<size_t>(io.Main().jpeg_data->width) ||
1509
0
      ysize != static_cast<size_t>(io.Main().jpeg_data->height)) {
1510
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1511
0
                         "JPEG dimensions don't match frame dimensions");
1512
0
  }
1513
0
  std::vector<jxl::ImageF> extra_channels(
1514
0
      frame_settings->enc->metadata.m.num_extra_channels);
1515
0
  for (auto& extra_channel : extra_channels) {
1516
0
    extra_channel = jxl::ImageF(xsize, ysize);
1517
0
    queued_frame->ec_initialized.push_back(0);
1518
0
  }
1519
0
  queued_frame->frame.SetExtraChannels(std::move(extra_channels));
1520
0
  queued_frame->frame.jpeg_data = std::move(io.Main().jpeg_data);
1521
0
  queued_frame->frame.color_transform = io.Main().color_transform;
1522
0
  queued_frame->frame.chroma_subsampling = io.Main().chroma_subsampling;
1523
1524
0
  QueueFrame(frame_settings, queued_frame);
1525
0
  return JXL_ENC_SUCCESS;
1526
0
}
1527
1528
JxlEncoderStatus JxlEncoderAddImageFrame(
1529
    const JxlEncoderFrameSettings* frame_settings,
1530
92
    const JxlPixelFormat* pixel_format, const void* buffer, size_t size) {
1531
92
  if (!frame_settings->enc->basic_info_set ||
1532
92
      (!frame_settings->enc->color_encoding_set &&
1533
92
       !frame_settings->enc->metadata.m.xyb_encoded)) {
1534
    // Basic Info must be set, and color encoding must be set directly,
1535
    // or set to XYB via JxlBasicInfo.uses_original_profile = JXL_FALSE
1536
    // Otherwise, this is an API misuse.
1537
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1538
0
                         "Basic info or color encoding not set yet");
1539
0
  }
1540
1541
92
  if (frame_settings->enc->frames_closed) {
1542
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1543
0
                         "Frame input already closed");
1544
0
  }
1545
92
  if (pixel_format->num_channels < 3) {
1546
0
    if (frame_settings->enc->basic_info.num_color_channels != 1) {
1547
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1548
0
                           "Grayscale pixel format input for an RGB image");
1549
0
    }
1550
92
  } else {
1551
92
    if (frame_settings->enc->basic_info.num_color_channels != 3) {
1552
0
      return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1553
0
                           "RGB pixel format input for a grayscale image");
1554
0
    }
1555
92
  }
1556
1557
92
  auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
1558
92
      &frame_settings->enc->memory_manager,
1559
      // JxlEncoderQueuedFrame is a struct with no constructors, so we use the
1560
      // default move constructor there.
1561
92
      jxl::JxlEncoderQueuedFrame{
1562
92
          frame_settings->values,
1563
92
          jxl::ImageBundle(&frame_settings->enc->metadata.m),
1564
92
          {}});
1565
1566
92
  if (!queued_frame) {
1567
    // TODO(jon): when can this happen? is this an API usage error?
1568
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1569
0
                         "No frame queued?");
1570
0
  }
1571
1572
92
  jxl::ColorEncoding c_current;
1573
92
  if (!frame_settings->enc->color_encoding_set) {
1574
0
    if ((pixel_format->data_type == JXL_TYPE_FLOAT) ||
1575
0
        (pixel_format->data_type == JXL_TYPE_FLOAT16)) {
1576
0
      c_current =
1577
0
          jxl::ColorEncoding::LinearSRGB(pixel_format->num_channels < 3);
1578
0
    } else {
1579
0
      c_current = jxl::ColorEncoding::SRGB(pixel_format->num_channels < 3);
1580
0
    }
1581
92
  } else {
1582
92
    c_current = frame_settings->enc->metadata.m.color_encoding;
1583
92
  }
1584
92
  uint32_t num_channels = pixel_format->num_channels;
1585
92
  size_t has_interleaved_alpha =
1586
92
      static_cast<size_t>(num_channels == 2 || num_channels == 4);
1587
92
  if (has_interleaved_alpha >
1588
92
      frame_settings->enc->metadata.m.num_extra_channels) {
1589
0
    return JXL_API_ERROR(
1590
0
        frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1591
0
        "number of extra channels mismatch (need 1 extra channel for alpha)");
1592
0
  }
1593
92
  size_t xsize, ysize;
1594
92
  if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
1595
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1596
0
                         "bad dimensions");
1597
0
  }
1598
92
  std::vector<jxl::ImageF> extra_channels(
1599
92
      frame_settings->enc->metadata.m.num_extra_channels);
1600
92
  for (auto& extra_channel : extra_channels) {
1601
0
    extra_channel = jxl::ImageF(xsize, ysize);
1602
0
  }
1603
92
  queued_frame->frame.SetExtraChannels(std::move(extra_channels));
1604
92
  for (auto& ec_info : frame_settings->enc->metadata.m.extra_channel_info) {
1605
0
    if (has_interleaved_alpha && ec_info.type == jxl::ExtraChannel::kAlpha) {
1606
0
      queued_frame->ec_initialized.push_back(1);
1607
0
      has_interleaved_alpha = 0;  // only first Alpha is initialized
1608
0
    } else {
1609
0
      queued_frame->ec_initialized.push_back(0);
1610
0
    }
1611
0
  }
1612
92
  queued_frame->frame.origin.x0 =
1613
92
      frame_settings->values.header.layer_info.crop_x0;
1614
92
  queued_frame->frame.origin.y0 =
1615
92
      frame_settings->values.header.layer_info.crop_y0;
1616
92
  queued_frame->frame.use_for_next_frame =
1617
92
      (frame_settings->values.header.layer_info.save_as_reference != 0u);
1618
92
  queued_frame->frame.blendmode =
1619
92
      frame_settings->values.header.layer_info.blend_info.blendmode ==
1620
92
              JXL_BLEND_REPLACE
1621
92
          ? jxl::BlendMode::kReplace
1622
92
          : jxl::BlendMode::kBlend;
1623
92
  queued_frame->frame.blend =
1624
92
      frame_settings->values.header.layer_info.blend_info.source > 0;
1625
1626
92
  if (!jxl::BufferToImageBundle(*pixel_format, xsize, ysize, buffer, size,
1627
92
                                frame_settings->enc->thread_pool.get(),
1628
92
                                c_current, &(queued_frame->frame))) {
1629
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1630
0
                         "Invalid input buffer");
1631
0
  }
1632
92
  if (frame_settings->values.lossless &&
1633
92
      frame_settings->enc->metadata.m.xyb_encoded) {
1634
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1635
0
                         "Set use_original_profile=true for lossless encoding");
1636
0
  }
1637
92
  queued_frame->option_values.cparams.level =
1638
92
      frame_settings->enc->codestream_level;
1639
1640
92
  QueueFrame(frame_settings, queued_frame);
1641
92
  return JXL_ENC_SUCCESS;
1642
92
}
1643
1644
0
JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc) {
1645
0
  if (enc->wrote_bytes) {
1646
0
    return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1647
0
                         "this setting can only be set at the beginning");
1648
0
  }
1649
0
  enc->use_boxes = true;
1650
0
  return JXL_ENC_SUCCESS;
1651
0
}
1652
1653
JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, const JxlBoxType type,
1654
                                  const uint8_t* contents, size_t size,
1655
0
                                  JXL_BOOL compress_box) {
1656
0
  if (!enc->use_boxes) {
1657
0
    return JXL_API_ERROR(
1658
0
        enc, JXL_ENC_ERR_API_USAGE,
1659
0
        "must set JxlEncoderUseBoxes at the beginning to add boxes");
1660
0
  }
1661
0
  if (compress_box) {
1662
0
    if (memcmp("jxl", type, 3) == 0) {
1663
0
      return JXL_API_ERROR(
1664
0
          enc, JXL_ENC_ERR_API_USAGE,
1665
0
          "brob box may not contain a type starting with \"jxl\"");
1666
0
    }
1667
0
    if (memcmp("jbrd", type, 4) == 0) {
1668
0
      return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1669
0
                           "jbrd box may not be brob compressed");
1670
0
    }
1671
0
    if (memcmp("brob", type, 4) == 0) {
1672
      // The compress_box will compress an existing non-brob box into a brob
1673
      // box. If already giving a valid brotli-compressed brob box, set
1674
      // compress_box to false since it is already compressed.
1675
0
      return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
1676
0
                           "a brob box cannot contain another brob box");
1677
0
    }
1678
0
  }
1679
1680
0
  auto box = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedBox>(
1681
0
      &enc->memory_manager);
1682
1683
0
  box->type = jxl::MakeBoxType(type);
1684
0
  box->contents.assign(contents, contents + size);
1685
0
  box->compress_box = !!compress_box;
1686
0
  QueueBox(enc, box);
1687
0
  return JXL_ENC_SUCCESS;
1688
0
}
1689
1690
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
1691
    const JxlEncoderOptions* frame_settings, const JxlPixelFormat* pixel_format,
1692
0
    const void* buffer, size_t size, uint32_t index) {
1693
0
  if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
1694
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1695
0
                         "Invalid value for the index of extra channel");
1696
0
  }
1697
0
  if (!frame_settings->enc->basic_info_set ||
1698
0
      !frame_settings->enc->color_encoding_set) {
1699
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1700
0
                         "Basic info has to be set first");
1701
0
  }
1702
0
  if (frame_settings->enc->input_queue.empty()) {
1703
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1704
0
                         "First add image frame, then extra channels");
1705
0
  }
1706
0
  if (frame_settings->enc->frames_closed) {
1707
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1708
0
                         "Frame input already closed");
1709
0
  }
1710
0
  size_t xsize, ysize;
1711
0
  if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
1712
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
1713
0
                         "bad dimensions");
1714
0
  }
1715
0
  if (!jxl::BufferToImageF(*pixel_format, xsize, ysize, buffer, size,
1716
0
                           frame_settings->enc->thread_pool.get(),
1717
0
                           &frame_settings->enc->input_queue.back()
1718
0
                                .frame->frame.extra_channels()[index])) {
1719
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1720
0
                         "Failed to set buffer for extra channel");
1721
0
  }
1722
0
  frame_settings->enc->input_queue.back().frame->ec_initialized[index] = 1;
1723
1724
0
  return JXL_ENC_SUCCESS;
1725
0
}
1726
1727
0
void JxlEncoderCloseFrames(JxlEncoder* enc) { enc->frames_closed = true; }
1728
1729
0
void JxlEncoderCloseBoxes(JxlEncoder* enc) { enc->boxes_closed = true; }
1730
1731
0
void JxlEncoderCloseInput(JxlEncoder* enc) {
1732
0
  JxlEncoderCloseFrames(enc);
1733
0
  JxlEncoderCloseBoxes(enc);
1734
0
}
1735
JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out,
1736
5.61k
                                         size_t* avail_out) {
1737
11.3k
  while (*avail_out > 0 &&
1738
11.3k
         (!enc->output_byte_queue.empty() || !enc->input_queue.empty())) {
1739
5.70k
    if (!enc->output_byte_queue.empty()) {
1740
5.61k
      size_t to_copy = std::min(*avail_out, enc->output_byte_queue.size());
1741
5.61k
      std::copy_n(enc->output_byte_queue.begin(), to_copy, *next_out);
1742
5.61k
      *next_out += to_copy;
1743
5.61k
      *avail_out -= to_copy;
1744
5.61k
      enc->output_byte_queue.erase(enc->output_byte_queue.begin(),
1745
5.61k
                                   enc->output_byte_queue.begin() + to_copy);
1746
5.61k
    } else if (!enc->input_queue.empty()) {
1747
92
      if (enc->RefillOutputByteQueue() != JXL_ENC_SUCCESS) {
1748
0
        return JXL_ENC_ERROR;
1749
0
      }
1750
92
    }
1751
5.70k
  }
1752
1753
5.61k
  if (!enc->output_byte_queue.empty() || !enc->input_queue.empty()) {
1754
5.52k
    return JXL_ENC_NEED_MORE_OUTPUT;
1755
5.52k
  }
1756
92
  return JXL_ENC_SUCCESS;
1757
5.61k
}
1758
1759
JxlEncoderStatus JxlEncoderSetFrameHeader(JxlEncoderOptions* frame_settings,
1760
92
                                          const JxlFrameHeader* frame_header) {
1761
92
  if (frame_header->layer_info.blend_info.source > 3) {
1762
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1763
0
                         "invalid blending source index");
1764
0
  }
1765
  // If there are no extra channels, it's ok for the value to be 0.
1766
92
  if (frame_header->layer_info.blend_info.alpha != 0 &&
1767
92
      frame_header->layer_info.blend_info.alpha >=
1768
0
          frame_settings->enc->metadata.m.extra_channel_info.size()) {
1769
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1770
0
                         "alpha blend channel index out of bounds");
1771
0
  }
1772
1773
92
  frame_settings->values.header = *frame_header;
1774
  // Setting the frame header resets the frame name, it must be set again with
1775
  // JxlEncoderSetFrameName if desired.
1776
92
  frame_settings->values.frame_name = "";
1777
1778
92
  return JXL_ENC_SUCCESS;
1779
92
}
1780
1781
JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo(
1782
    JxlEncoderOptions* frame_settings, size_t index,
1783
0
    const JxlBlendInfo* blend_info) {
1784
0
  if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
1785
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1786
0
                         "Invalid value for the index of extra channel");
1787
0
  }
1788
1789
0
  if (frame_settings->values.extra_channel_blend_info.size() !=
1790
0
      frame_settings->enc->metadata.m.num_extra_channels) {
1791
0
    JxlBlendInfo default_blend_info;
1792
0
    JxlEncoderInitBlendInfo(&default_blend_info);
1793
0
    frame_settings->values.extra_channel_blend_info.resize(
1794
0
        frame_settings->enc->metadata.m.num_extra_channels, default_blend_info);
1795
0
  }
1796
0
  frame_settings->values.extra_channel_blend_info[index] = *blend_info;
1797
0
  return JXL_ENC_SUCCESS;
1798
0
}
1799
1800
JxlEncoderStatus JxlEncoderSetFrameName(JxlEncoderFrameSettings* frame_settings,
1801
0
                                        const char* frame_name) {
1802
0
  std::string str = frame_name ? frame_name : "";
1803
0
  if (str.size() > 1071) {
1804
0
    return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
1805
0
                         "frame name can be max 1071 bytes long");
1806
0
  }
1807
0
  frame_settings->values.frame_name = str;
1808
0
  frame_settings->values.header.name_length = str.size();
1809
0
  return JXL_ENC_SUCCESS;
1810
0
}
1811
1812
void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding,
1813
0
                               JXL_BOOL is_gray) {
1814
0
  ConvertInternalToExternalColorEncoding(jxl::ColorEncoding::SRGB(is_gray),
1815
0
                                         color_encoding);
1816
0
}
1817
1818
void JxlColorEncodingSetToLinearSRGB(JxlColorEncoding* color_encoding,
1819
0
                                     JXL_BOOL is_gray) {
1820
0
  ConvertInternalToExternalColorEncoding(
1821
0
      jxl::ColorEncoding::LinearSRGB(is_gray), color_encoding);
1822
0
}