libjxl

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

jpg.cc (22239B)


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