tor-browser

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

jxl_test.cc (69848B)


      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 "lib/extras/dec/jxl.h"
      7 
      8 #include <jxl/cms.h>
      9 #include <jxl/color_encoding.h>
     10 #include <jxl/encode.h>
     11 #include <jxl/memory_manager.h>
     12 #include <jxl/types.h>
     13 
     14 #include <algorithm>
     15 #include <cstddef>
     16 #include <cstdint>
     17 #include <cstdio>
     18 #include <cstring>
     19 #include <future>
     20 #include <ostream>
     21 #include <string>
     22 #include <tuple>
     23 #include <vector>
     24 
     25 #include "lib/extras/codec.h"
     26 #include "lib/extras/dec/decode.h"
     27 #include "lib/extras/enc/encode.h"
     28 #include "lib/extras/enc/jxl.h"
     29 #include "lib/extras/packed_image.h"
     30 #include "lib/jxl/alpha.h"
     31 #include "lib/jxl/base/compiler_specific.h"
     32 #include "lib/jxl/base/data_parallel.h"
     33 #include "lib/jxl/base/sanitizer_definitions.h"  // JXL_MEMORY_SANITIZER
     34 #include "lib/jxl/base/span.h"
     35 #include "lib/jxl/base/status.h"
     36 #include "lib/jxl/codec_in_out.h"
     37 #include "lib/jxl/color_encoding_internal.h"
     38 #include "lib/jxl/common.h"  // JXL_HIGH_PRECISION
     39 #include "lib/jxl/enc_params.h"
     40 #include "lib/jxl/fake_parallel_runner_testonly.h"
     41 #include "lib/jxl/image.h"
     42 #include "lib/jxl/image_bundle.h"
     43 #include "lib/jxl/image_metadata.h"
     44 #include "lib/jxl/jpeg/enc_jpeg_data.h"
     45 #include "lib/jxl/test_image.h"
     46 #include "lib/jxl/test_memory_manager.h"
     47 #include "lib/jxl/test_utils.h"
     48 #include "lib/jxl/testing.h"
     49 
     50 namespace jxl {
     51 
     52 struct AuxOut;
     53 
     54 namespace {
     55 using ::jxl::extras::JXLCompressParams;
     56 using ::jxl::extras::JXLDecompressParams;
     57 using ::jxl::extras::PackedPixelFile;
     58 using ::jxl::test::ButteraugliDistance;
     59 using ::jxl::test::ComputeDistance2;
     60 using ::jxl::test::ReadTestData;
     61 using ::jxl::test::Roundtrip;
     62 using ::jxl::test::TestImage;
     63 using ::jxl::test::ThreadPoolForTests;
     64 
     65 #define JXL_TEST_NL 0  // Disabled in code
     66 
     67 TEST(JxlTest, RoundtripSinglePixel) {
     68  TestImage t;
     69  ASSERT_TRUE(t.SetDimensions(1, 1));
     70  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
     71  frame.ZeroFill();
     72  PackedPixelFile ppf_out;
     73  EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, nullptr, &ppf_out), 54, 10);
     74 }
     75 
     76 TEST(JxlTest, RoundtripSinglePixelWithAlpha) {
     77  TestImage t;
     78  ASSERT_TRUE(t.SetDimensions(1, 1));
     79  ASSERT_TRUE(t.SetChannels(4));
     80  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
     81  frame.ZeroFill();
     82  PackedPixelFile ppf_out;
     83  EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, nullptr, &ppf_out), 57, 10);
     84 }
     85 
     86 // Changing serialized signature causes Decode to fail.
     87 TEST(JxlTest, RoundtripMarker) {
     88  if (JXL_CRASH_ON_ERROR) {
     89    GTEST_SKIP() << "Skipping due to JXL_CRASH_ON_ERROR";
     90  }
     91  TestImage t;
     92  ASSERT_TRUE(t.SetDimensions(1, 1));
     93  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
     94  frame.ZeroFill();
     95  for (size_t i = 0; i < 2; ++i) {
     96    std::vector<uint8_t> compressed;
     97    EXPECT_TRUE(extras::EncodeImageJXL({}, t.ppf(), /*jpeg_bytes=*/nullptr,
     98                                       &compressed));
     99    compressed[i] ^= 0xFF;
    100    PackedPixelFile ppf_out;
    101    EXPECT_FALSE(extras::DecodeImageJXL(compressed.data(), compressed.size(),
    102                                        {}, /* decoded_bytes */ nullptr,
    103                                        &ppf_out));
    104  }
    105 }
    106 
    107 TEST(JxlTest, RoundtripTinyFast) {
    108  ThreadPool* pool = nullptr;
    109  const std::vector<uint8_t> orig =
    110      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    111  TestImage t;
    112  ASSERT_TRUE(t.DecodeFromBytes(orig));
    113  t.ClearMetadata();
    114  ASSERT_TRUE(t.SetDimensions(32, 32));
    115 
    116  JXLCompressParams cparams;
    117  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);
    118  cparams.distance = 4.0f;
    119 
    120  PackedPixelFile ppf_out;
    121  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 181, 15);
    122 }
    123 
    124 TEST(JxlTest, RoundtripSmallD1) {
    125  ThreadPool* pool = nullptr;
    126  const std::vector<uint8_t> orig =
    127      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    128  TestImage t;
    129  ASSERT_TRUE(t.DecodeFromBytes(orig));
    130  t.ClearMetadata();
    131  size_t xsize = t.ppf().info.xsize / 8;
    132  size_t ysize = t.ppf().info.ysize / 8;
    133  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
    134 
    135  {
    136    PackedPixelFile ppf_out;
    137    EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 916, 40);
    138    EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.92);
    139  }
    140 
    141  // With a lower intensity target than the default, the bitrate should be
    142  // smaller.
    143  t.ppf().info.intensity_target = 100.0f;
    144 
    145  {
    146    PackedPixelFile ppf_out;
    147    EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 723, 20);
    148    EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.3);
    149    EXPECT_EQ(ppf_out.info.intensity_target, t.ppf().info.intensity_target);
    150  }
    151 }
    152 TEST(JxlTest, RoundtripResample2) {
    153  ThreadPool* pool = nullptr;
    154  const std::vector<uint8_t> orig =
    155      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    156  TestImage t;
    157  ASSERT_TRUE(t.DecodeFromBytes(orig));
    158  t.ClearMetadata();
    159 
    160  JXLCompressParams cparams;
    161  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
    162  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3);  // kFalcon
    163 
    164  PackedPixelFile ppf_out;
    165  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 17300, 500);
    166  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 90);
    167 }
    168 
    169 TEST(JxlTest, RoundtripResample2Slow) {
    170  ThreadPool* pool = nullptr;
    171  const std::vector<uint8_t> orig =
    172      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    173  TestImage t;
    174  ASSERT_TRUE(t.DecodeFromBytes(orig));
    175  t.ClearMetadata();
    176 
    177  JXLCompressParams cparams;
    178  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
    179  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 9);  // kTortoise
    180  cparams.distance = 10.0;
    181 
    182  PackedPixelFile ppf_out;
    183  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 3888, 200);
    184  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 270);
    185 }
    186 
    187 TEST(JxlTest, RoundtripResample2MT) {
    188  ThreadPoolForTests pool(4);
    189  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    190  // image has to be large enough to have multiple groups after downsampling
    191  TestImage t;
    192  ASSERT_TRUE(t.DecodeFromBytes(orig));
    193  t.ClearMetadata();
    194 
    195  JXLCompressParams cparams;
    196  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
    197  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3);  // kFalcon
    198 
    199  PackedPixelFile ppf_out;
    200  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 206917,
    201              2000);
    202  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 340);
    203 }
    204 
    205 // Roundtrip the image using a parallel runner that executes single-threaded but
    206 // in random order.
    207 TEST(JxlTest, RoundtripOutOfOrderProcessing) {
    208  FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8);
    209  ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
    210  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    211  TestImage t;
    212  ASSERT_TRUE(t.DecodeFromBytes(orig));
    213  t.ClearMetadata();
    214  // Image size is selected so that the block border needed is larger than the
    215  // amount of pixels available on the next block.
    216  ASSERT_TRUE(t.SetDimensions(513, 515));
    217 
    218  JXLCompressParams cparams;
    219  // Force epf so we end up needing a lot of border.
    220  cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 3);
    221 
    222  PackedPixelFile ppf_out;
    223  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 26933, 400);
    224  EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 1.35);
    225 }
    226 
    227 TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
    228  FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8);
    229  ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
    230  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    231  TestImage t;
    232  ASSERT_TRUE(t.DecodeFromBytes(orig));
    233  t.ClearMetadata();
    234  // Image size is selected so that the block border needed is larger than the
    235  // amount of pixels available on the next block.
    236  ASSERT_TRUE(t.SetDimensions(513, 515));
    237 
    238  JXLCompressParams cparams;
    239  // Force epf so we end up needing a lot of border.
    240  cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 3);
    241  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
    242 
    243  PackedPixelFile ppf_out;
    244  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 9947, 200);
    245  EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 2.9);
    246 }
    247 
    248 TEST(JxlTest, RoundtripResample4) {
    249  ThreadPool* pool = nullptr;
    250  const std::vector<uint8_t> orig =
    251      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    252  TestImage t;
    253  ASSERT_TRUE(t.DecodeFromBytes(orig));
    254  t.ClearMetadata();
    255 
    256  JXLCompressParams cparams;
    257  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 4);
    258 
    259  PackedPixelFile ppf_out;
    260  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 5888, 100);
    261  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 22);
    262 }
    263 
    264 TEST(JxlTest, RoundtripResample8) {
    265  ThreadPool* pool = nullptr;
    266  const std::vector<uint8_t> orig =
    267      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    268  TestImage t;
    269  ASSERT_TRUE(t.DecodeFromBytes(orig));
    270  t.ClearMetadata();
    271 
    272  JXLCompressParams cparams;
    273  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 8);
    274 
    275  PackedPixelFile ppf_out;
    276  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 2036, 50);
    277  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 50);
    278 }
    279 
    280 TEST(JxlTest, RoundtripUnalignedD2) {
    281  ThreadPool* pool = nullptr;
    282  const std::vector<uint8_t> orig =
    283      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    284  TestImage t;
    285  ASSERT_TRUE(t.DecodeFromBytes(orig));
    286  t.ClearMetadata();
    287  size_t xsize = t.ppf().info.xsize / 12;
    288  size_t ysize = t.ppf().info.ysize / 7;
    289  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
    290 
    291  JXLCompressParams cparams;
    292  cparams.distance = 2.0;
    293 
    294  PackedPixelFile ppf_out;
    295  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 506, 30);
    296  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.72);
    297 }
    298 
    299 TEST(JxlTest, RoundtripMultiGroup) {
    300  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    301  TestImage t;
    302  ASSERT_TRUE(t.DecodeFromBytes(orig));
    303  t.ClearMetadata();
    304  ASSERT_TRUE(t.SetDimensions(600, 1024));
    305 
    306  auto test = [&](jxl::SpeedTier speed_tier, float target_distance,
    307                  size_t expected_size, float expected_distance) {
    308    ThreadPoolForTests pool(4);
    309    JXLCompressParams cparams;
    310    int64_t effort = 10 - static_cast<int>(speed_tier);
    311    cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, effort);
    312    cparams.distance = target_distance;
    313 
    314    PackedPixelFile ppf_out;
    315    EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out),
    316                expected_size, 700);
    317    EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out),
    318                          expected_distance);
    319  };
    320 
    321  auto run_kitten = std::async(std::launch::async, test, SpeedTier::kKitten,
    322                               1.0f, 64624u, 8.5);
    323  auto run_wombat = std::async(std::launch::async, test, SpeedTier::kWombat,
    324                               2.0f, 38887u, 15.5);
    325 }
    326 
    327 TEST(JxlTest, RoundtripRGBToGrayscale) {
    328  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
    329  ThreadPoolForTests pool(4);
    330  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    331  CodecInOut io{memory_manager};
    332  ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
    333  ASSERT_TRUE(io.ShrinkTo(600, 1024));
    334 
    335  CompressParams cparams;
    336  cparams.butteraugli_distance = 1.0f;
    337  cparams.speed_tier = SpeedTier::kFalcon;
    338 
    339  JXLDecompressParams dparams;
    340  dparams.color_space = "Gra_D65_Rel_SRG";
    341 
    342  CodecInOut io2{memory_manager};
    343  EXPECT_FALSE(io.Main().IsGray());
    344  size_t compressed_size;
    345  JXL_EXPECT_OK(
    346      Roundtrip(&io, cparams, dparams, &io2, _, &compressed_size, pool.get()));
    347  EXPECT_LE(compressed_size, 65000u);
    348  EXPECT_TRUE(io2.Main().IsGray());
    349 
    350  // Convert original to grayscale here, because TransformTo refuses to
    351  // convert between grayscale and RGB.
    352  ColorEncoding srgb_lin = ColorEncoding::LinearSRGB(/*is_gray=*/false);
    353  ASSERT_TRUE(io.frames[0].TransformTo(srgb_lin, *JxlGetDefaultCms()));
    354  Image3F* color = io.Main().color();
    355  for (size_t y = 0; y < color->ysize(); ++y) {
    356    float* row_r = color->PlaneRow(0, y);
    357    float* row_g = color->PlaneRow(1, y);
    358    float* row_b = color->PlaneRow(2, y);
    359    for (size_t x = 0; x < color->xsize(); ++x) {
    360      float luma = 0.2126 * row_r[x] + 0.7152 * row_g[x] + 0.0722 * row_b[x];
    361      row_r[x] = row_g[x] = row_b[x] = luma;
    362    }
    363  }
    364  ColorEncoding srgb_gamma = ColorEncoding::SRGB(/*is_gray=*/false);
    365  ASSERT_TRUE(io.frames[0].TransformTo(srgb_gamma, *JxlGetDefaultCms()));
    366  io.metadata.m.color_encoding = io2.Main().c_current();
    367  io.Main().OverrideProfile(io2.Main().c_current());
    368  EXPECT_SLIGHTLY_BELOW(
    369      ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
    370                          *JxlGetDefaultCms(),
    371                          /*distmap=*/nullptr, pool.get()),
    372      1.4);
    373 }
    374 
    375 TEST(JxlTest, RoundtripLargeFast) {
    376  ThreadPoolForTests pool(8);
    377  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    378  TestImage t;
    379  ASSERT_TRUE(t.DecodeFromBytes(orig));
    380  t.ClearMetadata();
    381 
    382  JXLCompressParams cparams;
    383  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    384 
    385  PackedPixelFile ppf_out;
    386  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 503000,
    387              12000);
    388  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 78);
    389 }
    390 
    391 JXL_X86_64_TEST(JxlTest, RoundtripLargeEmptyModular) {
    392  ThreadPoolForTests pool(8);
    393  TestImage t;
    394  ASSERT_TRUE(t.SetDimensions(4096, 4096));
    395  t.SetDataType(JXL_TYPE_UINT8);
    396  ASSERT_TRUE(t.SetChannels(4));
    397  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
    398  frame.ZeroFill();
    399  for (size_t c = 0; c < 4; ++c) {
    400    for (size_t y = 0; y < 1024; y += (c + 1)) {
    401      for (size_t x = 0; x < 1024; x += ((y % 4) + 3)) {
    402        ASSERT_TRUE(frame.SetValue(y, x, c, 0.88));
    403      }
    404    }
    405  }
    406 
    407  JXLCompressParams cparams;
    408  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1);
    409  cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR, 1);
    410  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
    411  cparams.AddOption(JXL_ENC_FRAME_SETTING_DECODING_SPEED, 2);
    412 
    413  PackedPixelFile ppf_out;
    414  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 669009,
    415              100000);
    416  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.19);
    417 }
    418 
    419 TEST(JxlTest, RoundtripOutputColorSpace) {
    420  ThreadPoolForTests pool(8);
    421  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    422  TestImage t;
    423  ASSERT_TRUE(t.DecodeFromBytes(orig));
    424  t.ClearMetadata();
    425 
    426  JXLCompressParams cparams;
    427  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    428 
    429  JXLDecompressParams dparams;
    430  dparams.color_space = "RGB_D65_DCI_Rel_709";
    431  PackedPixelFile ppf_out;
    432  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out),
    433              503000, 12000);
    434  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 78);
    435 }
    436 
    437 TEST(JxlTest, RoundtripDotsForceEpf) {
    438  ThreadPoolForTests pool(8);
    439  const std::vector<uint8_t> orig =
    440      ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
    441  TestImage t;
    442  ASSERT_TRUE(t.DecodeFromBytes(orig));
    443  t.ClearMetadata();
    444 
    445  JXLCompressParams cparams;
    446  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    447  cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 2);
    448  cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1);
    449 
    450  PackedPixelFile ppf_out;
    451  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 41777,
    452              700);
    453  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 18);
    454 }
    455 
    456 // Checks for differing size/distance in two consecutive runs of distance 2,
    457 // which involves additional processing including adaptive reconstruction.
    458 // Failing this may be a sign of race conditions or invalid memory accesses.
    459 TEST(JxlTest, RoundtripD2Consistent) {
    460  ThreadPoolForTests pool(8);
    461  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    462  TestImage t;
    463  ASSERT_TRUE(t.DecodeFromBytes(orig));
    464  t.ClearMetadata();
    465 
    466  JXLCompressParams cparams;
    467  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    468  cparams.distance = 2.0;
    469 
    470  // Try each xsize mod kBlockDim to verify right border handling.
    471  for (size_t xsize = 48; xsize > 40; --xsize) {
    472    ASSERT_TRUE(t.SetDimensions(xsize, 15));
    473 
    474    PackedPixelFile ppf2;
    475    const size_t size2 = Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf2);
    476 
    477    PackedPixelFile ppf3;
    478    const size_t size3 = Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf3);
    479 
    480    // Exact same compressed size.
    481    EXPECT_EQ(size2, size3);
    482 
    483    // Exact same distance.
    484    const float dist2 = ComputeDistance2(t.ppf(), ppf2);
    485    const float dist3 = ComputeDistance2(t.ppf(), ppf3);
    486    EXPECT_EQ(dist2, dist3);
    487  }
    488 }
    489 
    490 // Same as above, but for full image, testing multiple groups.
    491 TEST(JxlTest, RoundtripLargeConsistent) {
    492  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
    493  TestImage t;
    494  ASSERT_TRUE(t.DecodeFromBytes(orig));
    495  t.ClearMetadata();
    496 
    497  JXLCompressParams cparams;
    498  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    499  cparams.distance = 2.0;
    500 
    501  auto roundtrip_and_compare = [&]() {
    502    ThreadPoolForTests pool(8);
    503    PackedPixelFile ppf2;
    504    size_t size = Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf2);
    505    double dist = ComputeDistance2(t.ppf(), ppf2);
    506    return std::tuple<size_t, double>(size, dist);
    507  };
    508 
    509  // Try each xsize mod kBlockDim to verify right border handling.
    510  auto future2 = std::async(std::launch::async, roundtrip_and_compare);
    511  auto future3 = std::async(std::launch::async, roundtrip_and_compare);
    512 
    513  const auto result2 = future2.get();
    514  const auto result3 = future3.get();
    515 
    516  // Exact same compressed size.
    517  EXPECT_EQ(std::get<0>(result2), std::get<0>(result3));
    518 
    519  // Exact same distance.
    520  EXPECT_EQ(std::get<1>(result2), std::get<1>(result3));
    521 }
    522 
    523 TEST(JxlTest, RoundtripSmallNL) {
    524  ThreadPool* pool = nullptr;
    525  const std::vector<uint8_t> orig =
    526      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    527  TestImage t;
    528  ASSERT_TRUE(t.DecodeFromBytes(orig));
    529  t.ClearMetadata();
    530  size_t xsize = t.ppf().info.xsize / 8;
    531  size_t ysize = t.ppf().info.ysize / 8;
    532  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
    533 
    534  PackedPixelFile ppf_out;
    535  EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 916, 45);
    536  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.92);
    537 }
    538 
    539 TEST(JxlTest, RoundtripNoGaborishNoAR) {
    540  ThreadPool* pool = nullptr;
    541  const std::vector<uint8_t> orig =
    542      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    543  TestImage t;
    544  ASSERT_TRUE(t.DecodeFromBytes(orig));
    545  t.ClearMetadata();
    546 
    547  JXLCompressParams cparams;
    548  cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 0);
    549  cparams.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, 0);
    550 
    551  PackedPixelFile ppf_out;
    552  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 45241, 400);
    553  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.55);
    554 }
    555 
    556 TEST(JxlTest, RoundtripSmallNoGaborish) {
    557  ThreadPool* pool = nullptr;
    558  const std::vector<uint8_t> orig =
    559      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
    560  TestImage t;
    561  ASSERT_TRUE(t.DecodeFromBytes(orig));
    562  t.ClearMetadata();
    563  size_t xsize = t.ppf().info.xsize / 8;
    564  size_t ysize = t.ppf().info.ysize / 8;
    565  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
    566 
    567  JXLCompressParams cparams;
    568  cparams.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, 0);
    569 
    570  PackedPixelFile ppf_out;
    571  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 1042, 20);
    572  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.0);
    573 }
    574 
    575 TEST(JxlTest, RoundtripSmallPatchesAlpha) {
    576  ThreadPool* pool = nullptr;
    577  TestImage t;
    578  ASSERT_TRUE(t.SetDimensions(256, 256));
    579  ASSERT_TRUE(t.SetChannels(4));
    580  ASSERT_TRUE(t.SetColorEncoding("RGB_D65_SRG_Rel_Lin"));
    581  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
    582  frame.ZeroFill();
    583  // This pattern should be picked up by the patch detection heuristics.
    584  for (size_t y = 0; y < t.ppf().info.ysize; ++y) {
    585    for (size_t x = 0; x < t.ppf().info.xsize; ++x) {
    586      if (x % 4 == 0 && (y / 32) % 4 == 0) {
    587        ASSERT_TRUE(frame.SetValue(y, x, 1, 127.0f / 255.0f));
    588      }
    589      ASSERT_TRUE(frame.SetValue(y, x, 3, 1.0f));
    590    }
    591  }
    592 
    593  JXLCompressParams cparams;
    594  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    595  cparams.distance = 0.1f;
    596 
    597  PackedPixelFile ppf_out;
    598  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 462, 100);
    599  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.016f);
    600 }
    601 
    602 TEST(JxlTest, RoundtripSmallPatches) {
    603  ThreadPool* pool = nullptr;
    604  TestImage t;
    605  ASSERT_TRUE(t.SetDimensions(256, 256));
    606  ASSERT_TRUE(t.SetColorEncoding("RGB_D65_SRG_Rel_Lin"));
    607  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
    608  frame.ZeroFill();
    609  // This pattern should be picked up by the patch detection heuristics.
    610  for (size_t y = 0; y < t.ppf().info.ysize; ++y) {
    611    for (size_t x = 0; x < t.ppf().info.xsize; ++x) {
    612      if (x % 4 == 0 && (y / 32) % 4 == 0) {
    613        ASSERT_TRUE(frame.SetValue(y, x, 1, 127.0f / 255.0f));
    614      }
    615    }
    616  }
    617 
    618  JXLCompressParams cparams;
    619  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
    620  cparams.distance = 0.1f;
    621 
    622  PackedPixelFile ppf_out;
    623  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 486, 100);
    624  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.015f);
    625 }
    626 
    627 // TODO(szabadka) Add encoder and decoder API functions that accept frame
    628 // buffers in arbitrary unsigned and floating point formats, and then roundtrip
    629 // test the lossless codepath to make sure the exact binary representations
    630 // are preserved.
    631 #if JXL_FALSE
    632 TEST(JxlTest, RoundtripImageBundleOriginalBits) {
    633  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
    634  // Image does not matter, only io.metadata.m and io2.metadata.m are tested.
    635  JXL_TEST_ASSIGN_OR_DIE(Image3F image, Image3F::Create(memory_manager, 1, 1));
    636  ZeroFillImage(&image);
    637  CodecInOut io{memory_manager};
    638  io.metadata.m.color_encoding = ColorEncoding::LinearSRGB();
    639  io.SetFromImage(std::move(image), ColorEncoding::LinearSRGB());
    640 
    641  CompressParams cparams;
    642 
    643  // Test unsigned integers from 1 to 32 bits
    644  for (uint32_t bit_depth = 1; bit_depth <= 32; bit_depth++) {
    645    if (bit_depth == 32) {
    646      // TODO(lode): allow testing 32, however the code below ends up in
    647      // enc_modular which does not support 32. We only want to test the header
    648      // encoding though, so try without modular.
    649      break;
    650    }
    651 
    652    io.metadata.m.SetUintSamples(bit_depth);
    653    CodecInOut io2{memory_manager};
    654    JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _));
    655 
    656    EXPECT_EQ(bit_depth, io2.metadata.m.bit_depth.bits_per_sample);
    657    EXPECT_FALSE(io2.metadata.m.bit_depth.floating_point_sample);
    658    EXPECT_EQ(0u, io2.metadata.m.bit_depth.exponent_bits_per_sample);
    659    EXPECT_EQ(0u, io2.metadata.m.GetAlphaBits());
    660  }
    661 
    662  // Test various existing and non-existing floating point formats
    663  for (uint32_t bit_depth = 8; bit_depth <= 32; bit_depth++) {
    664    if (bit_depth != 32) {
    665      // TODO(user): test other float types once they work
    666      break;
    667    }
    668 
    669    uint32_t exponent_bit_depth;
    670    if (bit_depth < 10) {
    671      exponent_bit_depth = 2;
    672    } else if (bit_depth < 12) {
    673      exponent_bit_depth = 3;
    674    } else if (bit_depth < 16) {
    675      exponent_bit_depth = 4;
    676    } else if (bit_depth < 20) {
    677      exponent_bit_depth = 5;
    678    } else if (bit_depth < 24) {
    679      exponent_bit_depth = 6;
    680    } else if (bit_depth < 28) {
    681      exponent_bit_depth = 7;
    682    } else {
    683      exponent_bit_depth = 8;
    684    }
    685 
    686    io.metadata.m.bit_depth.bits_per_sample = bit_depth;
    687    io.metadata.m.bit_depth.floating_point_sample = true;
    688    io.metadata.m.bit_depth.exponent_bits_per_sample = exponent_bit_depth;
    689 
    690    CodecInOut io2{memory_manager};
    691    JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2));
    692 
    693    EXPECT_EQ(bit_depth, io2.metadata.m.bit_depth.bits_per_sample);
    694    EXPECT_TRUE(io2.metadata.m.bit_depth.floating_point_sample);
    695    EXPECT_EQ(exponent_bit_depth,
    696              io2.metadata.m.bit_depth.exponent_bits_per_sample);
    697    EXPECT_EQ(0u, io2.metadata.m.GetAlphaBits());
    698  }
    699 }
    700 #endif
    701 
    702 TEST(JxlTest, RoundtripGrayscale) {
    703  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
    704  const std::vector<uint8_t> orig = ReadTestData(
    705      "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
    706  CodecInOut io{memory_manager};
    707  ASSERT_TRUE(SetFromBytes(Bytes(orig), &io));
    708  ASSERT_NE(io.xsize(), 0u);
    709  ASSERT_TRUE(io.ShrinkTo(128, 128));
    710  EXPECT_TRUE(io.Main().IsGray());
    711  EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
    712  EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
    713  EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
    714  EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB());
    715 
    716  {
    717    CompressParams cparams;
    718    cparams.butteraugli_distance = 1.0;
    719 
    720    std::vector<uint8_t> compressed;
    721    EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed));
    722    CodecInOut io2{memory_manager};
    723    EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2));
    724    EXPECT_TRUE(io2.Main().IsGray());
    725 
    726    EXPECT_LE(compressed.size(), 7000u);
    727    EXPECT_SLIGHTLY_BELOW(
    728        ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
    729                            *JxlGetDefaultCms(),
    730                            /*distmap=*/nullptr),
    731        1.6);
    732  }
    733 
    734  // Test with larger butteraugli distance and other settings enabled so
    735  // different jxl codepaths trigger.
    736  {
    737    CompressParams cparams;
    738    cparams.butteraugli_distance = 8.0;
    739 
    740    std::vector<uint8_t> compressed;
    741    EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed));
    742    CodecInOut io2{memory_manager};
    743    EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2));
    744    EXPECT_TRUE(io2.Main().IsGray());
    745 
    746    EXPECT_LE(compressed.size(), 1300u);
    747    EXPECT_SLIGHTLY_BELOW(
    748        ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
    749                            *JxlGetDefaultCms(),
    750                            /*distmap=*/nullptr),
    751        6.7);
    752  }
    753 
    754  {
    755    CompressParams cparams;
    756    cparams.butteraugli_distance = 1.0;
    757 
    758    std::vector<uint8_t> compressed;
    759    EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed));
    760 
    761    CodecInOut io2{memory_manager};
    762    JXLDecompressParams dparams;
    763    dparams.color_space = "RGB_D65_SRG_Rel_SRG";
    764    EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2));
    765    EXPECT_FALSE(io2.Main().IsGray());
    766 
    767    EXPECT_LE(compressed.size(), 7000u);
    768    EXPECT_SLIGHTLY_BELOW(
    769        ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
    770                            *JxlGetDefaultCms(),
    771                            /*distmap=*/nullptr),
    772        1.6);
    773  }
    774 }
    775 
    776 TEST(JxlTest, RoundtripAlpha) {
    777  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
    778  const std::vector<uint8_t> orig =
    779      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
    780  CodecInOut io{memory_manager};
    781  ASSERT_TRUE(SetFromBytes(Bytes(orig), &io));
    782 
    783  ASSERT_NE(io.xsize(), 0u);
    784  ASSERT_TRUE(io.metadata.m.HasAlpha());
    785  ASSERT_TRUE(io.Main().HasAlpha());
    786  ASSERT_TRUE(io.ShrinkTo(300, 300));
    787 
    788  CompressParams cparams;
    789  cparams.butteraugli_distance = 1.0;
    790 
    791  EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
    792  EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
    793  EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
    794  EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB());
    795  std::vector<uint8_t> compressed;
    796  EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed));
    797 
    798  EXPECT_LE(compressed.size(), 20000u);
    799 
    800  for (bool use_image_callback : {false, true}) {
    801    for (bool unpremul_alpha : {false, true}) {
    802      CodecInOut io2{memory_manager};
    803      JXLDecompressParams dparams;
    804      dparams.use_image_callback = use_image_callback;
    805      dparams.unpremultiply_alpha = unpremul_alpha;
    806      EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2));
    807      EXPECT_SLIGHTLY_BELOW(
    808          ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
    809                              *JxlGetDefaultCms(),
    810                              /*distmap=*/nullptr),
    811          1.15);
    812    }
    813  }
    814 }
    815 
    816 namespace {
    817 // Performs "PremultiplyAlpha" for each ImageBundle (preview/frames).
    818 Status PremultiplyAlpha(CodecInOut& io) {
    819  const auto doPremultiplyAlpha = [](ImageBundle& bundle) -> Status {
    820    if (!bundle.HasAlpha()) return true;
    821    if (!bundle.HasColor()) return true;
    822    auto* color = bundle.color();
    823    const auto* alpha = bundle.alpha();
    824    JXL_ENSURE(color->ysize() == alpha->ysize());
    825    JXL_ENSURE(color->xsize() == alpha->xsize());
    826    for (size_t y = 0; y < color->ysize(); y++) {
    827      ::jxl::PremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y),
    828                              color->PlaneRow(2, y), alpha->Row(y),
    829                              color->xsize());
    830    }
    831    return true;
    832  };
    833  ExtraChannelInfo* eci = io.metadata.m.Find(ExtraChannel::kAlpha);
    834  JXL_ENSURE(eci != nullptr && !eci->alpha_associated);
    835  if (io.metadata.m.have_preview) {
    836    JXL_RETURN_IF_ERROR(doPremultiplyAlpha(io.preview_frame));
    837  }
    838  for (ImageBundle& ib : io.frames) {
    839    JXL_RETURN_IF_ERROR(doPremultiplyAlpha(ib));
    840  }
    841  eci->alpha_associated = true;
    842  return true;
    843 }
    844 
    845 Status UnpremultiplyAlpha(CodecInOut& io) {
    846  const auto doUnpremultiplyAlpha = [](ImageBundle& bundle) -> Status {
    847    if (!bundle.HasAlpha()) return true;
    848    if (!bundle.HasColor()) return true;
    849    auto* color = bundle.color();
    850    const auto* alpha = bundle.alpha();
    851    JXL_ENSURE(color->ysize() == alpha->ysize());
    852    JXL_ENSURE(color->xsize() == alpha->xsize());
    853    for (size_t y = 0; y < color->ysize(); y++) {
    854      ::jxl::UnpremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y),
    855                                color->PlaneRow(2, y), alpha->Row(y),
    856                                color->xsize());
    857    }
    858    return true;
    859  };
    860  ExtraChannelInfo* eci = io.metadata.m.Find(ExtraChannel::kAlpha);
    861  JXL_ENSURE(eci != nullptr && eci->alpha_associated);
    862  if (io.metadata.m.have_preview) {
    863    JXL_RETURN_IF_ERROR(doUnpremultiplyAlpha(io.preview_frame));
    864  }
    865  for (ImageBundle& ib : io.frames) {
    866    JXL_RETURN_IF_ERROR(doUnpremultiplyAlpha(ib));
    867  }
    868  eci->alpha_associated = false;
    869  return true;
    870 }
    871 }  // namespace
    872 
    873 TEST(JxlTest, RoundtripAlphaPremultiplied) {
    874  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
    875  const std::vector<uint8_t> orig =
    876      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
    877  CodecInOut io{memory_manager};
    878  CodecInOut io_nopremul{memory_manager};
    879  ASSERT_TRUE(SetFromBytes(Bytes(orig), &io));
    880  ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_nopremul));
    881 
    882  ASSERT_NE(io.xsize(), 0u);
    883  ASSERT_TRUE(io.metadata.m.HasAlpha());
    884  ASSERT_TRUE(io.Main().HasAlpha());
    885  ASSERT_TRUE(io.ShrinkTo(300, 300));
    886  ASSERT_TRUE(io_nopremul.ShrinkTo(300, 300));
    887 
    888  CompressParams cparams;
    889  cparams.butteraugli_distance = 1.0;
    890  cparams.SetCms(*JxlGetDefaultCms());
    891 
    892  EXPECT_FALSE(io.Main().AlphaIsPremultiplied());
    893  EXPECT_TRUE(PremultiplyAlpha(io));
    894  EXPECT_TRUE(io.Main().AlphaIsPremultiplied());
    895 
    896  EXPECT_FALSE(io_nopremul.Main().AlphaIsPremultiplied());
    897 
    898  std::vector<uint8_t> compressed;
    899  EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed));
    900  EXPECT_LE(compressed.size(), 18000u);
    901 
    902  for (bool use_image_callback : {false, true}) {
    903    for (bool unpremul_alpha : {false, true}) {
    904      for (bool use_uint8 : {false, true}) {
    905        printf(
    906            "Testing premultiplied alpha using %s %s requesting "
    907            "%spremultiplied output.\n",
    908            use_uint8 ? "uint8" : "float",
    909            use_image_callback ? "image callback" : "image_buffer",
    910            unpremul_alpha ? "un" : "");
    911        CodecInOut io2{memory_manager};
    912        JXLDecompressParams dparams;
    913        dparams.use_image_callback = use_image_callback;
    914        dparams.unpremultiply_alpha = unpremul_alpha;
    915        if (use_uint8) {
    916          dparams.accepted_formats = {
    917              {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}};
    918        }
    919        EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2));
    920 
    921        EXPECT_EQ(unpremul_alpha, !io2.Main().AlphaIsPremultiplied());
    922        if (!unpremul_alpha) {
    923          EXPECT_SLIGHTLY_BELOW(
    924              ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
    925                                  *JxlGetDefaultCms(),
    926                                  /*distmap=*/nullptr),
    927              1.111);
    928          EXPECT_TRUE(UnpremultiplyAlpha(io2));
    929          EXPECT_FALSE(io2.Main().AlphaIsPremultiplied());
    930        }
    931        EXPECT_SLIGHTLY_BELOW(
    932            ButteraugliDistance(io_nopremul.frames, io2.frames,
    933                                ButteraugliParams(), *JxlGetDefaultCms(),
    934                                /*distmap=*/nullptr),
    935            1.1);
    936      }
    937    }
    938  }
    939 }
    940 
    941 TEST(JxlTest, RoundtripAlphaResampling) {
    942  ThreadPool* pool = nullptr;
    943  const std::vector<uint8_t> orig =
    944      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
    945  TestImage t;
    946  ASSERT_TRUE(t.DecodeFromBytes(orig));
    947  t.ClearMetadata();
    948  ASSERT_NE(t.ppf().info.xsize, 0);
    949  ASSERT_TRUE(t.ppf().info.alpha_bits > 0);
    950 
    951  JXLCompressParams cparams;
    952  cparams.alpha_distance = 1.0;
    953  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 5);  // kHare
    954  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
    955  cparams.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 2);
    956 
    957  PackedPixelFile ppf_out;
    958  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 13600, 130);
    959  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 5.0);
    960 }
    961 
    962 TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
    963  ThreadPool* pool = nullptr;
    964  const std::vector<uint8_t> orig =
    965      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
    966  TestImage t;
    967  ASSERT_TRUE(t.DecodeFromBytes(orig));
    968  t.ClearMetadata();
    969  ASSERT_NE(t.ppf().info.xsize, 0);
    970  ASSERT_TRUE(t.ppf().info.alpha_bits > 0);
    971 
    972  JXLCompressParams cparams;
    973  cparams.alpha_distance = 1.0;
    974  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3);  // kFalcon
    975  cparams.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 2);
    976 
    977  PackedPixelFile ppf_out;
    978  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 33179, 1000);
    979  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.52);
    980 }
    981 
    982 TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
    983  ThreadPool* pool = nullptr;
    984  const std::vector<uint8_t> orig =
    985      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
    986  TestImage t;
    987  ASSERT_TRUE(t.DecodeFromBytes(orig));
    988  t.ClearMetadata();
    989  ASSERT_TRUE(t.SetDimensions(12, 12));
    990  ASSERT_NE(t.ppf().info.xsize, 0);
    991  ASSERT_TRUE(t.ppf().info.alpha_bits > 0);
    992  EXPECT_EQ(t.ppf().frames[0].color.format.data_type, JXL_TYPE_UINT8);
    993 
    994  PackedPixelFile ppf_out;
    995  EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 107, 10);
    996  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.006);
    997 }
    998 
    999 TEST(JxlTest, RoundtripAlpha16) {
   1000  ThreadPoolForTests pool(4);
   1001  // The image is wider than 512 pixels to ensure multiple groups are tested.
   1002  size_t xsize = 1200;
   1003  size_t ysize = 160;
   1004  TestImage t;
   1005  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
   1006  ASSERT_TRUE(t.SetChannels(4));
   1007  t.SetAllBitDepths(16);
   1008  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
   1009  // Generate 16-bit pattern that uses various colors and alpha values.
   1010  const float mul = 1.0f / 65535;
   1011  for (size_t y = 0; y < ysize; y++) {
   1012    for (size_t x = 0; x < xsize; x++) {
   1013      uint16_t r = y * 65535 / ysize;
   1014      uint16_t g = x * 65535 / xsize;
   1015      uint16_t b = (y + x) * 65535 / (xsize + ysize);
   1016      ASSERT_TRUE(frame.SetValue(y, x, 0, r * mul));
   1017      ASSERT_TRUE(frame.SetValue(y, x, 1, g * mul));
   1018      ASSERT_TRUE(frame.SetValue(y, x, 2, b * mul));
   1019      ASSERT_TRUE(frame.SetValue(y, x, 3, g * mul));
   1020    }
   1021  }
   1022 
   1023  ASSERT_NE(t.ppf().info.xsize, 0);
   1024  ASSERT_EQ(t.ppf().info.alpha_bits, 16);
   1025 
   1026  JXLCompressParams cparams;
   1027  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 6);  // kWombat
   1028  cparams.distance = 0.5;
   1029  cparams.alpha_distance = 0.5;
   1030 
   1031  PackedPixelFile ppf_out;
   1032  // TODO(szabadka) Investigate big size difference on i686
   1033  // This still keeps happening (2023-04-18).
   1034  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 4013, 120);
   1035  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.6);
   1036 }
   1037 
   1038 JXL_SLOW_TEST(JxlTest, RoundtripLossless8) {
   1039  ThreadPoolForTests pool(8);
   1040  const std::vector<uint8_t> orig =
   1041      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
   1042  TestImage t;
   1043  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1044  t.ClearMetadata();
   1045 
   1046  JXLCompressParams cparams = test::CompressParamsForLossless();
   1047  JXLDecompressParams dparams;
   1048  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1049 
   1050  PackedPixelFile ppf_out;
   1051  EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 223058);
   1052  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1053 }
   1054 
   1055 JXL_SLOW_TEST(JxlTest, RoundtripLossless8ThunderGradient) {
   1056  ThreadPoolForTests pool(8);
   1057  const std::vector<uint8_t> orig =
   1058      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
   1059  TestImage t;
   1060  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1061  t.ClearMetadata();
   1062 
   1063  JXLCompressParams cparams = test::CompressParamsForLossless();
   1064  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 2);             // kThunder
   1065  cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 5);  // Gradient
   1066  JXLDecompressParams dparams;
   1067  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1068 
   1069  PackedPixelFile ppf_out;
   1070  EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 261684);
   1071  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1072 }
   1073 
   1074 JXL_SLOW_TEST(JxlTest, RoundtripLossless8LightningGradient) {
   1075  ThreadPoolForTests pool(8);
   1076  const std::vector<uint8_t> orig =
   1077      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
   1078  TestImage t;
   1079  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1080  t.ClearMetadata();
   1081 
   1082  JXLCompressParams cparams = test::CompressParamsForLossless();
   1083  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1);  // kLightning
   1084  JXLDecompressParams dparams;
   1085  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1086 
   1087  PackedPixelFile ppf_out;
   1088  // Lax comparison because different SIMD will cause different compression.
   1089  EXPECT_SLIGHTLY_BELOW(
   1090      Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 286848u);
   1091  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1092 }
   1093 
   1094 JXL_SLOW_TEST(JxlTest, RoundtripLossless8Falcon) {
   1095  ThreadPoolForTests pool(8);
   1096  const std::vector<uint8_t> orig =
   1097      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
   1098  TestImage t;
   1099  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1100  t.ClearMetadata();
   1101 
   1102  JXLCompressParams cparams = test::CompressParamsForLossless();
   1103  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3);  // kFalcon
   1104  JXLDecompressParams dparams;
   1105  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1106 
   1107  PackedPixelFile ppf_out;
   1108  EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 230766);
   1109  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1110 }
   1111 
   1112 TEST(JxlTest, RoundtripLossless8Alpha) {
   1113  ThreadPool* pool = nullptr;
   1114  const std::vector<uint8_t> orig =
   1115      ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
   1116  TestImage t;
   1117  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1118  t.ClearMetadata();
   1119  ASSERT_EQ(t.ppf().info.alpha_bits, 8);
   1120  EXPECT_EQ(t.ppf().frames[0].color.format.data_type, JXL_TYPE_UINT8);
   1121 
   1122  JXLCompressParams cparams = test::CompressParamsForLossless();
   1123 
   1124  JXLDecompressParams dparams;
   1125  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1126 
   1127  PackedPixelFile ppf_out;
   1128  EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 251470);
   1129  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1130  EXPECT_EQ(ppf_out.info.alpha_bits, 8);
   1131  EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out));
   1132 }
   1133 
   1134 TEST(JxlTest, RoundtripLossless16Alpha) {
   1135  ThreadPool* pool = nullptr;
   1136  size_t xsize = 1200;
   1137  size_t ysize = 160;
   1138  TestImage t;
   1139  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
   1140  ASSERT_TRUE(t.SetChannels(4));
   1141  t.SetAllBitDepths(16);
   1142  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
   1143  frame.ZeroFill();
   1144  // Generate 16-bit pattern that uses various colors and alpha values.
   1145  const float mul = 1.0f / 65535;
   1146  for (size_t y = 0; y < ysize; y++) {
   1147    for (size_t x = 0; x < xsize; x++) {
   1148      uint16_t r = y * 65535 / ysize;
   1149      uint16_t g = x * 65535 / xsize + 37;
   1150      uint16_t b = (y + x) * 65535 / (xsize + ysize);
   1151      ASSERT_TRUE(frame.SetValue(y, x, 0, r * mul));
   1152      ASSERT_TRUE(frame.SetValue(y, x, 1, g * mul));
   1153      ASSERT_TRUE(frame.SetValue(y, x, 2, b * mul));
   1154      ASSERT_TRUE(frame.SetValue(y, x, 3, g * mul));
   1155    }
   1156  }
   1157  ASSERT_EQ(t.ppf().info.bits_per_sample, 16);
   1158  ASSERT_EQ(t.ppf().info.alpha_bits, 16);
   1159 
   1160  JXLCompressParams cparams = test::CompressParamsForLossless();
   1161 
   1162  JXLDecompressParams dparams;
   1163  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1164 
   1165  PackedPixelFile ppf_out;
   1166  // TODO(szabadka) Investigate big size difference on i686
   1167  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 4665, 100);
   1168  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1169  EXPECT_EQ(ppf_out.info.alpha_bits, 16);
   1170  EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out));
   1171 }
   1172 
   1173 TEST(JxlTest, RoundtripLossless16AlphaNotMisdetectedAs8Bit) {
   1174  ThreadPool* pool = nullptr;
   1175  size_t xsize = 128;
   1176  size_t ysize = 128;
   1177  TestImage t;
   1178  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
   1179  ASSERT_TRUE(t.SetChannels(4));
   1180  t.SetAllBitDepths(16);
   1181  JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
   1182  // All 16-bit values, both color and alpha, of this image are below 64.
   1183  // This allows testing if a code path wrongly concludes it's an 8-bit instead
   1184  // of 16-bit image (or even 6-bit).
   1185  const float mul = 1.0f / 65535;
   1186  for (size_t y = 0; y < ysize; y++) {
   1187    for (size_t x = 0; x < xsize; x++) {
   1188      uint16_t r = y * 64 / ysize;
   1189      uint16_t g = x * 64 / xsize + 37;
   1190      uint16_t b = (y + x) * 64 / (xsize + ysize);
   1191      ASSERT_TRUE(frame.SetValue(y, x, 0, r * mul));
   1192      ASSERT_TRUE(frame.SetValue(y, x, 1, g * mul));
   1193      ASSERT_TRUE(frame.SetValue(y, x, 2, b * mul));
   1194      ASSERT_TRUE(frame.SetValue(y, x, 3, g * mul));
   1195    }
   1196  }
   1197  ASSERT_EQ(t.ppf().info.bits_per_sample, 16);
   1198  ASSERT_EQ(t.ppf().info.alpha_bits, 16);
   1199 
   1200  JXLCompressParams cparams = test::CompressParamsForLossless();
   1201 
   1202  JXLDecompressParams dparams;
   1203  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1204 
   1205  PackedPixelFile ppf_out;
   1206  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 280, 50);
   1207  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1208  EXPECT_EQ(ppf_out.info.bits_per_sample, 16);
   1209  EXPECT_EQ(ppf_out.info.alpha_bits, 16);
   1210  EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out));
   1211 }
   1212 
   1213 TEST(JxlTest, RoundtripDots) {
   1214  ThreadPool* pool = nullptr;
   1215  const std::vector<uint8_t> orig =
   1216      ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
   1217  TestImage t;
   1218  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1219  t.ClearMetadata();
   1220  ASSERT_NE(t.ppf().info.xsize, 0);
   1221  EXPECT_EQ(t.ppf().info.bits_per_sample, 8);
   1222  EXPECT_EQ(t.ppf().color_encoding.transfer_function,
   1223            JXL_TRANSFER_FUNCTION_SRGB);
   1224 
   1225  JXLCompressParams cparams;
   1226  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
   1227  cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1);
   1228  cparams.distance = 0.04;
   1229 
   1230  PackedPixelFile ppf_out;
   1231  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 276166, 4000);
   1232  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.14);
   1233 }
   1234 
   1235 TEST(JxlTest, RoundtripDisablePerceptual) {
   1236  ThreadPool* pool = nullptr;
   1237  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
   1238  TestImage t;
   1239  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1240  t.ClearMetadata();
   1241  ASSERT_NE(t.ppf().info.xsize, 0);
   1242  EXPECT_EQ(t.ppf().info.bits_per_sample, 8);
   1243  EXPECT_EQ(t.ppf().color_encoding.transfer_function,
   1244            JXL_TRANSFER_FUNCTION_SRGB);
   1245 
   1246  JXLCompressParams cparams;
   1247  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
   1248  cparams.AddOption(JXL_ENC_FRAME_SETTING_DISABLE_PERCEPTUAL_HEURISTICS, 1);
   1249  cparams.distance = 1.0;
   1250 
   1251  PackedPixelFile ppf_out;
   1252 
   1253  size_t expected_size = 477778;
   1254  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), expected_size,
   1255              4000);
   1256  // TODO(veluca): figure out why we can't get below this value.
   1257  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 11.0);
   1258 }
   1259 
   1260 TEST(JxlTest, RoundtripNoise) {
   1261  ThreadPool* pool = nullptr;
   1262  const std::vector<uint8_t> orig =
   1263      ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
   1264  TestImage t;
   1265  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1266  t.ClearMetadata();
   1267  ASSERT_NE(t.ppf().info.xsize, 0);
   1268  EXPECT_EQ(t.ppf().info.bits_per_sample, 8);
   1269  EXPECT_EQ(t.ppf().color_encoding.transfer_function,
   1270            JXL_TRANSFER_FUNCTION_SRGB);
   1271 
   1272  JXLCompressParams cparams;
   1273  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7);  // kSquirrel
   1274  cparams.AddOption(JXL_ENC_FRAME_SETTING_NOISE, 1);
   1275 
   1276  PackedPixelFile ppf_out;
   1277  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 41009, 750);
   1278  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.42);
   1279 }
   1280 
   1281 TEST(JxlTest, RoundtripLossless8Gray) {
   1282  ThreadPool* pool = nullptr;
   1283  const std::vector<uint8_t> orig = ReadTestData(
   1284      "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
   1285  TestImage t;
   1286  ASSERT_TRUE(t.SetColorEncoding("Gra_D65_Rel_SRG"));
   1287  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1288  t.ClearMetadata();
   1289  EXPECT_EQ(t.ppf().color_encoding.color_space, JXL_COLOR_SPACE_GRAY);
   1290  EXPECT_EQ(t.ppf().info.bits_per_sample, 8);
   1291 
   1292  JXLCompressParams cparams = test::CompressParamsForLossless();
   1293 
   1294  JXLDecompressParams dparams;
   1295  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1296 
   1297  PackedPixelFile ppf_out;
   1298  EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 92185);
   1299  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1300  EXPECT_EQ(ppf_out.color_encoding.color_space, JXL_COLOR_SPACE_GRAY);
   1301  EXPECT_EQ(ppf_out.info.bits_per_sample, 8);
   1302 }
   1303 
   1304 TEST(JxlTest, RoundtripAnimation) {
   1305  if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) {
   1306    fprintf(stderr, "Skipping test because of missing GIF decoder.\n");
   1307    return;
   1308  }
   1309  ThreadPool* pool = nullptr;
   1310  const std::vector<uint8_t> orig = ReadTestData("jxl/traffic_light.gif");
   1311  TestImage t;
   1312  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1313  t.ClearMetadata();
   1314  EXPECT_EQ(4, t.ppf().frames.size());
   1315 
   1316  JXLDecompressParams dparams;
   1317  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1318 
   1319  PackedPixelFile ppf_out;
   1320  EXPECT_SLIGHTLY_BELOW(Roundtrip(t.ppf(), {}, dparams, pool, &ppf_out), 3370);
   1321 
   1322  ASSERT_TRUE(t.CoalesceGIFAnimationWithAlpha());
   1323  ASSERT_EQ(ppf_out.frames.size(), t.ppf().frames.size());
   1324  static constexpr double kMaxButteraugli =
   1325 #if JXL_HIGH_PRECISION
   1326      1.55;
   1327 #else
   1328      1.75;
   1329 #endif
   1330  EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), kMaxButteraugli);
   1331 }
   1332 
   1333 TEST(JxlTest, RoundtripLosslessAnimation) {
   1334  if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) {
   1335    fprintf(stderr, "Skipping test because of missing GIF decoder.\n");
   1336    return;
   1337  }
   1338  ThreadPool* pool = nullptr;
   1339  const std::vector<uint8_t> orig = ReadTestData("jxl/traffic_light.gif");
   1340  TestImage t;
   1341  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1342  t.ClearMetadata();
   1343  EXPECT_EQ(4, t.ppf().frames.size());
   1344 
   1345  JXLCompressParams cparams = test::CompressParamsForLossless();
   1346 
   1347  JXLDecompressParams dparams;
   1348  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1349 
   1350  PackedPixelFile ppf_out;
   1351  EXPECT_SLIGHTLY_BELOW(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out),
   1352                        958);
   1353 
   1354  ASSERT_TRUE(t.CoalesceGIFAnimationWithAlpha());
   1355  ASSERT_EQ(ppf_out.frames.size(), t.ppf().frames.size());
   1356  EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 5e-4);
   1357 }
   1358 
   1359 TEST(JxlTest, RoundtripAnimationPatches) {
   1360  if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) {
   1361    fprintf(stderr, "Skipping test because of missing GIF decoder.\n");
   1362    return;
   1363  }
   1364  ThreadPool* pool = nullptr;
   1365  const std::vector<uint8_t> orig = ReadTestData("jxl/animation_patches.gif");
   1366 
   1367  TestImage t;
   1368  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1369  t.ClearMetadata();
   1370  ASSERT_EQ(2u, t.ppf().frames.size());
   1371 
   1372  JXLCompressParams cparams;
   1373  cparams.AddOption(JXL_ENC_FRAME_SETTING_PATCHES, 1);
   1374 
   1375  JXLDecompressParams dparams;
   1376  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1377 
   1378  PackedPixelFile ppf_out;
   1379  // 40k with no patches, 27k with patch frames encoded multiple times.
   1380  EXPECT_SLIGHTLY_BELOW(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out),
   1381                        21220);
   1382  EXPECT_EQ(ppf_out.frames.size(), t.ppf().frames.size());
   1383  // >10 with broken patches; not all patches are detected on borders.
   1384  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.85);
   1385 }
   1386 
   1387 size_t RoundtripJpeg(const std::vector<uint8_t>& jpeg_in, ThreadPool* pool) {
   1388  std::vector<uint8_t> compressed;
   1389  EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_in,
   1390                                     &compressed));
   1391 
   1392  jxl::JXLDecompressParams dparams;
   1393  test::SetThreadParallelRunner(dparams, pool);
   1394  {
   1395    std::vector<uint8_t> out;
   1396    jxl::PackedPixelFile ppf;
   1397    EXPECT_FALSE(DecodeImageJXL(compressed.data(), compressed.size() - 1,
   1398                                dparams, nullptr, &ppf, &out));
   1399  }
   1400  std::vector<uint8_t> out;
   1401  jxl::PackedPixelFile ppf;
   1402  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
   1403                             nullptr, &ppf, &out));
   1404  EXPECT_EQ(out.size(), jpeg_in.size());
   1405  size_t failures = 0;
   1406  for (size_t i = 0; i < std::min(out.size(), jpeg_in.size()); i++) {
   1407    if (out[i] != jpeg_in[i]) {
   1408      EXPECT_EQ(out[i], jpeg_in[i])
   1409          << "byte mismatch " << i << " " << out[i] << " != " << jpeg_in[i];
   1410      if (++failures > 4) {
   1411        return compressed.size();
   1412      }
   1413    }
   1414  }
   1415  return compressed.size();
   1416 }
   1417 
   1418 void RoundtripJpegToPixels(const std::vector<uint8_t>& jpeg_in,
   1419                           JXLDecompressParams dparams, ThreadPool* pool,
   1420                           PackedPixelFile* ppf_out) {
   1421  std::vector<uint8_t> jpeg_bytes(jpeg_in.data(),
   1422                                  jpeg_in.data() + jpeg_in.size());
   1423  std::vector<uint8_t> compressed;
   1424  EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_bytes,
   1425                                     &compressed));
   1426 
   1427  test::DefaultAcceptedFormats(dparams);
   1428  test::SetThreadParallelRunner(dparams, pool);
   1429  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
   1430                             nullptr, ppf_out, nullptr));
   1431 }
   1432 
   1433 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression444) {
   1434  ThreadPoolForTests pool(8);
   1435  const std::vector<uint8_t> orig =
   1436      ReadTestData("jxl/flower/flower.png.im_q85_444.jpg");
   1437  // JPEG size is 696,659 bytes.
   1438  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 568891u, 20);
   1439 }
   1440 
   1441 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixels) {
   1442  TEST_LIBJPEG_SUPPORT();
   1443  ThreadPoolForTests pool(8);
   1444  const std::vector<uint8_t> orig =
   1445      ReadTestData("jxl/flower/flower.png.im_q85_444.jpg");
   1446  TestImage t;
   1447  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1448 
   1449  PackedPixelFile ppf_out;
   1450  RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
   1451  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 12);
   1452 }
   1453 
   1454 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixels420) {
   1455  TEST_LIBJPEG_SUPPORT();
   1456  ThreadPoolForTests pool(8);
   1457  const std::vector<uint8_t> orig =
   1458      ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
   1459  TestImage t;
   1460  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1461 
   1462  PackedPixelFile ppf_out;
   1463  RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
   1464  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 11);
   1465 }
   1466 
   1467 JXL_TRANSCODE_JPEG_TEST(JxlTest,
   1468                        RoundtripJpegRecompressionToPixels420EarlyFlush) {
   1469  TEST_LIBJPEG_SUPPORT();
   1470  ThreadPoolForTests pool(8);
   1471  const std::vector<uint8_t> orig =
   1472      ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
   1473  TestImage t;
   1474  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1475 
   1476  JXLDecompressParams dparams;
   1477  dparams.max_downsampling = 8;
   1478 
   1479  PackedPixelFile ppf_out;
   1480  RoundtripJpegToPixels(orig, dparams, pool.get(), &ppf_out);
   1481  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 4410);
   1482 }
   1483 
   1484 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixels420Mul16) {
   1485  TEST_LIBJPEG_SUPPORT();
   1486  ThreadPoolForTests pool(8);
   1487  const std::vector<uint8_t> orig =
   1488      ReadTestData("jxl/flower/flower_cropped.jpg");
   1489  TestImage t;
   1490  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1491 
   1492  PackedPixelFile ppf_out;
   1493  RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
   1494  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 4);
   1495 }
   1496 
   1497 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixelsAsymmetric) {
   1498  TEST_LIBJPEG_SUPPORT();
   1499  ThreadPoolForTests pool(8);
   1500  const std::vector<uint8_t> orig =
   1501      ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg");
   1502  TestImage t;
   1503  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1504 
   1505  PackedPixelFile ppf_out;
   1506  RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
   1507  EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 10);
   1508 }
   1509 
   1510 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionGray) {
   1511  ThreadPoolForTests pool(8);
   1512  const std::vector<uint8_t> orig =
   1513      ReadTestData("jxl/flower/flower.png.im_q85_gray.jpg");
   1514  // JPEG size is 456,528 bytes.
   1515  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 387496u, 200);
   1516 }
   1517 
   1518 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression420) {
   1519  ThreadPoolForTests pool(8);
   1520  const std::vector<uint8_t> orig =
   1521      ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
   1522  // JPEG size is 546,797 bytes.
   1523  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 455510u, 20);
   1524 }
   1525 
   1526 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionLumaSubsample) {
   1527  ThreadPoolForTests pool(8);
   1528  const std::vector<uint8_t> orig =
   1529      ReadTestData("jxl/flower/flower.png.im_q85_luma_subsample.jpg");
   1530  // JPEG size is 400,724 bytes.
   1531  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 325310u, 20);
   1532 }
   1533 
   1534 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression444wh12) {
   1535  // 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2).
   1536  ThreadPoolForTests pool(8);
   1537  const std::vector<uint8_t> orig =
   1538      ReadTestData("jxl/flower/flower.png.im_q85_444_1x2.jpg");
   1539  // JPEG size is 703,874 bytes.
   1540  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 569630u, 20);
   1541 }
   1542 
   1543 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression422) {
   1544  ThreadPoolForTests pool(8);
   1545  const std::vector<uint8_t> orig =
   1546      ReadTestData("jxl/flower/flower.png.im_q85_422.jpg");
   1547  // JPEG size is 522,057 bytes.
   1548  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 499236u, 20);
   1549 }
   1550 
   1551 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression440) {
   1552  ThreadPoolForTests pool(8);
   1553  const std::vector<uint8_t> orig =
   1554      ReadTestData("jxl/flower/flower.png.im_q85_440.jpg");
   1555  // JPEG size is 603,623 bytes.
   1556  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 501101u, 20);
   1557 }
   1558 
   1559 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionAsymmetric) {
   1560  // 2x vertical downsample of one chroma channel, 2x horizontal downsample of
   1561  // the other.
   1562  ThreadPoolForTests pool(8);
   1563  const std::vector<uint8_t> orig =
   1564      ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg");
   1565  // JPEG size is 604,601 bytes.
   1566  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 500548u, 20);
   1567 }
   1568 
   1569 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression420Progr) {
   1570  ThreadPoolForTests pool(8);
   1571  const std::vector<uint8_t> orig =
   1572      ReadTestData("jxl/flower/flower.png.im_q85_420_progr.jpg");
   1573  // JPEG size is 522,057 bytes.
   1574  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 455454u, 20);
   1575 }
   1576 
   1577 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionMetadata) {
   1578  ThreadPoolForTests pool(8);
   1579  const std::vector<uint8_t> orig =
   1580      ReadTestData("jxl/jpeg_reconstruction/1x1_exif_xmp.jpg");
   1581  // JPEG size is 4290 bytes
   1582  // 1370 on 386, so higher margin.
   1583  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 1334u, 100);
   1584 }
   1585 
   1586 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionEmptyExif) {
   1587  ThreadPoolForTests pool(8);
   1588  const std::vector<uint8_t> orig = {
   1589      // SOI
   1590      0xff, 0xd8,  //
   1591      // APP1
   1592      0xff, 0xe1, 0x00, 0x08, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,  //
   1593      // DQT
   1594      0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x03, 0x02,  //
   1595      0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,  //
   1596      0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07, 0x07, 0x06,  //
   1597      0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b, 0x0a, 0x0b, 0x0b, 0x0d,  //
   1598      0x0e, 0x12, 0x10, 0x0d, 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10,  //
   1599      0x16, 0x10, 0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,  //
   1600      0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, 0x14,        //
   1601      // SOF
   1602      0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01,  //
   1603      0x01, 0x11, 0x00,                                            //
   1604      // DHT
   1605      0xff, 0xc4, 0x00, 0xd2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01,  //
   1606      0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //
   1607      0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,  //
   1608      0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,  //
   1609      0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d,  //
   1610      0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,  //
   1611      0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,  //
   1612      0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52,  //
   1613      0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,  //
   1614      0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,  //
   1615      0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,  //
   1616      0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57,  //
   1617      0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,  //
   1618      0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,  //
   1619      0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,  //
   1620      0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,  //
   1621      0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,  //
   1622      0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,  //
   1623      0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,  //
   1624      0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,  //
   1625      0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,  //
   1626      0xf9, 0xfa,                                                  //
   1627      // SOS
   1628      0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00,  //
   1629      // entropy coded data
   1630      0xfc, 0xaa, 0xaf,  //
   1631      // EOI
   1632      0xff, 0xd9,  //
   1633  };
   1634  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 466u, 100);
   1635 }
   1636 
   1637 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionRestarts) {
   1638  ThreadPoolForTests pool(8);
   1639  const std::vector<uint8_t> orig =
   1640      ReadTestData("jxl/jpeg_reconstruction/bicycles_restarts.jpg");
   1641  // JPEG size is 87478 bytes
   1642  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 76054u, 30);
   1643 }
   1644 
   1645 JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionOrientationICC) {
   1646  ThreadPoolForTests pool(8);
   1647  const std::vector<uint8_t> orig =
   1648      ReadTestData("jxl/jpeg_reconstruction/sideways_bench.jpg");
   1649  // JPEG size is 15252 bytes
   1650  EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 12000u, 470);
   1651  // TODO(jon): investigate why 'Cross-compiling i686-linux-gnu' produces a
   1652  // larger result
   1653 }
   1654 
   1655 TEST(JxlTest, RoundtripProgressive) {
   1656  ThreadPoolForTests pool(4);
   1657  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
   1658  TestImage t;
   1659  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1660  t.ClearMetadata();
   1661  ASSERT_TRUE(t.SetDimensions(600, 1024));
   1662 
   1663  JXLCompressParams cparams;
   1664  cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 1);
   1665  cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1);
   1666  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
   1667 
   1668  PackedPixelFile ppf_out;
   1669  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 71544,
   1670              750);
   1671  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.4);
   1672 }
   1673 
   1674 TEST(JxlTest, RoundtripProgressiveLevel2Slow) {
   1675  ThreadPoolForTests pool(8);
   1676  const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
   1677  TestImage t;
   1678  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1679  t.ClearMetadata();
   1680  ASSERT_TRUE(t.SetDimensions(600, 1024));
   1681 
   1682  JXLCompressParams cparams;
   1683  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 9);  // kTortoise
   1684  cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2);
   1685  cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1);
   1686  cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
   1687 
   1688  PackedPixelFile ppf_out;
   1689  EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 76666,
   1690              1000);
   1691  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.17);
   1692 }
   1693 
   1694 TEST(JxlTest, RoundtripUnsignedCustomBitdepthLossless) {
   1695  ThreadPool* pool = nullptr;
   1696  for (uint32_t num_channels = 1; num_channels < 6; ++num_channels) {
   1697    for (JxlEndianness endianness : {JXL_LITTLE_ENDIAN, JXL_BIG_ENDIAN}) {
   1698      for (uint32_t bitdepth = 3; bitdepth <= 16; ++bitdepth) {
   1699        if (bitdepth <= 8 && endianness == JXL_BIG_ENDIAN) continue;
   1700        printf("Testing %u channel unsigned %u bit %s endian lossless.\n",
   1701               num_channels, bitdepth,
   1702               endianness == JXL_LITTLE_ENDIAN ? "little" : "big");
   1703        TestImage t;
   1704        ASSERT_TRUE(t.SetDimensions(256, 256));
   1705        ASSERT_TRUE(t.SetChannels(num_channels));
   1706        t.SetAllBitDepths(bitdepth).SetEndianness(endianness);
   1707        JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
   1708        frame.RandomFill();
   1709        t.ppf().input_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM;
   1710 
   1711        JXLCompressParams cparams = test::CompressParamsForLossless();
   1712 
   1713        JXLDecompressParams dparams;
   1714        dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1715        dparams.output_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM;
   1716 
   1717        PackedPixelFile ppf_out;
   1718        Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out);
   1719 
   1720        ASSERT_TRUE(test::SamePixels(t.ppf(), ppf_out));
   1721      }
   1722    }
   1723  }
   1724 }
   1725 
   1726 TEST(JxlTest, LosslessPNMRoundtrip) {
   1727  static const char* kChannels[] = {"", "g", "ga", "rgb", "rgba"};
   1728  static const char* kExtension[] = {"", ".pgm", ".pam", ".ppm", ".pam"};
   1729  for (size_t bit_depth = 1; bit_depth <= 16; ++bit_depth) {
   1730    for (size_t channels = 1; channels <= 4; ++channels) {
   1731      if (bit_depth == 1 && (channels == 2 || channels == 4)) continue;
   1732      std::string extension(kExtension[channels]);
   1733      std::string filename = "jxl/flower/flower_small." +
   1734                             std::string(kChannels[channels]) + ".depth" +
   1735                             std::to_string(bit_depth) + extension;
   1736      const std::vector<uint8_t> orig = ReadTestData(filename);
   1737      test::TestImage t;
   1738      if (channels < 3) {
   1739        ASSERT_TRUE(t.SetColorEncoding("Gra_D65_Rel_SRG"));
   1740      }
   1741      ASSERT_TRUE(t.DecodeFromBytes(orig));
   1742 
   1743      JXLCompressParams cparams = test::CompressParamsForLossless();
   1744      cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1);  // kLightning
   1745 
   1746      JXLDecompressParams dparams;
   1747      dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1748      dparams.output_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM;
   1749 
   1750      PackedPixelFile ppf_out;
   1751      Roundtrip(t.ppf(), cparams, dparams, nullptr, &ppf_out);
   1752 
   1753      extras::EncodedImage encoded;
   1754      auto encoder = extras::Encoder::FromExtension(extension);
   1755      ASSERT_TRUE(encoder.get());
   1756      ASSERT_TRUE(encoder->Encode(ppf_out, &encoded, nullptr));
   1757      ASSERT_EQ(encoded.bitstreams.size(), 1);
   1758      ASSERT_EQ(orig.size(), encoded.bitstreams[0].size());
   1759      EXPECT_EQ(0,
   1760                memcmp(orig.data(), encoded.bitstreams[0].data(), orig.size()));
   1761    }
   1762  }
   1763 }
   1764 
   1765 class JxlTest : public ::testing::TestWithParam<const char*> {};
   1766 
   1767 TEST_P(JxlTest, LosslessSmallFewColors) {
   1768  ThreadPoolForTests pool(8);
   1769  const std::vector<uint8_t> orig = ReadTestData(GetParam());
   1770  TestImage t;
   1771  ASSERT_TRUE(t.DecodeFromBytes(orig));
   1772  t.ClearMetadata();
   1773 
   1774  JXLCompressParams cparams;
   1775  cparams.distance = 0;
   1776  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1);
   1777  JXLDecompressParams dparams;
   1778  dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
   1779 
   1780  PackedPixelFile ppf_out;
   1781  Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out);
   1782  EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
   1783 }
   1784 
   1785 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
   1786    ImageTests, JxlTest,
   1787    ::testing::Values("jxl/blending/cropped_traffic_light_frame-0.png",
   1788                      "palette/358colors.png"));
   1789 
   1790 struct StreamingTestParam {
   1791  size_t xsize;
   1792  size_t ysize;
   1793  bool is_grey;
   1794  bool has_alpha;
   1795  int effort;
   1796  bool progressive;
   1797 
   1798  size_t num_channels() const {
   1799    return (is_grey ? 1 : 3) + (has_alpha ? 1 : 0);
   1800  }
   1801 
   1802  float max_psnr() const { return is_grey ? 90 : 50; }
   1803 
   1804  static std::vector<StreamingTestParam> All() {
   1805    std::vector<StreamingTestParam> params;
   1806    for (int e : {1, 3, 4, 7}) {
   1807      for (bool g : {false, true}) {
   1808        params.push_back(StreamingTestParam{357, 517, g, false, e, false});
   1809        params.push_back(StreamingTestParam{2247, 2357, g, false, e, false});
   1810      }
   1811    }
   1812    params.push_back(StreamingTestParam{2247, 2357, false, false, 1, true});
   1813    params.push_back(StreamingTestParam{2247, 2157, false, false, 5, false});
   1814    params.push_back(StreamingTestParam{2247, 2157, false, true, 5, false});
   1815    return params;
   1816  }
   1817 };
   1818 
   1819 std::ostream& operator<<(std::ostream& out, StreamingTestParam p) {
   1820  out << (p.is_grey ? "Grey" : "RGB");
   1821  out << p.xsize << "x" << p.ysize;
   1822  out << "e" << p.effort;
   1823  if (p.progressive) {
   1824    out << "Progressive";
   1825  }
   1826  return out;
   1827 }
   1828 
   1829 class JxlStreamingTest : public ::testing::TestWithParam<StreamingTestParam> {};
   1830 
   1831 TEST_P(JxlStreamingTest, Roundtrip) {
   1832  const StreamingTestParam& p = GetParam();
   1833 
   1834  jxl::test::TestImage image;
   1835  ASSERT_TRUE(image.SetDimensions(p.xsize, p.ysize));
   1836  image.SetDataType(JXL_TYPE_UINT8);
   1837  ASSERT_TRUE(image.SetChannels(p.num_channels()));
   1838  image.SetAllBitDepths(8);
   1839  JXL_TEST_ASSIGN_OR_DIE(auto frame, image.AddFrame());
   1840  frame.RandomFill();
   1841  JXLCompressParams cparams;
   1842  cparams.distance = 0.1;
   1843  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, p.effort);
   1844  cparams.AddOption(JXL_ENC_FRAME_SETTING_BUFFERING, 3);
   1845  if (p.progressive) {
   1846    cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1);
   1847  }
   1848 
   1849  ThreadPoolForTests pool(8);
   1850  PackedPixelFile ppf_out;
   1851  Roundtrip(image.ppf(), cparams, {}, pool.get(), &ppf_out);
   1852  EXPECT_GT(jxl::test::ComputePSNR(image.ppf(), ppf_out), p.max_psnr());
   1853 }
   1854 
   1855 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
   1856    JxlStreamingTest, JxlStreamingTest,
   1857    testing::ValuesIn(StreamingTestParam::All()));
   1858 
   1859 struct StreamingEncodingTestParam {
   1860  std::string file;
   1861  int effort;
   1862  float distance;
   1863  int group_size;
   1864  float palette_percent;
   1865 
   1866  static std::vector<StreamingEncodingTestParam> All() {
   1867    std::vector<StreamingEncodingTestParam> params;
   1868    for (const auto* file :
   1869         {"jxl/flower/flower.png", "jxl/flower/flower_alpha.png"}) {
   1870      for (int effort : {1, 3, 5, 6}) {
   1871        if (effort != 1) {
   1872          params.push_back(
   1873              StreamingEncodingTestParam{file, effort, 1.0, 1, -1});
   1874          params.push_back(
   1875              StreamingEncodingTestParam{file, effort, 4.0, 1, -1});
   1876        }
   1877        for (auto group_size : {-1, 0}) {
   1878          for (float palette_percent : {-1, 50, 100}) {
   1879            params.push_back(StreamingEncodingTestParam{
   1880                file, effort, 0.0, group_size, palette_percent});
   1881          }
   1882        }
   1883      }
   1884    }
   1885    return params;
   1886  }
   1887 };
   1888 
   1889 std::ostream& operator<<(std::ostream& out,
   1890                         const StreamingEncodingTestParam& p) {
   1891  out << p.file << "-";
   1892  out << "e" << p.effort;
   1893  if (p.distance == 0) {
   1894    out << "Lossless";
   1895    out << "G" << p.group_size << "P" << p.palette_percent;
   1896  } else {
   1897    out << "D" << p.distance;
   1898  }
   1899  return out;
   1900 }
   1901 
   1902 class JxlStreamingEncodingTest
   1903    : public ::testing::TestWithParam<StreamingEncodingTestParam> {};
   1904 
   1905 // This is broken on mingw32, so we only enable it for x86_64 now.
   1906 JXL_X86_64_TEST_P(JxlStreamingEncodingTest, StreamingSamePixels) {
   1907  const auto param = GetParam();
   1908 
   1909  const std::vector<uint8_t> orig = ReadTestData(param.file);
   1910  jxl::test::TestImage image;
   1911  ASSERT_TRUE(image.DecodeFromBytes(orig));
   1912 
   1913  JXLCompressParams cparams;
   1914  cparams.distance = param.distance;
   1915  cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, param.effort);
   1916  cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, param.group_size);
   1917  cparams.AddFloatOption(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT,
   1918                         param.palette_percent);
   1919  cparams.AddOption(JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS, 0);
   1920 
   1921  ThreadPoolForTests pool(8);
   1922  PackedPixelFile ppf_out;
   1923  Roundtrip(image.ppf(), cparams, {}, pool.get(), &ppf_out);
   1924 
   1925  cparams.AddOption(JXL_ENC_FRAME_SETTING_BUFFERING, 3);
   1926  PackedPixelFile ppf_out_streaming;
   1927  Roundtrip(image.ppf(), cparams, {}, pool.get(), &ppf_out_streaming);
   1928 
   1929  EXPECT_TRUE(jxl::test::SamePixels(ppf_out, ppf_out_streaming));
   1930 }
   1931 
   1932 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
   1933    JxlStreamingTest, JxlStreamingEncodingTest,
   1934    testing::ValuesIn(StreamingEncodingTestParam::All()));
   1935 
   1936 }  // namespace
   1937 }  // namespace jxl