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