tor-browser

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

codec_test.cc (18086B)


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