libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

encode_test.cc (85112B)


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