tor-browser

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

streaming_test.cc (8826B)


      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 <algorithm>
      7 #include <cstddef>
      8 #include <cstdint>
      9 #include <cstring>
     10 #include <ostream>
     11 #include <sstream>
     12 #include <string>
     13 #include <vector>
     14 
     15 #include "lib/jpegli/decode.h"
     16 #include "lib/jpegli/encode.h"
     17 #include "lib/jpegli/test_params.h"
     18 #include "lib/jpegli/test_utils.h"
     19 #include "lib/jpegli/testing.h"
     20 
     21 namespace jpegli {
     22 namespace {
     23 
     24 // A simple suspending source manager with an input buffer.
     25 struct SourceManager {
     26  jpeg_source_mgr pub;
     27  std::vector<uint8_t> buffer;
     28 
     29  SourceManager() {
     30    pub.next_input_byte = nullptr;
     31    pub.bytes_in_buffer = 0;
     32    pub.init_source = init_source;
     33    pub.fill_input_buffer = fill_input_buffer;
     34    pub.skip_input_data = skip_input_data;
     35    pub.resync_to_restart = jpegli_resync_to_restart;
     36    pub.term_source = term_source;
     37  }
     38 
     39  static void init_source(j_decompress_ptr cinfo) {}
     40  static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; }
     41  static void skip_input_data(j_decompress_ptr cinfo,
     42                              long num_bytes /* NOLINT */) {}
     43  static void term_source(j_decompress_ptr cinfo) {}
     44 };
     45 
     46 // A destination manager that empties its output buffer into a SourceManager's
     47 // input buffer. The buffer size is kept short because empty_output_buffer() is
     48 // called only when the output buffer is full, and we want to update the decoder
     49 // input frequently to demonstrate that streaming works.
     50 constexpr size_t kOutputBufferSize = 1024;
     51 struct DestinationManager {
     52  jpeg_destination_mgr pub;
     53  std::vector<uint8_t> buffer;
     54  SourceManager* dest;
     55 
     56  explicit DestinationManager(SourceManager* src)
     57      : buffer(kOutputBufferSize), dest(src) {
     58    pub.next_output_byte = buffer.data();
     59    pub.free_in_buffer = buffer.size();
     60    pub.init_destination = init_destination;
     61    pub.empty_output_buffer = empty_output_buffer;
     62    pub.term_destination = term_destination;
     63  }
     64 
     65  static void init_destination(j_compress_ptr cinfo) {}
     66 
     67  static boolean empty_output_buffer(j_compress_ptr cinfo) {
     68    auto* us = reinterpret_cast<DestinationManager*>(cinfo->dest);
     69    jpeg_destination_mgr* src = &us->pub;
     70    jpeg_source_mgr* dst = &us->dest->pub;
     71    std::vector<uint8_t>& src_buf = us->buffer;
     72    std::vector<uint8_t>& dst_buf = us->dest->buffer;
     73    if (dst->bytes_in_buffer > 0 && dst->bytes_in_buffer < dst_buf.size()) {
     74      memmove(dst_buf.data(), dst->next_input_byte, dst->bytes_in_buffer);
     75    }
     76    size_t src_len = src_buf.size() - src->free_in_buffer;
     77    dst_buf.resize(dst->bytes_in_buffer + src_len);
     78    memcpy(&dst_buf[dst->bytes_in_buffer], src_buf.data(), src_len);
     79    dst->next_input_byte = dst_buf.data();
     80    dst->bytes_in_buffer = dst_buf.size();
     81    src->next_output_byte = src_buf.data();
     82    src->free_in_buffer = src_buf.size();
     83    return TRUE;
     84  }
     85 
     86  static void term_destination(j_compress_ptr cinfo) {
     87    empty_output_buffer(cinfo);
     88  }
     89 };
     90 
     91 struct TestConfig {
     92  TestImage input;
     93  CompressParams jparams;
     94 };
     95 
     96 class StreamingTestParam : public ::testing::TestWithParam<TestConfig> {};
     97 
     98 TEST_P(StreamingTestParam, TestStreaming) {
     99  jpeg_decompress_struct dinfo = {};
    100  jpeg_compress_struct cinfo = {};
    101  SourceManager src;
    102  TestConfig config = GetParam();
    103  TestImage& input = config.input;
    104  TestImage output;
    105  GeneratePixels(&input);
    106  const auto try_catch_block = [&]() {
    107    ERROR_HANDLER_SETUP(jpegli);
    108    dinfo.err = cinfo.err;
    109    dinfo.client_data = cinfo.client_data;
    110    // Create a pair of compressor and decompressor objects, where the
    111    // compressor's output is connected to the decompressor's input.
    112    jpegli_create_decompress(&dinfo);
    113    jpegli_create_compress(&cinfo);
    114    dinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    115    DestinationManager dest(&src);
    116    cinfo.dest = reinterpret_cast<jpeg_destination_mgr*>(&dest);
    117 
    118    cinfo.image_width = input.xsize;
    119    cinfo.image_height = input.ysize;
    120    cinfo.input_components = input.components;
    121    cinfo.in_color_space = static_cast<J_COLOR_SPACE>(input.color_space);
    122    jpegli_set_defaults(&cinfo);
    123    cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0];
    124    jpegli_set_progressive_level(&cinfo, 0);
    125    cinfo.optimize_coding = FALSE;
    126    jpegli_start_compress(&cinfo, TRUE);
    127 
    128    size_t stride = cinfo.image_width * cinfo.input_components;
    129    size_t iMCU_height = 8 * cinfo.max_v_samp_factor;
    130    std::vector<uint8_t> row_bytes(iMCU_height * stride);
    131    size_t y_in = 0;
    132    size_t y_out = 0;
    133    while (y_in < cinfo.image_height) {
    134      // Feed one iMCU row at a time to the compressor.
    135      size_t lines_in = std::min(iMCU_height, cinfo.image_height - y_in);
    136      memcpy(row_bytes.data(), &input.pixels[y_in * stride], lines_in * stride);
    137      std::vector<JSAMPROW> rows_in(lines_in);
    138      for (size_t i = 0; i < lines_in; ++i) {
    139        rows_in[i] = &row_bytes[i * stride];
    140      }
    141      EXPECT_EQ(lines_in,
    142                jpegli_write_scanlines(&cinfo, rows_in.data(), lines_in));
    143      y_in += lines_in;
    144      if (y_in == cinfo.image_height) {
    145        jpegli_finish_compress(&cinfo);
    146      }
    147 
    148      // Atfer the first iMCU row, we don't yet expect any output because the
    149      // compressor delays processing to have context rows after the iMCU row.
    150      if (y_in < std::min<size_t>(2 * iMCU_height, cinfo.image_height)) {
    151        continue;
    152      }
    153 
    154      // After two iMCU rows, the compressor has started emitting compressed
    155      // data. We check here that at least the scan header was output, because
    156      // we expect that the compressor's output buffer was filled at least once
    157      // while emitting the first compressed iMCU row.
    158      if (y_in == std::min<size_t>(2 * iMCU_height, cinfo.image_height)) {
    159        EXPECT_EQ(JPEG_REACHED_SOS,
    160                  jpegli_read_header(&dinfo, /*require_image=*/TRUE));
    161        output.xsize = dinfo.image_width;
    162        output.ysize = dinfo.image_height;
    163        output.components = dinfo.num_components;
    164        EXPECT_EQ(output.xsize, input.xsize);
    165        EXPECT_EQ(output.ysize, input.ysize);
    166        EXPECT_EQ(output.components, input.components);
    167        EXPECT_TRUE(jpegli_start_decompress(&dinfo));
    168        output.pixels.resize(output.ysize * stride);
    169        if (y_in < cinfo.image_height) {
    170          continue;
    171        }
    172      }
    173 
    174      // After six iMCU rows, the compressor has emitted five iMCU rows of
    175      // compressed data, of which we expect four full iMCU row of compressed
    176      // data to be in the decoder's input buffer, but since the decoder also
    177      // needs context rows for upsampling and smoothing, we don't expect any
    178      // output to be ready yet.
    179      if (y_in < 7 * iMCU_height && y_in < cinfo.image_height) {
    180        continue;
    181      }
    182 
    183      // After five iMCU rows, we expect the decoder to have rendered the output
    184      // with four iMCU rows of delay.
    185      // TODO(szabadka) Reduce the processing delay in the decoder if possible.
    186      size_t lines_out =
    187          (y_in == cinfo.image_height ? cinfo.image_height - y_out
    188                                      : iMCU_height);
    189      std::vector<JSAMPROW> rows_out(lines_out);
    190      for (size_t i = 0; i < lines_out; ++i) {
    191        rows_out[i] =
    192            reinterpret_cast<JSAMPLE*>(&output.pixels[(y_out + i) * stride]);
    193      }
    194      EXPECT_EQ(lines_out,
    195                jpegli_read_scanlines(&dinfo, rows_out.data(), lines_out));
    196      VerifyOutputImage(input, output, y_out, lines_out, 3.8f);
    197      y_out += lines_out;
    198 
    199      if (y_out == cinfo.image_height) {
    200        EXPECT_TRUE(jpegli_finish_decompress(&dinfo));
    201      }
    202    }
    203    return true;
    204  };
    205  EXPECT_TRUE(try_catch_block());
    206  jpegli_destroy_decompress(&dinfo);
    207  jpegli_destroy_compress(&cinfo);
    208 }
    209 
    210 std::vector<TestConfig> GenerateTests() {
    211  std::vector<TestConfig> all_tests;
    212  const size_t xsize0 = 1920;
    213  const size_t ysize0 = 1080;
    214  for (int dysize : {0, 1, 8, 9}) {
    215    for (int v_sampling : {1, 2}) {
    216      TestConfig config;
    217      config.input.xsize = xsize0;
    218      config.input.ysize = ysize0 + dysize;
    219      config.jparams.h_sampling = {1, 1, 1};
    220      config.jparams.v_sampling = {v_sampling, 1, 1};
    221      all_tests.push_back(config);
    222    }
    223  }
    224  return all_tests;
    225 }
    226 
    227 std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
    228  os << c.input;
    229  os << c.jparams;
    230  return os;
    231 }
    232 
    233 std::string TestDescription(
    234    const testing::TestParamInfo<StreamingTestParam::ParamType>& info) {
    235  std::stringstream name;
    236  name << info.param;
    237  return name.str();
    238 }
    239 
    240 JPEGLI_INSTANTIATE_TEST_SUITE_P(StreamingTest, StreamingTestParam,
    241                                testing::ValuesIn(GenerateTests()),
    242                                TestDescription);
    243 
    244 }  // namespace
    245 }  // namespace jpegli