summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/extras/enc/jpg.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/jpeg-xl/lib/extras/enc/jpg.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/jpeg-xl/lib/extras/enc/jpg.cc')
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpg.cc618
1 files changed, 618 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/extras/enc/jpg.cc b/third_party/jpeg-xl/lib/extras/enc/jpg.cc
new file mode 100644
index 0000000000..c34dc6c13f
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jpg.cc
@@ -0,0 +1,618 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/enc/jpg.h"
+
+#if JPEGXL_ENABLE_JPEG
+#include <jpeglib.h>
+#include <setjmp.h>
+#endif
+#include <stdint.h>
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <fstream>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "lib/extras/exif.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/sanitizers.h"
+#if JPEGXL_ENABLE_SJPEG
+#include "sjpeg.h"
+#include "sjpegi.h"
+#endif
+
+namespace jxl {
+namespace extras {
+
+#if JPEGXL_ENABLE_JPEG
+namespace {
+
+constexpr unsigned char kICCSignature[12] = {
+ 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
+constexpr int kICCMarker = JPEG_APP0 + 2;
+constexpr size_t kMaxBytesInMarker = 65533;
+
+constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
+ 0x66, 0x00, 0x00};
+constexpr int kExifMarker = JPEG_APP0 + 1;
+
+enum class JpegEncoder {
+ kLibJpeg,
+ kSJpeg,
+};
+
+#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
+
+// Popular jpeg scan scripts
+// The fields of the individual scans are:
+// comps_in_scan, component_index[], Ss, Se, Ah, Al
+static constexpr jpeg_scan_info kScanScript1[] = {
+ {1, {0}, 0, 0, 0, 0}, //
+ {1, {1}, 0, 0, 0, 0}, //
+ {1, {2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 8, 0, 0}, //
+ {1, {0}, 9, 63, 0, 0}, //
+ {1, {1}, 1, 63, 0, 0}, //
+ {1, {2}, 1, 63, 0, 0}, //
+};
+static constexpr size_t kNumScans1 = ARRAY_SIZE(kScanScript1);
+
+static constexpr jpeg_scan_info kScanScript2[] = {
+ {1, {0}, 0, 0, 0, 0}, //
+ {1, {1}, 0, 0, 0, 0}, //
+ {1, {2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 2, 0, 1}, //
+ {1, {0}, 3, 63, 0, 1}, //
+ {1, {0}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 0, 0}, //
+ {1, {2}, 1, 63, 0, 0}, //
+};
+static constexpr size_t kNumScans2 = ARRAY_SIZE(kScanScript2);
+
+static constexpr jpeg_scan_info kScanScript3[] = {
+ {1, {0}, 0, 0, 0, 0}, //
+ {1, {1}, 0, 0, 0, 0}, //
+ {1, {2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 63, 0, 2}, //
+ {1, {0}, 1, 63, 2, 1}, //
+ {1, {0}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 0, 0}, //
+ {1, {2}, 1, 63, 0, 0}, //
+};
+static constexpr size_t kNumScans3 = ARRAY_SIZE(kScanScript3);
+
+static constexpr jpeg_scan_info kScanScript4[] = {
+ {3, {0, 1, 2}, 0, 0, 0, 1}, //
+ {1, {0}, 1, 5, 0, 2}, //
+ {1, {2}, 1, 63, 0, 1}, //
+ {1, {1}, 1, 63, 0, 1}, //
+ {1, {0}, 6, 63, 0, 2}, //
+ {1, {0}, 1, 63, 2, 1}, //
+ {3, {0, 1, 2}, 0, 0, 1, 0}, //
+ {1, {2}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 1, 0}, //
+ {1, {0}, 1, 63, 1, 0}, //
+};
+static constexpr size_t kNumScans4 = ARRAY_SIZE(kScanScript4);
+
+static constexpr jpeg_scan_info kScanScript5[] = {
+ {3, {0, 1, 2}, 0, 0, 0, 1}, //
+ {1, {0}, 1, 5, 0, 2}, //
+ {1, {1}, 1, 5, 0, 2}, //
+ {1, {2}, 1, 5, 0, 2}, //
+ {1, {1}, 6, 63, 0, 2}, //
+ {1, {2}, 6, 63, 0, 2}, //
+ {1, {0}, 6, 63, 0, 2}, //
+ {1, {0}, 1, 63, 2, 1}, //
+ {1, {1}, 1, 63, 2, 1}, //
+ {1, {2}, 1, 63, 2, 1}, //
+ {3, {0, 1, 2}, 0, 0, 1, 0}, //
+ {1, {0}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 1, 0}, //
+ {1, {2}, 1, 63, 1, 0}, //
+};
+static constexpr size_t kNumScans5 = ARRAY_SIZE(kScanScript5);
+
+// default progressive mode of jpegli
+static constexpr jpeg_scan_info kScanScript6[] = {
+ {3, {0, 1, 2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 2, 0, 0}, //
+ {1, {1}, 1, 2, 0, 0}, //
+ {1, {2}, 1, 2, 0, 0}, //
+ {1, {0}, 3, 63, 0, 2}, //
+ {1, {1}, 3, 63, 0, 2}, //
+ {1, {2}, 3, 63, 0, 2}, //
+ {1, {0}, 3, 63, 2, 1}, //
+ {1, {1}, 3, 63, 2, 1}, //
+ {1, {2}, 3, 63, 2, 1}, //
+ {1, {0}, 3, 63, 1, 0}, //
+ {1, {1}, 3, 63, 1, 0}, //
+ {1, {2}, 3, 63, 1, 0}, //
+};
+static constexpr size_t kNumScans6 = ARRAY_SIZE(kScanScript6);
+
+// Adapt RGB scan info to grayscale jpegs.
+void FilterScanComponents(const jpeg_compress_struct* cinfo,
+ jpeg_scan_info* si) {
+ const int all_comps_in_scan = si->comps_in_scan;
+ si->comps_in_scan = 0;
+ for (int j = 0; j < all_comps_in_scan; ++j) {
+ const int component = si->component_index[j];
+ if (component < cinfo->input_components) {
+ si->component_index[si->comps_in_scan++] = component;
+ }
+ }
+}
+
+Status SetJpegProgression(int progressive_id,
+ std::vector<jpeg_scan_info>* scan_infos,
+ jpeg_compress_struct* cinfo) {
+ if (progressive_id < 0) {
+ return true;
+ }
+ if (progressive_id == 0) {
+ jpeg_simple_progression(cinfo);
+ return true;
+ }
+ constexpr const jpeg_scan_info* kScanScripts[] = {kScanScript1, kScanScript2,
+ kScanScript3, kScanScript4,
+ kScanScript5, kScanScript6};
+ constexpr size_t kNumScans[] = {kNumScans1, kNumScans2, kNumScans3,
+ kNumScans4, kNumScans5, kNumScans6};
+ if (progressive_id > static_cast<int>(ARRAY_SIZE(kNumScans))) {
+ return JXL_FAILURE("Unknown jpeg scan script id %d", progressive_id);
+ }
+ const jpeg_scan_info* scan_script = kScanScripts[progressive_id - 1];
+ const size_t num_scans = kNumScans[progressive_id - 1];
+ // filter scan script for number of components
+ for (size_t i = 0; i < num_scans; ++i) {
+ jpeg_scan_info scan_info = scan_script[i];
+ FilterScanComponents(cinfo, &scan_info);
+ if (scan_info.comps_in_scan > 0) {
+ scan_infos->emplace_back(std::move(scan_info));
+ }
+ }
+ cinfo->scan_info = scan_infos->data();
+ cinfo->num_scans = scan_infos->size();
+ return true;
+}
+
+void WriteICCProfile(jpeg_compress_struct* const cinfo,
+ const std::vector<uint8_t>& icc) {
+ constexpr size_t kMaxIccBytesInMarker =
+ kMaxBytesInMarker - sizeof kICCSignature - 2;
+ const int num_markers =
+ static_cast<int>(DivCeil(icc.size(), kMaxIccBytesInMarker));
+ size_t begin = 0;
+ for (int current_marker = 0; current_marker < num_markers; ++current_marker) {
+ const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin);
+ jpeg_write_m_header(
+ cinfo, kICCMarker,
+ static_cast<unsigned int>(length + sizeof kICCSignature + 2));
+ for (const unsigned char c : kICCSignature) {
+ jpeg_write_m_byte(cinfo, c);
+ }
+ jpeg_write_m_byte(cinfo, current_marker + 1);
+ jpeg_write_m_byte(cinfo, num_markers);
+ for (size_t i = 0; i < length; ++i) {
+ jpeg_write_m_byte(cinfo, icc[begin]);
+ ++begin;
+ }
+ }
+}
+void WriteExif(jpeg_compress_struct* const cinfo,
+ const std::vector<uint8_t>& exif) {
+ jpeg_write_m_header(
+ cinfo, kExifMarker,
+ static_cast<unsigned int>(exif.size() + sizeof kExifSignature));
+ for (const unsigned char c : kExifSignature) {
+ jpeg_write_m_byte(cinfo, c);
+ }
+ for (size_t i = 0; i < exif.size(); ++i) {
+ jpeg_write_m_byte(cinfo, exif[i]);
+ }
+}
+
+Status SetChromaSubsampling(const std::string& subsampling,
+ jpeg_compress_struct* const cinfo) {
+ const std::pair<const char*,
+ std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>>
+ options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}},
+ {"420", {{{2, 1, 1}}, {{2, 1, 1}}}},
+ {"422", {{{2, 1, 1}}, {{1, 1, 1}}}},
+ {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}};
+ for (const auto& option : options) {
+ if (subsampling == option.first) {
+ for (size_t i = 0; i < 3; i++) {
+ cinfo->comp_info[i].h_samp_factor = option.second.first[i];
+ cinfo->comp_info[i].v_samp_factor = option.second.second[i];
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+struct JpegParams {
+ // Common between sjpeg and libjpeg
+ int quality = 100;
+ std::string chroma_subsampling = "444";
+ // Libjpeg parameters
+ int progressive_id = -1;
+ bool optimize_coding = true;
+ bool is_xyb = false;
+ // Sjpeg parameters
+ int libjpeg_quality = 0;
+ std::string libjpeg_chroma_subsampling = "444";
+ float psnr_target = 0;
+ std::string custom_base_quant_fn;
+ float search_q_start = 65.0f;
+ float search_q_min = 1.0f;
+ float search_q_max = 100.0f;
+ int search_max_iters = 20;
+ float search_tolerance = 0.1f;
+ float search_q_precision = 0.01f;
+ float search_first_iter_slope = 3.0f;
+ bool enable_adaptive_quant = true;
+};
+
+Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, const JpegParams& params,
+ std::vector<uint8_t>* bytes) {
+ if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) {
+ return JXL_FAILURE("Only 8 bit JSAMPLE is supported.");
+ }
+ jpeg_compress_struct cinfo = {};
+ jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ unsigned char* buffer = nullptr;
+ unsigned long size = 0;
+ jpeg_mem_dest(&cinfo, &buffer, &size);
+ cinfo.image_width = image.xsize;
+ cinfo.image_height = image.ysize;
+ cinfo.input_components = info.num_color_channels;
+ cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
+ jpeg_set_defaults(&cinfo);
+ cinfo.optimize_coding = params.optimize_coding;
+ if (cinfo.input_components == 3) {
+ JXL_RETURN_IF_ERROR(
+ SetChromaSubsampling(params.chroma_subsampling, &cinfo));
+ }
+ if (params.is_xyb) {
+ // Tell libjpeg not to convert XYB data to YCbCr.
+ jpeg_set_colorspace(&cinfo, JCS_RGB);
+ }
+ jpeg_set_quality(&cinfo, params.quality, TRUE);
+ std::vector<jpeg_scan_info> scan_infos;
+ JXL_RETURN_IF_ERROR(
+ SetJpegProgression(params.progressive_id, &scan_infos, &cinfo));
+ jpeg_start_compress(&cinfo, TRUE);
+ if (!icc.empty()) {
+ WriteICCProfile(&cinfo, icc);
+ }
+ if (!exif.empty()) {
+ ResetExifOrientation(exif);
+ WriteExif(&cinfo, exif);
+ }
+ if (cinfo.input_components > 3 || cinfo.input_components < 0)
+ return JXL_FAILURE("invalid numbers of components");
+
+ std::vector<uint8_t> row_bytes(image.stride);
+ const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
+ if (cinfo.num_components == (int)image.format.num_channels &&
+ image.format.data_type == JXL_TYPE_UINT8) {
+ for (size_t y = 0; y < info.ysize; ++y) {
+ memcpy(&row_bytes[0], pixels + y * image.stride, image.stride);
+ JSAMPROW row[] = {row_bytes.data()};
+ jpeg_write_scanlines(&cinfo, row, 1);
+ }
+ } else if (image.format.data_type == JXL_TYPE_UINT8) {
+ for (size_t y = 0; y < info.ysize; ++y) {
+ const uint8_t* image_row = pixels + y * image.stride;
+ for (size_t x = 0; x < info.xsize; ++x) {
+ const uint8_t* image_pixel = image_row + x * image.pixel_stride();
+ memcpy(&row_bytes[x * cinfo.num_components], image_pixel,
+ cinfo.num_components);
+ }
+ JSAMPROW row[] = {row_bytes.data()};
+ jpeg_write_scanlines(&cinfo, row, 1);
+ }
+ } else {
+ for (size_t y = 0; y < info.ysize; ++y) {
+ const uint8_t* image_row = pixels + y * image.stride;
+ for (size_t x = 0; x < info.xsize; ++x) {
+ const uint8_t* image_pixel = image_row + x * image.pixel_stride();
+ for (int c = 0; c < cinfo.num_components; ++c) {
+ uint32_t val16 = (image_pixel[2 * c] << 8) + image_pixel[2 * c + 1];
+ row_bytes[x * cinfo.num_components + c] = (val16 + 128) / 257;
+ }
+ }
+ JSAMPROW row[] = {row_bytes.data()};
+ jpeg_write_scanlines(&cinfo, row, 1);
+ }
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+ bytes->resize(size);
+ // Compressed image data is initialized by libjpeg, which we are not
+ // instrumenting with msan.
+ msan::UnpoisonMemory(buffer, size);
+ std::copy_n(buffer, size, bytes->data());
+ std::free(buffer);
+ return true;
+}
+
+#if JPEGXL_ENABLE_SJPEG
+struct MySearchHook : public sjpeg::SearchHook {
+ uint8_t base_tables[2][64];
+ float q_start;
+ float q_precision;
+ float first_iter_slope;
+ void ReadBaseTables(const std::string& fn) {
+ const uint8_t kJPEGAnnexKMatrices[2][64] = {
+ {16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92,
+ 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99},
+ {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}};
+ memcpy(base_tables[0], kJPEGAnnexKMatrices[0], sizeof(base_tables[0]));
+ memcpy(base_tables[1], kJPEGAnnexKMatrices[1], sizeof(base_tables[1]));
+ if (!fn.empty()) {
+ std::ifstream f(fn);
+ std::string line;
+ int idx = 0;
+ while (idx < 128 && std::getline(f, line)) {
+ if (line.empty() || line[0] == '#') continue;
+ std::istringstream line_stream(line);
+ std::string token;
+ while (idx < 128 && std::getline(line_stream, token, ',')) {
+ uint8_t val = std::stoi(token);
+ base_tables[idx / 64][idx % 64] = val;
+ idx++;
+ }
+ }
+ }
+ }
+ bool Setup(const sjpeg::EncoderParam& param) override {
+ sjpeg::SearchHook::Setup(param);
+ q = q_start;
+ return true;
+ }
+ void NextMatrix(int idx, uint8_t dst[64]) override {
+ float factor = (q <= 0) ? 5000.0f
+ : (q < 50.0f) ? 5000.0f / q
+ : (q < 100.0f) ? 2 * (100.0f - q)
+ : 0.0f;
+ sjpeg::SetQuantMatrix(base_tables[idx], factor, dst);
+ }
+ bool Update(float result) override {
+ value = result;
+ if (fabs(value - target) < tolerance * target) {
+ return true;
+ }
+ if (value > target) {
+ qmax = q;
+ } else {
+ qmin = q;
+ }
+ if (qmin == qmax) {
+ return true;
+ }
+ const float last_q = q;
+ if (pass == 0) {
+ q += first_iter_slope *
+ (for_size ? 0.1 * std::log(target / value) : (target - value));
+ q = std::max(qmin, std::min(qmax, q));
+ } else {
+ q = (qmin + qmax) / 2.;
+ }
+ return (pass > 0 && fabs(q - last_q) < q_precision);
+ }
+ ~MySearchHook() override {}
+};
+#endif
+
+Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, const JpegParams& params,
+ std::vector<uint8_t>* bytes) {
+#if !JPEGXL_ENABLE_SJPEG
+ return JXL_FAILURE("JPEG XL was built without sjpeg support");
+#else
+ if (image.format.data_type != JXL_TYPE_UINT8) {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
+ if (info.alpha_bits > 0) {
+ return JXL_FAILURE("alpha is not supported");
+ }
+ sjpeg::EncoderParam param(params.quality);
+ if (!icc.empty()) {
+ param.iccp.assign(icc.begin(), icc.end());
+ }
+ if (!exif.empty()) {
+ ResetExifOrientation(exif);
+ param.exif.assign(exif.begin(), exif.end());
+ }
+ if (params.chroma_subsampling == "444") {
+ param.yuv_mode = SJPEG_YUV_444;
+ } else if (params.chroma_subsampling == "420") {
+ param.yuv_mode = SJPEG_YUV_420;
+ } else if (params.chroma_subsampling == "420sharp") {
+ param.yuv_mode = SJPEG_YUV_SHARP;
+ } else {
+ return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
+ }
+ param.adaptive_quantization = params.enable_adaptive_quant;
+ std::unique_ptr<MySearchHook> hook;
+ if (params.libjpeg_quality > 0) {
+ JpegParams libjpeg_params;
+ libjpeg_params.quality = params.libjpeg_quality;
+ libjpeg_params.chroma_subsampling = params.libjpeg_chroma_subsampling;
+ std::vector<uint8_t> libjpeg_bytes;
+ JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, exif,
+ libjpeg_params, &libjpeg_bytes));
+ param.target_mode = sjpeg::EncoderParam::TARGET_SIZE;
+ param.target_value = libjpeg_bytes.size();
+ }
+ if (params.psnr_target > 0) {
+ param.target_mode = sjpeg::EncoderParam::TARGET_PSNR;
+ param.target_value = params.psnr_target;
+ }
+ if (param.target_mode != sjpeg::EncoderParam::TARGET_NONE) {
+ param.passes = params.search_max_iters;
+ param.tolerance = params.search_tolerance;
+ param.qmin = params.search_q_min;
+ param.qmax = params.search_q_max;
+ hook.reset(new MySearchHook());
+ hook->ReadBaseTables(params.custom_base_quant_fn);
+ hook->q_start = params.search_q_start;
+ hook->q_precision = params.search_q_precision;
+ hook->first_iter_slope = params.search_first_iter_slope;
+ param.search_hook = hook.get();
+ }
+ size_t stride = info.xsize * 3;
+ const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
+ std::string output;
+ JXL_RETURN_IF_ERROR(
+ sjpeg::Encode(pixels, image.xsize, image.ysize, stride, param, &output));
+ bytes->assign(
+ reinterpret_cast<const uint8_t*>(output.data()),
+ reinterpret_cast<const uint8_t*>(output.data() + output.size()));
+ return true;
+#endif
+}
+
+Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, JpegEncoder encoder,
+ const JpegParams& params, ThreadPool* pool,
+ std::vector<uint8_t>* bytes) {
+ if (params.quality > 100) {
+ return JXL_FAILURE("please specify a 0-100 JPEG quality");
+ }
+
+ switch (encoder) {
+ case JpegEncoder::kLibJpeg:
+ JXL_RETURN_IF_ERROR(
+ EncodeWithLibJpeg(image, info, icc, std::move(exif), params, bytes));
+ break;
+ case JpegEncoder::kSJpeg:
+ JXL_RETURN_IF_ERROR(
+ EncodeWithSJpeg(image, info, icc, std::move(exif), params, bytes));
+ break;
+ default:
+ return JXL_FAILURE("tried to use an unknown JPEG encoder");
+ }
+
+ return true;
+}
+
+class JPEGEncoder : public Encoder {
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/JXL_TYPE_UINT16,
+ /*endianness=*/JXL_BIG_ENDIAN,
+ /*align=*/0});
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
+ JpegParams params;
+ for (const auto& it : options()) {
+ if (it.first == "q") {
+ std::istringstream is(it.second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.quality));
+ } else if (it.first == "libjpeg_quality") {
+ std::istringstream is(it.second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.libjpeg_quality));
+ } else if (it.first == "chroma_subsampling") {
+ params.chroma_subsampling = it.second;
+ } else if (it.first == "libjpeg_chroma_subsampling") {
+ params.libjpeg_chroma_subsampling = it.second;
+ } else if (it.first == "jpeg_encoder") {
+ if (it.second == "libjpeg") {
+ jpeg_encoder = JpegEncoder::kLibJpeg;
+ } else if (it.second == "sjpeg") {
+ jpeg_encoder = JpegEncoder::kSJpeg;
+ } else {
+ return JXL_FAILURE("unknown jpeg encoder \"%s\"", it.second.c_str());
+ }
+ } else if (it.first == "progressive") {
+ std::istringstream is(it.second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.progressive_id));
+ } else if (it.first == "optimize" && it.second == "OFF") {
+ params.optimize_coding = false;
+ } else if (it.first == "adaptive_q" && it.second == "OFF") {
+ params.enable_adaptive_quant = false;
+ } else if (it.first == "psnr") {
+ params.psnr_target = std::stof(it.second);
+ } else if (it.first == "base_quant_fn") {
+ params.custom_base_quant_fn = it.second;
+ } else if (it.first == "search_q_start") {
+ params.search_q_start = std::stof(it.second);
+ } else if (it.first == "search_q_min") {
+ params.search_q_min = std::stof(it.second);
+ } else if (it.first == "search_q_max") {
+ params.search_q_max = std::stof(it.second);
+ } else if (it.first == "search_max_iters") {
+ params.search_max_iters = std::stoi(it.second);
+ } else if (it.first == "search_tolerance") {
+ params.search_tolerance = std::stof(it.second);
+ } else if (it.first == "search_q_precision") {
+ params.search_q_precision = std::stof(it.second);
+ } else if (it.first == "search_first_iter_slope") {
+ params.search_first_iter_slope = std::stof(it.second);
+ }
+ }
+ params.is_xyb = (ppf.color_encoding.color_space == JXL_COLOR_SPACE_XYB);
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImageJPG(
+ frame.color, ppf.info, ppf.icc, ppf.metadata.exif, jpeg_encoder,
+ params, pool, &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+#endif
+
+std::unique_ptr<Encoder> GetJPEGEncoder() {
+#if JPEGXL_ENABLE_JPEG
+ return jxl::make_unique<JPEGEncoder>();
+#else
+ return nullptr;
+#endif
+}
+
+} // namespace extras
+} // namespace jxl