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