libjxl

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

streaming_test.cc (8538B)


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