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