libjxl

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

jpegli_test.cc (14523B)


      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 #if JPEGXL_ENABLE_JPEGLI
      7 
      8 #include "lib/extras/dec/jpegli.h"
      9 
     10 #include <jxl/color_encoding.h>
     11 #include <jxl/types.h>
     12 #include <stdint.h>
     13 
     14 #include <cstddef>
     15 #include <cstdint>
     16 #include <cstdio>
     17 #include <cstring>
     18 #include <memory>
     19 #include <ostream>
     20 #include <sstream>
     21 #include <string>
     22 #include <utility>
     23 #include <vector>
     24 
     25 #include "lib/extras/dec/color_hints.h"
     26 #include "lib/extras/dec/decode.h"
     27 #include "lib/extras/dec/jpg.h"
     28 #include "lib/extras/enc/encode.h"
     29 #include "lib/extras/enc/jpegli.h"
     30 #include "lib/extras/enc/jpg.h"
     31 #include "lib/extras/packed_image.h"
     32 #include "lib/jxl/base/span.h"
     33 #include "lib/jxl/base/status.h"
     34 #include "lib/jxl/color_encoding_internal.h"
     35 #include "lib/jxl/test_image.h"
     36 #include "lib/jxl/test_utils.h"
     37 #include "lib/jxl/testing.h"
     38 
     39 namespace jxl {
     40 namespace extras {
     41 namespace {
     42 
     43 using test::Butteraugli3Norm;
     44 using test::ButteraugliDistance;
     45 using test::TestImage;
     46 
     47 Status ReadTestImage(const std::string& pathname, PackedPixelFile* ppf) {
     48   const std::vector<uint8_t> encoded = jxl::test::ReadTestData(pathname);
     49   ColorHints color_hints;
     50   if (pathname.find(".ppm") != std::string::npos) {
     51     color_hints.Add("color_space", "RGB_D65_SRG_Rel_SRG");
     52   } else if (pathname.find(".pgm") != std::string::npos) {
     53     color_hints.Add("color_space", "Gra_D65_Rel_SRG");
     54   }
     55   return DecodeBytes(Bytes(encoded), color_hints, ppf);
     56 }
     57 
     58 std::vector<uint8_t> GetAppData(const std::vector<uint8_t>& compressed) {
     59   std::vector<uint8_t> result;
     60   size_t pos = 2;  // After SOI
     61   while (pos + 4 < compressed.size()) {
     62     if (compressed[pos] != 0xff || compressed[pos + 1] < 0xe0 ||
     63         compressed[pos + 1] > 0xf0) {
     64       break;
     65     }
     66     size_t len = (compressed[pos + 2] << 8) + compressed[pos + 3] + 2;
     67     if (pos + len > compressed.size()) {
     68       break;
     69     }
     70     result.insert(result.end(), &compressed[pos], &compressed[pos] + len);
     71     pos += len;
     72   }
     73   return result;
     74 }
     75 
     76 Status DecodeWithLibjpeg(const std::vector<uint8_t>& compressed,
     77                          PackedPixelFile* ppf,
     78                          const JPGDecompressParams* dparams = nullptr) {
     79   return DecodeImageJPG(Bytes(compressed), ColorHints(), ppf,
     80                         /*constraints=*/nullptr, dparams);
     81 }
     82 
     83 Status EncodeWithLibjpeg(const PackedPixelFile& ppf, int quality,
     84                          std::vector<uint8_t>* compressed) {
     85   std::unique_ptr<Encoder> encoder = GetJPEGEncoder();
     86   encoder->SetOption("q", std::to_string(quality));
     87   EncodedImage encoded;
     88   JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, nullptr));
     89   JXL_RETURN_IF_ERROR(!encoded.bitstreams.empty());
     90   *compressed = std::move(encoded.bitstreams[0]);
     91   return true;
     92 }
     93 
     94 std::string Description(const JxlColorEncoding& color_encoding) {
     95   ColorEncoding c_enc;
     96   JXL_CHECK(c_enc.FromExternal(color_encoding));
     97   return Description(c_enc);
     98 }
     99 
    100 float BitsPerPixel(const PackedPixelFile& ppf,
    101                    const std::vector<uint8_t>& compressed) {
    102   const size_t num_pixels = ppf.info.xsize * ppf.info.ysize;
    103   return compressed.size() * 8.0 / num_pixels;
    104 }
    105 
    106 TEST(JpegliTest, JpegliSRGBDecodeTest) {
    107   TEST_LIBJPEG_SUPPORT();
    108   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    109   PackedPixelFile ppf0;
    110   ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
    111   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf0.color_encoding));
    112   EXPECT_EQ(8, ppf0.info.bits_per_sample);
    113 
    114   std::vector<uint8_t> compressed;
    115   ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
    116 
    117   PackedPixelFile ppf1;
    118   ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
    119   PackedPixelFile ppf2;
    120   JpegDecompressParams dparams;
    121   ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
    122   EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
    123 }
    124 
    125 TEST(JpegliTest, JpegliGrayscaleDecodeTest) {
    126   TEST_LIBJPEG_SUPPORT();
    127   std::string testimage = "jxl/flower/flower_small.g.depth8.pgm";
    128   PackedPixelFile ppf0;
    129   ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
    130   EXPECT_EQ("Gra_D65_Rel_SRG", Description(ppf0.color_encoding));
    131   EXPECT_EQ(8, ppf0.info.bits_per_sample);
    132 
    133   std::vector<uint8_t> compressed;
    134   ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
    135 
    136   PackedPixelFile ppf1;
    137   ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
    138   PackedPixelFile ppf2;
    139   JpegDecompressParams dparams;
    140   ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
    141   EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
    142 }
    143 
    144 TEST(JpegliTest, JpegliXYBEncodeTest) {
    145   TEST_LIBJPEG_SUPPORT();
    146   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    147   PackedPixelFile ppf_in;
    148   ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    149   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
    150   EXPECT_EQ(8, ppf_in.info.bits_per_sample);
    151 
    152   std::vector<uint8_t> compressed;
    153   JpegSettings settings;
    154   settings.xyb = true;
    155   ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    156 
    157   PackedPixelFile ppf_out;
    158   ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
    159   EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.45f));
    160   EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.32f));
    161 }
    162 
    163 TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) {
    164   TEST_LIBJPEG_SUPPORT();
    165   TestImage t;
    166   const size_t xsize = 2070;
    167   const size_t ysize = 1063;
    168   t.SetDimensions(xsize, ysize).SetChannels(3);
    169   t.SetAllBitDepths(8).SetEndianness(JXL_NATIVE_ENDIAN);
    170   TestImage::Frame frame = t.AddFrame();
    171   frame.RandomFill();
    172   // Create a large smooth area in the top half of the image. This is to test
    173   // that the bias statistics calculation can handle many blocks with all-zero
    174   // AC coefficients.
    175   for (size_t y = 0; y < ysize / 2; ++y) {
    176     for (size_t x = 0; x < xsize; ++x) {
    177       for (size_t c = 0; c < 3; ++c) {
    178         frame.SetValue(y, x, c, 0.5f);
    179       }
    180     }
    181   }
    182   const PackedPixelFile& ppf0 = t.ppf();
    183 
    184   std::vector<uint8_t> compressed;
    185   ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
    186 
    187   PackedPixelFile ppf1;
    188   JpegDecompressParams dparams;
    189   ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf1));
    190   EXPECT_LT(ButteraugliDistance(ppf0, ppf1), 3.0f);
    191 }
    192 
    193 TEST(JpegliTest, JpegliYUVEncodeTest) {
    194   TEST_LIBJPEG_SUPPORT();
    195   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    196   PackedPixelFile ppf_in;
    197   ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    198   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
    199   EXPECT_EQ(8, ppf_in.info.bits_per_sample);
    200 
    201   std::vector<uint8_t> compressed;
    202   JpegSettings settings;
    203   settings.xyb = false;
    204   ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    205 
    206   PackedPixelFile ppf_out;
    207   ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
    208   EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.7f));
    209   EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.32f));
    210 }
    211 
    212 TEST(JpegliTest, JpegliYUVChromaSubsamplingEncodeTest) {
    213   TEST_LIBJPEG_SUPPORT();
    214   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    215   PackedPixelFile ppf_in;
    216   ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    217   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
    218   EXPECT_EQ(8, ppf_in.info.bits_per_sample);
    219 
    220   std::vector<uint8_t> compressed;
    221   JpegSettings settings;
    222   for (const char* sampling : {"440", "422", "420"}) {
    223     settings.xyb = false;
    224     settings.chroma_subsampling = std::string(sampling);
    225     ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    226 
    227     PackedPixelFile ppf_out;
    228     ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
    229     EXPECT_LE(BitsPerPixel(ppf_in, compressed), 1.55f);
    230     EXPECT_LE(ButteraugliDistance(ppf_in, ppf_out), 1.82f);
    231   }
    232 }
    233 
    234 TEST(JpegliTest, JpegliYUVEncodeTestNoAq) {
    235   TEST_LIBJPEG_SUPPORT();
    236   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    237   PackedPixelFile ppf_in;
    238   ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    239   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
    240   EXPECT_EQ(8, ppf_in.info.bits_per_sample);
    241 
    242   std::vector<uint8_t> compressed;
    243   JpegSettings settings;
    244   settings.xyb = false;
    245   settings.use_adaptive_quantization = false;
    246   ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    247 
    248   PackedPixelFile ppf_out;
    249   ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
    250   EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.85f));
    251   EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.25f));
    252 }
    253 
    254 TEST(JpegliTest, JpegliHDRRoundtripTest) {
    255   std::string testimage = "jxl/hdr_room.png";
    256   PackedPixelFile ppf_in;
    257   ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    258   EXPECT_EQ("RGB_D65_202_Rel_HLG", Description(ppf_in.color_encoding));
    259   EXPECT_EQ(16, ppf_in.info.bits_per_sample);
    260 
    261   std::vector<uint8_t> compressed;
    262   JpegSettings settings;
    263   settings.xyb = false;
    264   ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    265 
    266   PackedPixelFile ppf_out;
    267   JpegDecompressParams dparams;
    268   dparams.output_data_type = JXL_TYPE_UINT16;
    269   ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf_out));
    270   EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(2.95f));
    271   EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.05f));
    272 }
    273 
    274 TEST(JpegliTest, JpegliSetAppData) {
    275   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    276   PackedPixelFile ppf_in;
    277   ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    278   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
    279   EXPECT_EQ(8, ppf_in.info.bits_per_sample);
    280 
    281   std::vector<uint8_t> compressed;
    282   JpegSettings settings;
    283   settings.app_data = {0xff, 0xe3, 0, 4, 0, 1};
    284   EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    285   EXPECT_EQ(settings.app_data, GetAppData(compressed));
    286 
    287   settings.app_data = {0xff, 0xe3, 0, 6, 0, 1, 2, 3, 0xff, 0xef, 0, 4, 0, 1};
    288   EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    289   EXPECT_EQ(settings.app_data, GetAppData(compressed));
    290 
    291   settings.xyb = true;
    292   EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    293   EXPECT_EQ(0, memcmp(settings.app_data.data(), GetAppData(compressed).data(),
    294                       settings.app_data.size()));
    295 
    296   settings.xyb = false;
    297   settings.app_data = {0};
    298   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    299 
    300   settings.app_data = {0xff, 0xe0};
    301   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    302 
    303   settings.app_data = {0xff, 0xe0, 0, 2};
    304   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    305 
    306   settings.app_data = {0xff, 0xeb, 0, 4, 0};
    307   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    308 
    309   settings.app_data = {0xff, 0xeb, 0, 4, 0, 1, 2, 3};
    310   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    311 
    312   settings.app_data = {0xff, 0xab, 0, 4, 0, 1};
    313   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    314 
    315   settings.xyb = false;
    316   settings.app_data = {
    317       0xff, 0xeb, 0,    4,    0,    1,                       //
    318       0xff, 0xe2, 0,    20,   0x49, 0x43, 0x43, 0x5F, 0x50,  //
    319       0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, 0,    1,     //
    320       0,    0,    0,    0,                                   //
    321   };
    322   EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    323   EXPECT_EQ(settings.app_data, GetAppData(compressed));
    324 
    325   settings.xyb = true;
    326   EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    327 }
    328 
    329 struct TestConfig {
    330   int num_colors;
    331   int passes;
    332   int dither;
    333 };
    334 
    335 class JpegliColorQuantTestParam : public ::testing::TestWithParam<TestConfig> {
    336 };
    337 
    338 TEST_P(JpegliColorQuantTestParam, JpegliColorQuantizeTest) {
    339   TEST_LIBJPEG_SUPPORT();
    340   TestConfig config = GetParam();
    341   std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    342   PackedPixelFile ppf0;
    343   ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
    344   EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf0.color_encoding));
    345   EXPECT_EQ(8, ppf0.info.bits_per_sample);
    346 
    347   std::vector<uint8_t> compressed;
    348   ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
    349 
    350   PackedPixelFile ppf1;
    351   JPGDecompressParams dparams1;
    352   dparams1.two_pass_quant = (config.passes == 2);
    353   dparams1.num_colors = config.num_colors;
    354   dparams1.dither_mode = config.dither;
    355   ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1, &dparams1));
    356 
    357   PackedPixelFile ppf2;
    358   JpegDecompressParams dparams2;
    359   dparams2.two_pass_quant = (config.passes == 2);
    360   dparams2.num_colors = config.num_colors;
    361   dparams2.dither_mode = config.dither;
    362   ASSERT_TRUE(DecodeJpeg(compressed, dparams2, nullptr, &ppf2));
    363 
    364   double dist1 = Butteraugli3Norm(ppf0, ppf1);
    365   double dist2 = Butteraugli3Norm(ppf0, ppf2);
    366   printf("distance: %f  vs %f\n", dist2, dist1);
    367   if (config.passes == 1) {
    368     if (config.num_colors == 16 && config.dither == 2) {
    369       // TODO(szabadka) Fix this case.
    370       EXPECT_LT(dist2, dist1 * 1.5);
    371     } else {
    372       EXPECT_LT(dist2, dist1 * 1.05);
    373     }
    374   } else if (config.num_colors > 64) {
    375     // TODO(szabadka) Fix 2pass quantization for <= 64 colors.
    376     EXPECT_LT(dist2, dist1 * 1.1);
    377   } else if (config.num_colors > 32) {
    378     EXPECT_LT(dist2, dist1 * 1.2);
    379   } else {
    380     EXPECT_LT(dist2, dist1 * 1.7);
    381   }
    382 }
    383 
    384 std::vector<TestConfig> GenerateTests() {
    385   std::vector<TestConfig> all_tests;
    386   for (int num_colors = 8; num_colors <= 256; num_colors *= 2) {
    387     for (int passes = 1; passes <= 2; ++passes) {
    388       for (int dither = 0; dither < 3; dither += passes) {
    389         TestConfig config;
    390         config.num_colors = num_colors;
    391         config.passes = passes;
    392         config.dither = dither;
    393         all_tests.push_back(config);
    394       }
    395     }
    396   }
    397   return all_tests;
    398 }
    399 
    400 std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
    401   static constexpr const char* kDitherModeStr[] = {"No", "Ordered", "FS"};
    402   os << c.passes << "pass";
    403   os << c.num_colors << "colors";
    404   os << kDitherModeStr[c.dither] << "dither";
    405   return os;
    406 }
    407 
    408 std::string TestDescription(const testing::TestParamInfo<TestConfig>& info) {
    409   std::stringstream name;
    410   name << info.param;
    411   return name.str();
    412 }
    413 
    414 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(JpegliColorQuantTest,
    415                                    JpegliColorQuantTestParam,
    416                                    testing::ValuesIn(GenerateTests()),
    417                                    TestDescription);
    418 
    419 }  // namespace
    420 }  // namespace extras
    421 }  // namespace jxl
    422 #endif  // JPEGXL_ENABLE_JPEGLI