output_suspension_test.cc (7323B)
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/encode.h" 7 #include "lib/jpegli/test_utils.h" 8 #include "lib/jpegli/testing.h" 9 10 namespace jpegli { 11 namespace { 12 13 constexpr size_t kInitialBufferSize = 1024; 14 constexpr size_t kFinalBufferSize = 18; 15 16 struct DestinationManager { 17 jpeg_destination_mgr pub; 18 std::vector<uint8_t> buffer; 19 20 DestinationManager() { 21 pub.init_destination = init_destination; 22 pub.empty_output_buffer = empty_output_buffer; 23 pub.term_destination = term_destination; 24 } 25 26 void Rewind() { 27 pub.next_output_byte = buffer.data(); 28 pub.free_in_buffer = buffer.size(); 29 } 30 31 void EmptyTo(std::vector<uint8_t>* output, size_t new_size = 0) { 32 output->insert(output->end(), buffer.data(), pub.next_output_byte); 33 if (new_size > 0) { 34 buffer.resize(new_size); 35 } 36 Rewind(); 37 } 38 39 static void init_destination(j_compress_ptr cinfo) { 40 auto* us = reinterpret_cast<DestinationManager*>(cinfo->dest); 41 us->buffer.resize(kInitialBufferSize); 42 us->Rewind(); 43 } 44 45 static boolean empty_output_buffer(j_compress_ptr cinfo) { return FALSE; } 46 47 static void term_destination(j_compress_ptr cinfo) {} 48 }; 49 50 struct TestConfig { 51 TestImage input; 52 CompressParams jparams; 53 size_t buffer_size; 54 size_t lines_batch_size; 55 }; 56 57 class OutputSuspensionTestParam : public ::testing::TestWithParam<TestConfig> { 58 }; 59 60 TEST_P(OutputSuspensionTestParam, PixelData) { 61 jpeg_compress_struct cinfo = {}; 62 TestConfig config = GetParam(); 63 TestImage& input = config.input; 64 GeneratePixels(&input); 65 DestinationManager dest; 66 std::vector<uint8_t> compressed; 67 const auto try_catch_block = [&]() -> bool { 68 ERROR_HANDLER_SETUP(jpegli); 69 jpegli_create_compress(&cinfo); 70 cinfo.dest = reinterpret_cast<jpeg_destination_mgr*>(&dest); 71 72 cinfo.image_width = input.xsize; 73 cinfo.image_height = input.ysize; 74 cinfo.input_components = input.components; 75 cinfo.in_color_space = JCS_RGB; 76 jpegli_set_defaults(&cinfo); 77 cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0]; 78 jpegli_set_progressive_level(&cinfo, 0); 79 cinfo.optimize_coding = FALSE; 80 jpegli_start_compress(&cinfo, TRUE); 81 82 size_t stride = cinfo.image_width * cinfo.input_components; 83 std::vector<uint8_t> row_bytes(config.lines_batch_size * stride); 84 while (cinfo.next_scanline < cinfo.image_height) { 85 size_t lines_left = cinfo.image_height - cinfo.next_scanline; 86 size_t num_lines = std::min(config.lines_batch_size, lines_left); 87 memcpy(row_bytes.data(), &input.pixels[cinfo.next_scanline * stride], 88 num_lines * stride); 89 std::vector<JSAMPROW> rows(num_lines); 90 for (size_t i = 0; i < num_lines; ++i) { 91 rows[i] = &row_bytes[i * stride]; 92 } 93 size_t lines_done = 0; 94 while (lines_done < num_lines) { 95 lines_done += jpegli_write_scanlines(&cinfo, &rows[lines_done], 96 num_lines - lines_done); 97 if (lines_done < num_lines) { 98 dest.EmptyTo(&compressed, config.buffer_size); 99 } 100 } 101 } 102 dest.EmptyTo(&compressed, kFinalBufferSize); 103 jpegli_finish_compress(&cinfo); 104 dest.EmptyTo(&compressed); 105 return true; 106 }; 107 ASSERT_TRUE(try_catch_block()); 108 jpegli_destroy_compress(&cinfo); 109 TestImage output; 110 DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output); 111 VerifyOutputImage(input, output, 2.5); 112 } 113 114 TEST_P(OutputSuspensionTestParam, RawData) { 115 jpeg_compress_struct cinfo = {}; 116 TestConfig config = GetParam(); 117 if (config.lines_batch_size != 1) return; 118 TestImage& input = config.input; 119 input.color_space = JCS_YCbCr; 120 GeneratePixels(&input); 121 GenerateRawData(config.jparams, &input); 122 DestinationManager dest; 123 std::vector<uint8_t> compressed; 124 const auto try_catch_block = [&]() -> bool { 125 ERROR_HANDLER_SETUP(jpegli); 126 jpegli_create_compress(&cinfo); 127 cinfo.dest = reinterpret_cast<jpeg_destination_mgr*>(&dest); 128 cinfo.image_width = input.xsize; 129 cinfo.image_height = input.ysize; 130 cinfo.input_components = input.components; 131 cinfo.in_color_space = JCS_YCbCr; 132 jpegli_set_defaults(&cinfo); 133 cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0]; 134 jpegli_set_progressive_level(&cinfo, 0); 135 cinfo.optimize_coding = FALSE; 136 cinfo.raw_data_in = TRUE; 137 jpegli_start_compress(&cinfo, TRUE); 138 139 std::vector<std::vector<uint8_t>> raw_data = input.raw_data; 140 size_t max_lines = config.jparams.max_v_sample() * DCTSIZE; 141 std::vector<std::vector<JSAMPROW>> rowdata(cinfo.num_components); 142 std::vector<JSAMPARRAY> data(cinfo.num_components); 143 for (int c = 0; c < cinfo.num_components; ++c) { 144 rowdata[c].resize(config.jparams.v_samp(c) * DCTSIZE); 145 data[c] = rowdata[c].data(); 146 } 147 while (cinfo.next_scanline < cinfo.image_height) { 148 for (int c = 0; c < cinfo.num_components; ++c) { 149 size_t cwidth = cinfo.comp_info[c].width_in_blocks * DCTSIZE; 150 size_t cheight = cinfo.comp_info[c].height_in_blocks * DCTSIZE; 151 size_t num_lines = config.jparams.v_samp(c) * DCTSIZE; 152 size_t y0 = (cinfo.next_scanline / max_lines) * num_lines; 153 for (size_t i = 0; i < num_lines; ++i) { 154 rowdata[c][i] = 155 (y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr); 156 } 157 } 158 while (jpegli_write_raw_data(&cinfo, data.data(), max_lines) == 0) { 159 dest.EmptyTo(&compressed, config.buffer_size); 160 } 161 } 162 dest.EmptyTo(&compressed, kFinalBufferSize); 163 jpegli_finish_compress(&cinfo); 164 dest.EmptyTo(&compressed); 165 return true; 166 }; 167 try_catch_block(); 168 jpegli_destroy_compress(&cinfo); 169 DecompressParams dparams; 170 dparams.output_mode = RAW_DATA; 171 TestImage output; 172 DecodeWithLibjpeg(CompressParams(), dparams, compressed, &output); 173 VerifyOutputImage(input, output, 3.5); 174 } 175 176 std::vector<TestConfig> GenerateTests() { 177 std::vector<TestConfig> all_tests; 178 const size_t xsize0 = 1920; 179 const size_t ysize0 = 1080; 180 for (int dysize : {0, 1, 8, 9}) { 181 for (int v_sampling : {1, 2}) { 182 for (int nlines : {1, 8, 117}) { 183 for (int bufsize : {1, 16, 16 << 10}) { 184 TestConfig config; 185 config.lines_batch_size = nlines; 186 config.buffer_size = bufsize; 187 config.input.xsize = xsize0; 188 config.input.ysize = ysize0 + dysize; 189 config.jparams.h_sampling = {1, 1, 1}; 190 config.jparams.v_sampling = {v_sampling, 1, 1}; 191 all_tests.push_back(config); 192 } 193 } 194 } 195 } 196 return all_tests; 197 } 198 199 std::ostream& operator<<(std::ostream& os, const TestConfig& c) { 200 os << c.input; 201 os << c.jparams; 202 os << "Lines" << c.lines_batch_size; 203 os << "BufSize" << c.buffer_size; 204 return os; 205 } 206 207 std::string TestDescription( 208 const testing::TestParamInfo<OutputSuspensionTestParam::ParamType>& info) { 209 std::stringstream name; 210 name << info.param; 211 return name.str(); 212 } 213 214 JPEGLI_INSTANTIATE_TEST_SUITE_P(OutputSuspensionTest, OutputSuspensionTestParam, 215 testing::ValuesIn(GenerateTests()), 216 TestDescription); 217 218 } // namespace 219 } // namespace jpegli