jpegli.cc (8998B)
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/extras/dec/jpegli.h" 7 8 #include <setjmp.h> 9 #include <stdint.h> 10 11 #include <algorithm> 12 #include <numeric> 13 #include <utility> 14 #include <vector> 15 16 #include "lib/jpegli/decode.h" 17 #include "lib/jxl/base/status.h" 18 #include "lib/jxl/sanitizers.h" 19 20 namespace jxl { 21 namespace extras { 22 23 namespace { 24 25 constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, 26 0x66, 0x00, 0x00}; 27 constexpr int kExifMarker = JPEG_APP0 + 1; 28 constexpr int kICCMarker = JPEG_APP0 + 2; 29 30 inline bool IsJPG(const std::vector<uint8_t>& bytes) { 31 if (bytes.size() < 2) return false; 32 if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false; 33 return true; 34 } 35 36 bool MarkerIsExif(const jpeg_saved_marker_ptr marker) { 37 return marker->marker == kExifMarker && 38 marker->data_length >= sizeof kExifSignature + 2 && 39 std::equal(std::begin(kExifSignature), std::end(kExifSignature), 40 marker->data); 41 } 42 43 Status ReadICCProfile(jpeg_decompress_struct* const cinfo, 44 std::vector<uint8_t>* const icc) { 45 uint8_t* icc_data_ptr; 46 unsigned int icc_data_len; 47 if (jpegli_read_icc_profile(cinfo, &icc_data_ptr, &icc_data_len)) { 48 icc->assign(icc_data_ptr, icc_data_ptr + icc_data_len); 49 free(icc_data_ptr); 50 return true; 51 } 52 return false; 53 } 54 55 void ReadExif(jpeg_decompress_struct* const cinfo, 56 std::vector<uint8_t>* const exif) { 57 constexpr size_t kExifSignatureSize = sizeof kExifSignature; 58 for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; 59 marker = marker->next) { 60 // marker is initialized by libjpeg, which we are not instrumenting with 61 // msan. 62 msan::UnpoisonMemory(marker, sizeof(*marker)); 63 msan::UnpoisonMemory(marker->data, marker->data_length); 64 if (!MarkerIsExif(marker)) continue; 65 size_t marker_length = marker->data_length - kExifSignatureSize; 66 exif->resize(marker_length); 67 std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data()); 68 return; 69 } 70 } 71 72 JpegliDataType ConvertDataType(JxlDataType type) { 73 switch (type) { 74 case JXL_TYPE_UINT8: 75 return JPEGLI_TYPE_UINT8; 76 case JXL_TYPE_UINT16: 77 return JPEGLI_TYPE_UINT16; 78 case JXL_TYPE_FLOAT: 79 return JPEGLI_TYPE_FLOAT; 80 default: 81 return JPEGLI_TYPE_UINT8; 82 } 83 } 84 85 JpegliEndianness ConvertEndianness(JxlEndianness type) { 86 switch (type) { 87 case JXL_NATIVE_ENDIAN: 88 return JPEGLI_NATIVE_ENDIAN; 89 case JXL_BIG_ENDIAN: 90 return JPEGLI_BIG_ENDIAN; 91 case JXL_LITTLE_ENDIAN: 92 return JPEGLI_LITTLE_ENDIAN; 93 default: 94 return JPEGLI_NATIVE_ENDIAN; 95 } 96 } 97 98 JxlColorSpace ConvertColorSpace(J_COLOR_SPACE colorspace) { 99 switch (colorspace) { 100 case JCS_GRAYSCALE: 101 return JXL_COLOR_SPACE_GRAY; 102 case JCS_RGB: 103 return JXL_COLOR_SPACE_RGB; 104 default: 105 return JXL_COLOR_SPACE_UNKNOWN; 106 } 107 } 108 109 void MyErrorExit(j_common_ptr cinfo) { 110 jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data); 111 (*cinfo->err->output_message)(cinfo); 112 jpegli_destroy_decompress(reinterpret_cast<j_decompress_ptr>(cinfo)); 113 longjmp(*env, 1); 114 } 115 116 void MyOutputMessage(j_common_ptr cinfo) { 117 #if JXL_DEBUG_WARNING == 1 118 char buf[JMSG_LENGTH_MAX + 1]; 119 (*cinfo->err->format_message)(cinfo, buf); 120 buf[JMSG_LENGTH_MAX] = 0; 121 JXL_WARNING("%s", buf); 122 #endif 123 } 124 125 void UnmapColors(uint8_t* row, size_t xsize, int components, 126 JSAMPARRAY colormap, size_t num_colors) { 127 JXL_CHECK(colormap != nullptr); 128 std::vector<uint8_t> tmp(xsize * components); 129 for (size_t x = 0; x < xsize; ++x) { 130 JXL_CHECK(row[x] < num_colors); 131 for (int c = 0; c < components; ++c) { 132 tmp[x * components + c] = colormap[c][row[x]]; 133 } 134 } 135 memcpy(row, tmp.data(), tmp.size()); 136 } 137 138 } // namespace 139 140 Status DecodeJpeg(const std::vector<uint8_t>& compressed, 141 const JpegDecompressParams& dparams, ThreadPool* pool, 142 PackedPixelFile* ppf) { 143 // Don't do anything for non-JPEG files (no need to report an error) 144 if (!IsJPG(compressed)) return false; 145 146 // TODO(veluca): use JPEGData also for pixels? 147 148 // We need to declare all the non-trivial destructor local variables before 149 // the call to setjmp(). 150 std::unique_ptr<JSAMPLE[]> row; 151 152 jpeg_decompress_struct cinfo; 153 const auto try_catch_block = [&]() -> bool { 154 // Setup error handling in jpeg library so we can deal with broken jpegs in 155 // the fuzzer. 156 jpeg_error_mgr jerr; 157 jmp_buf env; 158 cinfo.err = jpegli_std_error(&jerr); 159 jerr.error_exit = &MyErrorExit; 160 jerr.output_message = &MyOutputMessage; 161 if (setjmp(env)) { 162 return false; 163 } 164 cinfo.client_data = static_cast<void*>(&env); 165 166 jpegli_create_decompress(&cinfo); 167 jpegli_mem_src(&cinfo, 168 reinterpret_cast<const unsigned char*>(compressed.data()), 169 compressed.size()); 170 jpegli_save_markers(&cinfo, kICCMarker, 0xFFFF); 171 jpegli_save_markers(&cinfo, kExifMarker, 0xFFFF); 172 const auto failure = [&cinfo](const char* str) -> Status { 173 jpegli_abort_decompress(&cinfo); 174 jpegli_destroy_decompress(&cinfo); 175 return JXL_FAILURE("%s", str); 176 }; 177 jpegli_read_header(&cinfo, TRUE); 178 // Might cause CPU-zip bomb. 179 if (cinfo.arith_code) { 180 return failure("arithmetic code JPEGs are not supported"); 181 } 182 int nbcomp = cinfo.num_components; 183 if (nbcomp != 1 && nbcomp != 3) { 184 return failure("unsupported number of components in JPEG"); 185 } 186 if (dparams.force_rgb) { 187 cinfo.out_color_space = JCS_RGB; 188 } else if (dparams.force_grayscale) { 189 cinfo.out_color_space = JCS_GRAYSCALE; 190 } 191 if (ReadICCProfile(&cinfo, &ppf->icc)) { 192 ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; 193 } else { 194 ppf->primary_color_representation = 195 PackedPixelFile::kColorEncodingIsPrimary; 196 ppf->icc.clear(); 197 // Default to SRGB 198 ppf->color_encoding.color_space = 199 ConvertColorSpace(cinfo.out_color_space); 200 ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; 201 ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; 202 ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; 203 ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; 204 } 205 ReadExif(&cinfo, &ppf->metadata.exif); 206 207 ppf->info.xsize = cinfo.image_width; 208 ppf->info.ysize = cinfo.image_height; 209 if (dparams.output_data_type == JXL_TYPE_UINT8) { 210 ppf->info.bits_per_sample = 8; 211 ppf->info.exponent_bits_per_sample = 0; 212 } else if (dparams.output_data_type == JXL_TYPE_UINT16) { 213 ppf->info.bits_per_sample = 16; 214 ppf->info.exponent_bits_per_sample = 0; 215 } else if (dparams.output_data_type == JXL_TYPE_FLOAT) { 216 ppf->info.bits_per_sample = 32; 217 ppf->info.exponent_bits_per_sample = 8; 218 } else { 219 return failure("unsupported data type"); 220 } 221 ppf->info.uses_original_profile = JXL_TRUE; 222 223 // No alpha in JPG 224 ppf->info.alpha_bits = 0; 225 ppf->info.alpha_exponent_bits = 0; 226 ppf->info.orientation = JXL_ORIENT_IDENTITY; 227 228 jpegli_set_output_format(&cinfo, ConvertDataType(dparams.output_data_type), 229 ConvertEndianness(dparams.output_endianness)); 230 231 if (dparams.num_colors > 0) { 232 cinfo.quantize_colors = TRUE; 233 cinfo.desired_number_of_colors = dparams.num_colors; 234 cinfo.two_pass_quantize = static_cast<boolean>(dparams.two_pass_quant); 235 cinfo.dither_mode = static_cast<J_DITHER_MODE>(dparams.dither_mode); 236 } 237 238 jpegli_start_decompress(&cinfo); 239 240 ppf->info.num_color_channels = cinfo.out_color_components; 241 const JxlPixelFormat format{ 242 /*num_channels=*/static_cast<uint32_t>(cinfo.out_color_components), 243 dparams.output_data_type, 244 dparams.output_endianness, 245 /*align=*/0, 246 }; 247 ppf->frames.clear(); 248 // Allocates the frame buffer. 249 ppf->frames.emplace_back(cinfo.image_width, cinfo.image_height, format); 250 const auto& frame = ppf->frames.back(); 251 JXL_ASSERT(sizeof(JSAMPLE) * cinfo.out_color_components * 252 cinfo.image_width <= 253 frame.color.stride); 254 255 for (size_t y = 0; y < cinfo.image_height; ++y) { 256 JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>( 257 static_cast<uint8_t*>(frame.color.pixels()) + 258 frame.color.stride * y)}; 259 jpegli_read_scanlines(&cinfo, rows, 1); 260 if (dparams.num_colors > 0) { 261 UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components, 262 cinfo.colormap, cinfo.actual_number_of_colors); 263 } 264 } 265 266 jpegli_finish_decompress(&cinfo); 267 return true; 268 }; 269 bool success = try_catch_block(); 270 jpegli_destroy_decompress(&cinfo); 271 return success; 272 } 273 274 } // namespace extras 275 } // namespace jxl