tor-browser

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

roundtrip_test.cc (46961B)


      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/codestream_header.h>
      8 #include <jxl/color_encoding.h>
      9 #include <jxl/decode.h>
     10 #include <jxl/decode_cxx.h>
     11 #include <jxl/encode.h>
     12 #include <jxl/encode_cxx.h>
     13 #include <jxl/types.h>
     14 
     15 #include <cstddef>
     16 #include <cstdint>
     17 #include <cstdio>
     18 #include <cstring>
     19 #include <string>
     20 #include <utility>
     21 #include <vector>
     22 
     23 #include "lib/extras/codec.h"
     24 #include "lib/jxl/base/common.h"
     25 #include "lib/jxl/base/span.h"
     26 #include "lib/jxl/butteraugli/butteraugli.h"
     27 #include "lib/jxl/color_encoding_internal.h"
     28 #include "lib/jxl/dec_bit_reader.h"
     29 #include "lib/jxl/enc_external_image.h"
     30 #include "lib/jxl/encode_internal.h"
     31 #include "lib/jxl/image.h"
     32 #include "lib/jxl/image_ops.h"
     33 #include "lib/jxl/image_test_utils.h"
     34 #include "lib/jxl/test_memory_manager.h"
     35 #include "lib/jxl/test_utils.h"
     36 #include "lib/jxl/testing.h"
     37 
     38 namespace {
     39 
     40 using ::jxl::ImageF;
     41 using ::jxl::test::ButteraugliDistance;
     42 
     43 // Converts a test image to a CodecInOut.
     44 // icc_profile can be empty to automatically deduce profile from the pixel
     45 // format, or filled in to force this ICC profile
     46 jxl::CodecInOut ConvertTestImage(const std::vector<uint8_t>& buf,
     47                                 const size_t xsize, const size_t ysize,
     48                                 const JxlPixelFormat& pixel_format,
     49                                 const jxl::Bytes& icc_profile) {
     50  jxl::CodecInOut io{jxl::test::MemoryManager()};
     51  jxl::test::Check(io.SetSize(xsize, ysize));
     52 
     53  bool is_gray = pixel_format.num_channels < 3;
     54  bool has_alpha =
     55      pixel_format.num_channels == 2 || pixel_format.num_channels == 4;
     56 
     57  io.metadata.m.color_encoding.SetColorSpace(is_gray ? jxl::ColorSpace::kGray
     58                                                     : jxl::ColorSpace::kRGB);
     59  if (has_alpha) {
     60    // Note: alpha > 16 not yet supported by the C++ codec
     61    switch (pixel_format.data_type) {
     62      case JXL_TYPE_UINT8:
     63        io.metadata.m.SetAlphaBits(8);
     64        break;
     65      case JXL_TYPE_UINT16:
     66      case JXL_TYPE_FLOAT:
     67      case JXL_TYPE_FLOAT16:
     68        io.metadata.m.SetAlphaBits(16);
     69        break;
     70      default:
     71        ADD_FAILURE() << "Roundtrip tests for data type "
     72                      << pixel_format.data_type << " not yet implemented.";
     73    }
     74  }
     75  size_t bitdepth = 0;
     76  switch (pixel_format.data_type) {
     77    case JXL_TYPE_FLOAT:
     78      bitdepth = 32;
     79      io.metadata.m.SetFloat32Samples();
     80      break;
     81    case JXL_TYPE_FLOAT16:
     82      bitdepth = 16;
     83      io.metadata.m.SetFloat16Samples();
     84      break;
     85    case JXL_TYPE_UINT8:
     86      bitdepth = 8;
     87      io.metadata.m.SetUintSamples(8);
     88      break;
     89    case JXL_TYPE_UINT16:
     90      bitdepth = 16;
     91      io.metadata.m.SetUintSamples(16);
     92      break;
     93    default:
     94      ADD_FAILURE() << "Roundtrip tests for data type "
     95                    << pixel_format.data_type << " not yet implemented.";
     96  }
     97  jxl::ColorEncoding color_encoding;
     98  if (!icc_profile.empty()) {
     99    jxl::IccBytes icc_profile_copy;
    100    icc_profile.AppendTo(icc_profile_copy);
    101    EXPECT_TRUE(
    102        color_encoding.SetICC(std::move(icc_profile_copy), JxlGetDefaultCms()));
    103  } else if (pixel_format.data_type == JXL_TYPE_FLOAT) {
    104    color_encoding = jxl::ColorEncoding::LinearSRGB(is_gray);
    105  } else {
    106    color_encoding = jxl::ColorEncoding::SRGB(is_gray);
    107  }
    108  EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(buf), xsize, ysize, color_encoding,
    109                                  /*bits_per_sample=*/bitdepth, pixel_format,
    110                                  /*pool=*/nullptr, &io.Main()));
    111  return io;
    112 }
    113 
    114 template <typename T>
    115 T ConvertTestPixel(float val);
    116 
    117 template <>
    118 float ConvertTestPixel<float>(const float val) {
    119  return val;
    120 }
    121 
    122 template <>
    123 uint16_t ConvertTestPixel<uint16_t>(const float val) {
    124  return static_cast<uint16_t>(val * UINT16_MAX);
    125 }
    126 
    127 template <>
    128 uint8_t ConvertTestPixel<uint8_t>(const float val) {
    129  return static_cast<uint8_t>(val * UINT8_MAX);
    130 }
    131 
    132 // Returns a test image.
    133 template <typename T>
    134 std::vector<uint8_t> GetTestImage(const size_t xsize, const size_t ysize,
    135                                  const JxlPixelFormat& pixel_format) {
    136  std::vector<T> pixels(xsize * ysize * pixel_format.num_channels);
    137  for (size_t y = 0; y < ysize; y++) {
    138    for (size_t x = 0; x < xsize; x++) {
    139      for (size_t chan = 0; chan < pixel_format.num_channels; chan++) {
    140        float val;
    141        switch (chan % 4) {
    142          case 0:
    143            val = static_cast<float>(y) / static_cast<float>(ysize);
    144            break;
    145          case 1:
    146            val = static_cast<float>(x) / static_cast<float>(xsize);
    147            break;
    148          case 2:
    149            val = static_cast<float>(x + y) / static_cast<float>(xsize + ysize);
    150            break;
    151          case 3:
    152          default:
    153            val = static_cast<float>(x * y) / static_cast<float>(xsize * ysize);
    154            break;
    155        }
    156        pixels[(y * xsize + x) * pixel_format.num_channels + chan] =
    157            ConvertTestPixel<T>(val);
    158      }
    159    }
    160  }
    161  std::vector<uint8_t> bytes(pixels.size() * sizeof(T));
    162  memcpy(bytes.data(), pixels.data(), sizeof(T) * pixels.size());
    163  return bytes;
    164 }
    165 
    166 void EncodeWithEncoder(JxlEncoder* enc, std::vector<uint8_t>* compressed) {
    167  compressed->resize(64);
    168  uint8_t* next_out = compressed->data();
    169  size_t avail_out = compressed->size() - (next_out - compressed->data());
    170  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
    171  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    172    process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
    173    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    174      size_t offset = next_out - compressed->data();
    175      compressed->resize(compressed->size() * 2);
    176      next_out = compressed->data() + offset;
    177      avail_out = compressed->size() - offset;
    178    }
    179  }
    180  compressed->resize(next_out - compressed->data());
    181  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
    182 }
    183 
    184 // Generates some pixels using some dimensions and pixel_format,
    185 // compresses them, and verifies that the decoded version is similar to the
    186 // original pixels.
    187 // TODO(firsching): change this to be a parameterized test, like in
    188 // decode_test.cc
    189 template <typename T>
    190 void VerifyRoundtripCompression(
    191    const size_t xsize, const size_t ysize,
    192    const JxlPixelFormat& input_pixel_format,
    193    const JxlPixelFormat& output_pixel_format, const bool lossless,
    194    const bool use_container, const uint32_t resampling = 1,
    195    const bool already_downsampled = false,
    196    const std::vector<std::pair<JxlExtraChannelType, std::string>>&
    197        extra_channels = {},
    198    const int upsampling_mode = -1) {
    199  size_t orig_xsize = xsize;
    200  size_t orig_ysize = ysize;
    201  if (already_downsampled) {
    202    orig_xsize = jxl::DivCeil(xsize, resampling);
    203    orig_ysize = jxl::DivCeil(ysize, resampling);
    204  }
    205 
    206  JxlPixelFormat extra_channel_pixel_format = input_pixel_format;
    207  extra_channel_pixel_format.num_channels = 1;
    208  const std::vector<uint8_t> extra_channel_bytes =
    209      GetTestImage<T>(xsize, ysize, extra_channel_pixel_format);
    210  const std::vector<uint8_t> original_bytes =
    211      GetTestImage<T>(orig_xsize, orig_ysize, input_pixel_format);
    212  jxl::CodecInOut original_io = ConvertTestImage(
    213      original_bytes, orig_xsize, orig_ysize, input_pixel_format, {});
    214 
    215  JxlEncoder* enc = JxlEncoderCreate(nullptr);
    216  EXPECT_NE(nullptr, enc);
    217  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
    218  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, use_container));
    219  JxlBasicInfo basic_info;
    220  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &input_pixel_format);
    221  basic_info.xsize = xsize;
    222  basic_info.ysize = ysize;
    223  basic_info.uses_original_profile = lossless;
    224  uint32_t num_channels = input_pixel_format.num_channels;
    225  size_t has_interleaved_alpha = num_channels == 2 || num_channels == 4;
    226  JxlPixelFormat output_pixel_format_with_extra_channel_alpha =
    227      output_pixel_format;
    228 
    229  // In the case where we have an alpha channel, but it is provided as an extra
    230  // channel and not interleaved, we do two things here:
    231  // 1. modify the original_io to have the correct alpha channel
    232  // 2. change the output_format_with_extra_alpha to have an alpha channel
    233  bool alpha_in_extra_channels_vector = false;
    234  for (const auto& extra_channel : extra_channels) {
    235    if (extra_channel.first == JXL_CHANNEL_ALPHA) {
    236      alpha_in_extra_channels_vector = true;
    237    }
    238  }
    239  if (alpha_in_extra_channels_vector && !has_interleaved_alpha) {
    240    JXL_TEST_ASSIGN_OR_DIE(
    241        ImageF alpha_channel,
    242        ImageF::Create(jxl::test::MemoryManager(), xsize, ysize));
    243    EXPECT_TRUE(jxl::ConvertFromExternal(
    244        extra_channel_bytes.data(), extra_channel_bytes.size(), xsize, ysize,
    245        basic_info.bits_per_sample, extra_channel_pixel_format, 0,
    246        /*pool=*/nullptr, &alpha_channel));
    247 
    248    original_io.metadata.m.SetAlphaBits(basic_info.bits_per_sample);
    249    ASSERT_TRUE(original_io.Main().SetAlpha(std::move(alpha_channel)));
    250    output_pixel_format_with_extra_channel_alpha.num_channels++;
    251  }
    252  // Those are the num_extra_channels including a potential alpha channel.
    253  basic_info.num_extra_channels = extra_channels.size() + has_interleaved_alpha;
    254  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
    255  EXPECT_EQ(enc->metadata.m.num_extra_channels,
    256            extra_channels.size() + has_interleaved_alpha);
    257  JxlColorEncoding color_encoding;
    258  if (input_pixel_format.data_type == JXL_TYPE_FLOAT) {
    259    JxlColorEncodingSetToLinearSRGB(
    260        &color_encoding,
    261        /*is_gray=*/input_pixel_format.num_channels < 3);
    262  } else {
    263    JxlColorEncodingSetToSRGB(&color_encoding,
    264                              /*is_gray=*/input_pixel_format.num_channels < 3);
    265  }
    266 
    267  std::vector<JxlExtraChannelInfo> channel_infos;
    268  for (const auto& extra_channel : extra_channels) {
    269    auto channel_type = extra_channel.first;
    270    JxlExtraChannelInfo channel_info;
    271    JxlEncoderInitExtraChannelInfo(channel_type, &channel_info);
    272    channel_info.bits_per_sample = (lossless ? basic_info.bits_per_sample : 8);
    273    channel_info.exponent_bits_per_sample =
    274        (lossless ? basic_info.exponent_bits_per_sample : 0);
    275    channel_infos.push_back(channel_info);
    276  }
    277  for (size_t index = 0; index < channel_infos.size(); index++) {
    278    EXPECT_EQ(JXL_ENC_SUCCESS,
    279              JxlEncoderSetExtraChannelInfo(enc, index + has_interleaved_alpha,
    280                                            &channel_infos[index]));
    281    std::string name = extra_channels[index].second;
    282    EXPECT_EQ(JXL_ENC_SUCCESS,
    283              JxlEncoderSetExtraChannelName(enc, index + has_interleaved_alpha,
    284                                            name.c_str(), name.length()));
    285  }
    286  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
    287  if (resampling > 1) {
    288    EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, 3, 0));
    289    EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, resampling, -2));
    290    EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, resampling, 2));
    291  }
    292  EXPECT_EQ(JXL_ENC_SUCCESS,
    293            JxlEncoderSetUpsamplingMode(enc, resampling, upsampling_mode));
    294  JxlEncoderFrameSettings* frame_settings =
    295      JxlEncoderFrameSettingsCreate(enc, nullptr);
    296  JxlEncoderSetFrameLossless(frame_settings, lossless);
    297  if (resampling > 1) {
    298    EXPECT_EQ(
    299        JXL_ENC_SUCCESS,
    300        JxlEncoderFrameSettingsSetOption(
    301            frame_settings, JXL_ENC_FRAME_SETTING_RESAMPLING, resampling));
    302    EXPECT_EQ(JXL_ENC_SUCCESS,
    303              JxlEncoderFrameSettingsSetOption(
    304                  frame_settings, JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED,
    305                  already_downsampled));
    306  }
    307  EXPECT_EQ(
    308      JXL_ENC_SUCCESS,
    309      JxlEncoderAddImageFrame(frame_settings, &input_pixel_format,
    310                              static_cast<const void*>(original_bytes.data()),
    311                              original_bytes.size()));
    312  EXPECT_EQ(frame_settings->enc->input_queue.empty(), false);
    313  for (size_t index = 0; index < channel_infos.size(); index++) {
    314    EXPECT_EQ(JXL_ENC_SUCCESS,
    315              JxlEncoderSetExtraChannelBuffer(
    316                  frame_settings, &extra_channel_pixel_format,
    317                  static_cast<const void*>(extra_channel_bytes.data()),
    318                  extra_channel_bytes.size(), index + has_interleaved_alpha));
    319  }
    320  JxlEncoderCloseInput(enc);
    321  std::vector<uint8_t> compressed;
    322  EncodeWithEncoder(enc, &compressed);
    323  JxlEncoderDestroy(enc);
    324 
    325  JxlDecoder* dec = JxlDecoderCreate(nullptr);
    326  EXPECT_NE(nullptr, dec);
    327 
    328  const uint8_t* next_in = compressed.data();
    329  size_t avail_in = compressed.size();
    330 
    331  EXPECT_EQ(JXL_DEC_SUCCESS,
    332            JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
    333                                               JXL_DEC_COLOR_ENCODING |
    334                                               JXL_DEC_FULL_IMAGE));
    335 
    336  JxlDecoderSetInput(dec, next_in, avail_in);
    337  EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
    338  size_t buffer_size;
    339  EXPECT_EQ(
    340      JXL_DEC_SUCCESS,
    341      JxlDecoderImageOutBufferSize(
    342          dec, &output_pixel_format_with_extra_channel_alpha, &buffer_size));
    343  if (&input_pixel_format == &output_pixel_format_with_extra_channel_alpha &&
    344      !already_downsampled) {
    345    EXPECT_EQ(buffer_size, original_bytes.size());
    346  }
    347 
    348  JxlBasicInfo info;
    349  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
    350  EXPECT_EQ(xsize, info.xsize);
    351  EXPECT_EQ(ysize, info.ysize);
    352  EXPECT_EQ(extra_channels.size() + has_interleaved_alpha,
    353            info.num_extra_channels);
    354 
    355  EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
    356 
    357  size_t icc_profile_size;
    358  EXPECT_EQ(JXL_DEC_SUCCESS,
    359            JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
    360                                        &icc_profile_size));
    361  std::vector<uint8_t> icc_profile(icc_profile_size);
    362  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
    363                                 dec, JXL_COLOR_PROFILE_TARGET_DATA,
    364                                 icc_profile.data(), icc_profile.size()));
    365 
    366  std::vector<uint8_t> decoded_bytes(buffer_size);
    367 
    368  EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
    369 
    370  EXPECT_EQ(JXL_DEC_SUCCESS,
    371            JxlDecoderSetImageOutBuffer(
    372                dec, &output_pixel_format_with_extra_channel_alpha,
    373                decoded_bytes.data(), decoded_bytes.size()));
    374  std::vector<std::vector<uint8_t>> extra_channel_decoded_bytes(
    375      info.num_extra_channels - has_interleaved_alpha);
    376 
    377  for (size_t index = has_interleaved_alpha; index < info.num_extra_channels;
    378       index++) {
    379    JxlExtraChannelInfo channel_info;
    380    EXPECT_EQ(JXL_DEC_SUCCESS,
    381              JxlDecoderGetExtraChannelInfo(dec, index, &channel_info));
    382    EXPECT_EQ(channel_info.type,
    383              extra_channels[index - has_interleaved_alpha].first);
    384    std::string input_name =
    385        extra_channels[index - has_interleaved_alpha].second;
    386    const size_t name_length = channel_info.name_length;
    387    EXPECT_EQ(input_name.size(), name_length);
    388    std::vector<char> output_name(name_length + 1);
    389    EXPECT_EQ(JXL_DEC_SUCCESS,
    390              JxlDecoderGetExtraChannelName(dec, index, output_name.data(),
    391                                            output_name.size()));
    392    EXPECT_EQ(0,
    393              memcmp(input_name.data(), output_name.data(), input_name.size()));
    394    size_t extra_buffer_size;
    395    EXPECT_EQ(JXL_DEC_SUCCESS,
    396              JxlDecoderExtraChannelBufferSize(dec, &output_pixel_format,
    397                                               &extra_buffer_size, index));
    398    std::vector<uint8_t> extra_decoded_bytes(extra_buffer_size);
    399    extra_channel_decoded_bytes[index - has_interleaved_alpha] =
    400        std::move(extra_decoded_bytes);
    401    EXPECT_EQ(
    402        JXL_DEC_SUCCESS,
    403        JxlDecoderSetExtraChannelBuffer(
    404            dec, &output_pixel_format,
    405            extra_channel_decoded_bytes[index - has_interleaved_alpha].data(),
    406            extra_channel_decoded_bytes[index - has_interleaved_alpha].size(),
    407            index));
    408  }
    409  EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
    410  // Check if there are no further errors after getting the full image, e.g.
    411  // check that the final codestream box is actually marked as last.
    412  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
    413 
    414  JxlDecoderDestroy(dec);
    415 
    416  jxl::CodecInOut decoded_io = ConvertTestImage(
    417      decoded_bytes, xsize, ysize, output_pixel_format_with_extra_channel_alpha,
    418      jxl::Bytes(icc_profile));
    419 
    420  if (already_downsampled) {
    421    jxl::Image3F* color = decoded_io.Main().color();
    422    JXL_TEST_ASSIGN_OR_DIE(*color, jxl::DownsampleImage(*color, resampling));
    423    if (decoded_io.Main().HasAlpha()) {
    424      ImageF* alpha = decoded_io.Main().alpha();
    425      JXL_TEST_ASSIGN_OR_DIE(*alpha, jxl::DownsampleImage(*alpha, resampling));
    426    }
    427    EXPECT_TRUE(decoded_io.SetSize(color->xsize(), color->ysize()));
    428  }
    429 
    430  if (lossless && !already_downsampled) {
    431    JXL_EXPECT_OK(jxl::SamePixels(*original_io.Main().color(),
    432                                  *decoded_io.Main().color(), _));
    433  } else {
    434    jxl::ButteraugliParams butteraugli_params;
    435    float butteraugli_score =
    436        ButteraugliDistance(original_io.frames, decoded_io.frames,
    437                            butteraugli_params, *JxlGetDefaultCms(),
    438                            /*distmap=*/nullptr, nullptr);
    439    float target_score = 1.5f;
    440    // upsampling mode 1 (unlike default and NN) does not downscale back to the
    441    // already downsampled image
    442    if (upsampling_mode == 1 && resampling >= 4 && already_downsampled)
    443      target_score = 15.f;
    444    EXPECT_LE(butteraugli_score, target_score);
    445  }
    446  JxlPixelFormat extra_channel_output_pixel_format = output_pixel_format;
    447  extra_channel_output_pixel_format.num_channels = 1;
    448  for (auto& extra_channel : extra_channel_decoded_bytes) {
    449    EXPECT_EQ(extra_channel.size(), extra_channel_bytes.size());
    450    if (lossless) {
    451      EXPECT_EQ(jxl::test::ComparePixels(extra_channel.data(),
    452                                         extra_channel_bytes.data(), xsize,
    453                                         ysize, extra_channel_pixel_format,
    454                                         extra_channel_output_pixel_format),
    455                0u);
    456      EXPECT_EQ(extra_channel, extra_channel_bytes);
    457    }
    458  }
    459 }
    460 
    461 }  // namespace
    462 
    463 TEST(RoundtripTest, FloatFrameRoundtripTest) {
    464  std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>>
    465      extra_channels_cases = {{},
    466                              {{JXL_CHANNEL_ALPHA, "my extra alpha channel"}},
    467                              {{JXL_CHANNEL_CFA, "my cfa channel"}},
    468                              {{JXL_CHANNEL_DEPTH, "depth"},
    469                               {JXL_CHANNEL_SELECTION_MASK, "mask"},
    470                               {JXL_CHANNEL_BLACK, "black"},
    471                               {JXL_CHANNEL_CFA, "my cfa channel"},
    472                               {JXL_CHANNEL_OPTIONAL, "optional channel"}},
    473                              {{JXL_CHANNEL_DEPTH, "very deep"}}};
    474  for (bool use_container : {false, true}) {
    475    for (bool lossless : {false, true}) {
    476      for (uint32_t num_channels = 1; num_channels < 5; num_channels++) {
    477        for (auto& extra_channels : extra_channels_cases) {
    478          uint32_t has_alpha = static_cast<uint32_t>(num_channels % 2 == 0);
    479          uint32_t total_extra_channels = has_alpha + extra_channels.size();
    480          // There's no support (yet) for lossless extra float
    481          // channels, so we don't test it.
    482          if (total_extra_channels == 0 || !lossless) {
    483            JxlPixelFormat pixel_format = JxlPixelFormat{
    484                num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
    485            VerifyRoundtripCompression<float>(
    486                63, 129, pixel_format, pixel_format, lossless, use_container, 1,
    487                false, extra_channels);
    488          }
    489        }
    490      }
    491    }
    492  }
    493 }
    494 
    495 TEST(RoundtripTest, Uint16FrameRoundtripTest) {
    496  std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>>
    497      extra_channels_cases = {{},
    498                              {{JXL_CHANNEL_ALPHA, "my extra alpha channel"}},
    499                              {{JXL_CHANNEL_CFA, "my cfa channel"}},
    500                              {{JXL_CHANNEL_CFA, "my cfa channel"},
    501                               {JXL_CHANNEL_BLACK, "k_channel"}},
    502                              {{JXL_CHANNEL_DEPTH, "very deep"}}};
    503  for (int use_container = 0; use_container < 2; use_container++) {
    504    for (int lossless = 0; lossless < 2; lossless++) {
    505      for (uint32_t num_channels = 1; num_channels < 5; num_channels++) {
    506        for (auto& extra_channels : extra_channels_cases) {
    507          JxlPixelFormat pixel_format = JxlPixelFormat{
    508              num_channels, JXL_TYPE_UINT16, JXL_NATIVE_ENDIAN, 0};
    509          VerifyRoundtripCompression<uint16_t>(
    510              63, 129, pixel_format, pixel_format, static_cast<bool>(lossless),
    511              static_cast<bool>(use_container), 1, false, extra_channels);
    512        }
    513      }
    514    }
    515  }
    516 }
    517 
    518 TEST(RoundtripTest, Uint8FrameRoundtripTest) {
    519  std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>>
    520      extra_channels_cases = {{},
    521                              {{JXL_CHANNEL_THERMAL, "temperature"}},
    522                              {{JXL_CHANNEL_ALPHA, "my extra alpha channel"}},
    523                              {{JXL_CHANNEL_CFA, "my cfa channel"}},
    524                              {{JXL_CHANNEL_CFA, "my cfa channel"},
    525                               {JXL_CHANNEL_BLACK, "k_channel"}},
    526                              {{JXL_CHANNEL_DEPTH, "very deep"}}};
    527  for (int use_container = 0; use_container < 2; use_container++) {
    528    for (int lossless = 0; lossless < 2; lossless++) {
    529      for (uint32_t num_channels = 1; num_channels < 5; num_channels++) {
    530        for (auto& extra_channels : extra_channels_cases) {
    531          JxlPixelFormat pixel_format = JxlPixelFormat{
    532              num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
    533          VerifyRoundtripCompression<uint8_t>(
    534              63, 129, pixel_format, pixel_format, static_cast<bool>(lossless),
    535              static_cast<bool>(use_container), 1, false, extra_channels);
    536        }
    537      }
    538    }
    539  }
    540 }
    541 
    542 TEST(RoundtripTest, TestNonlinearSrgbAsXybEncoded) {
    543  for (int use_container = 0; use_container < 2; use_container++) {
    544    for (uint32_t num_channels = 1; num_channels < 5; num_channels++) {
    545      JxlPixelFormat pixel_format_in =
    546          JxlPixelFormat{num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
    547      JxlPixelFormat pixel_format_out =
    548          JxlPixelFormat{num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
    549      VerifyRoundtripCompression<uint8_t>(
    550          63, 129, pixel_format_in, pixel_format_out,
    551          /*lossless=*/false, static_cast<bool>(use_container), 1, false, {});
    552    }
    553  }
    554 }
    555 
    556 TEST(RoundtripTest, Resampling) {
    557  JxlPixelFormat pixel_format =
    558      JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
    559  VerifyRoundtripCompression<uint8_t>(63, 129, pixel_format, pixel_format,
    560                                      /*lossless=*/false,
    561                                      /*use_container=*/false, 2,
    562                                      /*already_downsampled=*/false);
    563 
    564  // TODO(lode): also make this work for odd sizes. This requires a fix in
    565  // enc_frame.cc to not set custom_size_or_origin to true due to even/odd
    566  // mismatch.
    567  for (int factor : {2, 4, 8}) {
    568    for (int upsampling_mode : {-1, 0, 1}) {
    569      VerifyRoundtripCompression<uint8_t>(
    570          64, 128, pixel_format, pixel_format,
    571          /*lossless=*/true,
    572          /*use_container=*/false, factor,
    573          /*already_downsampled=*/true, /*extra_channels=*/{}, upsampling_mode);
    574    }
    575  }
    576 }
    577 
    578 TEST(RoundtripTest, ExtraBoxesTest) {
    579  JxlPixelFormat pixel_format =
    580      JxlPixelFormat{4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
    581  const size_t xsize = 61;
    582  const size_t ysize = 71;
    583 
    584  const std::vector<uint8_t> original_bytes =
    585      GetTestImage<float>(xsize, ysize, pixel_format);
    586  jxl::CodecInOut original_io =
    587      ConvertTestImage(original_bytes, xsize, ysize, pixel_format, {});
    588 
    589  JxlEncoder* enc = JxlEncoderCreate(nullptr);
    590  EXPECT_NE(nullptr, enc);
    591 
    592  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, true));
    593  JxlBasicInfo basic_info;
    594  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
    595  basic_info.xsize = xsize;
    596  basic_info.ysize = ysize;
    597  basic_info.uses_original_profile = JXL_FALSE;
    598  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
    599  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
    600  JxlColorEncoding color_encoding;
    601  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
    602  if (pixel_format.data_type == JXL_TYPE_FLOAT) {
    603    JxlColorEncodingSetToLinearSRGB(&color_encoding, is_gray);
    604  } else {
    605    JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
    606  }
    607  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
    608  JxlEncoderFrameSettings* frame_settings =
    609      JxlEncoderFrameSettingsCreate(enc, nullptr);
    610  JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE);
    611  EXPECT_EQ(
    612      JXL_ENC_SUCCESS,
    613      JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    614                              static_cast<const void*>(original_bytes.data()),
    615                              original_bytes.size()));
    616  JxlEncoderCloseInput(enc);
    617 
    618  std::vector<uint8_t> compressed;
    619  EncodeWithEncoder(enc, &compressed);
    620  JxlEncoderDestroy(enc);
    621 
    622  std::vector<uint8_t> extra_data(1023);
    623  jxl::AppendBoxHeader(jxl::MakeBoxType("crud"), extra_data.size(), false,
    624                       &compressed);
    625  compressed.insert(compressed.end(), extra_data.begin(), extra_data.end());
    626 
    627  JxlDecoder* dec = JxlDecoderCreate(nullptr);
    628  EXPECT_NE(nullptr, dec);
    629 
    630  const uint8_t* next_in = compressed.data();
    631  size_t avail_in = compressed.size();
    632 
    633  EXPECT_EQ(JXL_DEC_SUCCESS,
    634            JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
    635                                               JXL_DEC_COLOR_ENCODING |
    636                                               JXL_DEC_FULL_IMAGE));
    637 
    638  JxlDecoderSetInput(dec, next_in, avail_in);
    639  EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
    640  size_t buffer_size;
    641  EXPECT_EQ(JXL_DEC_SUCCESS,
    642            JxlDecoderImageOutBufferSize(dec, &pixel_format, &buffer_size));
    643  EXPECT_EQ(buffer_size, original_bytes.size());
    644 
    645  JxlBasicInfo info;
    646  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
    647  EXPECT_EQ(xsize, info.xsize);
    648  EXPECT_EQ(ysize, info.ysize);
    649 
    650  EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
    651 
    652  size_t icc_profile_size;
    653  EXPECT_EQ(JXL_DEC_SUCCESS,
    654            JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
    655                                        &icc_profile_size));
    656  std::vector<uint8_t> icc_profile(icc_profile_size);
    657  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
    658                                 dec, JXL_COLOR_PROFILE_TARGET_DATA,
    659                                 icc_profile.data(), icc_profile.size()));
    660 
    661  std::vector<uint8_t> decoded_bytes(buffer_size);
    662 
    663  EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
    664 
    665  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec, &pixel_format,
    666                                                         decoded_bytes.data(),
    667                                                         decoded_bytes.size()));
    668 
    669  EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
    670 
    671  JxlDecoderDestroy(dec);
    672 
    673  jxl::CodecInOut decoded_io = ConvertTestImage(
    674      decoded_bytes, xsize, ysize, pixel_format, jxl::Bytes(icc_profile));
    675 
    676  jxl::ButteraugliParams butteraugli_params;
    677  float butteraugli_score =
    678      ButteraugliDistance(original_io.frames, decoded_io.frames,
    679                          butteraugli_params, *JxlGetDefaultCms(),
    680                          /*distmap=*/nullptr, nullptr);
    681  EXPECT_LE(butteraugli_score, 1.0f);
    682 }
    683 
    684 TEST(RoundtripTest, MultiFrameTest) {
    685  JxlPixelFormat pixel_format =
    686      JxlPixelFormat{4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
    687  const size_t xsize = 61;
    688  const size_t ysize = 71;
    689  const size_t nb_frames = 4;
    690  size_t compressed_size = 0;
    691 
    692  for (int index_frames : {0, 1}) {
    693    // use a vertical filmstrip of nb_frames frames
    694    const std::vector<uint8_t> original_bytes =
    695        GetTestImage<float>(xsize, ysize * nb_frames, pixel_format);
    696    jxl::CodecInOut original_io = ConvertTestImage(
    697        original_bytes, xsize, ysize * nb_frames, pixel_format, {});
    698 
    699    JxlEncoder* enc = JxlEncoderCreate(nullptr);
    700    EXPECT_NE(nullptr, enc);
    701 
    702    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, true));
    703    JxlBasicInfo basic_info;
    704    jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
    705    basic_info.xsize = xsize;
    706    basic_info.ysize = ysize;
    707    basic_info.uses_original_profile = JXL_FALSE;
    708    basic_info.have_animation = JXL_TRUE;
    709    basic_info.animation.tps_numerator = 1;
    710    basic_info.animation.tps_denominator = 1;
    711    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
    712 
    713    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
    714    JxlColorEncoding color_encoding;
    715    JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
    716    if (pixel_format.data_type == JXL_TYPE_FLOAT) {
    717      JxlColorEncodingSetToLinearSRGB(&color_encoding, is_gray);
    718    } else {
    719      JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
    720    }
    721    EXPECT_EQ(JXL_ENC_SUCCESS,
    722              JxlEncoderSetColorEncoding(enc, &color_encoding));
    723    JxlEncoderFrameSettings* frame_settings =
    724        JxlEncoderFrameSettingsCreate(enc, nullptr);
    725    JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE);
    726    if (index_frames == 1) {
    727      EXPECT_EQ(JXL_ENC_SUCCESS,
    728                JxlEncoderFrameSettingsSetOption(frame_settings,
    729                                                 JXL_ENC_FRAME_INDEX_BOX, 1));
    730    }
    731 
    732    size_t oneframesize = original_bytes.size() / nb_frames;
    733    JxlFrameHeader frame_header;
    734    JxlEncoderInitFrameHeader(&frame_header);
    735    frame_header.duration = 1;
    736    frame_header.is_last = JXL_FALSE;
    737 
    738    for (size_t i = 0; i < nb_frames; i++) {
    739      if (i + 1 == nb_frames) frame_header.is_last = JXL_TRUE;
    740      JxlEncoderSetFrameHeader(frame_settings, &frame_header);
    741      EXPECT_EQ(
    742          JXL_ENC_SUCCESS,
    743          JxlEncoderAddImageFrame(frame_settings, &pixel_format,
    744                                  static_cast<const void*>(
    745                                      original_bytes.data() + oneframesize * i),
    746                                  oneframesize));
    747    }
    748    JxlEncoderCloseInput(enc);
    749 
    750    std::vector<uint8_t> compressed;
    751    EncodeWithEncoder(enc, &compressed);
    752    JxlEncoderDestroy(enc);
    753 
    754    JxlDecoder* dec = JxlDecoderCreate(nullptr);
    755    EXPECT_NE(nullptr, dec);
    756 
    757    const uint8_t* next_in = compressed.data();
    758    size_t avail_in = compressed.size();
    759 
    760    if (index_frames == 0) {
    761      compressed_size = avail_in;
    762    } else {
    763      // a non-empty jxli box should be added
    764      EXPECT_LE(avail_in, compressed_size + 50);
    765      EXPECT_GE(avail_in, compressed_size + 10);
    766    }
    767 
    768    EXPECT_EQ(JXL_DEC_SUCCESS,
    769              JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
    770                                                 JXL_DEC_COLOR_ENCODING |
    771                                                 JXL_DEC_FULL_IMAGE));
    772 
    773    JxlDecoderSetInput(dec, next_in, avail_in);
    774    EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
    775    size_t buffer_size;
    776    EXPECT_EQ(JXL_DEC_SUCCESS,
    777              JxlDecoderImageOutBufferSize(dec, &pixel_format, &buffer_size));
    778    EXPECT_EQ(buffer_size, oneframesize);
    779 
    780    JxlBasicInfo info;
    781    EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
    782    EXPECT_EQ(xsize, info.xsize);
    783    EXPECT_EQ(ysize, info.ysize);
    784 
    785    EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
    786 
    787    size_t icc_profile_size;
    788    EXPECT_EQ(JXL_DEC_SUCCESS,
    789              JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
    790                                          &icc_profile_size));
    791    std::vector<uint8_t> icc_profile(icc_profile_size);
    792    EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
    793                                   dec, JXL_COLOR_PROFILE_TARGET_DATA,
    794                                   icc_profile.data(), icc_profile.size()));
    795 
    796    std::vector<uint8_t> decoded_bytes(buffer_size * nb_frames);
    797 
    798    EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
    799 
    800    for (size_t i = 0; i < nb_frames; i++) {
    801      EXPECT_EQ(JXL_DEC_SUCCESS,
    802                JxlDecoderSetImageOutBuffer(
    803                    dec, &pixel_format, decoded_bytes.data() + i * oneframesize,
    804                    buffer_size));
    805 
    806      EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
    807    }
    808    JxlDecoderDestroy(dec);
    809    jxl::CodecInOut decoded_io =
    810        ConvertTestImage(decoded_bytes, xsize, ysize * nb_frames, pixel_format,
    811                         jxl::Bytes(icc_profile));
    812 
    813    jxl::ButteraugliParams butteraugli_params;
    814    float butteraugli_score =
    815        ButteraugliDistance(original_io.frames, decoded_io.frames,
    816                            butteraugli_params, *JxlGetDefaultCms(),
    817                            /*distmap=*/nullptr, nullptr);
    818    EXPECT_LE(butteraugli_score, 1.0f);
    819  }
    820 }
    821 
    822 static const unsigned char kEncodedTestProfile[] = {
    823    0x1f, 0x8b, 0x1,  0x13, 0x10, 0x0,  0x0,  0x0,  0x20, 0x4c, 0xcc, 0x3,
    824    0xe7, 0xa0, 0xa5, 0xa2, 0x90, 0xa4, 0x27, 0xe8, 0x79, 0x1d, 0xe3, 0x26,
    825    0x57, 0x54, 0xef, 0x0,  0xe8, 0x97, 0x2,  0xce, 0xa1, 0xd7, 0x85, 0x16,
    826    0xb4, 0x29, 0x94, 0x58, 0xf2, 0x56, 0xc0, 0x76, 0xea, 0x23, 0xec, 0x7c,
    827    0x73, 0x51, 0x41, 0x40, 0x23, 0x21, 0x95, 0x4,  0x75, 0x12, 0xc9, 0xcc,
    828    0x16, 0xbd, 0xb6, 0x99, 0xad, 0xf8, 0x75, 0x35, 0xb6, 0x42, 0xae, 0xae,
    829    0xae, 0x86, 0x56, 0xf8, 0xcc, 0x16, 0x30, 0xb3, 0x45, 0xad, 0xd,  0x40,
    830    0xd6, 0xd1, 0xd6, 0x99, 0x40, 0xbe, 0xe2, 0xdc, 0x31, 0x7,  0xa6, 0xb9,
    831    0x27, 0x92, 0x38, 0x0,  0x3,  0x5e, 0x2c, 0xbe, 0xe6, 0xfb, 0x19, 0xbf,
    832    0xf3, 0x6d, 0xbc, 0x4d, 0x64, 0xe5, 0xba, 0x76, 0xde, 0x31, 0x65, 0x66,
    833    0x14, 0xa6, 0x3a, 0xc5, 0x8f, 0xb1, 0xb4, 0xba, 0x1f, 0xb1, 0xb8, 0xd4,
    834    0x75, 0xba, 0x18, 0x86, 0x95, 0x3c, 0x26, 0xf6, 0x25, 0x62, 0x53, 0xfd,
    835    0x9c, 0x94, 0x76, 0xf6, 0x95, 0x2c, 0xb1, 0xfd, 0xdc, 0xc0, 0xe4, 0x3f,
    836    0xb3, 0xff, 0x67, 0xde, 0xd5, 0x94, 0xcc, 0xb0, 0x83, 0x2f, 0x28, 0x93,
    837    0x92, 0x3,  0xa1, 0x41, 0x64, 0x60, 0x62, 0x70, 0x80, 0x87, 0xaf, 0xe7,
    838    0x60, 0x4a, 0x20, 0x23, 0xb3, 0x11, 0x7,  0x38, 0x38, 0xd4, 0xa,  0x66,
    839    0xb5, 0x93, 0x41, 0x90, 0x19, 0x17, 0x18, 0x60, 0xa5, 0xb,  0x7a, 0x24,
    840    0xaa, 0x20, 0x81, 0xac, 0xa9, 0xa1, 0x70, 0xa6, 0x12, 0x8a, 0x4a, 0xa3,
    841    0xa0, 0xf9, 0x9a, 0x97, 0xe7, 0xa8, 0xac, 0x8,  0xa8, 0xc4, 0x2a, 0x86,
    842    0xa7, 0x69, 0x1e, 0x67, 0xe6, 0xbe, 0xa4, 0xd3, 0xff, 0x91, 0x61, 0xf6,
    843    0x8a, 0xe6, 0xb5, 0xb3, 0x61, 0x9f, 0x19, 0x17, 0x98, 0x27, 0x6b, 0xe9,
    844    0x8,  0x98, 0xe1, 0x21, 0x4a, 0x9,  0xb5, 0xd7, 0xca, 0xfa, 0x94, 0xd0,
    845    0x69, 0x1a, 0xeb, 0x52, 0x1,  0x4e, 0xf5, 0xf6, 0xdf, 0x7f, 0xe7, 0x29,
    846    0x70, 0xee, 0x4,  0xda, 0x2f, 0xa4, 0xff, 0xfe, 0xbb, 0x6f, 0xa8, 0xff,
    847    0xfe, 0xdb, 0xaf, 0x8,  0xf6, 0x72, 0xa1, 0x40, 0x5d, 0xf0, 0x2d, 0x8,
    848    0x82, 0x5b, 0x87, 0xbd, 0x10, 0x8,  0xe9, 0x7,  0xee, 0x4b, 0x80, 0xda,
    849    0x4a, 0x4,  0xc5, 0x5e, 0xa0, 0xb7, 0x1e, 0x60, 0xb0, 0x59, 0x76, 0x60,
    850    0xb,  0x2e, 0x19, 0x8a, 0x2e, 0x1c, 0xe6, 0x6,  0x20, 0xb8, 0x64, 0x18,
    851    0x2a, 0xcf, 0x51, 0x94, 0xd4, 0xee, 0xc3, 0xfe, 0x39, 0x74, 0xd4, 0x2b,
    852    0x48, 0xc9, 0x83, 0x4c, 0x9b, 0xd0, 0x4c, 0x35, 0x10, 0xe3, 0x9,  0xf7,
    853    0x72, 0xf0, 0x7a, 0xe,  0xbf, 0x7d, 0x36, 0x2e, 0x19, 0x7e, 0x3f, 0xc,
    854    0xf7, 0x93, 0xe7, 0xf4, 0x1d, 0x32, 0xc6, 0xb0, 0x89, 0xad, 0xe0, 0x28,
    855    0xc1, 0xa7, 0x59, 0xe3, 0x0,
    856 };
    857 
    858 TEST(RoundtripTest, TestICCProfile) {
    859  // JxlEncoderSetICCProfile parses the ICC profile, so a valid profile is
    860  // needed. The profile should be passed correctly through the roundtrip.
    861  jxl::BitReader reader(
    862      jxl::Bytes(kEncodedTestProfile, sizeof(kEncodedTestProfile)));
    863  std::vector<uint8_t> icc;
    864  ASSERT_TRUE(jxl::test::ReadICC(&reader, &icc));
    865  ASSERT_TRUE(reader.Close());
    866 
    867  JxlPixelFormat format =
    868      JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
    869 
    870  size_t xsize = 25;
    871  size_t ysize = 37;
    872  const std::vector<uint8_t> original_bytes =
    873      GetTestImage<uint8_t>(xsize, ysize, format);
    874 
    875  JxlEncoder* enc = JxlEncoderCreate(nullptr);
    876  EXPECT_NE(nullptr, enc);
    877 
    878  JxlBasicInfo basic_info;
    879  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &format);
    880  basic_info.xsize = xsize;
    881  basic_info.ysize = ysize;
    882  basic_info.uses_original_profile = JXL_TRUE;
    883  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
    884 
    885  EXPECT_EQ(JXL_ENC_SUCCESS,
    886            JxlEncoderSetICCProfile(enc, icc.data(), icc.size()));
    887  JxlEncoderFrameSettings* frame_settings =
    888      JxlEncoderFrameSettingsCreate(enc, nullptr);
    889  EXPECT_EQ(
    890      JXL_ENC_SUCCESS,
    891      JxlEncoderAddImageFrame(frame_settings, &format,
    892                              static_cast<const void*>(original_bytes.data()),
    893                              original_bytes.size()));
    894  JxlEncoderCloseInput(enc);
    895 
    896  std::vector<uint8_t> compressed;
    897  EncodeWithEncoder(enc, &compressed);
    898  JxlEncoderDestroy(enc);
    899 
    900  JxlDecoder* dec = JxlDecoderCreate(nullptr);
    901  EXPECT_NE(nullptr, dec);
    902 
    903  const uint8_t* next_in = compressed.data();
    904  size_t avail_in = compressed.size();
    905 
    906  EXPECT_EQ(JXL_DEC_SUCCESS,
    907            JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
    908                                               JXL_DEC_COLOR_ENCODING |
    909                                               JXL_DEC_FULL_IMAGE));
    910 
    911  JxlDecoderSetInput(dec, next_in, avail_in);
    912  EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
    913  size_t buffer_size;
    914  EXPECT_EQ(JXL_DEC_SUCCESS,
    915            JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
    916  EXPECT_EQ(buffer_size, original_bytes.size());
    917 
    918  JxlBasicInfo info;
    919  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
    920  EXPECT_EQ(xsize, info.xsize);
    921  EXPECT_EQ(ysize, info.ysize);
    922 
    923  EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
    924 
    925  size_t dec_icc_size;
    926  EXPECT_EQ(JXL_DEC_SUCCESS,
    927            JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
    928                                        &dec_icc_size));
    929  EXPECT_EQ(icc.size(), dec_icc_size);
    930  std::vector<uint8_t> dec_icc(dec_icc_size);
    931  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
    932                                 dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
    933                                 dec_icc.data(), dec_icc.size()));
    934 
    935  std::vector<uint8_t> decoded_bytes(buffer_size);
    936 
    937  EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
    938 
    939  EXPECT_EQ(JXL_DEC_SUCCESS,
    940            JxlDecoderSetImageOutBuffer(dec, &format, decoded_bytes.data(),
    941                                        decoded_bytes.size()));
    942 
    943  EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
    944 
    945  EXPECT_EQ(icc, dec_icc);
    946 
    947  JxlDecoderDestroy(dec);
    948 }
    949 
    950 JXL_TRANSCODE_JPEG_TEST(RoundtripTest, TestJPEGReconstruction) {
    951  TEST_LIBJPEG_SUPPORT();
    952  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
    953  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
    954 
    955  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    956  JxlEncoderFrameSettings* frame_settings =
    957      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    958 
    959  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE));
    960  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
    961  EXPECT_EQ(JXL_ENC_SUCCESS,
    962            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
    963  JxlEncoderCloseInput(enc.get());
    964 
    965  std::vector<uint8_t> compressed;
    966  EncodeWithEncoder(enc.get(), &compressed);
    967 
    968  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
    969  EXPECT_EQ(JXL_DEC_SUCCESS,
    970            JxlDecoderSubscribeEvents(
    971                dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE));
    972  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
    973  EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec.get()));
    974  std::vector<uint8_t> reconstructed_buffer(128);
    975  EXPECT_EQ(JXL_DEC_SUCCESS,
    976            JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data(),
    977                                    reconstructed_buffer.size()));
    978  size_t used = 0;
    979  JxlDecoderStatus dec_process_result = JXL_DEC_JPEG_NEED_MORE_OUTPUT;
    980  while (dec_process_result == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
    981    used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
    982    reconstructed_buffer.resize(reconstructed_buffer.size() * 2);
    983    EXPECT_EQ(
    984        JXL_DEC_SUCCESS,
    985        JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data() + used,
    986                                reconstructed_buffer.size() - used));
    987    dec_process_result = JxlDecoderProcessInput(dec.get());
    988  }
    989  ASSERT_EQ(JXL_DEC_FULL_IMAGE, dec_process_result);
    990  used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
    991  ASSERT_EQ(used, orig.size());
    992  EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), orig.data(), used));
    993 }
    994 
    995 JXL_TRANSCODE_JPEG_TEST(RoundtripTest,
    996                        TestJPEGReconstructionWithIncompleteHuffmanCode) {
    997  TEST_LIBJPEG_SUPPORT();
    998  static constexpr uint8_t kJPEGBytes[] = {
    999      // SOI
   1000      0xff, 0xd8,  //
   1001      // SOF
   1002      0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01,  //
   1003      0x01, 0x11, 0x00,                                            //
   1004      // DQT
   1005      0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x03, 0x02,  //
   1006      0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,  //
   1007      0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07, 0x07, 0x06,  //
   1008      0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b, 0x0a, 0x0b, 0x0b, 0x0d,  //
   1009      0x0e, 0x12, 0x10, 0x0d, 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10,  //
   1010      0x16, 0x10, 0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,  //
   1011      0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, 0x14,        //
   1012      // DHT
   1013      0xff, 0xc4, 0x01, 0x30,  // marker, len
   1014      0x00,                    // slot id DC0
   1015      // counts for lengths 1 - 16 (total_count: 12)
   1016      0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,  //
   1017      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   1018      // symbols: 0 - 11
   1019      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,  //
   1020      0x0a, 0x0b,                                                  //
   1021      0x13,  // slot id AC3
   1022      // counts for lengths 1 - 16 (total_count: 256)
   1023      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,  //
   1024      0x01, 0x01, 0x01, 0x01, 0x01, 0xf9,                          //
   1025      // symbols: 0 - 255
   1026      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,  //
   1027      0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,  //
   1028      0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,  //
   1029      0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,  //
   1030      0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,  //
   1031      0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,  //
   1032      0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,  //
   1033      0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,  //
   1034      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,  //
   1035      0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63,  //
   1036      0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,  //
   1037      0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,  //
   1038      0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81,  //
   1039      0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b,  //
   1040      0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,  //
   1041      0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,  //
   1042      0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,  //
   1043      0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,  //
   1044      0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd,  //
   1045      0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,  //
   1046      0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1,  //
   1047      0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,  //
   1048      0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5,  //
   1049      0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,  //
   1050      0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,  //
   1051      0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
   1052      // SOS
   1053      0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x03, 0x00, 0x3f, 0x00,  //
   1054      // entropy coded data (first 16 bit: DC 0, next 9 bits: AC eob)
   1055      0xfc, 0xaa, 0x00, 0x00,  //
   1056      // EOI
   1057      0xff, 0xd9,  //
   1058  };
   1059  static constexpr size_t kJPEGSize = sizeof(kJPEGBytes);
   1060 
   1061  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
   1062  JxlEncoderFrameSettings* frame_settings =
   1063      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
   1064 
   1065  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE));
   1066  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
   1067  EXPECT_EQ(JXL_ENC_SUCCESS,
   1068            JxlEncoderAddJPEGFrame(frame_settings, kJPEGBytes, kJPEGSize));
   1069  JxlEncoderCloseInput(enc.get());
   1070 
   1071  std::vector<uint8_t> compressed;
   1072  EncodeWithEncoder(enc.get(), &compressed);
   1073 
   1074  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
   1075  EXPECT_EQ(JXL_DEC_SUCCESS,
   1076            JxlDecoderSubscribeEvents(
   1077                dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE));
   1078  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
   1079  EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec.get()));
   1080  std::vector<uint8_t> reconstructed_buffer(128);
   1081  EXPECT_EQ(JXL_DEC_SUCCESS,
   1082            JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data(),
   1083                                    reconstructed_buffer.size()));
   1084  size_t used = 0;
   1085  JxlDecoderStatus dec_process_result = JXL_DEC_JPEG_NEED_MORE_OUTPUT;
   1086  while (dec_process_result == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
   1087    used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
   1088    reconstructed_buffer.resize(reconstructed_buffer.size() * 2);
   1089    EXPECT_EQ(
   1090        JXL_DEC_SUCCESS,
   1091        JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data() + used,
   1092                                reconstructed_buffer.size() - used));
   1093    dec_process_result = JxlDecoderProcessInput(dec.get());
   1094  }
   1095  ASSERT_EQ(JXL_DEC_FULL_IMAGE, dec_process_result);
   1096  used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
   1097  ASSERT_EQ(used, kJPEGSize);
   1098  EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), kJPEGBytes, used));
   1099 }