tor-browser

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

jpg.cc (22308B)


      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/enc/jpg.h"
      7 
      8 #if JPEGXL_ENABLE_JPEG
      9 #include "lib/jxl/base/include_jpeglib.h"  // NOLINT
     10 #endif
     11 
     12 #include <algorithm>
     13 #include <array>
     14 #include <cmath>
     15 #include <cstdint>
     16 #include <fstream>
     17 #include <memory>
     18 #include <sstream>
     19 #include <utility>
     20 #include <vector>
     21 
     22 #include "lib/extras/exif.h"
     23 #include "lib/jxl/base/common.h"
     24 #include "lib/jxl/base/sanitizers.h"
     25 #include "lib/jxl/base/status.h"
     26 #if JPEGXL_ENABLE_SJPEG
     27 #include "sjpeg.h"
     28 #include "sjpegi.h"
     29 #endif
     30 
     31 namespace jxl {
     32 namespace extras {
     33 
     34 #if JPEGXL_ENABLE_JPEG
     35 namespace {
     36 
     37 constexpr unsigned char kICCSignature[12] = {
     38    0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
     39 constexpr int kICCMarker = JPEG_APP0 + 2;
     40 constexpr size_t kMaxBytesInMarker = 65533;
     41 
     42 constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
     43                                             0x66, 0x00, 0x00};
     44 constexpr int kExifMarker = JPEG_APP0 + 1;
     45 
     46 enum class JpegEncoder {
     47  kLibJpeg,
     48  kSJpeg,
     49 };
     50 
     51 // Popular jpeg scan scripts
     52 // The fields of the individual scans are:
     53 // comps_in_scan, component_index[], Ss, Se, Ah, Al
     54 constexpr auto kScanScript1 = to_array<jpeg_scan_info>({
     55    {1, {0}, 0, 0, 0, 0},   //
     56    {1, {1}, 0, 0, 0, 0},   //
     57    {1, {2}, 0, 0, 0, 0},   //
     58    {1, {0}, 1, 8, 0, 0},   //
     59    {1, {0}, 9, 63, 0, 0},  //
     60    {1, {1}, 1, 63, 0, 0},  //
     61    {1, {2}, 1, 63, 0, 0}   //
     62 });
     63 constexpr size_t kNumScans1 = kScanScript1.size();
     64 
     65 constexpr auto kScanScript2 = to_array<jpeg_scan_info>({
     66    {1, {0}, 0, 0, 0, 0},   //
     67    {1, {1}, 0, 0, 0, 0},   //
     68    {1, {2}, 0, 0, 0, 0},   //
     69    {1, {0}, 1, 2, 0, 1},   //
     70    {1, {0}, 3, 63, 0, 1},  //
     71    {1, {0}, 1, 63, 1, 0},  //
     72    {1, {1}, 1, 63, 0, 0},  //
     73    {1, {2}, 1, 63, 0, 0}   //
     74 });
     75 constexpr size_t kNumScans2 = kScanScript2.size();
     76 
     77 constexpr auto kScanScript3 = to_array<jpeg_scan_info>({
     78    {1, {0}, 0, 0, 0, 0},   //
     79    {1, {1}, 0, 0, 0, 0},   //
     80    {1, {2}, 0, 0, 0, 0},   //
     81    {1, {0}, 1, 63, 0, 2},  //
     82    {1, {0}, 1, 63, 2, 1},  //
     83    {1, {0}, 1, 63, 1, 0},  //
     84    {1, {1}, 1, 63, 0, 0},  //
     85    {1, {2}, 1, 63, 0, 0}   //
     86 });
     87 constexpr size_t kNumScans3 = kScanScript3.size();
     88 
     89 constexpr auto kScanScript4 = to_array<jpeg_scan_info>({
     90    {3, {0, 1, 2}, 0, 0, 0, 1},  //
     91    {1, {0}, 1, 5, 0, 2},        //
     92    {1, {2}, 1, 63, 0, 1},       //
     93    {1, {1}, 1, 63, 0, 1},       //
     94    {1, {0}, 6, 63, 0, 2},       //
     95    {1, {0}, 1, 63, 2, 1},       //
     96    {3, {0, 1, 2}, 0, 0, 1, 0},  //
     97    {1, {2}, 1, 63, 1, 0},       //
     98    {1, {1}, 1, 63, 1, 0},       //
     99    {1, {0}, 1, 63, 1, 0}        //
    100 });
    101 constexpr size_t kNumScans4 = kScanScript4.size();
    102 
    103 constexpr auto kScanScript5 = to_array<jpeg_scan_info>({
    104    {3, {0, 1, 2}, 0, 0, 0, 1},  //
    105    {1, {0}, 1, 5, 0, 2},        //
    106    {1, {1}, 1, 5, 0, 2},        //
    107    {1, {2}, 1, 5, 0, 2},        //
    108    {1, {1}, 6, 63, 0, 2},       //
    109    {1, {2}, 6, 63, 0, 2},       //
    110    {1, {0}, 6, 63, 0, 2},       //
    111    {1, {0}, 1, 63, 2, 1},       //
    112    {1, {1}, 1, 63, 2, 1},       //
    113    {1, {2}, 1, 63, 2, 1},       //
    114    {3, {0, 1, 2}, 0, 0, 1, 0},  //
    115    {1, {0}, 1, 63, 1, 0},       //
    116    {1, {1}, 1, 63, 1, 0},       //
    117    {1, {2}, 1, 63, 1, 0}        //
    118 });
    119 constexpr size_t kNumScans5 = kScanScript5.size();
    120 
    121 // default progressive mode of jpegli
    122 constexpr auto kScanScript6 = to_array<jpeg_scan_info>({
    123    {3, {0, 1, 2}, 0, 0, 0, 0},  //
    124    {1, {0}, 1, 2, 0, 0},        //
    125    {1, {1}, 1, 2, 0, 0},        //
    126    {1, {2}, 1, 2, 0, 0},        //
    127    {1, {0}, 3, 63, 0, 2},       //
    128    {1, {1}, 3, 63, 0, 2},       //
    129    {1, {2}, 3, 63, 0, 2},       //
    130    {1, {0}, 3, 63, 2, 1},       //
    131    {1, {1}, 3, 63, 2, 1},       //
    132    {1, {2}, 3, 63, 2, 1},       //
    133    {1, {0}, 3, 63, 1, 0},       //
    134    {1, {1}, 3, 63, 1, 0},       //
    135    {1, {2}, 3, 63, 1, 0},       //
    136 });
    137 constexpr size_t kNumScans6 = kScanScript6.size();
    138 
    139 // Adapt RGB scan info to grayscale jpegs.
    140 void FilterScanComponents(const jpeg_compress_struct* cinfo,
    141                          jpeg_scan_info* si) {
    142  const int all_comps_in_scan = si->comps_in_scan;
    143  si->comps_in_scan = 0;
    144  for (int j = 0; j < all_comps_in_scan; ++j) {
    145    const int component = si->component_index[j];
    146    if (component < cinfo->input_components) {
    147      si->component_index[si->comps_in_scan++] = component;
    148    }
    149  }
    150 }
    151 
    152 Status SetJpegProgression(int progressive_id,
    153                          std::vector<jpeg_scan_info>* scan_infos,
    154                          jpeg_compress_struct* cinfo) {
    155  if (progressive_id < 0) {
    156    return true;
    157  }
    158  if (progressive_id == 0) {
    159    jpeg_simple_progression(cinfo);
    160    return true;
    161  }
    162  const jpeg_scan_info* kScanScripts[] = {
    163      kScanScript1.data(), kScanScript2.data(), kScanScript3.data(),
    164      kScanScript4.data(), kScanScript5.data(), kScanScript6.data()};
    165  constexpr auto kNumScans = to_array<size_t>(
    166      {kNumScans1, kNumScans2, kNumScans3, kNumScans4, kNumScans5, kNumScans6});
    167  if (progressive_id > static_cast<int>(kNumScans.size())) {
    168    return JXL_FAILURE("Unknown jpeg scan script id %d", progressive_id);
    169  }
    170  const jpeg_scan_info* scan_script = kScanScripts[progressive_id - 1];
    171  const size_t num_scans = kNumScans[progressive_id - 1];
    172  // filter scan script for number of components
    173  for (size_t i = 0; i < num_scans; ++i) {
    174    jpeg_scan_info scan_info = scan_script[i];
    175    FilterScanComponents(cinfo, &scan_info);
    176    if (scan_info.comps_in_scan > 0) {
    177      scan_infos->emplace_back(scan_info);
    178    }
    179  }
    180  cinfo->scan_info = scan_infos->data();
    181  cinfo->num_scans = scan_infos->size();
    182  return true;
    183 }
    184 
    185 void WriteICCProfile(jpeg_compress_struct* const cinfo,
    186                     const std::vector<uint8_t>& icc) {
    187  constexpr size_t kMaxIccBytesInMarker =
    188      kMaxBytesInMarker - sizeof kICCSignature - 2;
    189  const int num_markers =
    190      static_cast<int>(DivCeil(icc.size(), kMaxIccBytesInMarker));
    191  size_t begin = 0;
    192  for (int current_marker = 0; current_marker < num_markers; ++current_marker) {
    193    const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin);
    194    jpeg_write_m_header(
    195        cinfo, kICCMarker,
    196        static_cast<unsigned int>(length + sizeof kICCSignature + 2));
    197    for (const unsigned char c : kICCSignature) {
    198      jpeg_write_m_byte(cinfo, c);
    199    }
    200    jpeg_write_m_byte(cinfo, current_marker + 1);
    201    jpeg_write_m_byte(cinfo, num_markers);
    202    for (size_t i = 0; i < length; ++i) {
    203      jpeg_write_m_byte(cinfo, icc[begin]);
    204      ++begin;
    205    }
    206  }
    207 }
    208 void WriteExif(jpeg_compress_struct* const cinfo,
    209               const std::vector<uint8_t>& exif) {
    210  jpeg_write_m_header(
    211      cinfo, kExifMarker,
    212      static_cast<unsigned int>(exif.size() + sizeof kExifSignature));
    213  for (const unsigned char c : kExifSignature) {
    214    jpeg_write_m_byte(cinfo, c);
    215  }
    216  for (uint8_t c : exif) {
    217    jpeg_write_m_byte(cinfo, c);
    218  }
    219 }
    220 
    221 Status SetChromaSubsampling(const std::string& subsampling,
    222                            jpeg_compress_struct* const cinfo) {
    223  const std::pair<const char*,
    224                  std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>>
    225      options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}},
    226                   {"420", {{{2, 1, 1}}, {{2, 1, 1}}}},
    227                   {"422", {{{2, 1, 1}}, {{1, 1, 1}}}},
    228                   {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}};
    229  for (const auto& option : options) {
    230    if (subsampling == option.first) {
    231      for (size_t i = 0; i < 3; i++) {
    232        cinfo->comp_info[i].h_samp_factor = option.second.first[i];
    233        cinfo->comp_info[i].v_samp_factor = option.second.second[i];
    234      }
    235      return true;
    236    }
    237  }
    238  return false;
    239 }
    240 
    241 struct JpegParams {
    242  // Common between sjpeg and libjpeg
    243  int quality = 100;
    244  std::string chroma_subsampling = "444";
    245  // Libjpeg parameters
    246  int progressive_id = -1;
    247  bool optimize_coding = true;
    248  bool is_xyb = false;
    249  // Sjpeg parameters
    250  int libjpeg_quality = 0;
    251  std::string libjpeg_chroma_subsampling = "444";
    252  float psnr_target = 0;
    253  std::string custom_base_quant_fn;
    254  float search_q_start = 65.0f;
    255  float search_q_min = 1.0f;
    256  float search_q_max = 100.0f;
    257  int search_max_iters = 20;
    258  float search_tolerance = 0.1f;
    259  float search_q_precision = 0.01f;
    260  float search_first_iter_slope = 3.0f;
    261  bool enable_adaptive_quant = true;
    262 };
    263 
    264 Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
    265                         const std::vector<uint8_t>& icc,
    266                         std::vector<uint8_t> exif, const JpegParams& params,
    267                         std::vector<uint8_t>* bytes) {
    268  if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) {
    269    return JXL_FAILURE("Only 8 bit JSAMPLE is supported.");
    270  }
    271  jpeg_compress_struct cinfo = {};
    272  jpeg_error_mgr jerr;
    273  cinfo.err = jpeg_std_error(&jerr);
    274  jpeg_create_compress(&cinfo);
    275  unsigned char* buffer = nullptr;
    276 #ifdef LIBJPEG_TURBO_VERSION
    277  unsigned long size = 0;  // NOLINT
    278 #else
    279  size_t size = 0;  // NOLINT
    280 #endif
    281  jpeg_mem_dest(&cinfo, &buffer, &size);
    282  cinfo.image_width = image.xsize;
    283  cinfo.image_height = image.ysize;
    284  cinfo.input_components = info.num_color_channels;
    285  cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
    286  jpeg_set_defaults(&cinfo);
    287  cinfo.optimize_coding = static_cast<boolean>(params.optimize_coding);
    288  if (cinfo.input_components == 3) {
    289    JXL_RETURN_IF_ERROR(
    290        SetChromaSubsampling(params.chroma_subsampling, &cinfo));
    291  }
    292  if (params.is_xyb) {
    293    // Tell libjpeg not to convert XYB data to YCbCr.
    294    jpeg_set_colorspace(&cinfo, JCS_RGB);
    295  }
    296  jpeg_set_quality(&cinfo, params.quality, TRUE);
    297  std::vector<jpeg_scan_info> scan_infos;
    298  JXL_RETURN_IF_ERROR(
    299      SetJpegProgression(params.progressive_id, &scan_infos, &cinfo));
    300  jpeg_start_compress(&cinfo, TRUE);
    301  if (!icc.empty()) {
    302    WriteICCProfile(&cinfo, icc);
    303  }
    304  if (!exif.empty()) {
    305    ResetExifOrientation(exif);
    306    WriteExif(&cinfo, exif);
    307  }
    308  if (cinfo.input_components > 3 || cinfo.input_components < 0)
    309    return JXL_FAILURE("invalid numbers of components");
    310 
    311  std::vector<uint8_t> row_bytes(image.stride);
    312  const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
    313  if (cinfo.num_components == static_cast<int>(image.format.num_channels) &&
    314      image.format.data_type == JXL_TYPE_UINT8) {
    315    for (size_t y = 0; y < info.ysize; ++y) {
    316      memcpy(row_bytes.data(), pixels + y * image.stride, image.stride);
    317      JSAMPROW row[] = {row_bytes.data()};
    318      jpeg_write_scanlines(&cinfo, row, 1);
    319    }
    320  } else if (image.format.data_type == JXL_TYPE_UINT8) {
    321    for (size_t y = 0; y < info.ysize; ++y) {
    322      const uint8_t* image_row = pixels + y * image.stride;
    323      for (size_t x = 0; x < info.xsize; ++x) {
    324        const uint8_t* image_pixel = image_row + x * image.pixel_stride();
    325        memcpy(&row_bytes[x * cinfo.num_components], image_pixel,
    326               cinfo.num_components);
    327      }
    328      JSAMPROW row[] = {row_bytes.data()};
    329      jpeg_write_scanlines(&cinfo, row, 1);
    330    }
    331  } else {
    332    for (size_t y = 0; y < info.ysize; ++y) {
    333      const uint8_t* image_row = pixels + y * image.stride;
    334      for (size_t x = 0; x < info.xsize; ++x) {
    335        const uint8_t* image_pixel = image_row + x * image.pixel_stride();
    336        for (int c = 0; c < cinfo.num_components; ++c) {
    337          uint32_t val16 = (image_pixel[2 * c] << 8) + image_pixel[2 * c + 1];
    338          row_bytes[x * cinfo.num_components + c] = (val16 + 128) / 257;
    339        }
    340      }
    341      JSAMPROW row[] = {row_bytes.data()};
    342      jpeg_write_scanlines(&cinfo, row, 1);
    343    }
    344  }
    345  jpeg_finish_compress(&cinfo);
    346  jpeg_destroy_compress(&cinfo);
    347  bytes->resize(size);
    348  // Compressed image data is initialized by libjpeg, which we are not
    349  // instrumenting with msan.
    350  msan::UnpoisonMemory(buffer, size);
    351  std::copy_n(buffer, size, bytes->data());
    352  std::free(buffer);
    353  return true;
    354 }
    355 
    356 #if JPEGXL_ENABLE_SJPEG
    357 struct MySearchHook : public sjpeg::SearchHook {
    358  uint8_t base_tables[2][64];
    359  float q_start;
    360  float q_precision;
    361  float first_iter_slope;
    362  void ReadBaseTables(const std::string& fn) {
    363    const uint8_t kJPEGAnnexKMatrices[2][64] = {
    364        {16, 11, 10, 16, 24,  40,  51,  61,  12, 12, 14, 19, 26,  58,  60,  55,
    365         14, 13, 16, 24, 40,  57,  69,  56,  14, 17, 22, 29, 51,  87,  80,  62,
    366         18, 22, 37, 56, 68,  109, 103, 77,  24, 35, 55, 64, 81,  104, 113, 92,
    367         49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99},
    368        {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99,
    369         24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99,
    370         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
    371         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}};
    372    memcpy(base_tables[0], kJPEGAnnexKMatrices[0], sizeof(base_tables[0]));
    373    memcpy(base_tables[1], kJPEGAnnexKMatrices[1], sizeof(base_tables[1]));
    374    if (!fn.empty()) {
    375      std::ifstream f(fn);
    376      std::string line;
    377      int idx = 0;
    378      while (idx < 128 && std::getline(f, line)) {
    379        if (line.empty() || line[0] == '#') continue;
    380        std::istringstream line_stream(line);
    381        std::string token;
    382        while (idx < 128 && std::getline(line_stream, token, ',')) {
    383          uint8_t val = std::stoi(token);
    384          base_tables[idx / 64][idx % 64] = val;
    385          idx++;
    386        }
    387      }
    388    }
    389  }
    390  bool Setup(const sjpeg::EncoderParam& param) override {
    391    sjpeg::SearchHook::Setup(param);
    392    q = q_start;
    393    return true;
    394  }
    395  void NextMatrix(int idx, uint8_t dst[64]) override {
    396    float factor = (q <= 0)       ? 5000.0f
    397                   : (q < 50.0f)  ? 5000.0f / q
    398                   : (q < 100.0f) ? 2 * (100.0f - q)
    399                                  : 0.0f;
    400    sjpeg::SetQuantMatrix(base_tables[idx], factor, dst);
    401  }
    402  bool Update(float result) override {
    403    value = result;
    404    if (std::fabs(value - target) < tolerance * target) {
    405      return true;
    406    }
    407    if (value > target) {
    408      qmax = q;
    409    } else {
    410      qmin = q;
    411    }
    412    if (qmin == qmax) {
    413      return true;
    414    }
    415    const float last_q = q;
    416    if (pass == 0) {
    417      q += first_iter_slope *
    418           (for_size ? 0.1 * std::log(target / value) : (target - value));
    419      q = std::max(qmin, std::min(qmax, q));
    420    } else {
    421      q = (qmin + qmax) / 2.;
    422    }
    423    return (pass > 0 && std::fabs(q - last_q) < q_precision);
    424  }
    425  ~MySearchHook() override = default;
    426 };
    427 #endif
    428 
    429 Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
    430                       const std::vector<uint8_t>& icc,
    431                       std::vector<uint8_t> exif, const JpegParams& params,
    432                       std::vector<uint8_t>* bytes) {
    433 #if !JPEGXL_ENABLE_SJPEG
    434  return JXL_FAILURE("JPEG XL was built without sjpeg support");
    435 #else
    436  if (image.format.data_type != JXL_TYPE_UINT8) {
    437    return JXL_FAILURE("Unsupported pixel data type");
    438  }
    439  if (info.alpha_bits > 0) {
    440    return JXL_FAILURE("alpha is not supported");
    441  }
    442  sjpeg::EncoderParam param(params.quality);
    443  if (!icc.empty()) {
    444    param.iccp.assign(icc.begin(), icc.end());
    445  }
    446  if (!exif.empty()) {
    447    ResetExifOrientation(exif);
    448    param.exif.assign(exif.begin(), exif.end());
    449  }
    450  if (params.chroma_subsampling == "444") {
    451    param.yuv_mode = SJPEG_YUV_444;
    452  } else if (params.chroma_subsampling == "420") {
    453    param.yuv_mode = SJPEG_YUV_420;
    454  } else if (params.chroma_subsampling == "420sharp") {
    455    param.yuv_mode = SJPEG_YUV_SHARP;
    456  } else {
    457    return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
    458  }
    459  param.adaptive_quantization = params.enable_adaptive_quant;
    460  std::unique_ptr<MySearchHook> hook;
    461  if (params.libjpeg_quality > 0) {
    462    JpegParams libjpeg_params;
    463    libjpeg_params.quality = params.libjpeg_quality;
    464    libjpeg_params.chroma_subsampling = params.libjpeg_chroma_subsampling;
    465    std::vector<uint8_t> libjpeg_bytes;
    466    JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, exif,
    467                                          libjpeg_params, &libjpeg_bytes));
    468    param.target_mode = sjpeg::EncoderParam::TARGET_SIZE;
    469    param.target_value = libjpeg_bytes.size();
    470  }
    471  if (params.psnr_target > 0) {
    472    param.target_mode = sjpeg::EncoderParam::TARGET_PSNR;
    473    param.target_value = params.psnr_target;
    474  }
    475  if (param.target_mode != sjpeg::EncoderParam::TARGET_NONE) {
    476    param.passes = params.search_max_iters;
    477    param.tolerance = params.search_tolerance;
    478    param.qmin = params.search_q_min;
    479    param.qmax = params.search_q_max;
    480    hook = jxl::make_unique<MySearchHook>();
    481    hook->ReadBaseTables(params.custom_base_quant_fn);
    482    hook->q_start = params.search_q_start;
    483    hook->q_precision = params.search_q_precision;
    484    hook->first_iter_slope = params.search_first_iter_slope;
    485    param.search_hook = hook.get();
    486  }
    487  size_t stride = info.xsize * 3;
    488  const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
    489  std::string output;
    490  JXL_RETURN_IF_ERROR(
    491      sjpeg::Encode(pixels, image.xsize, image.ysize, stride, param, &output));
    492  bytes->assign(
    493      reinterpret_cast<const uint8_t*>(output.data()),
    494      reinterpret_cast<const uint8_t*>(output.data() + output.size()));
    495  return true;
    496 #endif
    497 }
    498 
    499 Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
    500                      const std::vector<uint8_t>& icc,
    501                      std::vector<uint8_t> exif, JpegEncoder encoder,
    502                      const JpegParams& params, ThreadPool* pool,
    503                      std::vector<uint8_t>* bytes) {
    504  if (params.quality > 100) {
    505    return JXL_FAILURE("please specify a 0-100 JPEG quality");
    506  }
    507 
    508  switch (encoder) {
    509    case JpegEncoder::kLibJpeg:
    510      JXL_RETURN_IF_ERROR(
    511          EncodeWithLibJpeg(image, info, icc, std::move(exif), params, bytes));
    512      break;
    513    case JpegEncoder::kSJpeg:
    514      JXL_RETURN_IF_ERROR(
    515          EncodeWithSJpeg(image, info, icc, std::move(exif), params, bytes));
    516      break;
    517    default:
    518      return JXL_FAILURE("tried to use an unknown JPEG encoder");
    519  }
    520 
    521  return true;
    522 }
    523 
    524 class JPEGEncoder : public Encoder {
    525  std::vector<JxlPixelFormat> AcceptedFormats() const override {
    526    std::vector<JxlPixelFormat> formats;
    527    for (const uint32_t num_channels : {1, 2, 3, 4}) {
    528      for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
    529        formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
    530                                         /*data_type=*/JXL_TYPE_UINT8,
    531                                         /*endianness=*/endianness,
    532                                         /*align=*/0});
    533      }
    534      formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
    535                                       /*data_type=*/JXL_TYPE_UINT16,
    536                                       /*endianness=*/JXL_BIG_ENDIAN,
    537                                       /*align=*/0});
    538    }
    539    return formats;
    540  }
    541  Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
    542                ThreadPool* pool) const override {
    543    JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
    544    JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
    545    JpegParams params;
    546    for (const auto& it : options()) {
    547      if (it.first == "q") {
    548        std::istringstream is(it.second);
    549        JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.quality));
    550      } else if (it.first == "libjpeg_quality") {
    551        std::istringstream is(it.second);
    552        JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.libjpeg_quality));
    553      } else if (it.first == "chroma_subsampling") {
    554        params.chroma_subsampling = it.second;
    555      } else if (it.first == "libjpeg_chroma_subsampling") {
    556        params.libjpeg_chroma_subsampling = it.second;
    557      } else if (it.first == "jpeg_encoder") {
    558        if (it.second == "libjpeg") {
    559          jpeg_encoder = JpegEncoder::kLibJpeg;
    560        } else if (it.second == "sjpeg") {
    561          jpeg_encoder = JpegEncoder::kSJpeg;
    562        } else {
    563          return JXL_FAILURE("unknown jpeg encoder \"%s\"", it.second.c_str());
    564        }
    565      } else if (it.first == "progressive") {
    566        std::istringstream is(it.second);
    567        JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.progressive_id));
    568      } else if (it.first == "optimize" && it.second == "OFF") {
    569        params.optimize_coding = false;
    570      } else if (it.first == "adaptive_q" && it.second == "OFF") {
    571        params.enable_adaptive_quant = false;
    572      } else if (it.first == "psnr") {
    573        params.psnr_target = std::stof(it.second);
    574      } else if (it.first == "base_quant_fn") {
    575        params.custom_base_quant_fn = it.second;
    576      } else if (it.first == "search_q_start") {
    577        params.search_q_start = std::stof(it.second);
    578      } else if (it.first == "search_q_min") {
    579        params.search_q_min = std::stof(it.second);
    580      } else if (it.first == "search_q_max") {
    581        params.search_q_max = std::stof(it.second);
    582      } else if (it.first == "search_max_iters") {
    583        params.search_max_iters = std::stoi(it.second);
    584      } else if (it.first == "search_tolerance") {
    585        params.search_tolerance = std::stof(it.second);
    586      } else if (it.first == "search_q_precision") {
    587        params.search_q_precision = std::stof(it.second);
    588      } else if (it.first == "search_first_iter_slope") {
    589        params.search_first_iter_slope = std::stof(it.second);
    590      }
    591    }
    592    params.is_xyb = (ppf.color_encoding.color_space == JXL_COLOR_SPACE_XYB);
    593    encoded_image->bitstreams.clear();
    594    encoded_image->bitstreams.reserve(ppf.frames.size());
    595    for (const auto& frame : ppf.frames) {
    596      JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
    597      encoded_image->bitstreams.emplace_back();
    598      JXL_RETURN_IF_ERROR(EncodeImageJPG(
    599          frame.color, ppf.info, ppf.icc, ppf.metadata.exif, jpeg_encoder,
    600          params, pool, &encoded_image->bitstreams.back()));
    601    }
    602    return true;
    603  }
    604 };
    605 
    606 }  // namespace
    607 #endif
    608 
    609 std::unique_ptr<Encoder> GetJPEGEncoder() {
    610 #if JPEGXL_ENABLE_JPEG
    611  return jxl::make_unique<JPEGEncoder>();
    612 #else
    613  return nullptr;
    614 #endif
    615 }
    616 
    617 }  // namespace extras
    618 }  // namespace jxl