tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

encode_test.cc (85334B)


      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/cms.h>
      7 #include <jxl/cms_interface.h>
      8 #include <jxl/codestream_header.h>
      9 #include <jxl/color_encoding.h>
     10 #include <jxl/decode.h>
     11 #include <jxl/decode_cxx.h>
     12 #include <jxl/encode.h>
     13 #include <jxl/encode_cxx.h>
     14 #include <jxl/memory_manager.h>
     15 #include <jxl/types.h>
     16 
     17 #include <cstddef>
     18 #include <cstdint>
     19 #include <cstdio>
     20 #include <cstdlib>
     21 #include <cstring>
     22 #include <mutex>
     23 #include <ostream>
     24 #include <set>
     25 #include <string>
     26 #include <tuple>
     27 #include <utility>
     28 #include <vector>
     29 
     30 #include "lib/extras/codec.h"
     31 #include "lib/extras/dec/jxl.h"
     32 #include "lib/extras/metrics.h"
     33 #include "lib/extras/packed_image.h"
     34 #include "lib/jxl/base/byte_order.h"
     35 #include "lib/jxl/base/c_callback_support.h"
     36 #include "lib/jxl/base/override.h"
     37 #include "lib/jxl/base/span.h"
     38 #include "lib/jxl/base/status.h"
     39 #include "lib/jxl/common.h"  // JXL_HIGH_PRECISION
     40 #include "lib/jxl/enc_params.h"
     41 #include "lib/jxl/encode_internal.h"
     42 #include "lib/jxl/modular/options.h"
     43 #include "lib/jxl/test_image.h"
     44 #include "lib/jxl/test_memory_manager.h"
     45 #include "lib/jxl/test_utils.h"
     46 #include "lib/jxl/testing.h"
     47 
     48 namespace {
     49 bool SameDecodedPixels(const std::vector<uint8_t>& compressed0,
     50                       const std::vector<uint8_t>& compressed1) {
     51  jxl::extras::JXLDecompressParams dparams;
     52  dparams.accepted_formats = {
     53      {3, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0},
     54      {4, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0},
     55  };
     56  jxl::extras::PackedPixelFile ppf0;
     57  EXPECT_TRUE(DecodeImageJXL(compressed0.data(), compressed0.size(), dparams,
     58                             nullptr, &ppf0, nullptr));
     59  jxl::extras::PackedPixelFile ppf1;
     60  EXPECT_TRUE(DecodeImageJXL(compressed1.data(), compressed1.size(), dparams,
     61                             nullptr, &ppf1, nullptr));
     62  return jxl::test::SamePixels(ppf0, ppf1);
     63 }
     64 }  // namespace
     65 
     66 TEST(EncodeTest, AddFrameAfterCloseInputTest) {
     67  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
     68  EXPECT_NE(nullptr, enc.get());
     69 
     70  JxlEncoderCloseInput(enc.get());
     71 
     72  size_t xsize = 64;
     73  size_t ysize = 64;
     74  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
     75  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
     76 
     77  jxl::CodecInOut input_io =
     78      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);
     79 
     80  JxlBasicInfo basic_info;
     81  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
     82  basic_info.xsize = xsize;
     83  basic_info.ysize = ysize;
     84  basic_info.uses_original_profile = 0;
     85  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
     86  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
     87  JxlColorEncoding color_encoding;
     88  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
     89  JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
     90  EXPECT_EQ(JXL_ENC_SUCCESS,
     91            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
     92  JxlEncoderFrameSettings* frame_settings =
     93      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
     94  EXPECT_EQ(JXL_ENC_ERROR,
     95            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
     96                                    pixels.data(), pixels.size()));
     97 }
     98 
     99 TEST(EncodeTest, AddJPEGAfterCloseTest) {
    100  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    101  EXPECT_NE(nullptr, enc.get());
    102 
    103  JxlEncoderCloseInput(enc.get());
    104 
    105  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
    106  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
    107 
    108  JxlEncoderFrameSettings* frame_settings =
    109      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    110 
    111  EXPECT_EQ(JXL_ENC_ERROR,
    112            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
    113 }
    114 
    115 TEST(EncodeTest, AddFrameBeforeBasicInfoTest) {
    116  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    117  EXPECT_NE(nullptr, enc.get());
    118 
    119  size_t xsize = 64;
    120  size_t ysize = 64;
    121  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
    122  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
    123 
    124  jxl::CodecInOut input_io =
    125      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);
    126 
    127  JxlColorEncoding color_encoding;
    128  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
    129  JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
    130  EXPECT_EQ(JXL_ENC_ERROR,
    131            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
    132  JxlEncoderFrameSettings* frame_settings =
    133      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    134  EXPECT_EQ(JXL_ENC_ERROR,
    135            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    136                                    pixels.data(), pixels.size()));
    137 }
    138 
    139 TEST(EncodeTest, DefaultAllocTest) {
    140  JxlEncoder* enc = JxlEncoderCreate(nullptr);
    141  EXPECT_NE(nullptr, enc);
    142  JxlEncoderDestroy(enc);
    143 }
    144 
    145 TEST(EncodeTest, CustomAllocTest) {
    146  struct CalledCounters {
    147    int allocs = 0;
    148    int frees = 0;
    149  } counters;
    150 
    151  JxlMemoryManager mm;
    152  mm.opaque = &counters;
    153  mm.alloc = [](void* opaque, size_t size) {
    154    reinterpret_cast<CalledCounters*>(opaque)->allocs++;
    155    return malloc(size);
    156  };
    157  mm.free = [](void* opaque, void* address) {
    158    reinterpret_cast<CalledCounters*>(opaque)->frees++;
    159    free(address);
    160  };
    161 
    162  {
    163    JxlEncoderPtr enc = JxlEncoderMake(&mm);
    164    EXPECT_NE(nullptr, enc.get());
    165    EXPECT_LE(1, counters.allocs);
    166    EXPECT_EQ(0, counters.frees);
    167  }
    168  EXPECT_LE(1, counters.frees);
    169 }
    170 
    171 TEST(EncodeTest, DefaultParallelRunnerTest) {
    172  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    173  EXPECT_NE(nullptr, enc.get());
    174  EXPECT_EQ(JXL_ENC_SUCCESS,
    175            JxlEncoderSetParallelRunner(enc.get(), nullptr, nullptr));
    176 }
    177 
    178 void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
    179                         const JxlEncoderFrameSettings* frame_settings,
    180                         size_t max_compressed_size,
    181                         bool lossy_use_original_profile) {
    182  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
    183  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
    184 
    185  jxl::CodecInOut input_io =
    186      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);
    187 
    188  JxlBasicInfo basic_info;
    189  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
    190  basic_info.xsize = xsize;
    191  basic_info.ysize = ysize;
    192  if (frame_settings->values.lossless || lossy_use_original_profile) {
    193    basic_info.uses_original_profile = JXL_TRUE;
    194  } else {
    195    basic_info.uses_original_profile = JXL_FALSE;
    196  }
    197  // 16-bit alpha means this requires level 10
    198  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
    199  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
    200  JxlColorEncoding color_encoding;
    201  JxlColorEncodingSetToSRGB(&color_encoding, JXL_TRUE);
    202  EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetColorEncoding(enc, &color_encoding));
    203  JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
    204  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
    205  pixel_format.num_channels = 1;
    206  EXPECT_EQ(JXL_ENC_ERROR,
    207            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    208                                    pixels.data(), pixels.size()));
    209  pixel_format.num_channels = 4;
    210  EXPECT_EQ(JXL_ENC_SUCCESS,
    211            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    212                                    pixels.data(), pixels.size()));
    213  JxlEncoderCloseInput(enc);
    214 
    215  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
    216  uint8_t* next_out = compressed.data();
    217  size_t avail_out = compressed.size() - (next_out - compressed.data());
    218  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
    219  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    220    process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
    221    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    222      size_t offset = next_out - compressed.data();
    223      compressed.resize(compressed.size() * 2);
    224      next_out = compressed.data() + offset;
    225      avail_out = compressed.size() - offset;
    226    }
    227  }
    228  compressed.resize(next_out - compressed.data());
    229  EXPECT_LE(compressed.size(), max_compressed_size);
    230  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
    231  jxl::CodecInOut decoded_io{jxl::test::MemoryManager()};
    232  EXPECT_TRUE(jxl::test::DecodeFile(
    233      {}, jxl::Bytes(compressed.data(), compressed.size()), &decoded_io));
    234 
    235  static constexpr double kMaxButteraugli =
    236 #if JXL_HIGH_PRECISION
    237      3.2;
    238 #else
    239      8.7;
    240 #endif
    241  EXPECT_LE(
    242      ComputeDistance2(input_io.Main(), decoded_io.Main(), *JxlGetDefaultCms()),
    243      kMaxButteraugli);
    244 }
    245 
    246 void VerifyFrameEncoding(JxlEncoder* enc,
    247                         const JxlEncoderFrameSettings* frame_settings) {
    248  VerifyFrameEncoding(63, 129, enc, frame_settings, 27000,
    249                      /*lossy_use_original_profile=*/false);
    250 }
    251 
    252 TEST(EncodeTest, FrameEncodingTest) {
    253  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    254  EXPECT_NE(nullptr, enc.get());
    255  VerifyFrameEncoding(enc.get(),
    256                      JxlEncoderFrameSettingsCreate(enc.get(), nullptr));
    257 }
    258 
    259 TEST(EncodeTest, EncoderResetTest) {
    260  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    261  EXPECT_NE(nullptr, enc.get());
    262  VerifyFrameEncoding(50, 200, enc.get(),
    263                      JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 4599,
    264                      false);
    265  // Encoder should become reusable for a new image from scratch after using
    266  // reset.
    267  JxlEncoderReset(enc.get());
    268  VerifyFrameEncoding(157, 77, enc.get(),
    269                      JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 2300,
    270                      false);
    271 }
    272 
    273 TEST(EncodeTest, CmsTest) {
    274  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    275  EXPECT_NE(nullptr, enc.get());
    276  bool cms_called = false;
    277  JxlCmsInterface cms = *JxlGetDefaultCms();
    278  struct InitData {
    279    void* original_init_data;
    280    jpegxl_cms_init_func original_init;
    281    bool* cms_called;
    282  };
    283  InitData init_data = {/*original_init_data=*/cms.init_data,
    284                        /*original_init=*/cms.init,
    285                        /*cms_called=*/&cms_called};
    286  cms.init_data = &init_data;
    287  cms.init = +[](void* raw_init_data, size_t num_threads,
    288                 size_t pixels_per_thread, const JxlColorProfile* input_profile,
    289                 const JxlColorProfile* output_profile,
    290                 float intensity_target) {
    291    const InitData* init_data = static_cast<const InitData*>(raw_init_data);
    292    *init_data->cms_called = true;
    293    return init_data->original_init(init_data->original_init_data, num_threads,
    294                                    pixels_per_thread, input_profile,
    295                                    output_profile, intensity_target);
    296  };
    297  JxlEncoderSetCms(enc.get(), cms);
    298  JxlEncoderFrameSettings* frame_settings =
    299      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    300  JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE);
    301  ASSERT_EQ(JXL_ENC_SUCCESS,
    302            JxlEncoderFrameSettingsSetOption(frame_settings,
    303                                             JXL_ENC_FRAME_SETTING_EFFORT, 8));
    304  VerifyFrameEncoding(enc.get(), frame_settings);
    305  EXPECT_TRUE(cms_called);
    306 }
    307 
    308 TEST(EncodeTest, FrameSettingsTest) {
    309  {
    310    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    311    EXPECT_NE(nullptr, enc.get());
    312    JxlEncoderFrameSettings* frame_settings =
    313        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    314    EXPECT_EQ(JXL_ENC_SUCCESS,
    315              JxlEncoderFrameSettingsSetOption(
    316                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 5));
    317    VerifyFrameEncoding(enc.get(), frame_settings);
    318    EXPECT_EQ(jxl::SpeedTier::kHare, enc->last_used_cparams.speed_tier);
    319  }
    320 
    321  {
    322    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    323    EXPECT_NE(nullptr, enc.get());
    324    JxlEncoderFrameSettings* frame_settings =
    325        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    326    const size_t nb_options = 23;
    327    const JxlEncoderFrameSettingId options[nb_options] = {
    328        JXL_ENC_FRAME_SETTING_EFFORT,
    329        JXL_ENC_FRAME_SETTING_BROTLI_EFFORT,
    330        JXL_ENC_FRAME_SETTING_DECODING_SPEED,
    331        JXL_ENC_FRAME_SETTING_RESAMPLING,
    332        JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING,
    333        JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED,
    334        JXL_ENC_FRAME_SETTING_EPF,
    335        JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X,
    336        JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y,
    337        JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC,
    338        JXL_ENC_FRAME_SETTING_PALETTE_COLORS,
    339        JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM,
    340        JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE,
    341        JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE,
    342        JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR,
    343        JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS,
    344        JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL,
    345        JXL_ENC_FRAME_INDEX_BOX,
    346        JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES,
    347        JXL_ENC_FRAME_SETTING_BUFFERING,
    348        JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF,
    349        JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP,
    350        JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF};
    351    const int too_low[nb_options] = {0,  -2, -2, 3,  -2, -2, -2, -2,
    352                                     -2, -2, -2, -2, -2, -2, -2, -2,
    353                                     -2, -1, -2, -2, -2, -2, -2};
    354    const int too_high[nb_options] = {11, 12, 5,     16, 6,  2, 4,  -3,
    355                                      -3, 3,  70914, 3,  42, 4, 16, 12,
    356                                      2,  2,  2,     4,  2,  2, 2};
    357    const int in_range[nb_options] = {5,  5, 3,  1,  1,  1,  3,  -1,
    358                                      0,  1, -1, -1, 3,  2,  15, -1,
    359                                      -1, 1, 0,  0,  -1, -1, -1};
    360    for (size_t i = 0; i < nb_options; i++) {
    361      // Lower than currently supported values
    362      EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetOption(
    363                                   frame_settings, options[i], too_low[i]));
    364      // Higher than currently supported values
    365      EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetOption(
    366                                   frame_settings, options[i], too_high[i]));
    367      // Using SetFloatOption on integer options
    368      EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetFloatOption(
    369                                   frame_settings, options[i], 1.0f));
    370      // Within range of the currently supported values
    371      EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFrameSettingsSetOption(
    372                                     frame_settings, options[i], in_range[i]));
    373    }
    374    // Effort 11 should only work when expert options are allowed
    375    EXPECT_EQ(JXL_ENC_ERROR,
    376              JxlEncoderFrameSettingsSetOption(
    377                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 11));
    378    JxlEncoderAllowExpertOptions(enc.get());
    379    EXPECT_EQ(JXL_ENC_SUCCESS,
    380              JxlEncoderFrameSettingsSetOption(
    381                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 11));
    382 
    383    // Non-existing option
    384    EXPECT_EQ(JXL_ENC_ERROR,
    385              JxlEncoderFrameSettingsSetOption(
    386                  frame_settings, JXL_ENC_FRAME_SETTING_FILL_ENUM, 0));
    387    EXPECT_EQ(JXL_ENC_ERROR,
    388              JxlEncoderFrameSettingsSetFloatOption(
    389                  frame_settings, JXL_ENC_FRAME_SETTING_FILL_ENUM, 0.f));
    390 
    391    // Float options
    392    EXPECT_EQ(JXL_ENC_ERROR,
    393              JxlEncoderFrameSettingsSetFloatOption(
    394                  frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, -1.0f));
    395    EXPECT_EQ(JXL_ENC_SUCCESS,
    396              JxlEncoderFrameSettingsSetFloatOption(
    397                  frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 100.0f));
    398    EXPECT_EQ(
    399        JXL_ENC_ERROR,
    400        JxlEncoderFrameSettingsSetFloatOption(
    401            frame_settings,
    402            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 101.0f));
    403    EXPECT_EQ(
    404        JXL_ENC_ERROR,
    405        JxlEncoderFrameSettingsSetFloatOption(
    406            frame_settings,
    407            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, -2.0f));
    408    EXPECT_EQ(
    409        JXL_ENC_SUCCESS,
    410        JxlEncoderFrameSettingsSetFloatOption(
    411            frame_settings,
    412            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, -1.0f));
    413    EXPECT_EQ(JXL_ENC_ERROR,
    414              JxlEncoderFrameSettingsSetFloatOption(
    415                  frame_settings,
    416                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 101.0f));
    417    EXPECT_EQ(JXL_ENC_ERROR,
    418              JxlEncoderFrameSettingsSetFloatOption(
    419                  frame_settings,
    420                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, -2.0f));
    421    EXPECT_EQ(JXL_ENC_SUCCESS,
    422              JxlEncoderFrameSettingsSetFloatOption(
    423                  frame_settings,
    424                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, -1.0f));
    425    EXPECT_EQ(JXL_ENC_ERROR,
    426              JxlEncoderFrameSettingsSetFloatOption(
    427                  frame_settings,
    428                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 101.0f));
    429    EXPECT_EQ(JXL_ENC_ERROR,
    430              JxlEncoderFrameSettingsSetFloatOption(
    431                  frame_settings,
    432                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, -2.0f));
    433    EXPECT_EQ(JXL_ENC_SUCCESS,
    434              JxlEncoderFrameSettingsSetFloatOption(
    435                  frame_settings,
    436                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, -1.0f));
    437    EXPECT_EQ(JXL_ENC_ERROR,
    438              JxlEncoderFrameSettingsSetOption(
    439                  frame_settings,
    440                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 50.0f));
    441    EXPECT_EQ(JXL_ENC_ERROR,
    442              JxlEncoderFrameSettingsSetOption(
    443                  frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 50.0f));
    444 
    445    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3700, false);
    446  }
    447 
    448  {
    449    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    450    EXPECT_NE(nullptr, enc.get());
    451    JxlEncoderFrameSettings* frame_settings =
    452        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    453    EXPECT_EQ(JXL_ENC_SUCCESS,
    454              JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE));
    455    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3600, false);
    456    EXPECT_EQ(true, enc->last_used_cparams.IsLossless());
    457  }
    458 
    459  {
    460    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    461    EXPECT_NE(nullptr, enc.get());
    462    JxlEncoderFrameSettings* frame_settings =
    463        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    464    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetFrameDistance(frame_settings, 0.5));
    465    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3200, false);
    466    EXPECT_EQ(0.5, enc->last_used_cparams.butteraugli_distance);
    467  }
    468 
    469  {
    470    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    471    JxlEncoderFrameSettings* frame_settings =
    472        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    473    // Disallowed negative distance
    474    EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetFrameDistance(frame_settings, -1));
    475  }
    476 
    477  {
    478    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    479    EXPECT_NE(nullptr, enc.get());
    480    JxlEncoderFrameSettings* frame_settings =
    481        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    482    EXPECT_EQ(JXL_ENC_SUCCESS,
    483              JxlEncoderFrameSettingsSetOption(
    484                  frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, 2));
    485    VerifyFrameEncoding(enc.get(), frame_settings);
    486    EXPECT_EQ(2u, enc->last_used_cparams.decoding_speed_tier);
    487  }
    488 
    489  {
    490    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    491    EXPECT_NE(nullptr, enc.get());
    492    JxlEncoderFrameSettings* frame_settings =
    493        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    494    EXPECT_EQ(JXL_ENC_ERROR,
    495              JxlEncoderFrameSettingsSetOption(
    496                  frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER, 100));
    497    EXPECT_EQ(JXL_ENC_SUCCESS,
    498              JxlEncoderFrameSettingsSetOption(
    499                  frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER, 1));
    500    EXPECT_EQ(
    501        JXL_ENC_SUCCESS,
    502        JxlEncoderFrameSettingsSetOption(
    503            frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X, 5));
    504    VerifyFrameEncoding(enc.get(), frame_settings);
    505    EXPECT_EQ(true, enc->last_used_cparams.centerfirst);
    506    EXPECT_EQ(5, enc->last_used_cparams.center_x);
    507  }
    508 
    509  {
    510    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    511    EXPECT_NE(nullptr, enc.get());
    512    JxlEncoderFrameSettings* frame_settings =
    513        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    514    EXPECT_EQ(JXL_ENC_SUCCESS,
    515              JxlEncoderFrameSettingsSetOption(
    516                  frame_settings, JXL_ENC_FRAME_SETTING_RESPONSIVE, 0));
    517    EXPECT_EQ(JXL_ENC_SUCCESS,
    518              JxlEncoderFrameSettingsSetOption(
    519                  frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1));
    520    EXPECT_EQ(JXL_ENC_SUCCESS,
    521              JxlEncoderFrameSettingsSetOption(
    522                  frame_settings, JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, -1));
    523    EXPECT_EQ(JXL_ENC_SUCCESS,
    524              JxlEncoderFrameSettingsSetOption(
    525                  frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2));
    526    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3430,
    527                        /*lossy_use_original_profile=*/false);
    528    EXPECT_EQ(false, enc->last_used_cparams.responsive);
    529    EXPECT_EQ(jxl::Override::kOn, enc->last_used_cparams.progressive_mode);
    530    EXPECT_EQ(2, enc->last_used_cparams.progressive_dc);
    531  }
    532 
    533  {
    534    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    535    EXPECT_NE(nullptr, enc.get());
    536    JxlEncoderFrameSettings* frame_settings =
    537        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    538    EXPECT_EQ(
    539        JXL_ENC_SUCCESS,
    540        JxlEncoderFrameSettingsSetFloatOption(
    541            frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 1777.777));
    542    VerifyFrameEncoding(enc.get(), frame_settings);
    543    EXPECT_NEAR(1777.777f, enc->last_used_cparams.photon_noise_iso, 1E-4);
    544  }
    545 
    546  {
    547    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    548    EXPECT_NE(nullptr, enc.get());
    549    JxlEncoderFrameSettings* frame_settings =
    550        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    551    EXPECT_EQ(JXL_ENC_SUCCESS,
    552              JxlEncoderFrameSettingsSetFloatOption(
    553                  frame_settings,
    554                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 55.0f));
    555    EXPECT_EQ(JXL_ENC_SUCCESS,
    556              JxlEncoderFrameSettingsSetFloatOption(
    557                  frame_settings,
    558                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 25.0f));
    559    EXPECT_EQ(JXL_ENC_SUCCESS,
    560              JxlEncoderFrameSettingsSetOption(
    561                  frame_settings, JXL_ENC_FRAME_SETTING_PALETTE_COLORS, 70000));
    562    EXPECT_EQ(JXL_ENC_SUCCESS,
    563              JxlEncoderFrameSettingsSetOption(
    564                  frame_settings, JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, 1));
    565    VerifyFrameEncoding(enc.get(), frame_settings);
    566    EXPECT_NEAR(55.0f,
    567                enc->last_used_cparams.channel_colors_pre_transform_percent,
    568                1E-6);
    569    EXPECT_NEAR(25.0f, enc->last_used_cparams.channel_colors_percent, 1E-6);
    570    EXPECT_EQ(70000, enc->last_used_cparams.palette_colors);
    571    EXPECT_EQ(true, enc->last_used_cparams.lossy_palette);
    572  }
    573 
    574  {
    575    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    576    EXPECT_NE(nullptr, enc.get());
    577    JxlEncoderFrameSettings* frame_settings =
    578        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    579    EXPECT_EQ(
    580        JXL_ENC_SUCCESS,
    581        JxlEncoderFrameSettingsSetOption(
    582            frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, 30));
    583    EXPECT_EQ(JXL_ENC_SUCCESS,
    584              JxlEncoderFrameSettingsSetOption(
    585                  frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, 2));
    586    EXPECT_EQ(JXL_ENC_SUCCESS,
    587              JxlEncoderFrameSettingsSetOption(
    588                  frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 14));
    589    EXPECT_EQ(
    590        JXL_ENC_SUCCESS,
    591        JxlEncoderFrameSettingsSetFloatOption(
    592            frame_settings,
    593            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 77.0f));
    594    EXPECT_EQ(
    595        JXL_ENC_SUCCESS,
    596        JxlEncoderFrameSettingsSetOption(
    597            frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, 7));
    598    VerifyFrameEncoding(enc.get(), frame_settings);
    599    EXPECT_EQ(30, enc->last_used_cparams.colorspace);
    600    EXPECT_EQ(2, enc->last_used_cparams.modular_group_size_shift);
    601    EXPECT_EQ(jxl::Predictor::Best, enc->last_used_cparams.options.predictor);
    602    EXPECT_NEAR(0.77f, enc->last_used_cparams.options.nb_repeats, 1E-6);
    603    EXPECT_EQ(7, enc->last_used_cparams.options.max_properties);
    604  }
    605 
    606  {
    607    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    608    EXPECT_NE(nullptr, enc.get());
    609    JxlEncoderFrameSettings* frame_settings =
    610        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    611    EXPECT_EQ(JXL_ENC_SUCCESS,
    612              JxlEncoderFrameSettingsSetOption(
    613                  frame_settings, JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 0));
    614    VerifyFrameEncoding(enc.get(), frame_settings);
    615    EXPECT_EQ(false, enc->last_used_cparams.force_cfl_jpeg_recompression);
    616  }
    617 
    618  {
    619    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    620    EXPECT_NE(nullptr, enc.get());
    621    JxlEncoderFrameSettings* frame_settings =
    622        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    623    EXPECT_EQ(JXL_ENC_SUCCESS,
    624              JxlEncoderFrameSettingsSetOption(
    625                  frame_settings, JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 1));
    626    VerifyFrameEncoding(enc.get(), frame_settings);
    627    EXPECT_EQ(true, enc->last_used_cparams.force_cfl_jpeg_recompression);
    628  }
    629 }
    630 
    631 TEST(EncodeTest, LossyEncoderUseOriginalProfileTest) {
    632  {
    633    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    634    ASSERT_NE(nullptr, enc.get());
    635    JxlEncoderFrameSettings* frame_settings =
    636        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    637    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7897, true);
    638  }
    639  {
    640    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    641    ASSERT_NE(nullptr, enc.get());
    642    JxlEncoderFrameSettings* frame_settings =
    643        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    644    EXPECT_EQ(JXL_ENC_SUCCESS,
    645              JxlEncoderFrameSettingsSetOption(
    646                  frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2));
    647    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 8310, true);
    648  }
    649  {
    650    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    651    ASSERT_NE(nullptr, enc.get());
    652    JxlEncoderFrameSettings* frame_settings =
    653        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    654    ASSERT_EQ(JXL_ENC_SUCCESS,
    655              JxlEncoderFrameSettingsSetOption(
    656                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8));
    657    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7228, true);
    658  }
    659 }
    660 
    661 namespace {
    662 // Returns a copy of buf from offset to offset+size, or a new zeroed vector if
    663 // the result would have been out of bounds taking integer overflow into
    664 // account.
    665 std::vector<uint8_t> SliceSpan(const jxl::Span<const uint8_t>& buf,
    666                               size_t offset, size_t size) {
    667  if (offset + size >= buf.size()) {
    668    return std::vector<uint8_t>(size, 0);
    669  }
    670  if (offset + size < offset) {
    671    return std::vector<uint8_t>(size, 0);
    672  }
    673  return std::vector<uint8_t>(buf.data() + offset, buf.data() + offset + size);
    674 }
    675 
    676 struct Box {
    677  // The type of the box.
    678  // If "uuid", use extended_type instead
    679  char type[4] = {0, 0, 0, 0};
    680 
    681  // The extended_type is only used when type == "uuid".
    682  // Extended types are not used in JXL. However, the box format itself
    683  // supports this so they are handled correctly.
    684  char extended_type[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    685 
    686  // Box data.
    687  jxl::Span<const uint8_t> data = jxl::Bytes(nullptr, 0);
    688 
    689  // If the size is not given, the datasize extends to the end of the file.
    690  // If this field is false, the size field is not encoded when the box is
    691  // serialized.
    692  bool data_size_given = true;
    693 
    694  // If successful, returns true and sets `in` to be the rest data (if any).
    695  // If `in` contains a box with a size larger than `in.size()`, will not
    696  // modify `in`, and will return true but the data `Span<uint8_t>` will
    697  // remain set to nullptr.
    698  // If unsuccessful, returns error and doesn't modify `in`.
    699  jxl::Status Decode(jxl::Span<const uint8_t>* in) {
    700    // Total box_size including this header itself.
    701    uint64_t box_size = LoadBE32(SliceSpan(*in, 0, 4).data());
    702    size_t pos = 4;
    703 
    704    memcpy(type, SliceSpan(*in, pos, 4).data(), 4);
    705    pos += 4;
    706 
    707    if (box_size == 1) {
    708      // If the size is 1, it indicates extended size read from 64-bit integer.
    709      box_size = LoadBE64(SliceSpan(*in, pos, 8).data());
    710      pos += 8;
    711    }
    712 
    713    if (!memcmp("uuid", type, 4)) {
    714      memcpy(extended_type, SliceSpan(*in, pos, 16).data(), 16);
    715      pos += 16;
    716    }
    717 
    718    // This is the end of the box header, the box data begins here. Handle
    719    // the data size now.
    720    const size_t header_size = pos;
    721 
    722    if (box_size != 0) {
    723      if (box_size < header_size) {
    724        return JXL_FAILURE("Invalid box size");
    725      }
    726      if (box_size > in->size()) {
    727        // The box is fine, but the input is too short.
    728        return true;
    729      }
    730      data_size_given = true;
    731      data = jxl::Bytes(in->data() + header_size, box_size - header_size);
    732    } else {
    733      data_size_given = false;
    734      data = jxl::Bytes(in->data() + header_size, in->size() - header_size);
    735    }
    736 
    737    *in = jxl::Bytes(in->data() + header_size + data.size(),
    738                     in->size() - header_size - data.size());
    739    return true;
    740  }
    741 };
    742 
    743 struct Container {
    744  std::vector<Box> boxes;
    745 
    746  // If successful, returns true and sets `in` to be the rest data (if any).
    747  // If unsuccessful, returns error and doesn't modify `in`.
    748  jxl::Status Decode(jxl::Span<const uint8_t>* in) {
    749    boxes.clear();
    750 
    751    Box signature_box;
    752    JXL_RETURN_IF_ERROR(signature_box.Decode(in));
    753    if (memcmp("JXL ", signature_box.type, 4) != 0) {
    754      return JXL_FAILURE("Invalid magic signature");
    755    }
    756    if (signature_box.data.size() != 4)
    757      return JXL_FAILURE("Invalid magic signature");
    758    if (signature_box.data[0] != 0xd || signature_box.data[1] != 0xa ||
    759        signature_box.data[2] != 0x87 || signature_box.data[3] != 0xa) {
    760      return JXL_FAILURE("Invalid magic signature");
    761    }
    762 
    763    Box ftyp_box;
    764    JXL_RETURN_IF_ERROR(ftyp_box.Decode(in));
    765    if (memcmp("ftyp", ftyp_box.type, 4) != 0) {
    766      return JXL_FAILURE("Invalid ftyp");
    767    }
    768    if (ftyp_box.data.size() != 12) return JXL_FAILURE("Invalid ftyp");
    769    const char* expected = "jxl \0\0\0\0jxl ";
    770    if (memcmp(expected, ftyp_box.data.data(), 12) != 0)
    771      return JXL_FAILURE("Invalid ftyp");
    772 
    773    while (!in->empty()) {
    774      Box box = {};
    775      JXL_RETURN_IF_ERROR(box.Decode(in));
    776      if (box.data.data() == nullptr) {
    777        // The decoding encountered a box, but not enough data yet.
    778        return true;
    779      }
    780      boxes.emplace_back(box);
    781    }
    782 
    783    return true;
    784  }
    785 };
    786 
    787 }  // namespace
    788 
    789 TEST(EncodeTest, SingleFrameBoundedJXLCTest) {
    790  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    791  EXPECT_NE(nullptr, enc.get());
    792  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), true));
    793  JxlEncoderFrameSettings* frame_settings =
    794      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    795 
    796  size_t xsize = 71;
    797  size_t ysize = 23;
    798  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
    799  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
    800 
    801  JxlBasicInfo basic_info;
    802  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
    803  basic_info.xsize = xsize;
    804  basic_info.ysize = ysize;
    805  basic_info.uses_original_profile = JXL_FALSE;
    806  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
    807  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
    808  JxlColorEncoding color_encoding;
    809  JxlColorEncodingSetToSRGB(&color_encoding,
    810                            /*is_gray=*/JXL_FALSE);
    811  EXPECT_EQ(JXL_ENC_SUCCESS,
    812            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
    813  EXPECT_EQ(JXL_ENC_SUCCESS,
    814            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    815                                    pixels.data(), pixels.size()));
    816  JxlEncoderCloseInput(enc.get());
    817 
    818  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
    819  uint8_t* next_out = compressed.data();
    820  size_t avail_out = compressed.size() - (next_out - compressed.data());
    821  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
    822  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    823    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    824    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    825      size_t offset = next_out - compressed.data();
    826      compressed.resize(compressed.size() * 2);
    827      next_out = compressed.data() + offset;
    828      avail_out = compressed.size() - offset;
    829    }
    830  }
    831  compressed.resize(next_out - compressed.data());
    832  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
    833 
    834  Container container = {};
    835  jxl::Span<const uint8_t> encoded_span =
    836      jxl::Bytes(compressed.data(), compressed.size());
    837  EXPECT_TRUE(container.Decode(&encoded_span));
    838  EXPECT_EQ(0u, encoded_span.size());
    839  bool found_jxlc = false;
    840  bool found_jxlp = false;
    841  // The encoder is allowed to either emit a jxlc or one or more jxlp.
    842  for (const auto& box : container.boxes) {
    843    if (memcmp("jxlc", box.type, 4) == 0) {
    844      EXPECT_EQ(false, found_jxlc);  // Max 1 jxlc
    845      EXPECT_EQ(false, found_jxlp);  // Can't mix jxlc and jxlp
    846      found_jxlc = true;
    847    }
    848    if (memcmp("jxlp", box.type, 4) == 0) {
    849      EXPECT_EQ(false, found_jxlc);  // Can't mix jxlc and jxlp
    850      found_jxlp = true;
    851    }
    852    // The encoder shouldn't create an unbounded box in this case, with the
    853    // single frame it knows the full size in time, so can help make decoding
    854    // more efficient by giving the full box size of the final box.
    855    EXPECT_EQ(true, box.data_size_given);
    856  }
    857  EXPECT_EQ(true, found_jxlc || found_jxlp);
    858 }
    859 
    860 TEST(EncodeTest, CodestreamLevelTest) {
    861  size_t xsize = 64;
    862  size_t ysize = 64;
    863  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
    864  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
    865 
    866  jxl::CodecInOut input_io =
    867      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);
    868 
    869  JxlBasicInfo basic_info;
    870  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
    871  basic_info.xsize = xsize;
    872  basic_info.ysize = ysize;
    873  basic_info.uses_original_profile = 0;
    874 
    875  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    876  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
    877  JxlEncoderFrameSettings* frame_settings =
    878      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    879 
    880  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
    881  JxlColorEncoding color_encoding;
    882  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
    883  JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
    884  EXPECT_EQ(JXL_ENC_SUCCESS,
    885            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
    886  EXPECT_EQ(JXL_ENC_SUCCESS,
    887            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    888                                    pixels.data(), pixels.size()));
    889  JxlEncoderCloseInput(enc.get());
    890 
    891  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
    892  uint8_t* next_out = compressed.data();
    893  size_t avail_out = compressed.size() - (next_out - compressed.data());
    894  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
    895  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    896    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    897    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    898      size_t offset = next_out - compressed.data();
    899      compressed.resize(compressed.size() * 2);
    900      next_out = compressed.data() + offset;
    901      avail_out = compressed.size() - offset;
    902    }
    903  }
    904  compressed.resize(next_out - compressed.data());
    905  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
    906 
    907  Container container = {};
    908  jxl::Span<const uint8_t> encoded_span =
    909      jxl::Bytes(compressed.data(), compressed.size());
    910  EXPECT_TRUE(container.Decode(&encoded_span));
    911  EXPECT_EQ(0u, encoded_span.size());
    912  EXPECT_EQ(0, memcmp("jxll", container.boxes[0].type, 4));
    913 }
    914 
    915 TEST(EncodeTest, CodestreamLevelVerificationTest) {
    916  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0};
    917 
    918  JxlBasicInfo basic_info;
    919  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
    920  basic_info.xsize = 64;
    921  basic_info.ysize = 64;
    922  basic_info.uses_original_profile = JXL_FALSE;
    923 
    924  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    925  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
    926 
    927  EXPECT_EQ(5, JxlEncoderGetRequiredCodestreamLevel(enc.get()));
    928 
    929  // Set an image dimension that is too large for level 5, but fits in level 10
    930 
    931  basic_info.xsize = 1ull << 30ull;
    932  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 5));
    933  EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
    934  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
    935  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
    936  EXPECT_EQ(10, JxlEncoderGetRequiredCodestreamLevel(enc.get()));
    937 
    938  // Set an image dimension that is too large even for level 10
    939 
    940  basic_info.xsize = 1ull << 31ull;
    941  EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
    942 }
    943 
    944 JXL_TRANSCODE_JPEG_TEST(EncodeTest, JPEGReconstructionTest) {
    945  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
    946  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
    947 
    948  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    949  JxlEncoderFrameSettings* frame_settings =
    950      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    951 
    952  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
    953  EXPECT_EQ(JXL_ENC_SUCCESS,
    954            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
    955  JxlEncoderCloseInput(enc.get());
    956 
    957  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
    958  uint8_t* next_out = compressed.data();
    959  size_t avail_out = compressed.size() - (next_out - compressed.data());
    960  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
    961  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    962    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    963    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    964      size_t offset = next_out - compressed.data();
    965      compressed.resize(compressed.size() * 2);
    966      next_out = compressed.data() + offset;
    967      avail_out = compressed.size() - offset;
    968    }
    969  }
    970  compressed.resize(next_out - compressed.data());
    971  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
    972 
    973  jxl::extras::JXLDecompressParams dparams;
    974  jxl::test::DefaultAcceptedFormats(dparams);
    975  std::vector<uint8_t> decoded_jpeg_bytes;
    976  jxl::extras::PackedPixelFile ppf;
    977  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
    978                             nullptr, &ppf, &decoded_jpeg_bytes));
    979 
    980  EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size());
    981  EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size()));
    982 }
    983 
    984 JXL_TRANSCODE_JPEG_TEST(EncodeTest, ProgressiveJPEGReconstructionTest) {
    985  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
    986  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
    987 
    988  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    989  JxlEncoderFrameSettings* frame_settings =
    990      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    991 
    992  frame_settings->values.cparams.progressive_mode = jxl::Override::kOn;
    993 
    994  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
    995  EXPECT_EQ(JXL_ENC_SUCCESS,
    996            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
    997  JxlEncoderCloseInput(enc.get());
    998 
    999  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1000  uint8_t* next_out = compressed.data();
   1001  size_t avail_out = compressed.size() - (next_out - compressed.data());
   1002  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
   1003  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
   1004    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
   1005    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
   1006      size_t offset = next_out - compressed.data();
   1007      compressed.resize(compressed.size() * 2);
   1008      next_out = compressed.data() + offset;
   1009      avail_out = compressed.size() - offset;
   1010    }
   1011  }
   1012  compressed.resize(next_out - compressed.data());
   1013  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
   1014 
   1015  jxl::extras::JXLDecompressParams dparams;
   1016  jxl::test::DefaultAcceptedFormats(dparams);
   1017  std::vector<uint8_t> decoded_jpeg_bytes;
   1018  jxl::extras::PackedPixelFile ppf;
   1019  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
   1020                             nullptr, &ppf, &decoded_jpeg_bytes));
   1021 
   1022  EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size());
   1023  EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size()));
   1024 }
   1025 
   1026 static void ProcessEncoder(JxlEncoder* enc, std::vector<uint8_t>& compressed,
   1027                           uint8_t*& next_out, size_t& avail_out) {
   1028  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
   1029  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
   1030    process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
   1031    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
   1032      size_t offset = next_out - compressed.data();
   1033      compressed.resize(compressed.size() * 2);
   1034      next_out = compressed.data() + offset;
   1035      avail_out = compressed.size() - offset;
   1036    }
   1037  }
   1038  size_t offset = next_out - compressed.data();
   1039  compressed.resize(next_out - compressed.data());
   1040  next_out = compressed.data() + offset;
   1041  avail_out = compressed.size() - offset;
   1042  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
   1043 }
   1044 
   1045 TEST(EncodeTest, BasicInfoTest) {
   1046  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1047  EXPECT_NE(nullptr, enc.get());
   1048 
   1049  JxlEncoderFrameSettings* frame_settings =
   1050      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1051  size_t xsize = 1;
   1052  size_t ysize = 1;
   1053  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
   1054  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
   1055  JxlBasicInfo basic_info;
   1056  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
   1057  basic_info.xsize = xsize;
   1058  basic_info.ysize = ysize;
   1059  basic_info.uses_original_profile = 0;
   1060  basic_info.have_animation = 1;
   1061  basic_info.intensity_target = 123.4;
   1062  basic_info.min_nits = 5.0;
   1063  basic_info.linear_below = 12.7;
   1064  basic_info.orientation = JXL_ORIENT_ROTATE_90_CW;
   1065  basic_info.intrinsic_xsize = 88;
   1066  basic_info.intrinsic_ysize = 99;
   1067  basic_info.animation.tps_numerator = 55;
   1068  basic_info.animation.tps_denominator = 77;
   1069  basic_info.animation.num_loops = 10;
   1070  basic_info.animation.have_timecodes = JXL_TRUE;
   1071  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
   1072  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
   1073  JxlColorEncoding color_encoding;
   1074  JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
   1075  EXPECT_EQ(JXL_ENC_SUCCESS,
   1076            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
   1077 
   1078  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1079  uint8_t* next_out = compressed.data();
   1080  size_t avail_out = compressed.size() - (next_out - compressed.data());
   1081  EXPECT_EQ(JXL_ENC_SUCCESS,
   1082            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
   1083                                    pixels.data(), pixels.size()));
   1084  JxlEncoderCloseFrames(enc.get());
   1085  ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1086 
   1087  // Decode to verify the boxes, we don't decode to pixels, only the boxes.
   1088  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
   1089  EXPECT_NE(nullptr, dec.get());
   1090  EXPECT_EQ(JXL_DEC_SUCCESS,
   1091            JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO));
   1092  // Allow testing the orientation field, without this setting it will be
   1093  // overridden to identity.
   1094  JxlDecoderSetKeepOrientation(dec.get(), JXL_TRUE);
   1095  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
   1096  JxlDecoderCloseInput(dec.get());
   1097 
   1098  for (;;) {
   1099    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
   1100    if (status == JXL_DEC_ERROR) {
   1101      FAIL();
   1102    } else if (status == JXL_DEC_SUCCESS) {
   1103      break;
   1104    } else if (status == JXL_DEC_BASIC_INFO) {
   1105      JxlBasicInfo basic_info2;
   1106      EXPECT_EQ(JXL_DEC_SUCCESS,
   1107                JxlDecoderGetBasicInfo(dec.get(), &basic_info2));
   1108      EXPECT_EQ(basic_info.xsize, basic_info2.xsize);
   1109      EXPECT_EQ(basic_info.ysize, basic_info2.ysize);
   1110      EXPECT_EQ(basic_info.bits_per_sample, basic_info2.bits_per_sample);
   1111      EXPECT_EQ(basic_info.exponent_bits_per_sample,
   1112                basic_info2.exponent_bits_per_sample);
   1113      EXPECT_NEAR(basic_info.intensity_target, basic_info2.intensity_target,
   1114                  0.5);
   1115      EXPECT_NEAR(basic_info.min_nits, basic_info2.min_nits, 0.5);
   1116      EXPECT_NEAR(basic_info.linear_below, basic_info2.linear_below, 0.5);
   1117      EXPECT_EQ(basic_info.relative_to_max_display,
   1118                basic_info2.relative_to_max_display);
   1119      EXPECT_EQ(basic_info.uses_original_profile,
   1120                basic_info2.uses_original_profile);
   1121      EXPECT_EQ(basic_info.orientation, basic_info2.orientation);
   1122      EXPECT_EQ(basic_info.intrinsic_xsize, basic_info2.intrinsic_xsize);
   1123      EXPECT_EQ(basic_info.intrinsic_ysize, basic_info2.intrinsic_ysize);
   1124      EXPECT_EQ(basic_info.num_color_channels, basic_info2.num_color_channels);
   1125      // TODO(lode): also test num_extra_channels, but currently there may be a
   1126      // mismatch between 0 and 1 if there is alpha, until encoder support for
   1127      // extra channels is fully implemented.
   1128      EXPECT_EQ(basic_info.alpha_bits, basic_info2.alpha_bits);
   1129      EXPECT_EQ(basic_info.alpha_exponent_bits,
   1130                basic_info2.alpha_exponent_bits);
   1131      EXPECT_EQ(basic_info.alpha_premultiplied,
   1132                basic_info2.alpha_premultiplied);
   1133 
   1134      EXPECT_EQ(basic_info.have_preview, basic_info2.have_preview);
   1135      if (basic_info.have_preview) {
   1136        EXPECT_EQ(basic_info.preview.xsize, basic_info2.preview.xsize);
   1137        EXPECT_EQ(basic_info.preview.ysize, basic_info2.preview.ysize);
   1138      }
   1139 
   1140      EXPECT_EQ(basic_info.have_animation, basic_info2.have_animation);
   1141      if (basic_info.have_animation) {
   1142        EXPECT_EQ(basic_info.animation.tps_numerator,
   1143                  basic_info2.animation.tps_numerator);
   1144        EXPECT_EQ(basic_info.animation.tps_denominator,
   1145                  basic_info2.animation.tps_denominator);
   1146        EXPECT_EQ(basic_info.animation.num_loops,
   1147                  basic_info2.animation.num_loops);
   1148        EXPECT_EQ(basic_info.animation.have_timecodes,
   1149                  basic_info2.animation.have_timecodes);
   1150      }
   1151    } else {
   1152      FAIL();  // unexpected status
   1153    }
   1154  }
   1155 }
   1156 
   1157 TEST(EncodeTest, AnimationHeaderTest) {
   1158  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1159  EXPECT_NE(nullptr, enc.get());
   1160 
   1161  JxlEncoderFrameSettings* frame_settings =
   1162      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1163  size_t xsize = 1;
   1164  size_t ysize = 1;
   1165  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
   1166  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
   1167  JxlBasicInfo basic_info;
   1168  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
   1169  basic_info.xsize = xsize;
   1170  basic_info.ysize = ysize;
   1171  basic_info.have_animation = JXL_TRUE;
   1172  basic_info.animation.tps_numerator = 1000;
   1173  basic_info.animation.tps_denominator = 1;
   1174  basic_info.animation.have_timecodes = JXL_TRUE;
   1175  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
   1176  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
   1177  JxlColorEncoding color_encoding;
   1178  JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
   1179  EXPECT_EQ(JXL_ENC_SUCCESS,
   1180            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
   1181 
   1182  std::string frame_name = "test frame";
   1183  JxlFrameHeader header;
   1184  JxlEncoderInitFrameHeader(&header);
   1185  header.duration = 50;
   1186  header.timecode = 800;
   1187  header.layer_info.blend_info.blendmode = JXL_BLEND_BLEND;
   1188  header.layer_info.blend_info.source = 2;
   1189  header.layer_info.blend_info.clamp = 1;
   1190  JxlBlendInfo extra_channel_blend_info;
   1191  JxlEncoderInitBlendInfo(&extra_channel_blend_info);
   1192  extra_channel_blend_info.blendmode = JXL_BLEND_MULADD;
   1193  JxlEncoderSetFrameHeader(frame_settings, &header);
   1194  JxlEncoderSetExtraChannelBlendInfo(frame_settings, 0,
   1195                                     &extra_channel_blend_info);
   1196  JxlEncoderSetFrameName(frame_settings, frame_name.c_str());
   1197 
   1198  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1199  uint8_t* next_out = compressed.data();
   1200  size_t avail_out = compressed.size() - (next_out - compressed.data());
   1201  EXPECT_EQ(JXL_ENC_SUCCESS,
   1202            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
   1203                                    pixels.data(), pixels.size()));
   1204  JxlEncoderCloseFrames(enc.get());
   1205  ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1206 
   1207  // Decode to verify the boxes, we don't decode to pixels, only the boxes.
   1208  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
   1209  EXPECT_NE(nullptr, dec.get());
   1210 
   1211  // To test the blend_info fields, coalescing must be set to false in the
   1212  // decoder.
   1213  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec.get(), JXL_FALSE));
   1214  EXPECT_EQ(JXL_DEC_SUCCESS,
   1215            JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME));
   1216  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
   1217  JxlDecoderCloseInput(dec.get());
   1218 
   1219  bool seen_frame = false;
   1220 
   1221  for (;;) {
   1222    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
   1223    if (status == JXL_DEC_ERROR) {
   1224      FAIL();
   1225    } else if (status == JXL_DEC_SUCCESS) {
   1226      break;
   1227    } else if (status == JXL_DEC_FRAME) {
   1228      seen_frame = true;
   1229      JxlFrameHeader header2;
   1230      EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec.get(), &header2));
   1231      EXPECT_EQ(header.duration, header2.duration);
   1232      EXPECT_EQ(header.timecode, header2.timecode);
   1233      EXPECT_EQ(header.layer_info.blend_info.blendmode,
   1234                header2.layer_info.blend_info.blendmode);
   1235      EXPECT_EQ(header.layer_info.blend_info.clamp,
   1236                header2.layer_info.blend_info.clamp);
   1237      EXPECT_EQ(header.layer_info.blend_info.source,
   1238                header2.layer_info.blend_info.source);
   1239      EXPECT_EQ(frame_name.size(), header2.name_length);
   1240      JxlBlendInfo extra_channel_blend_info2;
   1241      JxlDecoderGetExtraChannelBlendInfo(dec.get(), 0,
   1242                                         &extra_channel_blend_info2);
   1243      EXPECT_EQ(extra_channel_blend_info.blendmode,
   1244                extra_channel_blend_info2.blendmode);
   1245      if (header2.name_length > 0) {
   1246        std::string frame_name2(header2.name_length + 1, '\0');
   1247        EXPECT_EQ(JXL_DEC_SUCCESS,
   1248                  JxlDecoderGetFrameName(dec.get(), &frame_name2.front(),
   1249                                         frame_name2.size()));
   1250        frame_name2.resize(header2.name_length);
   1251        EXPECT_EQ(frame_name, frame_name2);
   1252      }
   1253    } else {
   1254      FAIL();  // unexpected status
   1255    }
   1256  }
   1257 
   1258  EXPECT_EQ(true, seen_frame);
   1259 }
   1260 TEST(EncodeTest, CroppedFrameTest) {
   1261  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1262  EXPECT_NE(nullptr, enc.get());
   1263 
   1264  JxlEncoderFrameSettings* frame_settings =
   1265      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1266  size_t xsize = 300;
   1267  size_t ysize = 300;
   1268  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
   1269  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
   1270  std::vector<uint8_t> pixels2(pixels.size());
   1271  JxlBasicInfo basic_info;
   1272  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
   1273  // Encoding a 300x300 frame in an image that is only 100x100
   1274  basic_info.xsize = 100;
   1275  basic_info.ysize = 100;
   1276  basic_info.uses_original_profile = JXL_TRUE;
   1277  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
   1278  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
   1279  JxlColorEncoding color_encoding;
   1280  JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
   1281  EXPECT_EQ(JXL_ENC_SUCCESS,
   1282            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
   1283 
   1284  JxlFrameHeader header;
   1285  JxlEncoderInitFrameHeader(&header);
   1286  header.layer_info.have_crop = JXL_TRUE;
   1287  header.layer_info.xsize = xsize;
   1288  header.layer_info.ysize = ysize;
   1289  header.layer_info.crop_x0 = -50;
   1290  header.layer_info.crop_y0 = -250;
   1291  JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE);
   1292  JxlEncoderSetFrameHeader(frame_settings, &header);
   1293  JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT,
   1294                                   1);
   1295 
   1296  std::vector<uint8_t> compressed = std::vector<uint8_t>(100);
   1297  uint8_t* next_out = compressed.data();
   1298  size_t avail_out = compressed.size() - (next_out - compressed.data());
   1299  EXPECT_EQ(JXL_ENC_SUCCESS,
   1300            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
   1301                                    pixels.data(), pixels.size()));
   1302  JxlEncoderCloseFrames(enc.get());
   1303  ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1304 
   1305  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
   1306  EXPECT_NE(nullptr, dec.get());
   1307  // Non-coalesced decoding so we can get the full uncropped frame
   1308  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec.get(), JXL_FALSE));
   1309  EXPECT_EQ(
   1310      JXL_DEC_SUCCESS,
   1311      JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
   1312  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
   1313  JxlDecoderCloseInput(dec.get());
   1314 
   1315  bool seen_frame = false;
   1316  bool checked_frame = false;
   1317  for (;;) {
   1318    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
   1319    if (status == JXL_DEC_ERROR) {
   1320      FAIL();
   1321    } else if (status == JXL_DEC_SUCCESS) {
   1322      break;
   1323    } else if (status == JXL_DEC_FRAME) {
   1324      seen_frame = true;
   1325      JxlFrameHeader header2;
   1326      EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec.get(), &header2));
   1327      EXPECT_EQ(header.layer_info.xsize, header2.layer_info.xsize);
   1328      EXPECT_EQ(header.layer_info.ysize, header2.layer_info.ysize);
   1329      EXPECT_EQ(header.layer_info.crop_x0, header2.layer_info.crop_x0);
   1330      EXPECT_EQ(header.layer_info.crop_y0, header2.layer_info.crop_y0);
   1331    } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
   1332      EXPECT_EQ(JXL_DEC_SUCCESS,
   1333                JxlDecoderSetImageOutBuffer(dec.get(), &pixel_format,
   1334                                            pixels2.data(), pixels2.size()));
   1335    } else if (status == JXL_DEC_FULL_IMAGE) {
   1336      EXPECT_EQ(0, memcmp(pixels.data(), pixels2.data(), pixels.size()));
   1337      checked_frame = true;
   1338    } else {
   1339      FAIL();  // unexpected status
   1340    }
   1341  }
   1342  EXPECT_EQ(true, checked_frame);
   1343  EXPECT_EQ(true, seen_frame);
   1344 }
   1345 
   1346 struct EncodeBoxTest : public testing::TestWithParam<std::tuple<bool, size_t>> {
   1347 };
   1348 
   1349 JXL_BOXES_TEST_P(EncodeBoxTest, BoxTest) {
   1350  // Test with uncompressed boxes and with brob boxes
   1351  bool compress_box = std::get<0>(GetParam());
   1352  size_t xml_box_size = std::get<1>(GetParam());
   1353  // TODO(firsching): use xml_box_size
   1354  (void)xml_box_size;
   1355  // Tests adding two metadata boxes with the encoder: an exif box before the
   1356  // image frame, and an xml box after the image frame. Then verifies the
   1357  // decoder can decode them, they are in the expected place, and have the
   1358  // correct content after decoding.
   1359  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1360  EXPECT_NE(nullptr, enc.get());
   1361 
   1362  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseBoxes(enc.get()));
   1363 
   1364  JxlEncoderFrameSettings* frame_settings =
   1365      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1366  size_t xsize = 50;
   1367  size_t ysize = 17;
   1368  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
   1369  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
   1370  JxlBasicInfo basic_info;
   1371  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
   1372  basic_info.xsize = xsize;
   1373  basic_info.ysize = ysize;
   1374  basic_info.uses_original_profile = JXL_FALSE;
   1375  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
   1376  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
   1377  JxlColorEncoding color_encoding;
   1378  JxlColorEncodingSetToSRGB(&color_encoding,
   1379                            /*is_gray=*/JXL_FALSE);
   1380  EXPECT_EQ(JXL_ENC_SUCCESS,
   1381            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
   1382 
   1383  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1384  uint8_t* next_out = compressed.data();
   1385  size_t avail_out = compressed.size() - (next_out - compressed.data());
   1386 
   1387  // Add an early metadata box. Also add a valid 4-byte TIFF offset header
   1388  // before the fake exif data of these box contents.
   1389  constexpr const char* exif_test_string = "\0\0\0\0exif test data";
   1390  const uint8_t* exif_data = reinterpret_cast<const uint8_t*>(exif_test_string);
   1391  // Skip the 4 zeroes for strlen
   1392  const size_t exif_size = 4 + strlen(exif_test_string + 4);
   1393  JxlEncoderAddBox(enc.get(), "Exif", exif_data, exif_size,
   1394                   TO_JXL_BOOL(compress_box));
   1395 
   1396  // Write to output
   1397  ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1398 
   1399  // Add image frame
   1400  EXPECT_EQ(JXL_ENC_SUCCESS,
   1401            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
   1402                                    pixels.data(), pixels.size()));
   1403  // Indicate this is the last frame
   1404  JxlEncoderCloseFrames(enc.get());
   1405 
   1406  // Write to output
   1407  ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1408 
   1409  // Add a late metadata box
   1410  constexpr const char* xml_test_string = "<some random xml data>";
   1411  const uint8_t* xml_data = reinterpret_cast<const uint8_t*>(xml_test_string);
   1412  size_t xml_size = strlen(xml_test_string);
   1413  JxlEncoderAddBox(enc.get(), "XML ", xml_data, xml_size,
   1414                   TO_JXL_BOOL(compress_box));
   1415 
   1416  // Indicate this is the last box
   1417  JxlEncoderCloseBoxes(enc.get());
   1418 
   1419  // Write to output
   1420  ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1421 
   1422  // Decode to verify the boxes, we don't decode to pixels, only the boxes.
   1423  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
   1424  EXPECT_NE(nullptr, dec.get());
   1425 
   1426  if (compress_box) {
   1427    EXPECT_EQ(JXL_DEC_SUCCESS,
   1428              JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE));
   1429  }
   1430 
   1431  EXPECT_EQ(JXL_DEC_SUCCESS,
   1432            JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME | JXL_DEC_BOX));
   1433 
   1434  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
   1435  JxlDecoderCloseInput(dec.get());
   1436 
   1437  std::vector<uint8_t> dec_exif_box(exif_size);
   1438  std::vector<uint8_t> dec_xml_box(xml_size);
   1439 
   1440  for (bool post_frame = false;;) {
   1441    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
   1442    if (status == JXL_DEC_ERROR) {
   1443      FAIL();
   1444    } else if (status == JXL_DEC_SUCCESS) {
   1445      EXPECT_EQ(0, JxlDecoderReleaseBoxBuffer(dec.get()));
   1446      break;
   1447    } else if (status == JXL_DEC_FRAME) {
   1448      post_frame = true;
   1449    } else if (status == JXL_DEC_BOX) {
   1450      // Since we gave the exif/xml box output buffer of the exact known
   1451      // correct size, 0 bytes should be released. Same when no buffer was
   1452      // set.
   1453      EXPECT_EQ(0, JxlDecoderReleaseBoxBuffer(dec.get()));
   1454      JxlBoxType type;
   1455      EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec.get(), type, true));
   1456      if (memcmp(type, "Exif", 4) == 0) {
   1457        // This box should have been encoded before the image frame
   1458        EXPECT_EQ(false, post_frame);
   1459        JxlDecoderSetBoxBuffer(dec.get(), dec_exif_box.data(),
   1460                               dec_exif_box.size());
   1461      } else if (memcmp(type, "XML ", 4) == 0) {
   1462        // This box should have been encoded after the image frame
   1463        EXPECT_EQ(true, post_frame);
   1464        JxlDecoderSetBoxBuffer(dec.get(), dec_xml_box.data(),
   1465                               dec_xml_box.size());
   1466      }
   1467    } else {
   1468      FAIL();  // unexpected status
   1469    }
   1470  }
   1471 
   1472  EXPECT_EQ(0, memcmp(exif_data, dec_exif_box.data(), exif_size));
   1473  EXPECT_EQ(0, memcmp(xml_data, dec_xml_box.data(), xml_size));
   1474 }
   1475 
   1476 std::string nameBoxTest(
   1477    const ::testing::TestParamInfo<std::tuple<bool, size_t>>& info) {
   1478  return (std::get<0>(info.param) ? "C" : "Unc") + std::string("ompressed") +
   1479         "_BoxSize_" + std::to_string((std::get<1>(info.param)));
   1480 }
   1481 
   1482 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
   1483    EncodeBoxParamsTest, EncodeBoxTest,
   1484    testing::Combine(testing::Values(false, true),
   1485                     testing::Values(256,
   1486                                     jxl::kLargeBoxContentSizeThreshold + 77)),
   1487    nameBoxTest);
   1488 
   1489 JXL_TRANSCODE_JPEG_TEST(EncodeTest, JPEGFrameTest) {
   1490  TEST_LIBJPEG_SUPPORT();
   1491  for (int skip_basic_info = 0; skip_basic_info < 2; skip_basic_info++) {
   1492    for (int skip_color_encoding = 0; skip_color_encoding < 2;
   1493         skip_color_encoding++) {
   1494      // cannot set color encoding if basic info is not set
   1495      if (skip_basic_info && !skip_color_encoding) continue;
   1496      const std::string jpeg_path = "jxl/flower/flower_cropped.jpg";
   1497      const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
   1498      jxl::extras::PackedPixelFile orig_ppf;
   1499      ASSERT_TRUE(
   1500          DecodeBytes(jxl::Bytes(orig), jxl::extras::ColorHints(), &orig_ppf));
   1501 
   1502      JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1503      JxlEncoderFrameSettings* frame_settings =
   1504          JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1505      JxlEncoderFrameSettingsSetOption(frame_settings,
   1506                                       JXL_ENC_FRAME_SETTING_EFFORT, 1);
   1507      if (!skip_basic_info) {
   1508        JxlBasicInfo basic_info;
   1509        JxlEncoderInitBasicInfo(&basic_info);
   1510        basic_info.xsize = orig_ppf.xsize();
   1511        basic_info.ysize = orig_ppf.ysize();
   1512        basic_info.uses_original_profile = JXL_TRUE;
   1513        EXPECT_EQ(JXL_ENC_SUCCESS,
   1514                  JxlEncoderSetBasicInfo(enc.get(), &basic_info));
   1515      }
   1516      if (!skip_color_encoding) {
   1517        JxlColorEncoding color_encoding;
   1518        JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
   1519        EXPECT_EQ(JXL_ENC_SUCCESS,
   1520                  JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
   1521      }
   1522      EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderAddJPEGFrame(
   1523                                     frame_settings, orig.data(), orig.size()));
   1524      JxlEncoderCloseInput(enc.get());
   1525 
   1526      std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1527      uint8_t* next_out = compressed.data();
   1528      size_t avail_out = compressed.size() - (next_out - compressed.data());
   1529      JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
   1530      while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
   1531        process_result =
   1532            JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
   1533        if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
   1534          size_t offset = next_out - compressed.data();
   1535          compressed.resize(compressed.size() * 2);
   1536          next_out = compressed.data() + offset;
   1537          avail_out = compressed.size() - offset;
   1538        }
   1539      }
   1540      compressed.resize(next_out - compressed.data());
   1541      EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
   1542 
   1543      jxl::extras::PackedPixelFile decoded_ppf;
   1544      EXPECT_TRUE(DecodeBytes(jxl::Bytes(compressed.data(), compressed.size()),
   1545                              jxl::extras::ColorHints(), &decoded_ppf));
   1546 
   1547      EXPECT_LE(jxl::test::ComputeDistance2(orig_ppf, decoded_ppf), 3.5);
   1548    }
   1549  }
   1550 }
   1551 
   1552 namespace {
   1553 class JxlStreamingAdapter {
   1554 public:
   1555  JxlStreamingAdapter(JxlEncoder* encoder, bool return_large_buffers,
   1556                      bool can_seek)
   1557      : return_large_buffers_(return_large_buffers) {
   1558    struct JxlEncoderOutputProcessor output_processor;
   1559    output_processor.opaque = this;
   1560    output_processor.get_buffer =
   1561        METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::GetBuffer);
   1562    if (can_seek) {
   1563      output_processor.seek = METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::Seek);
   1564    } else {
   1565      output_processor.seek = nullptr;
   1566    }
   1567    output_processor.set_finalized_position =
   1568        METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::SetFinalizedPosition);
   1569    output_processor.release_buffer =
   1570        METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::ReleaseBuffer);
   1571    EXPECT_EQ(JxlEncoderSetOutputProcessor(encoder, output_processor),
   1572              JXL_ENC_SUCCESS);
   1573  }
   1574 
   1575  std::vector<uint8_t> output() && {
   1576    output_.resize(position_);
   1577    return std::move(output_);
   1578  }
   1579 
   1580  void* GetBuffer(size_t* size) {
   1581    if (!return_large_buffers_) {
   1582      *size = 1;
   1583    }
   1584    if (position_ + *size > output_.size()) {
   1585      output_.resize(position_ + *size, 0xDA);
   1586    }
   1587    if (return_large_buffers_) {
   1588      *size = output_.size() - position_;
   1589    }
   1590    return output_.data() + position_;
   1591  }
   1592 
   1593  void ReleaseBuffer(size_t written_bytes) {
   1594    // TODO(veluca): check no more bytes were written.
   1595    Seek(position_ + written_bytes);
   1596  }
   1597 
   1598  void Seek(uint64_t position) {
   1599    EXPECT_GE(position, finalized_position_);
   1600    position_ = position;
   1601  }
   1602 
   1603  void SetFinalizedPosition(uint64_t finalized_position) {
   1604    EXPECT_GE(finalized_position, finalized_position_);
   1605    finalized_position_ = finalized_position;
   1606    EXPECT_GE(position_, finalized_position_);
   1607  }
   1608 
   1609  void CheckFinalWatermarkPosition() const {
   1610    EXPECT_EQ(finalized_position_, position_);
   1611  }
   1612 
   1613 private:
   1614  std::vector<uint8_t> output_;
   1615  size_t position_ = 0;
   1616  size_t finalized_position_ = 0;
   1617  bool return_large_buffers_;
   1618 };
   1619 
   1620 class JxlChunkedFrameInputSourceAdapter {
   1621 private:
   1622  static const void* GetDataAt(const jxl::extras::PackedImage& image,
   1623                               size_t xpos, size_t ypos, size_t* row_offset) {
   1624    JxlDataType data_type = image.format.data_type;
   1625    size_t num_channels = image.format.num_channels;
   1626    size_t bytes_per_pixel =
   1627        num_channels * jxl::extras::PackedImage::BitsPerChannel(data_type) / 8;
   1628    *row_offset = image.stride;
   1629    return static_cast<uint8_t*>(image.pixels()) + bytes_per_pixel * xpos +
   1630           ypos * image.stride;
   1631  }
   1632 
   1633 public:
   1634  // Constructor to wrap the image data or any other state
   1635  explicit JxlChunkedFrameInputSourceAdapter(
   1636      jxl::extras::PackedImage color_channel,
   1637      jxl::extras::PackedImage extra_channel)
   1638      : colorchannel_(std::move(color_channel)),
   1639        extra_channel_(std::move(extra_channel)) {}
   1640  ~JxlChunkedFrameInputSourceAdapter() { EXPECT_TRUE(active_buffers_.empty()); }
   1641 
   1642  void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) {
   1643    *pixel_format = colorchannel_.format;
   1644  }
   1645 
   1646  const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t xsize,
   1647                                    size_t ysize, size_t* row_offset) {
   1648    const void* p = GetDataAt(colorchannel_, xpos, ypos, row_offset);
   1649    std::lock_guard<std::mutex> lock(mtx_);
   1650    active_buffers_.insert(p);
   1651    return p;
   1652  }
   1653 
   1654  void GetExtraChannelPixelFormat(size_t ec_index,
   1655                                  JxlPixelFormat* pixel_format) {
   1656    // In this test, we we the same color channel data, so `ec_index` is never
   1657    // used
   1658    *pixel_format = extra_channel_.format;
   1659  }
   1660 
   1661  const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos,
   1662                                    size_t xsize, size_t ysize,
   1663                                    size_t* row_offset) {
   1664    // In this test, we we the same color channel data, so `ec_index` is never
   1665    // used
   1666    const void* p = GetDataAt(extra_channel_, xpos, ypos, row_offset);
   1667    std::lock_guard<std::mutex> lock(mtx_);
   1668    active_buffers_.insert(p);
   1669    return p;
   1670  }
   1671  void ReleaseCurrentData(const void* buffer) {
   1672    std::lock_guard<std::mutex> lock(mtx_);
   1673    auto iter = active_buffers_.find(buffer);
   1674    if (iter != active_buffers_.end()) {
   1675      active_buffers_.erase(iter);
   1676    }
   1677  }
   1678 
   1679  JxlChunkedFrameInputSource GetInputSource() {
   1680    return JxlChunkedFrameInputSource{
   1681        this,
   1682        METHOD_TO_C_CALLBACK(
   1683            &JxlChunkedFrameInputSourceAdapter::GetColorChannelsPixelFormat),
   1684        METHOD_TO_C_CALLBACK(
   1685            &JxlChunkedFrameInputSourceAdapter::GetColorChannelDataAt),
   1686        METHOD_TO_C_CALLBACK(
   1687            &JxlChunkedFrameInputSourceAdapter::GetExtraChannelPixelFormat),
   1688        METHOD_TO_C_CALLBACK(
   1689            &JxlChunkedFrameInputSourceAdapter::GetExtraChannelDataAt),
   1690        METHOD_TO_C_CALLBACK(
   1691            &JxlChunkedFrameInputSourceAdapter::ReleaseCurrentData)};
   1692  }
   1693 
   1694 private:
   1695  const jxl::extras::PackedImage colorchannel_;
   1696  const jxl::extras::PackedImage extra_channel_;
   1697  std::mutex mtx_;
   1698  std::set<const void*> active_buffers_;
   1699 };
   1700 
   1701 struct StreamingTestParam {
   1702  size_t bitmask;
   1703  bool use_container() const { return static_cast<bool>(bitmask & 0x1); }
   1704  bool return_large_buffers() const { return static_cast<bool>(bitmask & 0x2); }
   1705  bool multiple_frames() const { return static_cast<bool>(bitmask & 0x4); }
   1706  bool fast_lossless() const { return static_cast<bool>(bitmask & 0x8); }
   1707  bool can_seek() const { return static_cast<bool>(bitmask & 0x10); }
   1708  bool with_extra_channels() const { return static_cast<bool>(bitmask & 0x20); }
   1709  bool color_includes_alpha() const {
   1710    return static_cast<bool>(bitmask & 0x40);
   1711  }
   1712  bool onegroup() const { return static_cast<bool>(bitmask & 0x80); }
   1713 
   1714  bool is_lossless() const { return fast_lossless(); }
   1715 
   1716  static std::vector<StreamingTestParam> All() {
   1717    std::vector<StreamingTestParam> params;
   1718    for (size_t bitmask = 0; bitmask < 256; bitmask++) {
   1719      params.push_back(StreamingTestParam{bitmask});
   1720    }
   1721    return params;
   1722  }
   1723 };
   1724 
   1725 std::ostream& operator<<(std::ostream& out, StreamingTestParam p) {
   1726  if (p.use_container()) {
   1727    out << "WithContainer_";
   1728  } else {
   1729    out << "WithoutContainer_";
   1730  }
   1731  if (p.return_large_buffers()) {
   1732    out << "WithLargeBuffers_";
   1733  } else {
   1734    out << "WithSmallBuffers_";
   1735  }
   1736  if (p.multiple_frames()) out << "WithMultipleFrames_";
   1737  if (p.fast_lossless()) out << "FastLossless_";
   1738  if (!p.can_seek()) {
   1739    out << "CannotSeek_";
   1740  } else {
   1741    out << "CanSeek_";
   1742  }
   1743  if (p.with_extra_channels()) {
   1744    out << "WithExtraChannels_";
   1745  } else {
   1746    out << "WithoutExtraChannels_";
   1747  }
   1748  if (p.color_includes_alpha()) {
   1749    out << "ColorIncludesAlpha_";
   1750  } else {
   1751    out << "ColorWithoutAlpha_";
   1752  }
   1753  if (p.onegroup()) {
   1754    out << "OneGroup_";
   1755  } else {
   1756    out << "MultiGroup_";
   1757  }
   1758  return out;
   1759 }
   1760 
   1761 }  // namespace
   1762 
   1763 class EncoderStreamingTest : public testing::TestWithParam<StreamingTestParam> {
   1764 public:
   1765  static void SetupImage(const StreamingTestParam& p, size_t xsize,
   1766                         size_t ysize, size_t num_channels,
   1767                         size_t bits_per_sample, jxl::test::TestImage& image) {
   1768    ASSERT_TRUE(image.SetDimensions(xsize, ysize));
   1769    image.SetDataType(JXL_TYPE_UINT8);
   1770    ASSERT_TRUE(image.SetChannels(num_channels));
   1771    image.SetAllBitDepths(bits_per_sample);
   1772    if (p.onegroup()) {
   1773      image.SetRowAlignment(128);
   1774    }
   1775    JXL_TEST_ASSIGN_OR_DIE(auto frame, image.AddFrame());
   1776    frame.RandomFill();
   1777  }
   1778  static void SetUpBasicInfo(JxlBasicInfo& basic_info, size_t xsize,
   1779                             size_t ysize, size_t number_extra_channels,
   1780                             bool include_alpha, bool is_lossless) {
   1781    basic_info.xsize = xsize;
   1782    basic_info.ysize = ysize;
   1783    basic_info.num_extra_channels =
   1784        number_extra_channels + (include_alpha ? 1 : 0);
   1785    basic_info.uses_original_profile = TO_JXL_BOOL(is_lossless);
   1786  }
   1787 
   1788  static void SetupEncoder(JxlEncoderFrameSettings* frame_settings,
   1789                           const StreamingTestParam& p,
   1790                           const JxlBasicInfo& basic_info,
   1791                           size_t number_extra_channels, bool streaming) {
   1792    JxlEncoderStruct* enc = frame_settings->enc;
   1793    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
   1794    if (p.fast_lossless()) {
   1795      EXPECT_EQ(JXL_ENC_SUCCESS,
   1796                JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE));
   1797      EXPECT_EQ(JXL_ENC_SUCCESS,
   1798                JxlEncoderFrameSettingsSetOption(
   1799                    frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 1));
   1800    }
   1801    JxlColorEncoding color_encoding;
   1802    JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
   1803    EXPECT_EQ(JXL_ENC_SUCCESS,
   1804              JxlEncoderSetColorEncoding(enc, &color_encoding));
   1805    EXPECT_EQ(JXL_ENC_SUCCESS,
   1806              JxlEncoderFrameSettingsSetOption(frame_settings,
   1807                                               JXL_ENC_FRAME_SETTING_BUFFERING,
   1808                                               streaming ? 3 : 0));
   1809    EXPECT_EQ(JXL_ENC_SUCCESS,
   1810              JxlEncoderFrameSettingsSetOption(
   1811                  frame_settings,
   1812                  JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS, 0));
   1813    if (p.use_container()) {
   1814      EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
   1815    }
   1816    for (size_t i = 0; i < number_extra_channels; i++) {
   1817      JxlExtraChannelInfo channel_info;
   1818      JxlExtraChannelType channel_type = JXL_CHANNEL_THERMAL;
   1819      JxlEncoderInitExtraChannelInfo(channel_type, &channel_info);
   1820      EXPECT_EQ(JXL_ENC_SUCCESS,
   1821                JxlEncoderSetExtraChannelInfo(enc, i, &channel_info));
   1822    }
   1823  }
   1824 
   1825  static void SetupInputNonStreaming(JxlEncoderFrameSettings* frame_settings,
   1826                                     const StreamingTestParam& p,
   1827                                     size_t number_extra_channels,
   1828                                     const jxl::extras::PackedImage& frame,
   1829                                     const jxl::extras::PackedImage& ec_frame) {
   1830    size_t frame_count = static_cast<int>(p.multiple_frames()) + 1;
   1831    for (size_t i = 0; i < frame_count; i++) {
   1832      {
   1833        // Copy pixel data here because it is only guaranteed to be available
   1834        // during the call to JxlEncoderAddImageFrame().
   1835        std::vector<uint8_t> pixels(frame.pixels_size);
   1836        memcpy(pixels.data(), frame.pixels(), pixels.size());
   1837        EXPECT_EQ(JXL_ENC_SUCCESS,
   1838                  JxlEncoderAddImageFrame(frame_settings, &frame.format,
   1839                                          pixels.data(), pixels.size()));
   1840      }
   1841      for (size_t i = 0; i < number_extra_channels; i++) {
   1842        // Copy pixel data here because it is only guaranteed to be available
   1843        // during the call to JxlEncoderSetExtraChannelBuffer().
   1844        std::vector<uint8_t> ec_pixels(ec_frame.pixels_size);
   1845        memcpy(ec_pixels.data(), ec_frame.pixels(), ec_pixels.size());
   1846        EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelBuffer(
   1847                                       frame_settings, &ec_frame.format,
   1848                                       ec_pixels.data(), ec_pixels.size(), i));
   1849      }
   1850    }
   1851    JxlEncoderCloseInput(frame_settings->enc);
   1852  }
   1853 
   1854  static void SetupInputStreaming(JxlEncoderFrameSettings* frame_settings,
   1855                                  const StreamingTestParam& p,
   1856                                  size_t number_extra_channels,
   1857                                  const jxl::extras::PackedImage& frame,
   1858                                  const jxl::extras::PackedImage& ec_frame) {
   1859    size_t frame_count = static_cast<int>(p.multiple_frames()) + 1;
   1860    for (size_t i = 0; i < frame_count; i++) {
   1861      // Create local copy of pixels and adapter because they are only
   1862      // guarantted to be available during the JxlEncoderAddChunkedFrame() call.
   1863      JxlChunkedFrameInputSourceAdapter chunked_frame_adapter(frame.Copy(),
   1864                                                              ec_frame.Copy());
   1865      EXPECT_EQ(JXL_ENC_SUCCESS,
   1866                JxlEncoderAddChunkedFrame(
   1867                    // should only set `JXL_TRUE` in the lass pass of the loop
   1868                    frame_settings, i + 1 == frame_count ? JXL_TRUE : JXL_FALSE,
   1869                    chunked_frame_adapter.GetInputSource()));
   1870    }
   1871  }
   1872 };
   1873 
   1874 TEST_P(EncoderStreamingTest, OutputCallback) {
   1875  const StreamingTestParam p = GetParam();
   1876  size_t xsize = p.onegroup() ? 17 : 257;
   1877  size_t ysize = p.onegroup() ? 19 : 259;
   1878  size_t number_extra_channels = p.with_extra_channels() ? 5 : 0;
   1879  jxl::test::TestImage image;
   1880  SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3,
   1881             p.use_container() ? 16 : 8, image);
   1882  jxl::test::TestImage ec_image;
   1883  SetupImage(p, xsize, ysize, 1, 8, ec_image);
   1884  const auto& frame = image.ppf().frames[0].color;
   1885  const auto& ec_frame = ec_image.ppf().frames[0].color;
   1886  JxlBasicInfo basic_info = image.ppf().info;
   1887  SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels,
   1888                 p.color_includes_alpha(), p.is_lossless());
   1889 
   1890  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1891  // without streaming
   1892  {
   1893    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1894    ASSERT_NE(nullptr, enc.get());
   1895    JxlEncoderFrameSettings* frame_settings =
   1896        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1897    SetupEncoder(frame_settings, p, basic_info, number_extra_channels, false);
   1898    SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame,
   1899                           ec_frame);
   1900    uint8_t* next_out = compressed.data();
   1901    size_t avail_out = compressed.size();
   1902    ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1903  }
   1904 
   1905  std::vector<uint8_t> streaming_compressed;
   1906  // with streaming
   1907  {
   1908    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1909    ASSERT_NE(nullptr, enc.get());
   1910    JxlEncoderFrameSettings* frame_settings =
   1911        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1912    SetupEncoder(frame_settings, p, basic_info, number_extra_channels, true);
   1913    SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame,
   1914                           ec_frame);
   1915    JxlStreamingAdapter streaming_adapter(enc.get(), p.return_large_buffers(),
   1916                                          p.can_seek());
   1917    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFlushInput(enc.get()));
   1918    streaming_adapter.CheckFinalWatermarkPosition();
   1919    streaming_compressed = std::move(streaming_adapter).output();
   1920  }
   1921 
   1922  EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed));
   1923  EXPECT_LE(streaming_compressed.size(), compressed.size() + 1024);
   1924 }
   1925 
   1926 TEST_P(EncoderStreamingTest, ChunkedFrame) {
   1927  const StreamingTestParam p = GetParam();
   1928  size_t xsize = p.onegroup() ? 17 : 257;
   1929  size_t ysize = p.onegroup() ? 19 : 259;
   1930  size_t number_extra_channels = p.with_extra_channels() ? 5 : 0;
   1931  jxl::test::TestImage image;
   1932  SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3,
   1933             p.use_container() ? 16 : 8, image);
   1934  jxl::test::TestImage ec_image;
   1935  SetupImage(p, xsize, ysize, 1, 8, ec_image);
   1936  const auto& frame = image.ppf().frames[0].color;
   1937  const auto& ec_frame = ec_image.ppf().frames[0].color;
   1938  JxlBasicInfo basic_info = image.ppf().info;
   1939  SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels,
   1940                 p.color_includes_alpha(), p.is_lossless());
   1941  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1942  std::vector<uint8_t> streaming_compressed = std::vector<uint8_t>(64);
   1943 
   1944  // without streaming
   1945  {
   1946    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1947    ASSERT_NE(nullptr, enc.get());
   1948    JxlEncoderFrameSettings* frame_settings =
   1949        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1950    SetupEncoder(frame_settings, p, basic_info, number_extra_channels, false);
   1951    SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame,
   1952                           ec_frame);
   1953    uint8_t* next_out = compressed.data();
   1954    size_t avail_out = compressed.size();
   1955    ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   1956  }
   1957 
   1958  // with streaming
   1959  {
   1960    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1961    ASSERT_NE(nullptr, enc.get());
   1962    JxlEncoderFrameSettings* frame_settings =
   1963        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1964    SetupEncoder(frame_settings, p, basic_info, number_extra_channels, true);
   1965    SetupInputStreaming(frame_settings, p, number_extra_channels, frame,
   1966                        ec_frame);
   1967    uint8_t* next_out = streaming_compressed.data();
   1968    size_t avail_out = streaming_compressed.size();
   1969    ProcessEncoder(enc.get(), streaming_compressed, next_out, avail_out);
   1970  }
   1971 
   1972  EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed));
   1973  EXPECT_LE(streaming_compressed.size(), compressed.size() + 1024);
   1974 }
   1975 
   1976 TEST_P(EncoderStreamingTest, ChunkedAndOutputCallback) {
   1977  const StreamingTestParam p = GetParam();
   1978  size_t xsize = p.onegroup() ? 17 : 257;
   1979  size_t ysize = p.onegroup() ? 19 : 259;
   1980  size_t number_extra_channels = p.with_extra_channels() ? 5 : 0;
   1981  jxl::test::TestImage image;
   1982  SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3,
   1983             p.use_container() ? 16 : 8, image);
   1984  jxl::test::TestImage ec_image;
   1985  SetupImage(p, xsize, ysize, 1, 8, ec_image);
   1986  const auto& frame = image.ppf().frames[0].color;
   1987  const auto& ec_frame = ec_image.ppf().frames[0].color;
   1988  JxlBasicInfo basic_info = image.ppf().info;
   1989  SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels,
   1990                 p.color_includes_alpha(), p.is_lossless());
   1991 
   1992  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   1993 
   1994  // without streaming
   1995  {
   1996    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1997    ASSERT_NE(nullptr, enc.get());
   1998    JxlEncoderFrameSettings* frame_settings =
   1999        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   2000    SetupEncoder(frame_settings, p, basic_info, number_extra_channels, false);
   2001    SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame,
   2002                           ec_frame);
   2003    uint8_t* next_out = compressed.data();
   2004    size_t avail_out = compressed.size();
   2005    ProcessEncoder(enc.get(), compressed, next_out, avail_out);
   2006  }
   2007 
   2008  std::vector<uint8_t> streaming_compressed;
   2009  // with streaming
   2010  {
   2011    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   2012    ASSERT_NE(nullptr, enc.get());
   2013    JxlEncoderFrameSettings* frame_settings =
   2014        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   2015    SetupEncoder(frame_settings, p, basic_info, number_extra_channels, true);
   2016    JxlStreamingAdapter streaming_adapter =
   2017        JxlStreamingAdapter(enc.get(), p.return_large_buffers(), p.can_seek());
   2018    SetupInputStreaming(frame_settings, p, number_extra_channels, frame,
   2019                        ec_frame);
   2020    streaming_adapter.CheckFinalWatermarkPosition();
   2021    streaming_compressed = std::move(streaming_adapter).output();
   2022  }
   2023 
   2024  EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed));
   2025  EXPECT_LE(streaming_compressed.size(), compressed.size() + 1024);
   2026 }
   2027 
   2028 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
   2029    EncoderStreamingTest, EncoderStreamingTest,
   2030    testing::ValuesIn(StreamingTestParam::All()));
   2031 
   2032 TEST(EncoderTest, CMYK) {
   2033  size_t xsize = 257;
   2034  size_t ysize = 259;
   2035  jxl::test::TestImage image;
   2036  ASSERT_TRUE(image.SetDimensions(xsize, ysize));
   2037  image.SetDataType(JXL_TYPE_UINT8);
   2038  ASSERT_TRUE(image.SetChannels(3));
   2039  image.SetAllBitDepths(8);
   2040  JXL_TEST_ASSIGN_OR_DIE(auto frame0, image.AddFrame());
   2041  frame0.RandomFill();
   2042  jxl::test::TestImage ec_image;
   2043  ec_image.SetDataType(JXL_TYPE_UINT8);
   2044  ASSERT_TRUE(ec_image.SetDimensions(xsize, ysize));
   2045  ASSERT_TRUE(ec_image.SetChannels(1));
   2046  ec_image.SetAllBitDepths(8);
   2047  JXL_TEST_ASSIGN_OR_DIE(auto frame1, ec_image.AddFrame());
   2048  frame1.RandomFill();
   2049  const auto& frame = image.ppf().frames[0].color;
   2050  const auto& ec_frame = ec_image.ppf().frames[0].color;
   2051  JxlBasicInfo basic_info = image.ppf().info;
   2052  basic_info.xsize = xsize;
   2053  basic_info.ysize = ysize;
   2054  basic_info.num_extra_channels = 1;
   2055  basic_info.uses_original_profile = JXL_TRUE;
   2056 
   2057  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
   2058  JxlEncoderPtr enc_ptr = JxlEncoderMake(nullptr);
   2059  JxlEncoderStruct* enc = enc_ptr.get();
   2060  ASSERT_NE(nullptr, enc);
   2061  JxlEncoderFrameSettings* frame_settings =
   2062      JxlEncoderFrameSettingsCreate(enc, nullptr);
   2063 
   2064  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
   2065  JxlExtraChannelInfo channel_info;
   2066  JxlExtraChannelType channel_type = JXL_CHANNEL_BLACK;
   2067  JxlEncoderInitExtraChannelInfo(channel_type, &channel_info);
   2068  EXPECT_EQ(JXL_ENC_SUCCESS,
   2069            JxlEncoderSetExtraChannelInfo(enc, 0, &channel_info));
   2070  const std::vector<uint8_t> icc = jxl::test::ReadTestData(
   2071      "external/Compact-ICC-Profiles/profiles/"
   2072      "CGATS001Compat-v2-micro.icc");
   2073  EXPECT_EQ(JXL_ENC_SUCCESS,
   2074            JxlEncoderSetICCProfile(enc, icc.data(), icc.size()));
   2075  EXPECT_EQ(JXL_ENC_SUCCESS,
   2076            JxlEncoderAddImageFrame(frame_settings, &frame.format,
   2077                                    frame.pixels(), frame.pixels_size));
   2078  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelBuffer(
   2079                                 frame_settings, &ec_frame.format,
   2080                                 ec_frame.pixels(), ec_frame.pixels_size, 0));
   2081  JxlEncoderCloseInput(frame_settings->enc);
   2082  uint8_t* next_out = compressed.data();
   2083  size_t avail_out = compressed.size();
   2084  ProcessEncoder(enc, compressed, next_out, avail_out);
   2085 
   2086  jxl::extras::JXLDecompressParams dparams;
   2087  dparams.accepted_formats = {
   2088      {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0},
   2089  };
   2090  jxl::extras::PackedPixelFile ppf;
   2091  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
   2092                             nullptr, &ppf, nullptr));
   2093 }