libjxl

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

roundtrip_test.cc (41141B)


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