tor-browser

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

jpegli_test.cc (14528B)


      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 
     13 #include <cstddef>
     14 #include <cstdint>
     15 #include <cstdio>
     16 #include <cstring>
     17 #include <memory>
     18 #include <ostream>
     19 #include <sstream>
     20 #include <string>
     21 #include <utility>
     22 #include <vector>
     23 
     24 #include "lib/extras/dec/color_hints.h"
     25 #include "lib/extras/dec/decode.h"
     26 #include "lib/extras/dec/jpg.h"
     27 #include "lib/extras/enc/encode.h"
     28 #include "lib/extras/enc/jpegli.h"
     29 #include "lib/extras/enc/jpg.h"
     30 #include "lib/extras/packed_image.h"
     31 #include "lib/jxl/base/span.h"
     32 #include "lib/jxl/base/status.h"
     33 #include "lib/jxl/color_encoding_internal.h"
     34 #include "lib/jxl/test_image.h"
     35 #include "lib/jxl/test_utils.h"
     36 #include "lib/jxl/testing.h"
     37 
     38 namespace jxl {
     39 namespace extras {
     40 namespace {
     41 
     42 using ::jxl::test::Butteraugli3Norm;
     43 using ::jxl::test::ButteraugliDistance;
     44 using ::jxl::test::TestImage;
     45 
     46 Status ReadTestImage(const std::string& pathname, PackedPixelFile* ppf) {
     47  const std::vector<uint8_t> encoded = jxl::test::ReadTestData(pathname);
     48  ColorHints color_hints;
     49  if (pathname.find(".ppm") != std::string::npos) {
     50    color_hints.Add("color_space", "RGB_D65_SRG_Rel_SRG");
     51  } else if (pathname.find(".pgm") != std::string::npos) {
     52    color_hints.Add("color_space", "Gra_D65_Rel_SRG");
     53  }
     54  return DecodeBytes(Bytes(encoded), color_hints, ppf);
     55 }
     56 
     57 std::vector<uint8_t> GetAppData(const std::vector<uint8_t>& compressed) {
     58  std::vector<uint8_t> result;
     59  size_t pos = 2;  // After SOI
     60  while (pos + 4 < compressed.size()) {
     61    if (compressed[pos] != 0xff || compressed[pos + 1] < 0xe0 ||
     62        compressed[pos + 1] > 0xf0) {
     63      break;
     64    }
     65    size_t len = (compressed[pos + 2] << 8) + compressed[pos + 3] + 2;
     66    if (pos + len > compressed.size()) {
     67      break;
     68    }
     69    result.insert(result.end(), &compressed[pos], &compressed[pos] + len);
     70    pos += len;
     71  }
     72  return result;
     73 }
     74 
     75 Status DecodeWithLibjpeg(const std::vector<uint8_t>& compressed,
     76                         PackedPixelFile* ppf,
     77                         const JPGDecompressParams* dparams = nullptr) {
     78  return DecodeImageJPG(Bytes(compressed), ColorHints(), ppf,
     79                        /*constraints=*/nullptr, dparams);
     80 }
     81 
     82 Status EncodeWithLibjpeg(const PackedPixelFile& ppf, int quality,
     83                         std::vector<uint8_t>* compressed) {
     84  std::unique_ptr<Encoder> encoder = GetJPEGEncoder();
     85  encoder->SetOption("q", std::to_string(quality));
     86  EncodedImage encoded;
     87  JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, nullptr));
     88  JXL_RETURN_IF_ERROR(!encoded.bitstreams.empty());
     89  *compressed = std::move(encoded.bitstreams[0]);
     90  return true;
     91 }
     92 
     93 std::string Description(const JxlColorEncoding& color_encoding) {
     94  ColorEncoding c_enc;
     95  EXPECT_TRUE(c_enc.FromExternal(color_encoding));
     96  return Description(c_enc);
     97 }
     98 
     99 float BitsPerPixel(const PackedPixelFile& ppf,
    100                   const std::vector<uint8_t>& compressed) {
    101  const size_t num_pixels = ppf.info.xsize * ppf.info.ysize;
    102  return compressed.size() * 8.0 / num_pixels;
    103 }
    104 
    105 TEST(JpegliTest, JpegliSRGBDecodeTest) {
    106  TEST_LIBJPEG_SUPPORT();
    107  std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    108  PackedPixelFile ppf0;
    109  ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
    110  EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf0.color_encoding));
    111  EXPECT_EQ(8, ppf0.info.bits_per_sample);
    112 
    113  std::vector<uint8_t> compressed;
    114  ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
    115 
    116  PackedPixelFile ppf1;
    117  ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
    118  PackedPixelFile ppf2;
    119  JpegDecompressParams dparams;
    120  ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
    121  EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
    122 }
    123 
    124 TEST(JpegliTest, JpegliGrayscaleDecodeTest) {
    125  TEST_LIBJPEG_SUPPORT();
    126  std::string testimage = "jxl/flower/flower_small.g.depth8.pgm";
    127  PackedPixelFile ppf0;
    128  ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
    129  EXPECT_EQ("Gra_D65_Rel_SRG", Description(ppf0.color_encoding));
    130  EXPECT_EQ(8, ppf0.info.bits_per_sample);
    131 
    132  std::vector<uint8_t> compressed;
    133  ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
    134 
    135  PackedPixelFile ppf1;
    136  ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
    137  PackedPixelFile ppf2;
    138  JpegDecompressParams dparams;
    139  ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
    140  EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
    141 }
    142 
    143 TEST(JpegliTest, JpegliXYBEncodeTest) {
    144  TEST_LIBJPEG_SUPPORT();
    145  std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
    146  PackedPixelFile ppf_in;
    147  ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
    148  EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
    149  EXPECT_EQ(8, ppf_in.info.bits_per_sample);
    150 
    151  std::vector<uint8_t> compressed;
    152  JpegSettings settings;
    153  settings.xyb = true;
    154  ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
    155 
    156  PackedPixelFile ppf_out;
    157  ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
    158  EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.45f);
    159  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.32f);
    160 }
    161 
    162 TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) {
    163  TEST_LIBJPEG_SUPPORT();
    164  TestImage t;
    165  const size_t xsize = 2070;
    166  const size_t ysize = 1063;
    167  ASSERT_TRUE(t.SetDimensions(xsize, ysize));
    168  ASSERT_TRUE(t.SetChannels(3));
    169  t.SetAllBitDepths(8).SetEndianness(JXL_NATIVE_ENDIAN);
    170  JXL_TEST_ASSIGN_OR_DIE(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        ASSERT_TRUE(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_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.7f);
    209  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 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_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.85f);
    251  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 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("Rec2100HLG", 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_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 2.95f);
    271  EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 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