libjxl

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

jxl_test.cc (63690B)


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