libjxl

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

codec_test.cc (16375B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 
      6 #include <jxl/codestream_header.h>
      7 #include <jxl/color_encoding.h>
      8 #include <jxl/encode.h>
      9 #include <jxl/types.h>
     10 #include <stddef.h>
     11 
     12 #include <algorithm>
     13 #include <cstdint>
     14 #include <cstdio>
     15 #include <cstring>
     16 #include <memory>
     17 #include <sstream>
     18 #include <string>
     19 #include <utility>
     20 #include <vector>
     21 
     22 #include "lib/extras/common.h"
     23 #include "lib/extras/dec/color_hints.h"
     24 #include "lib/extras/dec/decode.h"
     25 #include "lib/extras/dec/pnm.h"
     26 #include "lib/extras/enc/encode.h"
     27 #include "lib/extras/packed_image.h"
     28 #include "lib/jxl/base/byte_order.h"
     29 #include "lib/jxl/base/random.h"
     30 #include "lib/jxl/base/span.h"
     31 #include "lib/jxl/base/status.h"
     32 #include "lib/jxl/color_encoding_internal.h"
     33 #include "lib/jxl/test_utils.h"
     34 #include "lib/jxl/testing.h"
     35 
     36 namespace jxl {
     37 
     38 using test::ThreadPoolForTests;
     39 
     40 namespace extras {
     41 namespace {
     42 
     43 using ::testing::AllOf;
     44 using ::testing::Contains;
     45 using ::testing::Field;
     46 using ::testing::IsEmpty;
     47 using ::testing::SizeIs;
     48 
     49 std::string ExtensionFromCodec(Codec codec, const bool is_gray,
     50                                const bool has_alpha,
     51                                const size_t bits_per_sample) {
     52   switch (codec) {
     53     case Codec::kJPG:
     54       return ".jpg";
     55     case Codec::kPGX:
     56       return ".pgx";
     57     case Codec::kPNG:
     58       return ".png";
     59     case Codec::kPNM:
     60       if (bits_per_sample == 32) return ".pfm";
     61       if (has_alpha) return ".pam";
     62       return is_gray ? ".pgm" : ".ppm";
     63     case Codec::kEXR:
     64       return ".exr";
     65     default:
     66       return std::string();
     67   }
     68 }
     69 
     70 void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
     71                      const PackedImage& im1, size_t bits_per_sample1,
     72                      bool lossless = true) {
     73   ASSERT_EQ(im0.xsize, im1.xsize);
     74   ASSERT_EQ(im0.ysize, im1.ysize);
     75   ASSERT_EQ(im0.format.num_channels, im1.format.num_channels);
     76   auto get_factor = [](JxlPixelFormat f, size_t bits) -> double {
     77     return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1);
     78   };
     79   double factor0 = get_factor(im0.format, bits_per_sample0);
     80   double factor1 = get_factor(im1.format, bits_per_sample1);
     81   const auto* pixels0 = static_cast<const uint8_t*>(im0.pixels());
     82   const auto* pixels1 = static_cast<const uint8_t*>(im1.pixels());
     83   auto rgba0 =
     84       test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0);
     85   auto rgba1 =
     86       test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1);
     87   double tolerance =
     88       lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f;
     89   if (bits_per_sample0 == 32 || bits_per_sample1 == 32) {
     90     tolerance = 0.5 * std::max(factor0, factor1);
     91   }
     92   for (size_t y = 0; y < im0.ysize; ++y) {
     93     for (size_t x = 0; x < im0.xsize; ++x) {
     94       for (size_t c = 0; c < im0.format.num_channels; ++c) {
     95         size_t ix = (y * im0.xsize + x) * 4 + c;
     96         double val0 = rgba0[ix];
     97         double val1 = rgba1[ix];
     98         ASSERT_NEAR(val1, val0, tolerance)
     99             << "y = " << y << " x = " << x << " c = " << c;
    100       }
    101     }
    102   }
    103 }
    104 
    105 JxlColorEncoding CreateTestColorEncoding(bool is_gray) {
    106   JxlColorEncoding c;
    107   c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
    108   c.white_point = JXL_WHITE_POINT_D65;
    109   c.primaries = JXL_PRIMARIES_P3;
    110   c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
    111   c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
    112   // Roundtrip through internal color encoding to fill in primaries and white
    113   // point CIE xy coordinates.
    114   ColorEncoding c_internal;
    115   JXL_CHECK(c_internal.FromExternal(c));
    116   c = c_internal.ToExternal();
    117   return c;
    118 }
    119 
    120 std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
    121   ColorEncoding c;
    122   JXL_CHECK(c.FromExternal(color_encoding));
    123   JXL_CHECK(!c.ICC().empty());
    124   return c.ICC();
    125 }
    126 
    127 void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format,
    128                       size_t bits_per_sample) {
    129   uint64_t max_val = (1ull << bits_per_sample) - 1;
    130   if (format.data_type == JXL_TYPE_UINT8) {
    131     *out = rng->UniformU(0, max_val);
    132   } else if (format.data_type == JXL_TYPE_UINT16) {
    133     uint32_t val = rng->UniformU(0, max_val);
    134     if (format.endianness == JXL_BIG_ENDIAN) {
    135       StoreBE16(val, out);
    136     } else {
    137       StoreLE16(val, out);
    138     }
    139   } else {
    140     ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT);
    141     float val = rng->UniformF(0.0, 1.0);
    142     uint32_t uval;
    143     memcpy(&uval, &val, 4);
    144     if (format.endianness == JXL_BIG_ENDIAN) {
    145       StoreBE32(uval, out);
    146     } else {
    147       StoreLE32(uval, out);
    148     }
    149   }
    150 }
    151 
    152 void FillPackedImage(size_t bits_per_sample, PackedImage* image) {
    153   JxlPixelFormat format = image->format;
    154   size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8;
    155   uint8_t* out = static_cast<uint8_t*>(image->pixels());
    156   size_t stride = image->xsize * format.num_channels * bytes_per_channel;
    157   ASSERT_EQ(image->pixels_size, image->ysize * stride);
    158   Rng rng(129);
    159   for (size_t y = 0; y < image->ysize; ++y) {
    160     for (size_t x = 0; x < image->xsize; ++x) {
    161       for (size_t c = 0; c < format.num_channels; ++c) {
    162         StoreRandomValue(out, &rng, format, bits_per_sample);
    163         out += bytes_per_channel;
    164       }
    165     }
    166   }
    167 }
    168 
    169 struct TestImageParams {
    170   Codec codec;
    171   size_t xsize;
    172   size_t ysize;
    173   size_t bits_per_sample;
    174   bool is_gray;
    175   bool add_alpha;
    176   bool big_endian;
    177   bool add_extra_channels;
    178 
    179   bool ShouldTestRoundtrip() const {
    180     if (codec == Codec::kPNG) {
    181       return bits_per_sample <= 16;
    182     } else if (codec == Codec::kPNM) {
    183       // TODO(szabadka) Make PNM encoder endianness-aware.
    184       return ((bits_per_sample <= 16 && big_endian) ||
    185               (bits_per_sample == 32 && !add_alpha && !big_endian));
    186     } else if (codec == Codec::kPGX) {
    187       return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
    188               !add_alpha);
    189     } else if (codec == Codec::kEXR) {
    190 #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
    191     defined(THREAD_SANITIZER)
    192       // OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool
    193       return false;
    194 #else
    195       return bits_per_sample == 32 && !is_gray;
    196 #endif
    197     } else if (codec == Codec::kJPG) {
    198       return bits_per_sample == 8 && !add_alpha;
    199     } else {
    200       return false;
    201     }
    202   }
    203 
    204   JxlPixelFormat PixelFormat() const {
    205     JxlPixelFormat format;
    206     format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0);
    207     format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT
    208                         : bits_per_sample > 8 ? JXL_TYPE_UINT16
    209                                               : JXL_TYPE_UINT8);
    210     format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN;
    211     format.align = 0;
    212     return format;
    213   }
    214 
    215   std::string DebugString() const {
    216     std::ostringstream os;
    217     os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha
    218        << " be: " << big_endian << " ec: " << add_extra_channels;
    219     return os.str();
    220   }
    221 };
    222 
    223 void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) {
    224   ppf->info.xsize = params.xsize;
    225   ppf->info.ysize = params.ysize;
    226   ppf->info.bits_per_sample = params.bits_per_sample;
    227   ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0;
    228   ppf->info.num_color_channels = params.is_gray ? 1 : 3;
    229   ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0;
    230   ppf->info.alpha_premultiplied = TO_JXL_BOOL(params.codec == Codec::kEXR);
    231 
    232   JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
    233   ppf->icc = GenerateICC(color_encoding);
    234   ppf->color_encoding = color_encoding;
    235 
    236   PackedFrame frame(params.xsize, params.ysize, params.PixelFormat());
    237   FillPackedImage(params.bits_per_sample, &frame.color);
    238   if (params.add_extra_channels) {
    239     for (size_t i = 0; i < 7; ++i) {
    240       JxlPixelFormat ec_format = params.PixelFormat();
    241       ec_format.num_channels = 1;
    242       PackedImage ec(params.xsize, params.ysize, ec_format);
    243       FillPackedImage(params.bits_per_sample, &ec);
    244       frame.extra_channels.emplace_back(std::move(ec));
    245       PackedExtraChannel pec;
    246       pec.ec_info.bits_per_sample = params.bits_per_sample;
    247       pec.ec_info.type = static_cast<JxlExtraChannelType>(i);
    248       ppf->extra_channels_info.emplace_back(std::move(pec));
    249     }
    250   }
    251   ppf->frames.emplace_back(std::move(frame));
    252 }
    253 
    254 // Ensures reading a newly written file leads to the same image pixels.
    255 void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
    256   if (!params.ShouldTestRoundtrip()) return;
    257 
    258   std::string extension = ExtensionFromCodec(
    259       params.codec, params.is_gray, params.add_alpha, params.bits_per_sample);
    260   printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());
    261 
    262   PackedPixelFile ppf_in;
    263   CreateTestImage(params, &ppf_in);
    264 
    265   EncodedImage encoded;
    266   auto encoder = Encoder::FromExtension(extension);
    267   if (!encoder) {
    268     fprintf(stderr, "Skipping test because of missing codec support.\n");
    269     return;
    270   }
    271   ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
    272   ASSERT_EQ(encoded.bitstreams.size(), 1);
    273 
    274   PackedPixelFile ppf_out;
    275   ColorHints color_hints;
    276   if (params.codec == Codec::kPNM || params.codec == Codec::kPGX) {
    277     color_hints.Add("color_space",
    278                     params.is_gray ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
    279   }
    280   ASSERT_TRUE(DecodeBytes(Bytes(encoded.bitstreams[0]), color_hints, &ppf_out));
    281   if (params.codec == Codec::kPNG && ppf_out.icc.empty()) {
    282     // Decoding a PNG may drop the ICC profile if there's a valid cICP chunk.
    283     // Rendering intent is not preserved in this case.
    284     EXPECT_EQ(ppf_in.color_encoding.color_space,
    285               ppf_out.color_encoding.color_space);
    286     EXPECT_EQ(ppf_in.color_encoding.white_point,
    287               ppf_out.color_encoding.white_point);
    288     if (ppf_in.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) {
    289       EXPECT_EQ(ppf_in.color_encoding.primaries,
    290                 ppf_out.color_encoding.primaries);
    291     }
    292     EXPECT_EQ(ppf_in.color_encoding.transfer_function,
    293               ppf_out.color_encoding.transfer_function);
    294     EXPECT_EQ(ppf_out.color_encoding.rendering_intent,
    295               JXL_RENDERING_INTENT_RELATIVE);
    296   } else if (params.codec != Codec::kPNM && params.codec != Codec::kPGX &&
    297              params.codec != Codec::kEXR) {
    298     EXPECT_EQ(ppf_in.icc, ppf_out.icc);
    299   }
    300 
    301   ASSERT_EQ(ppf_out.frames.size(), 1);
    302   const auto& frame_in = ppf_in.frames[0];
    303   const auto& frame_out = ppf_out.frames[0];
    304   VerifySameImage(frame_in.color, ppf_in.info.bits_per_sample, frame_out.color,
    305                   ppf_out.info.bits_per_sample,
    306                   /*lossless=*/params.codec != Codec::kJPG);
    307   ASSERT_EQ(frame_in.extra_channels.size(), frame_out.extra_channels.size());
    308   ASSERT_EQ(ppf_out.extra_channels_info.size(),
    309             frame_out.extra_channels.size());
    310   for (size_t i = 0; i < frame_in.extra_channels.size(); ++i) {
    311     VerifySameImage(frame_in.extra_channels[i], ppf_in.info.bits_per_sample,
    312                     frame_out.extra_channels[i], ppf_out.info.bits_per_sample,
    313                     /*lossless=*/true);
    314     EXPECT_EQ(ppf_out.extra_channels_info[i].ec_info.type,
    315               ppf_in.extra_channels_info[i].ec_info.type);
    316   }
    317 }
    318 
    319 TEST(CodecTest, TestRoundTrip) {
    320   ThreadPoolForTests pool(12);
    321 
    322   TestImageParams params;
    323   params.xsize = 7;
    324   params.ysize = 4;
    325 
    326   for (Codec codec :
    327        {Codec::kPNG, Codec::kPNM, Codec::kPGX, Codec::kEXR, Codec::kJPG}) {
    328     for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
    329       for (bool is_gray : {false, true}) {
    330         for (bool add_alpha : {false, true}) {
    331           for (bool big_endian : {false, true}) {
    332             params.codec = codec;
    333             params.bits_per_sample = static_cast<size_t>(bits_per_sample);
    334             params.is_gray = is_gray;
    335             params.add_alpha = add_alpha;
    336             params.big_endian = big_endian;
    337             params.add_extra_channels = false;
    338             TestRoundTrip(params, &pool);
    339             if (codec == Codec::kPNM && add_alpha) {
    340               params.add_extra_channels = true;
    341               TestRoundTrip(params, &pool);
    342             }
    343           }
    344         }
    345       }
    346     }
    347   }
    348 }
    349 
    350 TEST(CodecTest, LosslessPNMRoundtrip) {
    351   ThreadPoolForTests pool(12);
    352 
    353   static const char* kChannels[] = {"", "g", "ga", "rgb", "rgba"};
    354   static const char* kExtension[] = {"", ".pgm", ".pam", ".ppm", ".pam"};
    355   for (size_t bit_depth = 1; bit_depth <= 16; ++bit_depth) {
    356     for (size_t channels = 1; channels <= 4; ++channels) {
    357       if (bit_depth == 1 && (channels == 2 || channels == 4)) continue;
    358       std::string extension(kExtension[channels]);
    359       std::string filename = "jxl/flower/flower_small." +
    360                              std::string(kChannels[channels]) + ".depth" +
    361                              std::to_string(bit_depth) + extension;
    362       const std::vector<uint8_t> orig = jxl::test::ReadTestData(filename);
    363 
    364       PackedPixelFile ppf;
    365       ColorHints color_hints;
    366       color_hints.Add("color_space",
    367                       channels < 3 ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
    368       ASSERT_TRUE(
    369           DecodeBytes(Bytes(orig.data(), orig.size()), color_hints, &ppf));
    370 
    371       EncodedImage encoded;
    372       auto encoder = Encoder::FromExtension(extension);
    373       ASSERT_TRUE(encoder.get());
    374       ASSERT_TRUE(encoder->Encode(ppf, &encoded, &pool));
    375       ASSERT_EQ(encoded.bitstreams.size(), 1);
    376       ASSERT_EQ(orig.size(), encoded.bitstreams[0].size());
    377       EXPECT_EQ(0,
    378                 memcmp(orig.data(), encoded.bitstreams[0].data(), orig.size()));
    379     }
    380   }
    381 }
    382 
    383 TEST(CodecTest, TestPNM) { TestCodecPNM(); }
    384 
    385 TEST(CodecTest, FormatNegotiation) {
    386   const std::vector<JxlPixelFormat> accepted_formats = {
    387       {/*num_channels=*/4,
    388        /*data_type=*/JXL_TYPE_UINT16,
    389        /*endianness=*/JXL_NATIVE_ENDIAN,
    390        /*align=*/0},
    391       {/*num_channels=*/3,
    392        /*data_type=*/JXL_TYPE_UINT8,
    393        /*endianness=*/JXL_NATIVE_ENDIAN,
    394        /*align=*/0},
    395       {/*num_channels=*/3,
    396        /*data_type=*/JXL_TYPE_UINT16,
    397        /*endianness=*/JXL_NATIVE_ENDIAN,
    398        /*align=*/0},
    399       {/*num_channels=*/1,
    400        /*data_type=*/JXL_TYPE_UINT8,
    401        /*endianness=*/JXL_NATIVE_ENDIAN,
    402        /*align=*/0},
    403   };
    404 
    405   JxlBasicInfo info;
    406   JxlEncoderInitBasicInfo(&info);
    407   info.bits_per_sample = 12;
    408   info.num_color_channels = 2;
    409 
    410   JxlPixelFormat format;
    411   EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));
    412 
    413   info.num_color_channels = 3;
    414   ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
    415   EXPECT_EQ(format.num_channels, info.num_color_channels);
    416   // 16 is the smallest accepted format that can accommodate the 12-bit data.
    417   EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
    418 }
    419 
    420 TEST(CodecTest, EncodeToPNG) {
    421   ThreadPool* const pool = nullptr;
    422 
    423   std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
    424   if (!png_encoder) {
    425     fprintf(stderr, "Skipping test because of missing codec support.\n");
    426     return;
    427   }
    428 
    429   const std::vector<uint8_t> original_png = jxl::test::ReadTestData(
    430       "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
    431   PackedPixelFile ppf;
    432   ASSERT_TRUE(extras::DecodeBytes(Bytes(original_png), ColorHints(), &ppf));
    433 
    434   const JxlPixelFormat& format = ppf.frames.front().color.format;
    435   ASSERT_THAT(
    436       png_encoder->AcceptedFormats(),
    437       Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels),
    438                      Field(&JxlPixelFormat::data_type, format.data_type),
    439                      Field(&JxlPixelFormat::endianness, format.endianness))));
    440   EncodedImage encoded_png;
    441   ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
    442   EXPECT_THAT(encoded_png.icc, IsEmpty());
    443   ASSERT_THAT(encoded_png.bitstreams, SizeIs(1));
    444 
    445   PackedPixelFile decoded_ppf;
    446   ASSERT_TRUE(extras::DecodeBytes(Bytes(encoded_png.bitstreams.front()),
    447                                   ColorHints(), &decoded_ppf));
    448 
    449   ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
    450   ASSERT_EQ(decoded_ppf.frames.size(), 1);
    451   VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
    452                   decoded_ppf.frames[0].color,
    453                   decoded_ppf.info.bits_per_sample);
    454 }
    455 
    456 }  // namespace
    457 }  // namespace extras
    458 }  // namespace jxl