libjxl

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

test_image.cc (16333B)


      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/jxl/test_image.h"
      7 
      8 #include <jxl/encode.h>
      9 
     10 #include <algorithm>
     11 #include <cstring>
     12 #include <utility>
     13 
     14 #include "lib/extras/dec/color_description.h"
     15 #include "lib/extras/dec/color_hints.h"
     16 #include "lib/extras/dec/decode.h"
     17 #include "lib/jxl/base/byte_order.h"
     18 #include "lib/jxl/base/random.h"
     19 #include "lib/jxl/base/span.h"
     20 #include "lib/jxl/base/status.h"
     21 #include "lib/jxl/color_encoding_internal.h"
     22 
     23 namespace jxl {
     24 namespace test {
     25 
     26 namespace {
     27 
     28 void StoreValue(float val, size_t bits_per_sample, JxlPixelFormat format,
     29                 uint8_t** out) {
     30   const float mul = (1u << bits_per_sample) - 1;
     31   if (format.data_type == JXL_TYPE_UINT8) {
     32     **out = val * mul;
     33   } else if (format.data_type == JXL_TYPE_UINT16) {
     34     uint16_t uval = val * mul;
     35     if (SwapEndianness(format.endianness)) {
     36       uval = JXL_BSWAP16(uval);
     37     }
     38     memcpy(*out, &uval, 2);
     39   } else if (format.data_type == JXL_TYPE_FLOAT) {
     40     // TODO(szabadka) Add support for custom bits / exponent bits floats.
     41     if (SwapEndianness(format.endianness)) {
     42       val = BSwapFloat(val);
     43     }
     44     memcpy(*out, &val, 4);
     45   } else {
     46     // TODO(szabadka) Add support for FLOAT16.
     47   }
     48   *out += extras::PackedImage::BitsPerChannel(format.data_type) / 8;
     49 }
     50 
     51 void FillPackedImage(size_t bits_per_sample, uint16_t seed,
     52                      extras::PackedImage* image) {
     53   const size_t xsize = image->xsize;
     54   const size_t ysize = image->ysize;
     55   const JxlPixelFormat format = image->format;
     56 
     57   // Cause more significant image difference for successive seeds.
     58   Rng generator(seed);
     59 
     60   // Returns random integer in interval [0, max_value)
     61   auto rngu = [&generator](size_t max_value) -> size_t {
     62     return generator.UniformU(0, max_value);
     63   };
     64 
     65   // Returns random float in interval [0.0, max_value)
     66   auto rngf = [&generator](float max_value) {
     67     return generator.UniformF(0.0f, max_value);
     68   };
     69 
     70   // Dark background gradient color
     71   float r0 = rngf(0.5f);
     72   float g0 = rngf(0.5f);
     73   float b0 = rngf(0.5f);
     74   float a0 = rngf(0.5f);
     75   float r1 = rngf(0.5f);
     76   float g1 = rngf(0.5f);
     77   float b1 = rngf(0.5f);
     78   float a1 = rngf(0.5f);
     79 
     80   // Circle with different color
     81   size_t circle_x = rngu(xsize);
     82   size_t circle_y = rngu(ysize);
     83   size_t circle_r = rngu(std::min(xsize, ysize));
     84 
     85   // Rectangle with random noise
     86   size_t rect_x0 = rngu(xsize);
     87   size_t rect_y0 = rngu(ysize);
     88   size_t rect_x1 = rngu(xsize);
     89   size_t rect_y1 = rngu(ysize);
     90   if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1);
     91   if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1);
     92 
     93   // Create pixel content to test, actual content does not matter as long as it
     94   // can be compared after roundtrip.
     95   const float imul16 = 1.0f / 65536.0f;
     96   for (size_t y = 0; y < ysize; y++) {
     97     uint8_t* out =
     98         reinterpret_cast<uint8_t*>(image->pixels()) + y * image->stride;
     99     for (size_t x = 0; x < xsize; x++) {
    100       float r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize;
    101       float g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize;
    102       float b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize;
    103       float a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize;
    104       // put some shape in there for visual debugging
    105       if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <
    106           circle_r * circle_r) {
    107         r = std::min(1.0f, ((65535 - x * y) ^ seed) * imul16);
    108         g = std::min(1.0f, ((x << 8) + y + seed) * imul16);
    109         b = std::min(1.0f, ((y << 8) + x * seed) * imul16);
    110         a = std::min(1.0f, (32768 + x * 256 - y) * imul16);
    111       } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) {
    112         r = rngf(1.0f);
    113         g = rngf(1.0f);
    114         b = rngf(1.0f);
    115         a = rngf(1.0f);
    116       }
    117       if (format.num_channels == 1) {
    118         StoreValue(g, bits_per_sample, format, &out);
    119       } else if (format.num_channels == 2) {
    120         StoreValue(g, bits_per_sample, format, &out);
    121         StoreValue(a, bits_per_sample, format, &out);
    122       } else if (format.num_channels == 3) {
    123         StoreValue(r, bits_per_sample, format, &out);
    124         StoreValue(g, bits_per_sample, format, &out);
    125         StoreValue(b, bits_per_sample, format, &out);
    126       } else if (format.num_channels == 4) {
    127         StoreValue(r, bits_per_sample, format, &out);
    128         StoreValue(g, bits_per_sample, format, &out);
    129         StoreValue(b, bits_per_sample, format, &out);
    130         StoreValue(a, bits_per_sample, format, &out);
    131       }
    132     }
    133   }
    134 }
    135 
    136 }  // namespace
    137 
    138 std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
    139                                       size_t num_channels, uint16_t seed) {
    140   // Cause more significant image difference for successive seeds.
    141   Rng generator(seed);
    142 
    143   // Returns random integer in interval [0, max_value)
    144   auto rng = [&generator](size_t max_value) -> size_t {
    145     return generator.UniformU(0, max_value);
    146   };
    147 
    148   // Dark background gradient color
    149   uint16_t r0 = rng(32768);
    150   uint16_t g0 = rng(32768);
    151   uint16_t b0 = rng(32768);
    152   uint16_t a0 = rng(32768);
    153   uint16_t r1 = rng(32768);
    154   uint16_t g1 = rng(32768);
    155   uint16_t b1 = rng(32768);
    156   uint16_t a1 = rng(32768);
    157 
    158   // Circle with different color
    159   size_t circle_x = rng(xsize);
    160   size_t circle_y = rng(ysize);
    161   size_t circle_r = rng(std::min(xsize, ysize));
    162 
    163   // Rectangle with random noise
    164   size_t rect_x0 = rng(xsize);
    165   size_t rect_y0 = rng(ysize);
    166   size_t rect_x1 = rng(xsize);
    167   size_t rect_y1 = rng(ysize);
    168   if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1);
    169   if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1);
    170 
    171   size_t num_pixels = xsize * ysize;
    172   // 16 bits per channel, big endian, 4 channels
    173   std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
    174   // Create pixel content to test, actual content does not matter as long as it
    175   // can be compared after roundtrip.
    176   for (size_t y = 0; y < ysize; y++) {
    177     for (size_t x = 0; x < xsize; x++) {
    178       uint16_t r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize;
    179       uint16_t g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize;
    180       uint16_t b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize;
    181       uint16_t a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize;
    182       // put some shape in there for visual debugging
    183       if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <
    184           circle_r * circle_r) {
    185         r = (65535 - x * y) ^ seed;
    186         g = (x << 8) + y + seed;
    187         b = (y << 8) + x * seed;
    188         a = 32768 + x * 256 - y;
    189       } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) {
    190         r = rng(65536);
    191         g = rng(65536);
    192         b = rng(65536);
    193         a = rng(65536);
    194       }
    195       size_t i = (y * xsize + x) * 2 * num_channels;
    196       pixels[i + 0] = (r >> 8);
    197       pixels[i + 1] = (r & 255);
    198       if (num_channels >= 2) {
    199         // This may store what is called 'g' in the alpha channel of a 2-channel
    200         // image, but that's ok since the content is arbitrary
    201         pixels[i + 2] = (g >> 8);
    202         pixels[i + 3] = (g & 255);
    203       }
    204       if (num_channels >= 3) {
    205         pixels[i + 4] = (b >> 8);
    206         pixels[i + 5] = (b & 255);
    207       }
    208       if (num_channels >= 4) {
    209         pixels[i + 6] = (a >> 8);
    210         pixels[i + 7] = (a & 255);
    211       }
    212     }
    213   }
    214   return pixels;
    215 }
    216 
    217 TestImage::TestImage() {
    218   SetChannels(3);
    219   SetAllBitDepths(8);
    220   SetColorEncoding("RGB_D65_SRG_Rel_SRG");
    221 }
    222 
    223 TestImage& TestImage::DecodeFromBytes(const std::vector<uint8_t>& bytes) {
    224   ColorEncoding c_enc;
    225   JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding));
    226   extras::ColorHints color_hints;
    227   color_hints.Add("color_space", Description(c_enc));
    228   JXL_CHECK(extras::DecodeBytes(Bytes(bytes), color_hints, &ppf_));
    229   return *this;
    230 }
    231 
    232 TestImage& TestImage::ClearMetadata() {
    233   ppf_.metadata = extras::PackedMetadata();
    234   return *this;
    235 }
    236 
    237 TestImage& TestImage::SetDimensions(size_t xsize, size_t ysize) {
    238   if (xsize <= ppf_.info.xsize && ysize <= ppf_.info.ysize) {
    239     for (auto& frame : ppf_.frames) {
    240       CropLayerInfo(xsize, ysize, &frame.frame_info.layer_info);
    241       CropImage(xsize, ysize, &frame.color);
    242       for (auto& ec : frame.extra_channels) {
    243         CropImage(xsize, ysize, &ec);
    244       }
    245     }
    246   } else {
    247     JXL_CHECK(ppf_.info.xsize == 0 && ppf_.info.ysize == 0);
    248   }
    249   ppf_.info.xsize = xsize;
    250   ppf_.info.ysize = ysize;
    251   return *this;
    252 }
    253 
    254 TestImage& TestImage::SetChannels(size_t num_channels) {
    255   JXL_CHECK(ppf_.frames.empty());
    256   JXL_CHECK(!ppf_.preview_frame);
    257   ppf_.info.num_color_channels = num_channels < 3 ? 1 : 3;
    258   ppf_.info.num_extra_channels = num_channels - ppf_.info.num_color_channels;
    259   if (ppf_.info.num_extra_channels > 0 && ppf_.info.alpha_bits == 0) {
    260     ppf_.info.alpha_bits = ppf_.info.bits_per_sample;
    261     ppf_.info.alpha_exponent_bits = ppf_.info.exponent_bits_per_sample;
    262   }
    263   ppf_.extra_channels_info.clear();
    264   for (size_t i = 1; i < ppf_.info.num_extra_channels; ++i) {
    265     extras::PackedExtraChannel ec;
    266     ec.index = i;
    267     JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &ec.ec_info);
    268     if (ec.ec_info.bits_per_sample == 0) {
    269       ec.ec_info.bits_per_sample = ppf_.info.bits_per_sample;
    270       ec.ec_info.exponent_bits_per_sample = ppf_.info.exponent_bits_per_sample;
    271     }
    272     ppf_.extra_channels_info.emplace_back(std::move(ec));
    273   }
    274   format_.num_channels = std::min(static_cast<size_t>(4), num_channels);
    275   if (ppf_.info.num_color_channels == 1 &&
    276       ppf_.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) {
    277     SetColorEncoding("Gra_D65_Rel_SRG");
    278   }
    279   return *this;
    280 }
    281 
    282 // Sets the same bit depth on color, alpha and all extra channels.
    283 TestImage& TestImage::SetAllBitDepths(uint32_t bits_per_sample,
    284                                       uint32_t exponent_bits_per_sample) {
    285   ppf_.info.bits_per_sample = bits_per_sample;
    286   ppf_.info.exponent_bits_per_sample = exponent_bits_per_sample;
    287   if (ppf_.info.num_extra_channels > 0) {
    288     ppf_.info.alpha_bits = bits_per_sample;
    289     ppf_.info.alpha_exponent_bits = exponent_bits_per_sample;
    290   }
    291   for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
    292     extras::PackedExtraChannel& ec = ppf_.extra_channels_info[i];
    293     ec.ec_info.bits_per_sample = bits_per_sample;
    294     ec.ec_info.exponent_bits_per_sample = exponent_bits_per_sample;
    295   }
    296   format_.data_type = DefaultDataType(ppf_.info);
    297   return *this;
    298 }
    299 
    300 TestImage& TestImage::SetDataType(JxlDataType data_type) {
    301   format_.data_type = data_type;
    302   return *this;
    303 }
    304 
    305 TestImage& TestImage::SetEndianness(JxlEndianness endianness) {
    306   format_.endianness = endianness;
    307   return *this;
    308 }
    309 
    310 TestImage& TestImage::SetRowAlignment(size_t align) {
    311   format_.align = align;
    312   return *this;
    313 }
    314 
    315 TestImage& TestImage::SetColorEncoding(const std::string& description) {
    316   JXL_CHECK(ParseDescription(description, &ppf_.color_encoding));
    317   ColorEncoding c_enc;
    318   JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding));
    319   IccBytes icc = c_enc.ICC();
    320   JXL_CHECK(!icc.empty());
    321   ppf_.icc.assign(icc.begin(), icc.end());
    322   return *this;
    323 }
    324 
    325 TestImage& TestImage::CoalesceGIFAnimationWithAlpha() {
    326   extras::PackedFrame canvas = ppf_.frames[0].Copy();
    327   JXL_CHECK(canvas.color.format.num_channels == 3);
    328   JXL_CHECK(canvas.color.format.data_type == JXL_TYPE_UINT8);
    329   JXL_CHECK(canvas.extra_channels.size() == 1);
    330   for (size_t i = 1; i < ppf_.frames.size(); i++) {
    331     const extras::PackedFrame& frame = ppf_.frames[i];
    332     JXL_CHECK(frame.extra_channels.size() == 1);
    333     const JxlLayerInfo& layer_info = frame.frame_info.layer_info;
    334     extras::PackedFrame rendered = canvas.Copy();
    335     uint8_t* pixels_rendered =
    336         reinterpret_cast<uint8_t*>(rendered.color.pixels());
    337     const uint8_t* pixels_frame =
    338         reinterpret_cast<const uint8_t*>(frame.color.pixels());
    339     uint8_t* alpha_rendered =
    340         reinterpret_cast<uint8_t*>(rendered.extra_channels[0].pixels());
    341     const uint8_t* alpha_frame =
    342         reinterpret_cast<const uint8_t*>(frame.extra_channels[0].pixels());
    343     for (size_t y = 0; y < frame.color.ysize; y++) {
    344       for (size_t x = 0; x < frame.color.xsize; x++) {
    345         size_t idx_frame = y * frame.color.xsize + x;
    346         size_t idx_rendered = ((layer_info.crop_y0 + y) * rendered.color.xsize +
    347                                (layer_info.crop_x0 + x));
    348         if (alpha_frame[idx_frame] != 0) {
    349           memcpy(&pixels_rendered[idx_rendered * 3],
    350                  &pixels_frame[idx_frame * 3], 3);
    351           alpha_rendered[idx_rendered] = alpha_frame[idx_frame];
    352         }
    353       }
    354     }
    355     if (layer_info.save_as_reference != 0) {
    356       canvas = rendered.Copy();
    357     }
    358     ppf_.frames[i] = std::move(rendered);
    359   }
    360   return *this;
    361 }
    362 
    363 TestImage::Frame::Frame(TestImage* parent, bool is_preview, size_t index)
    364     : parent_(parent), is_preview_(is_preview), index_(index) {}
    365 
    366 void TestImage::Frame::ZeroFill() {
    367   memset(frame().color.pixels(), 0, frame().color.pixels_size);
    368   for (auto& ec : frame().extra_channels) {
    369     memset(ec.pixels(), 0, ec.pixels_size);
    370   }
    371 }
    372 
    373 void TestImage::Frame::RandomFill(uint16_t seed) {
    374   FillPackedImage(ppf().info.bits_per_sample, seed, &frame().color);
    375   for (size_t i = 0; i < ppf().extra_channels_info.size(); ++i) {
    376     FillPackedImage(ppf().extra_channels_info[i].ec_info.bits_per_sample,
    377                     seed + 1 + i, &frame().extra_channels[i]);
    378   }
    379 }
    380 
    381 void TestImage::Frame::SetValue(size_t y, size_t x, size_t c, float val) {
    382   const extras::PackedImage& color = frame().color;
    383   JxlPixelFormat format = color.format;
    384   JXL_CHECK(y < ppf().info.ysize);
    385   JXL_CHECK(x < ppf().info.xsize);
    386   JXL_CHECK(c < format.num_channels);
    387   size_t pwidth = extras::PackedImage::BitsPerChannel(format.data_type) / 8;
    388   size_t idx = ((y * color.xsize + x) * format.num_channels + c) * pwidth;
    389   uint8_t* pixels = reinterpret_cast<uint8_t*>(frame().color.pixels());
    390   uint8_t* p = pixels + idx;
    391   StoreValue(val, ppf().info.bits_per_sample, frame().color.format, &p);
    392 }
    393 
    394 TestImage::Frame TestImage::AddFrame() {
    395   size_t index = ppf_.frames.size();
    396   extras::PackedFrame frame(ppf_.info.xsize, ppf_.info.ysize, format_);
    397   for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
    398     JxlPixelFormat ec_format = {1, format_.data_type, format_.endianness, 0};
    399     extras::PackedImage image(ppf_.info.xsize, ppf_.info.ysize, ec_format);
    400     frame.extra_channels.emplace_back(std::move(image));
    401   }
    402   ppf_.frames.emplace_back(std::move(frame));
    403   return Frame(this, false, index);
    404 }
    405 
    406 TestImage::Frame TestImage::AddPreview(size_t xsize, size_t ysize) {
    407   extras::PackedFrame frame(xsize, ysize, format_);
    408   for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
    409     JxlPixelFormat ec_format = {1, format_.data_type, format_.endianness, 0};
    410     extras::PackedImage image(xsize, ysize, ec_format);
    411     frame.extra_channels.emplace_back(std::move(image));
    412   }
    413   ppf_.preview_frame = make_unique<extras::PackedFrame>(std::move(frame));
    414   return Frame(this, true, 0);
    415 }
    416 
    417 void TestImage::CropLayerInfo(size_t xsize, size_t ysize, JxlLayerInfo* info) {
    418   if (info->crop_x0 < static_cast<ssize_t>(xsize)) {
    419     info->xsize = std::min<size_t>(info->xsize, xsize - info->crop_x0);
    420   } else {
    421     info->xsize = 0;
    422   }
    423   if (info->crop_y0 < static_cast<ssize_t>(ysize)) {
    424     info->ysize = std::min<size_t>(info->ysize, ysize - info->crop_y0);
    425   } else {
    426     info->ysize = 0;
    427   }
    428 }
    429 
    430 void TestImage::CropImage(size_t xsize, size_t ysize,
    431                           extras::PackedImage* image) {
    432   size_t new_stride = (image->stride / image->xsize) * xsize;
    433   uint8_t* buf = reinterpret_cast<uint8_t*>(image->pixels());
    434   for (size_t y = 0; y < ysize; ++y) {
    435     memmove(&buf[y * new_stride], &buf[y * image->stride], new_stride);
    436   }
    437   image->xsize = xsize;
    438   image->ysize = ysize;
    439   image->stride = new_stride;
    440   image->pixels_size = ysize * new_stride;
    441 }
    442 
    443 JxlDataType TestImage::DefaultDataType(const JxlBasicInfo& info) {
    444   if (info.bits_per_sample == 16 && info.exponent_bits_per_sample == 5) {
    445     return JXL_TYPE_FLOAT16;
    446   } else if (info.exponent_bits_per_sample > 0 || info.bits_per_sample > 16) {
    447     return JXL_TYPE_FLOAT;
    448   } else if (info.bits_per_sample > 8) {
    449     return JXL_TYPE_UINT16;
    450   } else {
    451     return JXL_TYPE_UINT8;
    452   }
    453 }
    454 
    455 }  // namespace test
    456 }  // namespace jxl