tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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