libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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