tor-browser

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

test_image.cc (16167B)


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