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