libjpeg_test_util.cc (10605B)
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/libjpeg_test_util.h" 7 8 #include <cstdlib> 9 #include <cstring> 10 11 #include "lib/jxl/base/compiler_specific.h" 12 #include "lib/jxl/base/include_jpeglib.h" // NOLINT 13 #include "lib/jxl/base/sanitizers.h" 14 15 namespace jpegli { 16 17 namespace { 18 19 void Check(bool ok) { 20 if (!ok) { 21 JXL_CRASH(); 22 } 23 } 24 25 #define JPEG_API_FN(name) jpeg_##name 26 #include "lib/jpegli/test_utils-inl.h" 27 #undef JPEG_API_FN 28 29 void ReadOutputPass(j_decompress_ptr cinfo, const DecompressParams& dparams, 30 TestImage* output) { 31 JDIMENSION xoffset = 0; 32 JDIMENSION yoffset = 0; 33 JDIMENSION xsize_cropped = cinfo->output_width; 34 JDIMENSION ysize_cropped = cinfo->output_height; 35 if (dparams.crop_output) { 36 xoffset = xsize_cropped = cinfo->output_width / 3; 37 yoffset = ysize_cropped = cinfo->output_height / 3; 38 jpeg_crop_scanline(cinfo, &xoffset, &xsize_cropped); 39 Check(xsize_cropped == cinfo->output_width); 40 } 41 output->xsize = xsize_cropped; 42 output->ysize = ysize_cropped; 43 output->components = cinfo->out_color_components; 44 if (cinfo->quantize_colors) { 45 JSAMPLE** colormap = cinfo->colormap; 46 jxl::msan::UnpoisonMemory(reinterpret_cast<void*>(colormap), 47 cinfo->out_color_components * sizeof(JSAMPLE*)); 48 for (int c = 0; c < cinfo->out_color_components; ++c) { 49 jxl::msan::UnpoisonMemory( 50 reinterpret_cast<void*>(colormap[c]), 51 cinfo->actual_number_of_colors * sizeof(JSAMPLE)); 52 } 53 } 54 if (!cinfo->raw_data_out) { 55 size_t stride = output->xsize * output->components; 56 output->pixels.resize(output->ysize * stride); 57 output->color_space = cinfo->out_color_space; 58 if (yoffset > 0) { 59 jpeg_skip_scanlines(cinfo, yoffset); 60 } 61 for (size_t y = 0; y < output->ysize; ++y) { 62 JSAMPROW rows[] = { 63 reinterpret_cast<JSAMPLE*>(&output->pixels[y * stride])}; 64 Check(1 == jpeg_read_scanlines(cinfo, rows, 1)); 65 jxl::msan::UnpoisonMemory( 66 rows[0], sizeof(JSAMPLE) * cinfo->output_components * output->xsize); 67 if (cinfo->quantize_colors) { 68 UnmapColors(rows[0], cinfo->output_width, cinfo->out_color_components, 69 cinfo->colormap, cinfo->actual_number_of_colors); 70 } 71 } 72 if (cinfo->output_scanline < cinfo->output_height) { 73 jpeg_skip_scanlines(cinfo, cinfo->output_height - cinfo->output_scanline); 74 } 75 } else { 76 output->color_space = cinfo->jpeg_color_space; 77 for (int c = 0; c < cinfo->num_components; ++c) { 78 size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; 79 size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; 80 std::vector<uint8_t> plane(ysize * xsize); 81 output->raw_data.emplace_back(std::move(plane)); 82 } 83 while (cinfo->output_scanline < cinfo->output_height) { 84 size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE; 85 Check(cinfo->output_scanline == cinfo->output_iMCU_row * iMCU_height); 86 std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components); 87 std::vector<JSAMPARRAY> data(cinfo->num_components); 88 for (int c = 0; c < cinfo->num_components; ++c) { 89 size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; 90 size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; 91 size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE; 92 rowdata[c].resize(num_lines); 93 size_t y0 = cinfo->output_iMCU_row * num_lines; 94 for (size_t i = 0; i < num_lines; ++i) { 95 rowdata[c][i] = 96 y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr; 97 } 98 data[c] = rowdata[c].data(); 99 } 100 Check(iMCU_height == jpeg_read_raw_data(cinfo, data.data(), iMCU_height)); 101 } 102 } 103 Check(cinfo->total_iMCU_rows == 104 DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE)); 105 } 106 107 void DecodeWithLibjpeg(const CompressParams& jparams, 108 const DecompressParams& dparams, j_decompress_ptr cinfo, 109 TestImage* output) { 110 if (jparams.add_marker) { 111 jpeg_save_markers(cinfo, kSpecialMarker0, 0xffff); 112 jpeg_save_markers(cinfo, kSpecialMarker1, 0xffff); 113 } 114 if (!jparams.icc.empty()) { 115 jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xffff); 116 } 117 Check(JPEG_REACHED_SOS == jpeg_read_header(cinfo, /*require_image=*/TRUE)); 118 if (!jparams.icc.empty()) { 119 uint8_t* icc_data = nullptr; 120 unsigned int icc_len = 0; // "unpoison" via initialization 121 Check(jpeg_read_icc_profile(cinfo, &icc_data, &icc_len)); 122 Check(icc_data); 123 jxl::msan::UnpoisonMemory(icc_data, icc_len); 124 Check(0 == memcmp(jparams.icc.data(), icc_data, icc_len)); 125 free(icc_data); 126 } 127 SetDecompressParams(dparams, cinfo); 128 VerifyHeader(jparams, cinfo); 129 if (dparams.output_mode == COEFFICIENTS) { 130 jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(cinfo); 131 Check(coef_arrays != nullptr); 132 jxl::msan::UnpoisonMemory(coef_arrays, 133 cinfo->num_components * sizeof(jvirt_barray_ptr)); 134 CopyCoefficients(cinfo, coef_arrays, output); 135 } else { 136 Check(jpeg_start_decompress(cinfo)); 137 VerifyScanHeader(jparams, cinfo); 138 ReadOutputPass(cinfo, dparams, output); 139 } 140 Check(jpeg_finish_decompress(cinfo)); 141 } 142 143 } // namespace 144 145 // Verifies that an image encoded with libjpegli can be decoded with libjpeg, 146 // and checks that the jpeg coding metadata matches jparams. 147 void DecodeAllScansWithLibjpeg(const CompressParams& jparams, 148 const DecompressParams& dparams, 149 const std::vector<uint8_t>& compressed, 150 std::vector<TestImage>* output_progression) { 151 jpeg_decompress_struct cinfo = {}; 152 const auto try_catch_block = [&]() { 153 jpeg_error_mgr jerr; 154 jmp_buf env; 155 cinfo.err = jpeg_std_error(&jerr); 156 if (setjmp(env)) { 157 return false; 158 } 159 cinfo.client_data = reinterpret_cast<void*>(&env); 160 cinfo.err->error_exit = [](j_common_ptr cinfo) { 161 (*cinfo->err->output_message)(cinfo); 162 jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data); 163 jpeg_destroy(cinfo); 164 longjmp(*env, 1); 165 }; 166 jpeg_create_decompress(&cinfo); 167 jpeg_mem_src(&cinfo, compressed.data(), compressed.size()); 168 if (jparams.add_marker) { 169 jpeg_save_markers(&cinfo, kSpecialMarker0, 0xffff); 170 jpeg_save_markers(&cinfo, kSpecialMarker1, 0xffff); 171 } 172 Check(JPEG_REACHED_SOS == jpeg_read_header(&cinfo, /*require_image=*/TRUE)); 173 cinfo.buffered_image = TRUE; 174 SetDecompressParams(dparams, &cinfo); 175 VerifyHeader(jparams, &cinfo); 176 Check(jpeg_start_decompress(&cinfo)); 177 // start decompress should not read the whole input in buffered image mode 178 Check(!jpeg_input_complete(&cinfo)); 179 Check(cinfo.output_scan_number == 0); 180 int sos_marker_cnt = 1; // read header reads the first SOS marker 181 while (!jpeg_input_complete(&cinfo)) { 182 Check(cinfo.input_scan_number == sos_marker_cnt); 183 if (dparams.skip_scans && (cinfo.input_scan_number % 2) != 1) { 184 int result = JPEG_SUSPENDED; 185 while (result != JPEG_REACHED_SOS && result != JPEG_REACHED_EOI) { 186 result = jpeg_consume_input(&cinfo); 187 } 188 if (result == JPEG_REACHED_SOS) ++sos_marker_cnt; 189 continue; 190 } 191 SetScanDecompressParams(dparams, &cinfo, cinfo.input_scan_number); 192 Check(jpeg_start_output(&cinfo, cinfo.input_scan_number)); 193 // start output sets output_scan_number, but does not change 194 // input_scan_number 195 Check(cinfo.output_scan_number == cinfo.input_scan_number); 196 Check(cinfo.input_scan_number == sos_marker_cnt); 197 VerifyScanHeader(jparams, &cinfo); 198 TestImage output; 199 ReadOutputPass(&cinfo, dparams, &output); 200 output_progression->emplace_back(std::move(output)); 201 // read scanlines/read raw data does not change input/output scan number 202 if (!cinfo.progressive_mode) { 203 Check(cinfo.input_scan_number == sos_marker_cnt); 204 Check(cinfo.output_scan_number == cinfo.input_scan_number); 205 } 206 Check(jpeg_finish_output(&cinfo)); 207 ++sos_marker_cnt; // finish output reads the next SOS marker or EOI 208 if (dparams.output_mode == COEFFICIENTS) { 209 jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(&cinfo); 210 Check(coef_arrays != nullptr); 211 jxl::msan::UnpoisonMemory( 212 coef_arrays, cinfo.num_components * sizeof(jvirt_barray_ptr)); 213 CopyCoefficients(&cinfo, coef_arrays, &output_progression->back()); 214 } 215 } 216 Check(jpeg_finish_decompress(&cinfo)); 217 return true; 218 }; 219 Check(try_catch_block()); 220 jpeg_destroy_decompress(&cinfo); 221 } 222 223 // Returns the number of bytes read from compressed. 224 size_t DecodeWithLibjpeg(const CompressParams& jparams, 225 const DecompressParams& dparams, 226 const uint8_t* table_stream, size_t table_stream_size, 227 const uint8_t* compressed, size_t len, 228 TestImage* output) { 229 jpeg_decompress_struct cinfo = {}; 230 size_t bytes_read; 231 const auto try_catch_block = [&]() { 232 jpeg_error_mgr jerr; 233 jmp_buf env; 234 cinfo.err = jpeg_std_error(&jerr); 235 if (setjmp(env)) { 236 return false; 237 } 238 cinfo.client_data = reinterpret_cast<void*>(&env); 239 cinfo.err->error_exit = [](j_common_ptr cinfo) { 240 (*cinfo->err->output_message)(cinfo); 241 jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data); 242 jpeg_destroy(cinfo); 243 longjmp(*env, 1); 244 }; 245 jpeg_create_decompress(&cinfo); 246 if (table_stream != nullptr) { 247 jpeg_mem_src(&cinfo, table_stream, table_stream_size); 248 jpeg_read_header(&cinfo, FALSE); 249 } 250 jpeg_mem_src(&cinfo, compressed, len); 251 DecodeWithLibjpeg(jparams, dparams, &cinfo, output); 252 jxl::msan::UnpoisonMemory(cinfo.src, sizeof(jpeg_source_mgr)); 253 bytes_read = len - cinfo.src->bytes_in_buffer; 254 return true; 255 }; 256 Check(try_catch_block()); 257 jpeg_destroy_decompress(&cinfo); 258 return bytes_read; 259 } 260 261 void DecodeWithLibjpeg(const CompressParams& jparams, 262 const DecompressParams& dparams, 263 const std::vector<uint8_t>& compressed, 264 TestImage* output) { 265 DecodeWithLibjpeg(jparams, dparams, nullptr, 0, compressed.data(), 266 compressed.size(), output); 267 } 268 269 } // namespace jpegli